summaryrefslogtreecommitdiff
path: root/src/objects
diff options
context:
space:
mode:
authorclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
committerclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
commit9f61c9e6ac6b1ac5692cf6352d2ebbd47a31a686 (patch)
treea84756b82513739a2672db3a1f0ec579db6d18ff /src/objects
downloadre3-miami.tar.gz
re3-miami.zip
Import Cai1Hsu/re3 @ miami (reVC / GTA:VC decompilation)HEADmiami
Snapshot import (no upstream history) into git.ancap.in.ua/claude, per @lzcnt. Source: https://github.com/Cai1Hsu/re3 branch miami.
Diffstat (limited to 'src/objects')
-rw-r--r--src/objects/CutsceneObject.cpp182
-rw-r--r--src/objects/CutsceneObject.h24
-rw-r--r--src/objects/DummyObject.cpp14
-rw-r--r--src/objects/DummyObject.h12
-rw-r--r--src/objects/Object.cpp836
-rw-r--r--src/objects/Object.h116
-rw-r--r--src/objects/ObjectData.cpp161
-rw-r--r--src/objects/ObjectData.h27
-rw-r--r--src/objects/ParticleObject.cpp1246
-rw-r--r--src/objects/ParticleObject.h112
-rw-r--r--src/objects/Projectile.cpp15
-rw-r--r--src/objects/Projectile.h9
-rw-r--r--src/objects/Stinger.cpp232
-rw-r--r--src/objects/Stinger.h40
14 files changed, 3026 insertions, 0 deletions
diff --git a/src/objects/CutsceneObject.cpp b/src/objects/CutsceneObject.cpp
new file mode 100644
index 0000000..7d9fe64
--- /dev/null
+++ b/src/objects/CutsceneObject.cpp
@@ -0,0 +1,182 @@
+#include "common.h"
+
+#include "main.h"
+#include "RwHelper.h"
+#include "Lights.h"
+#include "PointLights.h"
+#include "RpAnimBlend.h"
+#include "AnimBlendClumpData.h"
+#include "Bones.h"
+#include "Renderer.h"
+#include "ModelIndices.h"
+#include "Shadows.h"
+#include "Timecycle.h"
+#include "CutsceneShadow.h"
+#include "CutsceneObject.h"
+#include "ModelIndices.h"
+#include "RpAnimBlend.h"
+
+
+CCutsceneObject::CCutsceneObject(void)
+{
+ SetStatus(STATUS_SIMPLE);
+ bUsesCollision = false;
+ bStreamingDontDelete = true;
+ ObjectCreatedBy = CUTSCENE_OBJECT;
+ m_fMass = 1.0f;
+ m_fTurnMass = 1.0f;
+
+ m_pAttachTo = nil;
+ m_pAttachmentObject = nil;
+ m_pShadow = nil;
+}
+
+CCutsceneObject::~CCutsceneObject(void)
+{
+ if ( m_pShadow )
+ {
+ delete m_pShadow;
+ m_pShadow = nil;
+ }
+}
+
+void
+CCutsceneObject::SetModelIndex(uint32 id)
+{
+ CEntity::SetModelIndex(id);
+ assert(RwObjectGetType(m_rwObject) == rpCLUMP);
+ RpAnimBlendClumpInit((RpClump*)m_rwObject);
+ (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity3d = &m_vecMoveSpeed;
+ (*RPANIMBLENDCLUMPDATA(m_rwObject))->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION_3D;
+}
+
+void
+CCutsceneObject::CreateShadow(void)
+{
+ if ( IsPedModel(GetModelIndex()) )
+ {
+ m_pShadow = new CCutsceneShadow();
+ if (!m_pShadow->IsInitialized())
+ m_pShadow->Create(m_rwObject, 6, true, 4, true);
+ }
+}
+
+void
+CCutsceneObject::ProcessControl(void)
+{
+ CPhysical::ProcessControl();
+
+ if ( m_pAttachTo )
+ {
+ if ( m_pAttachmentObject )
+ GetMatrix() = CMatrix((RwMatrix*)m_pAttachTo);
+ else
+ GetMatrix() = CMatrix(RwFrameGetLTM((RwFrame*)m_pAttachTo));
+ }
+ else
+ {
+ if(CTimer::GetTimeStep() < 1/100.0f)
+ m_vecMoveSpeed *= 100.0f;
+ else
+ m_vecMoveSpeed *= 1.0f/CTimer::GetTimeStep();
+
+ ApplyMoveSpeed();
+ }
+}
+
+static RpMaterial*
+MaterialSetAlpha(RpMaterial *material, void *data)
+{
+ ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data;
+ return material;
+}
+
+void
+CCutsceneObject::PreRender(void)
+{
+ if ( m_pAttachTo )
+ {
+ if ( m_pAttachmentObject )
+ {
+ m_pAttachmentObject->UpdateRpHAnim();
+ GetMatrix() = CMatrix((RwMatrix*)m_pAttachTo);
+ }
+ else
+ GetMatrix() = CMatrix(RwFrameGetLTM((RwFrame*)m_pAttachTo));
+
+ if ( RwObjectGetType(m_rwObject) == rpCLUMP && IsClumpSkinned(GetClump()) )
+ {
+ RpAtomic *atomic = GetFirstAtomic(GetClump());
+ atomic->boundingSphere.center = (*RPANIMBLENDCLUMPDATA(GetClump()))->frames[0].hanimFrame->t;
+ }
+ }
+
+ if ( RwObjectGetType(m_rwObject) == rpCLUMP )
+ UpdateRpHAnim();
+
+ if(IsPedModel(GetModelIndex()))
+ {
+ if ( m_pShadow == nil )
+ {
+ CShadows::StoreShadowForPedObject(this,
+ CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]);
+ }
+ else
+ {
+ if ( m_pShadow->IsInitialized() )
+ m_pShadow->UpdateForCutscene();
+
+ CShadows::StoreShadowForCutscenePedObject(this,
+ CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue],
+ CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]);
+ }
+
+ // For some reason xbox/android limbs are transparent here...
+ RpGeometry *geometry = RpAtomicGetGeometry(GetFirstAtomic(GetClump()));
+ RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR);
+ RpGeometryForAllMaterials(geometry, MaterialSetAlpha, (void*)255);
+ }
+}
+
+void
+CCutsceneObject::Render(void)
+{
+ SetCullMode(rwCULLMODECULLNONE);
+ CObject::Render();
+ SetCullMode(rwCULLMODECULLBACK);
+}
+
+bool
+CCutsceneObject::SetupLighting(void)
+{
+ ActivateDirectional();
+ SetAmbientColoursForPedsCarsAndObjects();
+
+ if(bRenderScorched){
+ WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f);
+ }else{
+ CVector coors = GetPosition();
+ float lighting = CPointLights::GenerateLightsAffectingObject(&coors);
+ if(lighting != 1.0f){
+ SetAmbientAndDirectionalColours(lighting);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+CCutsceneObject::RemoveLighting(bool reset)
+{
+ CRenderer::RemoveVehiclePedLights(this, reset);
+}
diff --git a/src/objects/CutsceneObject.h b/src/objects/CutsceneObject.h
new file mode 100644
index 0000000..af24c0a
--- /dev/null
+++ b/src/objects/CutsceneObject.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "Object.h"
+
+class CCutsceneShadow;
+
+class CCutsceneObject : public CObject
+{
+public:
+ CCutsceneShadow *m_pShadow;
+ void *m_pAttachTo;
+ CObject *m_pAttachmentObject;
+
+ CCutsceneObject(void);
+ ~CCutsceneObject(void);
+
+ void SetModelIndex(uint32 id);
+ void CreateShadow(void);
+ void ProcessControl(void);
+ void PreRender(void);
+ void Render(void);
+ bool SetupLighting(void);
+ void RemoveLighting(bool reset);
+};
diff --git a/src/objects/DummyObject.cpp b/src/objects/DummyObject.cpp
new file mode 100644
index 0000000..8dd1643
--- /dev/null
+++ b/src/objects/DummyObject.cpp
@@ -0,0 +1,14 @@
+#include "common.h"
+
+#include "DummyObject.h"
+#include "Pools.h"
+
+CDummyObject::CDummyObject(CObject *obj)
+{
+ SetModelIndexNoCreate(obj->GetModelIndex());
+ if(obj->m_rwObject)
+ AttachToRwObject(obj->m_rwObject);
+ obj->DetachFromRwObject();
+ m_level = obj->m_level;
+ m_area = obj->m_area;
+}
diff --git a/src/objects/DummyObject.h b/src/objects/DummyObject.h
new file mode 100644
index 0000000..680df68
--- /dev/null
+++ b/src/objects/DummyObject.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Dummy.h"
+
+class CObject;
+
+class CDummyObject : public CDummy
+{
+public:
+ CDummyObject(void) {}
+ CDummyObject(CObject *obj);
+};
diff --git a/src/objects/Object.cpp b/src/objects/Object.cpp
new file mode 100644
index 0000000..0605f54
--- /dev/null
+++ b/src/objects/Object.cpp
@@ -0,0 +1,836 @@
+#include "common.h"
+
+#include "main.h"
+#include "Lights.h"
+#include "Pools.h"
+#include "Radar.h"
+#include "Object.h"
+#include "DummyObject.h"
+#include "Particle.h"
+#include "General.h"
+#include "ObjectData.h"
+#include "World.h"
+#include "Floater.h"
+#include "soundlist.h"
+#include "WaterLevel.h"
+#include "Timecycle.h"
+#include "Stats.h"
+#include "SpecialFX.h"
+
+#define BEACHBALL_MAX_SCORE 250
+// the proportion of the ball speed compared to the player speed when it hits the player
+#define BEACHBALL_SPEED_PROPORTION 0.4f
+
+int16 CObject::nNoTempObjects;
+//int16 CObject::nBodyCastHealth = 1000;
+float CObject::fDistToNearestTree;
+
+void *CObject::operator new(size_t sz) { return CPools::GetObjectPool()->New(); }
+void *CObject::operator new(size_t sz, int handle) { return CPools::GetObjectPool()->New(handle);};
+void CObject::operator delete(void *p, size_t sz) { CPools::GetObjectPool()->Delete((CObject*)p); }
+void CObject::operator delete(void *p, int handle) { CPools::GetObjectPool()->Delete((CObject*)p); }
+
+CObject::CObject(void)
+{
+ m_type = ENTITY_TYPE_OBJECT;
+ m_fUprootLimit = 0.0f;
+ m_nCollisionDamageEffect = 0;
+ m_nSpecialCollisionResponseCases = COLLRESPONSE_NONE;
+ m_bCameraToAvoidThisObject = false;
+ ObjectCreatedBy = UNKNOWN_OBJECT;
+ m_nEndOfLifeTime = 0;
+ // m_nRefModelIndex = -1; // duplicate
+ // bUseVehicleColours = false; // duplicate
+ m_colour2 = 0;
+ m_colour1 = m_colour2;
+ m_nBonusValue = 0;
+ m_nCostValue = 0;
+ bIsPickup = false;
+ bPickupObjWithMessage = false;
+ bOutOfStock = false;
+ bGlassCracked = false;
+ bGlassBroken = false;
+ bHasBeenDamaged = false;
+ m_nRefModelIndex = -1;
+ bUseVehicleColours = false;
+ // bIsStreetLight = false; // duplicate
+ m_pCurSurface = nil;
+ m_pCollidingEntity = nil;
+ m_nBeachballBounces = 0;
+ bIsStreetLight = false;
+ m_area = AREA_EVERYWHERE;
+}
+
+CObject::CObject(int32 mi, bool createRW)
+{
+ if (createRW)
+ SetModelIndex(mi);
+ else
+ SetModelIndexNoCreate(mi);
+ Init();
+}
+
+CObject::CObject(CDummyObject *dummy)
+{
+ SetModelIndexNoCreate(dummy->GetModelIndex());
+
+ if (dummy->m_rwObject)
+ AttachToRwObject(dummy->m_rwObject);
+ else
+ GetMatrix() = dummy->GetMatrix();
+
+ m_objectMatrix = dummy->GetMatrix();
+ dummy->DetachFromRwObject();
+ Init();
+ m_level = dummy->m_level;
+ m_area = dummy->m_area;
+}
+
+CObject::~CObject(void)
+{
+ CRadar::ClearBlipForEntity(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(this));
+
+ if (m_nRefModelIndex != -1)
+ CModelInfo::GetModelInfo(m_nRefModelIndex)->RemoveRef();
+
+ if (ObjectCreatedBy == TEMP_OBJECT && nNoTempObjects != 0)
+ nNoTempObjects--;
+}
+
+void
+CObject::ProcessControl(void)
+{
+ CVector point, impulse;
+ if (m_nCollisionDamageEffect)
+ ObjectDamage(m_fDamageImpulse);
+ CPhysical::ProcessControl();
+ if (mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)) {
+ bIsInWater = true;
+ SetIsStatic(false);
+ ApplyMoveForce(impulse);
+ ApplyTurnForce(impulse, point);
+ float fTimeStep = Pow(0.97f, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= fTimeStep;
+ m_vecTurnSpeed *= fTimeStep;
+ }
+ int16 mi = GetModelIndex();
+ if ((mi == MI_EXPLODINGBARREL || mi == MI_PETROLPUMP || mi == MI_PETROLPUMP2) && bHasBeenDamaged && bIsVisible
+ && (CGeneral::GetRandomNumber() & 0x1F) == 10) {
+ bExplosionProof = true;
+ bIsVisible = false;
+ bUsesCollision = false;
+ bAffectedByGravity = false;
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ }
+ if (mi == MI_RCBOMB) {
+ float fTurnForce = -(m_fTurnMass / 20.0f);
+ CPhysical::ApplyTurnForce(m_vecMoveSpeed * fTurnForce, -GetForward());
+ float fScalar = 1.0f - m_vecMoveSpeed.MagnitudeSqr() / 5.0f;
+ float fScalarTimed = Pow(fScalar, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= fScalarTimed;
+ }
+ if (mi == MI_BEACHBALL) {
+ float fTimeStep = Pow(0.95f, CTimer::GetTimeStep());
+ float fPreviousVecSpeedMag = m_vecMoveSpeed.Magnitude2D();
+ m_vecMoveSpeed.x *= fTimeStep;
+ m_vecMoveSpeed.y *= fTimeStep;
+ m_vecMoveSpeed.z += fPreviousVecSpeedMag - m_vecMoveSpeed.Magnitude2D();
+ if (!FindPlayerVehicle()) {
+ CVector distance;
+ distance.x = FindPlayerCoors().x - GetPosition().x;
+ distance.y = FindPlayerCoors().y - GetPosition().y;
+ distance.z = FindPlayerCoors().z - GetPosition().z;
+ if (distance.z > 0.0 && distance.z < 1.5f && distance.Magnitude2D() < 1.0f) {
+ CVector playerSpeed = FindPlayerSpeed();
+ if (fPreviousVecSpeedMag < 0.05f && playerSpeed.Magnitude() > 0.1f) {
+ playerSpeed.z = 0.0f;
+ playerSpeed.Normalise();
+ playerSpeed.z = 0.3f;
+ m_vecMoveSpeed = CVector(
+ playerSpeed.x * BEACHBALL_SPEED_PROPORTION,
+ playerSpeed.y * BEACHBALL_SPEED_PROPORTION,
+ 0.3f * BEACHBALL_SPEED_PROPORTION
+ );
+ PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, GetPosition());
+ m_vecTurnSpeed += CVector(
+ ((CGeneral::GetRandomNumber() % 16) - 7) / 10.0f,
+ ((CGeneral::GetRandomNumber() % 16) - 7) / 10.0f,
+ 0.0f);
+ if (m_nBeachballBounces > 0) {
+ m_nBeachballBounces++;
+ }
+ if (m_nBeachballBounces > 0) {
+ sprintf(gString, "%d", m_nBeachballBounces);
+ CMoneyMessages::RegisterOne(GetPosition(), gString, 255, 50, 0, 0.6f, 0.5f);
+ CStats::RegisterHighestScore(3, m_nBeachballBounces);
+ }
+ }
+ }
+ if (distance.z > -1.05 && distance.z < -0.6 && m_vecMoveSpeed.z < 0.0f && distance.Magnitude2D() < 0.9f) {
+ m_vecMoveSpeed.x += (CGeneral::GetRandomNumber() % 8 - 3) / 100.0f;
+ m_vecMoveSpeed.y += (CGeneral::GetRandomNumber() % 8 - 3) / 100.0f;
+ m_vecMoveSpeed.z = Max(m_vecMoveSpeed.z + 0.3f, 0.2f);
+ PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, GetPosition());
+ m_vecTurnSpeed.x += (CGeneral::GetRandomNumber() % 16 - 7) / 10.0f;
+ m_vecTurnSpeed.y += (CGeneral::GetRandomNumber() % 16 - 7) / 10.0f;
+ m_nBeachballBounces++;
+ m_nBeachballBounces = Min(m_nBeachballBounces, BEACHBALL_MAX_SCORE);
+ sprintf(gString, "%d", m_nBeachballBounces);
+ CMoneyMessages::RegisterOne(GetPosition(), gString, 255, 50, 0, 0.6f, 0.5f);
+ CStats::RegisterHighestScore(3, m_nBeachballBounces);
+ }
+ }
+ }
+ if (bIsBIGBuilding) {
+ bIsInSafePosition = true;
+ }
+}
+
+void
+CObject::Teleport(CVector vecPos)
+{
+ CWorld::Remove(this);
+ m_matrix.GetPosition() = vecPos;
+ m_matrix.UpdateRW();
+ UpdateRwFrame();
+ CWorld::Add(this);
+}
+
+void
+CObject::Render(void)
+{
+ if (bDoNotRender)
+ return;
+
+ if (m_nRefModelIndex != -1 && ObjectCreatedBy == TEMP_OBJECT && bUseVehicleColours) {
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nRefModelIndex);
+ assert(mi->GetModelType() == MITYPE_VEHICLE);
+ mi->SetVehicleColour(m_colour1, m_colour2);
+ }
+
+ float red = (0.8f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj()) * 165.75f;
+ float green = (0.8f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj()) * 165.75f;
+ float blue = (0.8f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj()) * 165.75f;
+
+ red = clamp(red, 0.0f, 255.0f);
+ green = clamp(green, 0.0f, 255.0f);
+ blue = clamp(blue, 0.0f, 255.0f);
+
+ int alpha = CGeneral::GetRandomNumberInRange(196, 225);
+
+ RwRGBA color = { (uint8)red, (uint8)green, (uint8)blue, (uint8)alpha };
+
+ if (this->GetModelIndex() == MI_YT_MAIN_BODY) {
+ float moveSpeedMagnitude = this->GetMoveSpeed().Magnitude();
+ if (moveSpeedMagnitude > 0.0f) {
+ float scaleMax = GetColModel()->boundingBox.max.y * 0.85f;
+
+ CVector dir = this->GetMoveSpeed() + 0.3f * this->GetRight() - 0.5f * this->GetForward();
+ dir.z += 0.05f * moveSpeedMagnitude;
+
+ CVector pos = scaleMax * this->GetForward() + 2.25f * this->GetRight() + this->GetPosition();
+
+ float fWaterLevel;
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.75f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 1.2f * moveSpeedMagnitude, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+
+ float scaleMin = GetColModel()->boundingBox.min.y;
+
+ dir = this->GetMoveSpeed() - 0.5f * this->GetForward();
+ dir.z += 0.05f * moveSpeedMagnitude;
+
+ pos = scaleMin * this->GetForward() + 4.5f * this->GetRight() + this->GetPosition();
+
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.55f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+
+ pos = scaleMin * 1.1f * this->GetForward() + 2.25f * this->GetRight() + this->GetPosition();
+
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.55f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+
+ pos = scaleMin * 1.1f * this->GetForward() - 0.05f * this->GetRight() + this->GetPosition();
+
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.55f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+ }
+ }
+
+ if (this->GetModelIndex() == MI_YT_MAIN_BODY2) {
+ float moveSpeedMagnitude = this->GetMoveSpeed().Magnitude();
+ if (moveSpeedMagnitude > 0.0f) {
+ float scaleMax = GetColModel()->boundingBox.max.y * 0.85f;
+
+ CVector dir = this->GetMoveSpeed() - 0.3f * this->GetRight() - 0.5f * this->GetForward();
+ dir.z += 0.05f * moveSpeedMagnitude;
+
+ CVector pos = scaleMax * this->GetForward() - 2.25f * this->GetRight() + this->GetPosition();
+
+ float fWaterLevel;
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.75f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 1.2f * moveSpeedMagnitude, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+
+ float scaleMin = GetColModel()->boundingBox.min.y;
+
+ dir = this->GetMoveSpeed() - 0.5f * this->GetForward();
+ dir.z += 0.05f * moveSpeedMagnitude;
+
+ pos = scaleMin * this->GetForward() - 4.5f * this->GetRight() + this->GetPosition();
+
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.55f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+
+ pos = scaleMin * 1.1f * this->GetForward() - 2.25f * this->GetRight() + this->GetPosition();
+
+ CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true);
+ pos.z = fWaterLevel + 0.55f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0);
+ }
+ }
+
+ CEntity::Render();
+}
+
+bool
+CObject::SetupLighting(void)
+{
+ if (bRenderScorched) {
+ WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f);
+ return true;
+ } else if (bIsPickup) {
+ SetFullAmbient();
+ return true;
+ } else if (bIsWeapon) {
+ ActivateDirectional();
+ SetAmbientColoursForPedsCarsAndObjects();
+ return true;
+ }
+ return false;
+}
+
+void
+CObject::RemoveLighting(bool reset)
+{
+ if (reset) {
+ SetAmbientColours();
+ DeActivateDirectional();
+ }
+}
+
+void
+CObject::ObjectDamage(float amount)
+{
+ if (!m_nCollisionDamageEffect || !bUsesCollision)
+ return;
+ static int8 nFrameGen = 0;
+ bool bBodyCastDamageEffect = false;
+#if 0
+ if (GetModelIndex() == MI_BODYCAST) {
+ if (amount > 50.0f)
+ nBodyCastHealth = (int16)(nBodyCastHealth - 0.5f * amount);
+ if (nBodyCastHealth < 0)
+ nBodyCastHealth = 0;
+ if (nBodyCastHealth < 200)
+ bBodyCastDamageEffect = true;
+ amount = 0.0f;
+ }
+#endif
+ if ((amount * m_fCollisionDamageMultiplier > 150.0f || bBodyCastDamageEffect) && m_nCollisionDamageEffect) {
+ const CVector &vecPos = m_matrix.GetPosition();
+ const float fDirectionZ = 0.0002f * amount;
+ switch (m_nCollisionDamageEffect) {
+ case DAMAGE_EFFECT_CHANGE_MODEL:
+ bRenderDamaged = true;
+ return;
+ case DAMAGE_EFFECT_SPLIT_MODEL:
+ return;
+ case DAMAGE_EFFECT_SMASH_AND_DAMAGE_TRAFFICLIGHTS:
+ {
+ static RwRGBA debrisColor = { 0xc8,0xc8,0xc8,0xff };
+ if (bRenderDamaged) {
+ break;
+ }
+ bRenderDamaged = true;
+ CVector min = 0.85f * GetColModel()->boundingBox.min;
+ CVector max = 0.85f * GetColModel()->boundingBox.max;
+ min.z = max.z;
+ min = GetMatrix() * min;
+ max = GetMatrix() * max;
+ CVector temp = (max - min) * 0.02f;
+ for (int32 i = 0; i < 50; i++) {
+ CVector vecDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f)
+ );
+ ++nFrameGen;
+ int32 currentFrame = nFrameGen & 3;
+ CVector pos = min + temp * (float)i;
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ float fColorFactor = CGeneral::GetRandomNumberInRange(0.6f, 1.2f);
+ RwRGBA color = debrisColor;
+ color.red *= fColorFactor;
+ color.green *= fColorFactor;
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-0.40f, 0.40f);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, pos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0);
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, min);
+ break;
+ }
+ case DAMAGE_EFFECT_CHANGE_THEN_SMASH: {
+ if (!bRenderDamaged) {
+ bRenderDamaged = true;
+ return;
+ }
+ // fall through
+ }
+ case DAMAGE_EFFECT_SMASH_COMPLETELY: {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY:
+ case DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ const RwRGBA color = { 96, 48, 0, 255 };
+ for (int32 i = 0; i < 25; i++) {
+ CVector vecDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ
+ );
+ ++nFrameGen;
+ int32 currentFrame = nFrameGen & 3;
+ RwRGBA randomColor = color;
+ switch (m_nCollisionDamageEffect) {
+ case DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY: {
+ float fRandom = CGeneral::GetRandomNumberInRange(0.01f, 1.0f);
+ randomColor.red *= fRandom;
+ randomColor.green *= fRandom;
+ randomColor.blue *= fRandom;
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY: {
+ randomColor.red = 0xff;
+ randomColor.green = 0xfc;
+ break;
+ }
+ }
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0);
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ static const RwRGBA color = { 128, 128, 128, 255 };
+ CVector position = GetPosition();
+ for (int32 i = 0; i < 45; i++) {
+ CVector vecDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ
+ );
+ ++nFrameGen;
+ int32 currentFrame = nFrameGen & 3;
+ float fRandom = CGeneral::GetRandomNumberInRange(0.5f, 1.0f);
+ RwRGBA randomColor = { uint8(color.red * fRandom), uint8(color.green * fRandom), uint8(color.blue * fRandom), color.alpha };
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0);
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_1, vecPos);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY:
+ case DAMAGE_EFFECT_BURST_BEACHBALL:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ const RwRGBA color1 = { 200, 0, 0, 255 };
+ const RwRGBA color2 = { 200, 200, 200, 255 };
+ for (int32 i = 0; i < 10; i++) {
+ CVector vecDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ
+ );
+ ++nFrameGen;
+ int32 currentFrame = nFrameGen & 3;
+ RwRGBA color = color2;
+ if (nFrameGen & 1)
+ color = color1;
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0);
+ }
+ if (m_nCollisionDamageEffect == DAMAGE_EFFECT_BURST_BEACHBALL) {
+ PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, vecPos);
+ } else {
+ PlayOneShotScriptObject(SCRIPT_SOUND_TIRE_COLLISION, vecPos);
+ }
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ const RwRGBA color1 = { 200, 0, 0, 255 };
+ const RwRGBA color2 = { 200, 200, 200, 255 };
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ for (int32 i = 0; i < 32; i++) {
+ CVector vecDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ
+ );
+ ++nFrameGen;
+ int32 currentFrame = nFrameGen & 3;
+ const RwRGBA &color = nFrameGen & 1 ? color1 : color2;
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0);
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW1:
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW2:
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW3:
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW4:
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW5:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ CRGBA possibleColor1;
+ CRGBA possibleColor2;
+ switch (m_nCollisionDamageEffect) {
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW1:
+ possibleColor1 = CRGBA(0xC0, 0x3E, 0xC, 0xFF);
+ possibleColor2 = possibleColor1;
+ break;
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW2:
+ possibleColor1 = CRGBA(0xA3, 0x36, 0x21, 0xFF);
+ possibleColor2 = possibleColor1;
+ break;
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW3:
+ possibleColor1 = CRGBA(0x12, 0x31, 0x24, 0xFF);
+ possibleColor2 = possibleColor1;
+ break;
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW4:
+ possibleColor1 = CRGBA(0xC0, 0xC8, 0xBE, 0xFF);
+ possibleColor2 = CRGBA(0x10, 0x57, 0x85, 0xFF);
+ break;
+ case DAMAGE_EFFECT_SMASH_NEWSTANDNEW5:
+ possibleColor1 = CRGBA(0xD0, 0x94, 0x1B, 0xFF);
+ possibleColor2 = possibleColor1;
+ break;
+ }
+ for (int32 i = 0; i < 16; i++) {
+ CVector vecDir(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.15f) + fDirectionZ
+ );
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ ++nFrameGen;
+ int32 nCurFrame = nFrameGen & 0x3;
+ CRGBA &selectedColor = nFrameGen & 0x1 ? possibleColor1 : possibleColor2;
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, selectedColor, nRotationSpeed, 0, nCurFrame, 0);
+ if (!(i % 7)) {
+ static CRGBA secondParticleColors[4] = {
+ CRGBA(0xA0, 0x60, 0x60, 0xFF),
+ CRGBA(0x60, 0xA0, 0x60, 0xFF),
+ CRGBA(0x60, 0x60, 0xA0, 0xFF),
+ CRGBA(0xA0, 0xA0, 0xA0, 0xFF)
+ };
+ vecDir *= 0.5f;
+ CRGBA &secondParticleColor = secondParticleColors[nFrameGen & 3];
+ int32 nSecondRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_DEBRIS, vecPos, vecDir, nil, 0.1f, secondParticleColor, nSecondRotationSpeed, 0, 1, 0);
+ }
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_VEGPALM:
+ {
+ static RwRGBA primaryColor1 = { 0x39, 0x4D, 0x29, 0xff };
+ static RwRGBA primaryColor2 = { 0x94, 0x7D, 0x73, 0xff };
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ float fRadius = GetColModel()->boundingSphere.radius;
+ for (int32 i = 0; i < 32; i++) {
+ CVector particleDir = CVector(
+ CGeneral::GetRandomNumberInRange(-0.25f, 0.25f),
+ CGeneral::GetRandomNumberInRange(-0.25f, 0.25f),
+ CGeneral::GetRandomNumberInRange(-0.05f, 0.05f) + fDirectionZ
+ );
+ CVector particlePos = vecPos;
+ particlePos.z += CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * fRadius;
+ ++nFrameGen;
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ int32 nCurFrame = nFrameGen & 0x3;
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ RwRGBA& particleColor = nFrameGen & 1 ? primaryColor1 : primaryColor2;
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, particlePos, particleDir, nil, fSize, particleColor, nRotationSpeed, 0, nCurFrame, 0);
+ if ((i % 7) == 0) {
+ static RwRGBA secondaryColor = { 0x9A, 0x99, 0x99, 0x3E };
+ CParticle::AddParticle(PARTICLE_DEBRIS, particlePos, particleDir, nil, 0.3, secondaryColor, nRotationSpeed, 0, 0, 0);
+ }
+ }
+ PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos);
+ break;
+ }
+ case DAMAGE_EFFECT_SMASH_BLACKBAG:
+ case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD:
+ case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL:
+ {
+ bIsVisible = false;
+ bUsesCollision = false;
+ if (!GetIsStatic()) {
+ RemoveFromMovingList();
+ }
+ SetIsStatic(true);
+ bExplosionProof = true;
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ CRGBA possibleColor1;
+ CRGBA possibleColor2;
+ switch (m_nCollisionDamageEffect) {
+ case DAMAGE_EFFECT_SMASH_BLACKBAG:
+ possibleColor1 = CRGBA(0, 0, 0, 0xFF);
+ possibleColor2 = possibleColor1;
+ break;
+ case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD:
+ possibleColor1 = CRGBA(0x8F, 0x8A, 0x8C, 0xFF);
+ possibleColor2 = CRGBA(0x73, 0x75, 0x7B, 0xFF);
+ break;
+ case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL:
+ possibleColor1 = CRGBA(0x52, 0x92, 0x4A, 0xFF);
+ possibleColor2 = CRGBA(0xCE, 0xCF, 0xCE, 0xFF);
+ break;
+ }
+ for (int32 i = 0; i < 16; i++) {
+ CVector vecDir(
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(-0.35f, 0.35f),
+ CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ
+ );
+ ++nFrameGen;
+ int32 nCurFrame = nFrameGen & 0x3;
+ CRGBA &selectedColor = nFrameGen & 0x1 ? possibleColor1 : possibleColor2;
+ float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f);
+ int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40);
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, selectedColor, nRotationSpeed, 0, nCurFrame, 0);
+ }
+ if (m_nCollisionDamageEffect == DAMAGE_EFFECT_SMASH_BLACKBAG) {
+ PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos);
+ } else if (m_nCollisionDamageEffect == DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD) {
+ PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos);
+ }
+ break;
+ }
+ default:
+ DEV("Unhandled collision damage effect id: %d\n", m_nCollisionDamageEffect);
+ return;
+ }
+ }
+}
+
+void
+CObject::RefModelInfo(int32 modelId)
+{
+ m_nRefModelIndex = modelId;
+ CModelInfo::GetModelInfo(modelId)->AddRef();
+}
+
+void
+CObject::Init(void)
+{
+ m_type = ENTITY_TYPE_OBJECT;
+ CObjectData::SetObjectData(GetModelIndex(), *this);
+ m_nEndOfLifeTime = 0;
+ ObjectCreatedBy = GAME_OBJECT;
+ SetIsStatic(true);
+ bIsPickup = false;
+ bPickupObjWithMessage = false;
+ bOutOfStock = false;
+ bGlassCracked = false;
+ bGlassBroken = false;
+ bHasBeenDamaged = false;
+ bUseVehicleColours = false;
+ m_nRefModelIndex = -1;
+ m_colour1 = 0;
+ m_colour2 = 0;
+ m_nBonusValue = 0;
+ bIsWeapon = false;
+ m_nCostValue = 0;
+ m_pCollidingEntity = nil;
+ CColPoint point;
+ CEntity *outEntity = nil;
+ const CVector &vecPos = m_matrix.GetPosition();
+ if (CWorld::ProcessVerticalLine(vecPos, vecPos.z - 10.0f, point, outEntity, true, false, false, false, false, false, nil))
+ m_pCurSurface = outEntity;
+ else
+ m_pCurSurface = nil;
+
+ if (GetModelIndex() == MI_BUOY)
+ bTouchingWater = true;
+
+ if (CModelInfo::GetModelInfo(GetModelIndex())->GetModelType() == MITYPE_WEAPON)
+ bIsWeapon = true;
+ bIsStreetLight = IsLightObject(GetModelIndex());
+
+ m_area = AREA_EVERYWHERE;
+}
+
+bool
+CObject::CanBeDeleted(void)
+{
+ switch (ObjectCreatedBy) {
+ case GAME_OBJECT:
+ return true;
+ case MISSION_OBJECT:
+ return false;
+ case TEMP_OBJECT:
+ return true;
+ case CUTSCENE_OBJECT:
+ return false;
+ case CONTROLLED_SUB_OBJECT:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void
+CObject::DeleteAllMissionObjects()
+{
+ CObjectPool *objectPool = CPools::GetObjectPool();
+ for (int32 i = 0; i < objectPool->GetSize(); i++) {
+ CObject *pObject = objectPool->GetSlot(i);
+ if (pObject && pObject->ObjectCreatedBy == MISSION_OBJECT) {
+ CWorld::Remove(pObject);
+ delete pObject;
+ }
+ }
+}
+
+void
+CObject::DeleteAllTempObjects()
+{
+ CObjectPool *objectPool = CPools::GetObjectPool();
+ for (int32 i = 0; i < objectPool->GetSize(); i++) {
+ CObject *pObject = objectPool->GetSlot(i);
+ if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT) {
+ CWorld::Remove(pObject);
+ delete pObject;
+ }
+ }
+}
+
+void
+CObject::DeleteAllTempObjectsInArea(CVector point, float fRadius)
+{
+ CObjectPool *objectPool = CPools::GetObjectPool();
+ for (int32 i = 0; i < objectPool->GetSize(); i++) {
+ CObject *pObject = objectPool->GetSlot(i);
+ if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT && (point - pObject->GetPosition()).MagnitudeSqr() < SQR(fRadius)) {
+ CWorld::Remove(pObject);
+ delete pObject;
+ }
+ }
+}
+
+bool
+IsObjectPointerValid(CObject *pObject)
+{
+ if (!pObject)
+ return false;
+ int index = CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pObject);
+#ifdef FIX_BUGS
+ if (index < 0 || index >= CPools::GetObjectPool()->GetSize())
+#else
+ if (index < 0 || index > CPools::GetObjectPool()->GetSize())
+#endif
+ return false;
+ return pObject->bIsBIGBuilding || pObject->m_entryInfoList.first;
+}
diff --git a/src/objects/Object.h b/src/objects/Object.h
new file mode 100644
index 0000000..e34043a
--- /dev/null
+++ b/src/objects/Object.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "Physical.h"
+
+enum {
+ UNKNOWN_OBJECT = 0,
+ GAME_OBJECT = 1,
+ MISSION_OBJECT = 2,
+ TEMP_OBJECT = 3,
+ CUTSCENE_OBJECT = 4,
+ CONTROLLED_SUB_OBJECT = 5,
+};
+
+enum CollisionSpecialResponseCase
+{
+ COLLRESPONSE_NONE,
+ COLLRESPONSE_LAMPOST,
+ COLLRESPONSE_SMALLBOX,
+ COLLRESPONSE_BIGBOX,
+ COLLRESPONSE_FENCEPART,
+ COLLRESPONSE_UNKNOWN5
+};
+
+enum CollisionDamageEffect
+{
+ DAMAGE_EFFECT_NONE,
+ DAMAGE_EFFECT_CHANGE_MODEL,
+ DAMAGE_EFFECT_SPLIT_MODEL,
+ DAMAGE_EFFECT_SMASH_AND_DAMAGE_TRAFFICLIGHTS,
+
+ DAMAGE_EFFECT_SMASH_COMPLETELY = 20,
+ DAMAGE_EFFECT_CHANGE_THEN_SMASH,
+
+ DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY = 50,
+ DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY = 51,
+
+ DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY = 60,
+ DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY = 70,
+ DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY = 80,
+
+ DAMAGE_EFFECT_SMASH_NEWSTANDNEW1 = 91,
+ DAMAGE_EFFECT_SMASH_NEWSTANDNEW2 = 92,
+ DAMAGE_EFFECT_SMASH_NEWSTANDNEW3 = 93,
+ DAMAGE_EFFECT_SMASH_NEWSTANDNEW4 = 94,
+ DAMAGE_EFFECT_SMASH_NEWSTANDNEW5 = 95,
+
+ DAMAGE_EFFECT_SMASH_BLACKBAG = 100,
+ DAMAGE_EFFECT_SMASH_VEGPALM = 110,
+ DAMAGE_EFFECT_BURST_BEACHBALL = 120,
+ DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD = 131,
+ DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL = 132,
+};
+
+class CVehicle;
+class CDummyObject;
+
+class CObject : public CPhysical
+{
+public:
+ CMatrix m_objectMatrix;
+ float m_fUprootLimit;
+ int8 ObjectCreatedBy;
+ uint8 bIsPickup : 1;
+ uint8 bAmmoCollected : 1;
+ uint8 bPickupObjWithMessage : 1;
+ uint8 bOutOfStock : 1;
+ uint8 bGlassCracked : 1;
+ uint8 bGlassBroken : 1;
+ uint8 bHasBeenDamaged : 1;
+ uint8 bUseVehicleColours : 1;
+ uint8 bIsWeapon : 1;
+ uint8 bIsStreetLight : 1;
+ int8 m_nBonusValue;
+ uint16 m_nCostValue;
+ float m_fCollisionDamageMultiplier;
+ uint8 m_nCollisionDamageEffect;
+ uint8 m_nSpecialCollisionResponseCases;
+ bool m_bCameraToAvoidThisObject;
+ uint8 m_nBeachballBounces;
+ uint32 m_obj_unused1;
+ uint32 m_nEndOfLifeTime;
+ int16 m_nRefModelIndex;
+ CEntity *m_pCurSurface;
+ CEntity *m_pCollidingEntity;
+ int8 m_colour1, m_colour2;
+
+ static int16 nNoTempObjects;
+ static float fDistToNearestTree;
+
+ static void *operator new(size_t);
+ static void *operator new(size_t, int);
+ static void operator delete(void*, size_t);
+ static void operator delete(void*, int);
+
+ CObject(void);
+ CObject(int32, bool);
+ CObject(CDummyObject*);
+ ~CObject(void);
+
+ void ProcessControl(void);
+ void Teleport(CVector vecPos);
+ void Render(void);
+ bool SetupLighting(void);
+ void RemoveLighting(bool reset);
+
+ void ObjectDamage(float amount);
+ void RefModelInfo(int32 modelId);
+ void Init(void);
+ bool CanBeDeleted(void);
+
+ static void DeleteAllMissionObjects();
+ static void DeleteAllTempObjects();
+ static void DeleteAllTempObjectsInArea(CVector point, float fRadius);
+};
+
+bool IsObjectPointerValid(CObject* pObject);
diff --git a/src/objects/ObjectData.cpp b/src/objects/ObjectData.cpp
new file mode 100644
index 0000000..8b23f0a
--- /dev/null
+++ b/src/objects/ObjectData.cpp
@@ -0,0 +1,161 @@
+#include "common.h"
+
+#include "main.h"
+#include "ModelInfo.h"
+#include "Object.h"
+#include "FileMgr.h"
+#include "ObjectData.h"
+
+CObjectInfo CObjectData::ms_aObjectInfo[NUMOBJECTINFO];
+
+// Another ugly file reader
+void
+CObjectData::Initialise(const char *filename)
+{
+ char *p, *lp;
+ char line[1024], name[256];
+ int id;
+ float percentSubmerged;
+ int damageEffect, responseCase, camAvoid;
+ CBaseModelInfo *mi;
+
+ ms_aObjectInfo[0].m_fMass = 99999.0f;
+ ms_aObjectInfo[0].m_fTurnMass = 99999.0f;
+ ms_aObjectInfo[0].m_fAirResistance = 0.99f;
+ ms_aObjectInfo[0].m_fElasticity = 0.1f;
+ ms_aObjectInfo[0].m_fBuoyancy = GRAVITY * ms_aObjectInfo[0].m_fMass * 2.0f;
+ ms_aObjectInfo[0].m_fUprootLimit = 0.0f;
+ ms_aObjectInfo[0].m_fCollisionDamageMultiplier = 1.0f;
+ ms_aObjectInfo[0].m_nCollisionDamageEffect = 0;
+ ms_aObjectInfo[0].m_nSpecialCollisionResponseCases = 0;
+ ms_aObjectInfo[0].m_bCameraToAvoidThisObject = false;
+
+ ms_aObjectInfo[1].m_fMass = 99999.0f;
+ ms_aObjectInfo[1].m_fTurnMass = 99999.0f;
+ ms_aObjectInfo[1].m_fAirResistance = 0.99f;
+ ms_aObjectInfo[1].m_fElasticity = 0.1f;
+ ms_aObjectInfo[1].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy;
+ ms_aObjectInfo[1].m_fUprootLimit = 0.0f;
+ ms_aObjectInfo[1].m_fCollisionDamageMultiplier = 1.0f;
+ ms_aObjectInfo[1].m_nCollisionDamageEffect = 0;
+ ms_aObjectInfo[1].m_nSpecialCollisionResponseCases = 0;
+ ms_aObjectInfo[1].m_bCameraToAvoidThisObject = true;
+
+ ms_aObjectInfo[2].m_fMass = 99999.0f;
+ ms_aObjectInfo[2].m_fTurnMass = 99999.0f;
+ ms_aObjectInfo[2].m_fAirResistance = 0.99f;
+ ms_aObjectInfo[2].m_fElasticity = 0.1f;
+ ms_aObjectInfo[2].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy;
+ ms_aObjectInfo[2].m_fUprootLimit = 0.0f;
+ ms_aObjectInfo[2].m_fCollisionDamageMultiplier = 1.0f;
+ ms_aObjectInfo[2].m_nCollisionDamageEffect = 0;
+ ms_aObjectInfo[2].m_bCameraToAvoidThisObject = false;
+ ms_aObjectInfo[2].m_nSpecialCollisionResponseCases = 4;
+
+ ms_aObjectInfo[3].m_fMass = 99999.0f;
+ ms_aObjectInfo[3].m_fTurnMass = 99999.0f;
+ ms_aObjectInfo[3].m_fAirResistance = 0.99f;
+ ms_aObjectInfo[3].m_fElasticity = 0.1f;
+ ms_aObjectInfo[3].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy;
+ ms_aObjectInfo[3].m_fUprootLimit = 0.0f;
+ ms_aObjectInfo[3].m_fCollisionDamageMultiplier = 1.0f;
+ ms_aObjectInfo[3].m_nCollisionDamageEffect = 0;
+ ms_aObjectInfo[3].m_nSpecialCollisionResponseCases = 4;
+ ms_aObjectInfo[3].m_bCameraToAvoidThisObject = true;
+
+ CFileMgr::SetDir("");
+ CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
+
+ id = 4;
+ p = (char*)work_buff;
+ while(*p != '*'){
+ // skip over white space and comments
+ while(*p == ' ' || *p == '\n' || *p == '\r' || *p == ';')
+ if(*p == ';')
+ while(*p != '\n' && *p != '*')
+ p++;
+ else
+ p++;
+
+ if(*p == '*')
+ break;
+
+ // read one line
+ lp = line;
+ while(*p != '\n' && *p != '*'){
+ *lp++ = *p == ',' ? ' ' : *p;
+ p++;
+ }
+ if(*p == '\n')
+ p++;
+#ifdef FIX_BUGS
+ *lp = '\0'; // FIX: game wrote '\n' here
+#else
+ *lp = '\n';
+#endif
+
+ assert(id < NUMOBJECTINFO);
+ sscanf(line, "%s %f %f %f %f %f %f %f %d %d %d", name,
+ &ms_aObjectInfo[id].m_fMass,
+ &ms_aObjectInfo[id].m_fTurnMass,
+ &ms_aObjectInfo[id].m_fAirResistance,
+ &ms_aObjectInfo[id].m_fElasticity,
+ &percentSubmerged,
+ &ms_aObjectInfo[id].m_fUprootLimit,
+ &ms_aObjectInfo[id].m_fCollisionDamageMultiplier,
+ &damageEffect, &responseCase, &camAvoid);
+
+ ms_aObjectInfo[id].m_fBuoyancy = 100.0f/percentSubmerged * GRAVITY *ms_aObjectInfo[id].m_fMass;
+ ms_aObjectInfo[id].m_nCollisionDamageEffect = damageEffect;
+ ms_aObjectInfo[id].m_nSpecialCollisionResponseCases = responseCase;
+ ms_aObjectInfo[id].m_bCameraToAvoidThisObject = camAvoid;
+
+ mi = CModelInfo::GetModelInfo(name, nil);
+ if (mi) {
+ if (ms_aObjectInfo[0].m_fMass != ms_aObjectInfo[id].m_fMass
+ || ms_aObjectInfo[0].m_fCollisionDamageMultiplier != ms_aObjectInfo[id].m_fCollisionDamageMultiplier
+ || ms_aObjectInfo[0].m_nCollisionDamageEffect != ms_aObjectInfo[id].m_nCollisionDamageEffect
+ || ((ms_aObjectInfo[0].m_nSpecialCollisionResponseCases != ms_aObjectInfo[id].m_nSpecialCollisionResponseCases)
+ && (ms_aObjectInfo[2].m_nSpecialCollisionResponseCases != ms_aObjectInfo[id].m_nSpecialCollisionResponseCases))) {
+ mi->SetObjectID(id++);
+ } else if (ms_aObjectInfo[0].m_nSpecialCollisionResponseCases == ms_aObjectInfo[id].m_nSpecialCollisionResponseCases) {
+ if (ms_aObjectInfo[0].m_bCameraToAvoidThisObject == ms_aObjectInfo[id].m_bCameraToAvoidThisObject)
+ mi->SetObjectID(0);
+ else
+ mi->SetObjectID(1);
+ } else if (ms_aObjectInfo[2].m_bCameraToAvoidThisObject == ms_aObjectInfo[id].m_bCameraToAvoidThisObject)
+ mi->SetObjectID(2);
+ else
+ mi->SetObjectID(3);
+ } else
+ debug("CObjectData: Cannot find object %s\n", name);
+ }
+}
+
+void
+CObjectData::SetObjectData(int32 modelId, CObject &object)
+{
+ CObjectInfo *objinfo;
+
+ if(CModelInfo::GetModelInfo(modelId)->GetObjectID() == -1)
+ return;
+
+ objinfo = &ms_aObjectInfo[CModelInfo::GetModelInfo(modelId)->GetObjectID()];
+
+ object.m_fMass = objinfo->m_fMass;
+ object.m_fTurnMass = objinfo->m_fTurnMass;
+ object.m_fAirResistance = objinfo->m_fAirResistance;
+ object.m_fElasticity = objinfo->m_fElasticity;
+ object.m_fBuoyancy = objinfo->m_fBuoyancy;
+ object.m_fUprootLimit = objinfo->m_fUprootLimit;
+ object.m_fCollisionDamageMultiplier = objinfo->m_fCollisionDamageMultiplier;
+ object.m_nCollisionDamageEffect = objinfo->m_nCollisionDamageEffect;
+ object.m_nSpecialCollisionResponseCases = objinfo->m_nSpecialCollisionResponseCases;
+ object.m_bCameraToAvoidThisObject = objinfo->m_bCameraToAvoidThisObject;
+ if(object.m_fMass >= 99998.0f){
+ object.bInfiniteMass = true;
+ object.m_phy_flagA08 = true;
+ object.bAffectedByGravity = false;
+ object.bExplosionProof = true;
+ }
+}
diff --git a/src/objects/ObjectData.h b/src/objects/ObjectData.h
new file mode 100644
index 0000000..e25c1ae
--- /dev/null
+++ b/src/objects/ObjectData.h
@@ -0,0 +1,27 @@
+#pragma once
+
+class CObject;
+
+class CObjectInfo
+{
+public:
+ float m_fMass;
+ float m_fTurnMass;
+ float m_fAirResistance;
+ float m_fElasticity;
+ float m_fBuoyancy;
+ float m_fUprootLimit;
+ float m_fCollisionDamageMultiplier;
+ uint8 m_nCollisionDamageEffect;
+ uint8 m_nSpecialCollisionResponseCases;
+ bool m_bCameraToAvoidThisObject;
+};
+VALIDATE_SIZE(CObjectInfo, 0x20);
+
+class CObjectData
+{
+ static CObjectInfo ms_aObjectInfo[NUMOBJECTINFO];
+public:
+ static void Initialise(const char *filename);
+ static void SetObjectData(int32 modelId, CObject &object);
+};
diff --git a/src/objects/ParticleObject.cpp b/src/objects/ParticleObject.cpp
new file mode 100644
index 0000000..4d080d1
--- /dev/null
+++ b/src/objects/ParticleObject.cpp
@@ -0,0 +1,1246 @@
+#include "common.h"
+
+#include "ParticleObject.h"
+#include "Timer.h"
+#include "General.h"
+#include "ParticleMgr.h"
+#include "Particle.h"
+#include "Camera.h"
+#include "Game.h"
+#include "DMAudio.h"
+#include "screendroplets.h"
+
+
+CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS];
+
+CParticleObject *CParticleObject::pCloseListHead;
+CParticleObject *CParticleObject::pFarListHead;
+CParticleObject *CParticleObject::pUnusedListHead;
+
+CAudioHydrant List[MAX_AUDIOHYDRANTS];
+
+CAudioHydrant *CAudioHydrant::Get(int n) { return &List[n]; }
+
+bool
+CAudioHydrant::Add(CParticleObject *particleobject)
+{
+ for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ )
+ {
+ if ( List[i].AudioEntity == AEHANDLE_NONE )
+ {
+ List[i].AudioEntity = DMAudio.CreateEntity(AUDIOTYPE_FIREHYDRANT, particleobject);
+
+ if ( AEHANDLE_IS_FAILED(List[i].AudioEntity) )
+ return false;
+
+ DMAudio.SetEntityStatus(List[i].AudioEntity, true);
+
+ List[i].pParticleObject = particleobject;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+CAudioHydrant::Remove(CParticleObject *particleobject)
+{
+ for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ )
+ {
+ if ( List[i].pParticleObject == particleobject )
+ {
+ DMAudio.DestroyEntity(List[i].AudioEntity);
+ List[i].AudioEntity = AEHANDLE_NONE;
+ List[i].pParticleObject = nil;
+ }
+ }
+}
+
+CParticleObject::CParticleObject() :
+ CPlaceable(),
+ m_nFrameCounter(0),
+ m_nState(POBJECTSTATE_INITIALISED),
+ m_pNext(nil),
+ m_pPrev(nil),
+ m_nRemoveTimer(0)
+
+{
+ ;
+}
+
+CParticleObject::~CParticleObject()
+{
+
+}
+
+void
+CParticleObject::Initialise()
+{
+ pCloseListHead = nil;
+ pFarListHead = nil;
+
+ pUnusedListHead = &gPObjectArray[0];
+
+ for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ )
+ {
+ if ( i == 0 )
+ gPObjectArray[i].m_pPrev = nil;
+ else
+ gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1];
+
+ if ( i == MAX_PARTICLEOBJECTS-1 )
+ gPObjectArray[i].m_pNext = nil;
+ else
+ gPObjectArray[i].m_pNext = &gPObjectArray[i + 1];
+
+ gPObjectArray[i].m_nState = POBJECTSTATE_FREE;
+ }
+}
+
+CParticleObject *
+CParticleObject::AddObject(uint16 type, CVector const &pos, uint8 remove)
+{
+ CRGBA color(0, 0, 0, 0);
+ CVector target(0.0f, 0.0f, 0.0f);
+ return AddObject(type, pos, target, 0.0f, 0, color, remove);
+}
+
+CParticleObject *
+CParticleObject::AddObject(uint16 type, CVector const &pos, float size, uint8 remove)
+{
+ CRGBA color(0, 0, 0, 0);
+ CVector target(0.0f, 0.0f, 0.0f);
+ return AddObject(type, pos, target, size, 0, color, remove);
+}
+
+CParticleObject *
+CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove)
+{
+ CRGBA color(0, 0, 0, 0);
+ return AddObject(type, pos, target, size, 0, color, remove);
+}
+
+CParticleObject *
+CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove)
+{
+ CParticleObject *pobj = pUnusedListHead;
+
+ if ( pobj == nil )
+ {
+ printf("Error: No particle objects available!\n");
+ return nil;
+ }
+
+ MoveToList(&pUnusedListHead, &pCloseListHead, pobj);
+
+ pobj->m_nState = POBJECTSTATE_UPDATE_CLOSE;
+ pobj->m_Type = (eParticleObjectType)type;
+
+ pobj->SetPosition(pos);
+ pobj->m_vecTarget = target;
+
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_nFrameCounter = 0;
+
+ pobj->m_bRemove = remove;
+
+ pobj->m_pParticle = nil;
+
+ if ( lifeTime != 0 )
+ pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime;
+ else
+ pobj->m_nRemoveTimer = 0;
+
+ if ( color.alpha != 0 )
+ pobj->m_Color = color;
+ else
+ pobj->m_Color.alpha = 0;
+
+ pobj->m_fSize = size;
+ pobj->m_fRandVal = 0.0f;
+
+ switch ( type )
+ {
+ case POBJECT_PAVEMENT_STEAM:
+ {
+ pobj->m_ParticleType = PARTICLE_STEAM_NY;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 3;
+ pobj->m_nCreationChance = 8;
+ break;
+ }
+
+ case POBJECT_PAVEMENT_STEAM_SLOWMOTION:
+ {
+ pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 8;
+ break;
+ }
+
+ case POBJECT_WALL_STEAM:
+ {
+ pobj->m_ParticleType = PARTICLE_STEAM_NY;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 3;
+ pobj->m_nCreationChance = 8;
+ break;
+ }
+
+ case POBJECT_WALL_STEAM_SLOWMOTION:
+ {
+ pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 8;
+ break;
+ }
+
+ case POBJECT_DARK_SMOKE:
+ {
+ pobj->m_ParticleType = PARTICLE_STEAM_NY;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 3;
+ pobj->m_nCreationChance = 8;
+ pobj->m_Color = CRGBA(16, 16, 16, 255);
+ break;
+ }
+
+ case POBJECT_WATER_FOUNTAIN_VERT:
+ {
+ pobj->m_ParticleType = PARTICLE_WATER_HYDRANT;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.1f);
+ break;
+ }
+
+ case POBJECT_WATER_FOUNTAIN_HORIZ:
+ {
+ pobj->m_ParticleType = PARTICLE_WATER_HYDRANT;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ break;
+ }
+
+ case POBJECT_FIRE_HYDRANT:
+ {
+ pobj->m_ParticleType = PARTICLE_WATER_HYDRANT;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.3f);
+ pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + 5000;
+ CAudioHydrant::Add(pobj);
+ break;
+ }
+
+ case POBJECT_CAR_WATER_SPLASH:
+ case POBJECT_PED_WATER_SPLASH:
+ {
+ pobj->m_ParticleType = PARTICLE_CAR_SPLASH;
+ pobj->m_nNumEffectCycles = 0;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+#ifdef SCREEN_DROPLETS
+ ScreenDroplets::RegisterSplash(pobj);
+#endif
+ break;
+ }
+
+ case POBJECT_SPLASHES_AROUND:
+ {
+ pobj->m_ParticleType = PARTICLE_SPLASH;
+ pobj->m_nNumEffectCycles = 15;
+ pobj->m_nSkipFrames = 2;
+ pobj->m_nCreationChance = 0;
+ break;
+ }
+
+ case POBJECT_SMALL_FIRE:
+ {
+ pobj->m_ParticleType = PARTICLE_FLAME;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 2;
+ pobj->m_nCreationChance = 2;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f);
+ break;
+ }
+
+ case POBJECT_BIG_FIRE:
+ {
+ pobj->m_ParticleType = PARTICLE_FLAME;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 2;
+ pobj->m_nCreationChance = 4;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f);
+ break;
+ }
+
+ case POBJECT_DRY_ICE:
+ {
+ pobj->m_ParticleType = PARTICLE_SMOKE;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f);
+ break;
+ }
+
+ case POBJECT_DRY_ICE_SLOWMOTION:
+ {
+ pobj->m_ParticleType = PARTICLE_SMOKE_SLOWMOTION;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f);
+ break;
+ }
+
+ case POBJECT_FIRE_TRAIL:
+ {
+ pobj->m_ParticleType = PARTICLE_EXPLOSION_MEDIUM;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 3;
+ pobj->m_nCreationChance = 2;
+ pobj->m_fRandVal = 0.01f;
+ break;
+ }
+
+ case POBJECT_SMOKE_TRAIL:
+ {
+ pobj->m_ParticleType = PARTICLE_FIREBALL_SMOKE;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 2;
+ pobj->m_fRandVal = 0.02f;
+ break;
+ }
+
+ case POBJECT_FIREBALL_AND_SMOKE:
+ {
+ pobj->m_ParticleType = PARTICLE_FLAME;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_fRandVal = 0.1f;
+ break;
+ }
+
+ case POBJECT_ROCKET_TRAIL:
+ {
+ pobj->m_ParticleType = PARTICLE_FLAME;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 2;
+ pobj->m_nCreationChance = 8;
+ pobj->m_fRandVal = 0.1f;
+ break;
+ }
+
+ case POBJECT_EXPLOSION_ONCE:
+ {
+ pobj->m_ParticleType = PARTICLE_EXPLOSION_LARGE;
+ pobj->m_nNumEffectCycles = 1;
+ pobj->m_nSkipFrames = 1;
+ pobj->m_nCreationChance = 0;
+ pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds();
+ break;
+ }
+ }
+
+ return pobj;
+}
+
+CParticleObject *
+CParticleObject::AddObject(tParticleType type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, uint8 numEffectCycles, uint8 skipFrames, uint16 creationChance, uint8 remove)
+{
+ CParticleObject *pobj = pUnusedListHead;
+
+ ASSERT(pobj != nil);
+
+ if ( pobj == nil )
+ {
+ printf("Error: No particle objects available!\n");
+ return nil;
+ }
+
+ MoveToList(&pUnusedListHead, &pCloseListHead, pobj);
+
+ pobj->m_nState = POBJECTSTATE_UPDATE_CLOSE;
+ pobj->m_Type = (eParticleObjectType)-1;
+ pobj->m_ParticleType = type;
+
+ pobj->SetPosition(pos);
+ pobj->m_vecTarget = target;
+
+ pobj->m_nNumEffectCycles = numEffectCycles;
+ pobj->m_nSkipFrames = skipFrames;
+ pobj->m_nCreationChance = creationChance;
+ pobj->m_nFrameCounter = 0;
+
+ pobj->m_bRemove = remove;
+
+ if ( lifeTime != 0 )
+ pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime;
+ else
+ pobj->m_nRemoveTimer = 0;
+
+ pobj->m_Color.alpha = 0;
+
+ pobj->m_fSize = size;
+ pobj->m_fRandVal = 0.0f;
+
+ return pobj;
+}
+
+void
+CParticleObject::RemoveObject(void)
+{
+ switch ( this->m_nState )
+ {
+ case POBJECTSTATE_UPDATE_CLOSE:
+ {
+ MoveToList(&pCloseListHead, &pUnusedListHead, this);
+ this->m_nState = POBJECTSTATE_FREE;
+ break;
+ }
+ case POBJECTSTATE_UPDATE_FAR:
+ {
+ MoveToList(&pFarListHead, &pUnusedListHead, this);
+ this->m_nState = POBJECTSTATE_FREE;
+ break;
+ }
+ }
+}
+
+void
+CParticleObject::UpdateAll(void)
+{
+ {
+ CParticleObject *pobj = pCloseListHead;
+ CParticleObject *nextpobj;
+ if ( pobj != nil )
+ {
+ do
+ {
+ nextpobj = pobj->m_pNext;
+ pobj->UpdateClose();
+ pobj = nextpobj;
+ }
+ while ( nextpobj != nil );
+ }
+ }
+
+ {
+ int32 frame = CTimer::GetFrameCounter() & 31;
+ int32 counter = 0;
+
+ CParticleObject *pobj = pFarListHead;
+ CParticleObject *nextpobj;
+ if ( pobj != nil )
+ {
+ do
+ {
+ nextpobj = pobj->m_pNext;
+
+ if ( counter == frame )
+ {
+ pobj->UpdateFar();
+ frame += 32;
+ }
+
+ counter++;
+
+ pobj = nextpobj;
+ }
+ while ( nextpobj != nil );
+ }
+ }
+}
+
+void CParticleObject::UpdateClose(void)
+{
+ if ( !CGame::playingIntro )
+ {
+ if ( (this->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(100.0f) )
+ {
+ if ( this->m_bRemove )
+ {
+ if ( this->m_Type == POBJECT_FIRE_HYDRANT )
+ CAudioHydrant::Remove(this);
+
+ MoveToList(&pCloseListHead, &pUnusedListHead, this);
+ this->m_nState = POBJECTSTATE_FREE;
+ }
+ else
+ {
+ MoveToList(&pCloseListHead, &pFarListHead, this);
+ this->m_nState = POBJECTSTATE_UPDATE_FAR;
+ }
+
+ return;
+ }
+ }
+
+ if ( ++this->m_nFrameCounter >= this->m_nSkipFrames )
+ {
+ this->m_nFrameCounter = 0;
+
+ int32 randVal;
+ if ( this->m_nCreationChance != 0 )
+ randVal = CGeneral::GetRandomNumber() % this->m_nCreationChance;
+
+ if ( this->m_nCreationChance == 0
+ || randVal == 0 && this->m_nCreationChance < 0
+ || randVal != 0 && this->m_nCreationChance > 0)
+ {
+ switch ( this->m_Type )
+ {
+ case POBJECT_SMALL_FIRE:
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+ CVector flamevel;
+
+ flamevel.x = vel.x;
+ flamevel.y = vel.y;
+ flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*size, 0.1f*size);
+
+ CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, nil, size);
+
+
+ CVector possmoke = pos;
+
+ possmoke.x += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f);
+ possmoke.y += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f);
+ possmoke.z += CGeneral::GetRandomNumberInRange(0.625f* size, size*2.5f);
+
+ CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, possmoke, vel);
+
+ break;
+ }
+
+ case POBJECT_BIG_FIRE:
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+
+ float s = 0.7f*size;
+
+ CVector flamevel;
+
+ flamevel.x = vel.x;
+ flamevel.y = vel.y;
+ flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*s, 0.1f*s);
+
+ float flamesize = 0.8f*size;
+
+ CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, nil, flamesize);
+
+
+ for ( int32 i = 0; i < 4; i++ )
+ {
+ CVector smokepos = pos;
+
+ smokepos.x += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size);
+ smokepos.y += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size);
+ smokepos.z += CGeneral::GetRandomNumberInRange(0.625f* size, 3.5f *size);
+
+ CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, smokepos, vel);
+ }
+
+ break;
+ }
+
+ case POBJECT_FIREBALL_AND_SMOKE:
+ {
+ if ( this->m_pParticle == nil )
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+ CVector expvel = 1.2f*vel;
+ float expsize = 1.2f*size;
+ this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, expvel, nil, expsize);
+ }
+ else
+ {
+ CVector pos = this->GetPosition(); // this->m_pParticle->m_vecPosition ?
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+ CVector veloffset = 0.35f*vel;
+ CVector fireballvel = vel;
+
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ fireballvel.x += CGeneral::GetRandomNumberInRange(-veloffset.x, veloffset.x);
+ fireballvel.y += CGeneral::GetRandomNumberInRange(-veloffset.y, veloffset.y);
+ fireballvel.z += CGeneral::GetRandomNumberInRange(-veloffset.z, veloffset.z);
+
+ CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, nil, size);
+ }
+ }
+
+ break;
+ }
+
+ case POBJECT_ROCKET_TRAIL:
+ {
+ if ( this->m_pParticle == nil )
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+ this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, vel, nil, size);
+ }
+ else
+ {
+ CVector pos = this->m_pParticle->m_vecPosition;
+ CVector vel = this->m_vecTarget;
+ float size = this->m_fSize;
+
+ float fireballsize = size * 1.5f;
+ CVector fireballvel = vel * -0.8f;
+
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, nil, fireballsize);
+ }
+ }
+
+ break;
+ }
+
+ case POBJECT_FIRE_TRAIL:
+ {
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CVector vel = this->m_vecTarget;
+
+ if ( vel.x != 0.0f )
+ vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ if ( vel.y != 0.0f )
+ vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ if ( vel.z != 0.0f )
+ vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, nil, this->m_fSize,
+ CGeneral::GetRandomNumberInRange(-6.0f, 6.0f));
+ }
+
+ break;
+ }
+
+ case POBJECT_PED_WATER_SPLASH:
+ {
+ CRGBA colorsmoke(255, 255, 255, 196);
+
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+
+ for ( int32 i = 0; i < 3; i++ )
+ {
+ int32 angle = 90 * i;
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos;
+ CVector splashvel;
+
+ splashpos = pos + CVector(0.75f*fCos, 0.75f*fSin, 0.0f);
+ splashvel = vel + CVector(0.05f*fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.75f*fCos, 0.75f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.05f*fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));
+
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.75f*-fCos, 0.75f*fSin, 0.0f);
+ splashvel = vel + CVector(0.05f*-fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));
+
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.75f*-fCos, 0.75f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.05f*-fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);
+ }
+
+ for ( int32 i = 0; i < 1; i++ )
+ {
+ int32 angle = 180 * (i + 1);
+
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos;
+ CVector splashvel;
+
+ splashpos = pos + CVector(0.5f*fCos, 0.5f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.5f*fCos, 0.5f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.5f*-fCos, 0.5f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);
+
+
+ splashpos = pos + CVector(0.5f*-fCos, 0.5f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);
+ }
+
+ break;
+ }
+
+ case POBJECT_CAR_WATER_SPLASH:
+ {
+ CRGBA colorsmoke(255, 255, 255, 196);
+
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+
+ float size = CGeneral::GetRandomNumberInRange(1.0f, 2.5f);
+
+ for ( int32 i = 0; i < 3; i++ )
+ {
+ int32 angle = 90 * i;
+
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos;
+ CVector splashvel;
+
+ splashpos = pos + CVector(2.0f*fCos, 2.0f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil, size, colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+
+ splashpos = pos + CVector(2.0f*fCos, 2.0f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil, size, colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+ splashpos = pos + CVector(2.0f*-fCos, 2.0f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil, size, colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+ splashpos = pos + CVector(2.0f*-fCos, 2.0f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, nil, size, colorsmoke);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+ }
+
+ for ( int32 i = 0; i < 1; i++ )
+ {
+ int32 angle = 180 * (i + 1);
+
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos;
+ CVector splashvel;
+
+
+ splashpos = pos + CVector(1.25f*fCos, 1.25f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+ splashpos = pos + CVector(1.25f*fCos, 1.25f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+ splashpos = pos + CVector(1.25f*-fCos, 1.25f*fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+
+ splashpos = pos + CVector(1.25f*-fCos, 1.25f*-fSin, 0.0f);
+ splashvel = vel;
+ splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos;
+ splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin;
+ splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 0.0f, this->m_Color);
+ }
+
+ break;
+ }
+
+ case POBJECT_SPLASHES_AROUND:
+ {
+ CVector pos = this->GetPosition();
+ float size = this->m_fSize;
+
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CVector splashpos = pos;
+
+ splashpos.x += CGeneral::GetRandomNumberInRange(-size, size);
+ splashpos.y += CGeneral::GetRandomNumberInRange(-size, size);
+
+ if ( CGeneral::GetRandomNumber() & 1 )
+ {
+ CParticle::AddParticle(PARTICLE_RAIN_SPLASH, splashpos, CVector(0.0f, 0.0f, 0.0f),
+ nil, 0.1f, this->m_Color);
+ }
+ else
+ {
+ CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashpos, CVector(0.0f, 0.0f, 0.0f),
+ nil, 0.12f, this->m_Color);
+ }
+ }
+
+ break;
+ }
+
+ case POBJECT_FIRE_HYDRANT:
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+
+ if ( (TheCamera.GetPosition() - pos).Magnitude() > 5.0f )
+ {
+ for ( int32 i = 0; i < 1; i++ )
+ {
+ int32 angle = 180 * i;
+
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos, splashvel;
+
+ splashpos = pos + CVector(0.01f*fCos, 0.01f*fSin, 0.0f);
+ splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300);
+
+ splashpos = pos + CVector(0.01f*fCos, 0.01f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300);
+
+ splashpos = pos + CVector(0.01f*-fCos, 0.01f*fSin, 0.0f);
+ splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300);
+
+ splashpos = pos + CVector(0.01f*-fCos, 0.01f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300);
+ }
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CParticle::AddParticle(this->m_ParticleType, pos, vel, nil, 0.0f, this->m_Color);
+ }
+ }
+
+ break;
+ }
+
+ case POBJECT_WATER_FOUNTAIN_VERT:
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+
+ for ( int32 i = 0; i < 2; i++ )
+ {
+ int32 angle = 180 * i;
+
+ float fCos = CParticle::Cos(angle);
+ float fSin = CParticle::Sin(angle);
+
+ CVector splashpos, splashvel;
+
+ splashpos = pos + CVector(0.015f*fCos, 0.015f*fSin, 0.0f);
+ splashvel = vel + CVector(0.015f*fCos, 0.015f*fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000);
+
+ splashpos = pos + CVector(0.015f*fCos, 0.015f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.015f*fCos, 0.015f*-fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000);
+
+ splashpos = pos + CVector(0.015f*-fCos, 0.015f*fSin, 0.0f);
+ splashvel = vel + CVector(0.015f*-fCos, 0.015f*fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000);
+
+ splashpos = pos + CVector(0.015f*-fCos, 0.015f*-fSin, 0.0f);
+ splashvel = vel + CVector(0.015f*-fCos, 0.015f*-fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f));
+
+ CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil,
+ CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000);
+ }
+
+ break;
+ }
+
+ case POBJECT_WATER_FOUNTAIN_HORIZ:
+ {
+ CVector pos = this->GetPosition();
+ CVector vel = this->m_vecTarget;
+
+ for ( int32 i = 0; i < 3; i++ )
+ {
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, nil, 0.001f, this->m_Color, 0, 0, 1, 1000);
+ }
+
+ break;
+ }
+
+ default:
+ {
+ if ( this->m_fRandVal != 0.0f )
+ {
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CVector vel = this->m_vecTarget;
+
+ if ( vel.x != 0.0f )
+ vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ if ( vel.y != 0.0f )
+ vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ if ( vel.z != 0.0f )
+ vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);
+
+ CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, nil,
+ this->m_fSize, this->m_Color);
+ }
+ }
+ else
+ {
+ for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
+ {
+ CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), this->m_vecTarget, nil,
+ this->m_fSize, this->m_Color);
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() )
+ {
+ MoveToList(&pCloseListHead, &pUnusedListHead, this);
+ this->m_nState = POBJECTSTATE_FREE;
+
+ if ( this->m_Type == POBJECT_FIRE_HYDRANT )
+ CAudioHydrant::Remove(this);
+ }
+}
+
+void
+CParticleObject::UpdateFar(void)
+{
+ if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() )
+ {
+ MoveToList(&pFarListHead, &pUnusedListHead, this);
+ this->m_nState = POBJECTSTATE_FREE;
+
+ if ( this->m_Type == POBJECT_FIRE_HYDRANT )
+ CAudioHydrant::Remove(this);
+ }
+
+ CVector2D dist = this->GetPosition() - TheCamera.GetPosition();
+ if ( dist.MagnitudeSqr() < SQR(100.0f)/*10000.0f*/ )
+ {
+ MoveToList(&pFarListHead, &pCloseListHead, this);
+ this->m_nState = POBJECTSTATE_UPDATE_CLOSE;
+ }
+}
+
+bool
+CParticleObject::SaveParticle(uint8 *buffer, uint32 *length)
+{
+ ASSERT( buffer != nil );
+ ASSERT( length != nil );
+
+ int32 numObjects = 0;
+
+ for ( CParticleObject *p = pCloseListHead; p != nil; p = p->m_pNext )
+ ++numObjects;
+
+ for ( CParticleObject *p = pFarListHead; p != nil; p = p->m_pNext )
+ ++numObjects;
+
+ *(int32 *)buffer = numObjects;
+ buffer += sizeof(int32);
+
+ int32 objectsLength = sizeof(CParticleObject) * (numObjects + 1);
+ int32 dataLength = objectsLength + sizeof(int32);
+
+ for ( CParticleObject *p = pCloseListHead; p != nil; p = p->m_pNext )
+ {
+#if 0 // todo better
+ *(CParticleObject*)buffer = *p;
+#else
+ memcpy(buffer, p, sizeof(CParticleObject));
+#endif
+ buffer += sizeof(CParticleObject);
+ }
+
+ for ( CParticleObject *p = pFarListHead; p != nil; p = p->m_pNext )
+ {
+#if 0 // todo better
+ *(CParticleObject*)buffer = *p;
+#else
+ memcpy(buffer, p, sizeof(CParticleObject));
+#endif
+ buffer += sizeof(CParticleObject);
+ }
+
+ *length = dataLength;
+
+ return true;
+}
+
+bool
+CParticleObject::LoadParticle(uint8 *buffer, uint32 length)
+{
+ ASSERT( buffer != nil );
+
+ RemoveAllParticleObjects();
+
+ int32 numObjects = *(int32 *)buffer;
+ buffer += sizeof(int32);
+
+ if ( length != sizeof(CParticleObject) * (numObjects + 1) + sizeof(int32) )
+ return false;
+
+ if ( numObjects == 0 )
+ return true;
+
+
+ int32 i = 0;
+ while ( i < numObjects )
+ {
+ CParticleObject *dst = pUnusedListHead;
+ CParticleObject *src = (CParticleObject *)buffer;
+ buffer += sizeof(CParticleObject);
+
+ if ( dst == nil )
+ return false;
+
+ MoveToList(&pUnusedListHead, &pCloseListHead, dst);
+
+ dst->m_nState = POBJECTSTATE_UPDATE_CLOSE;
+ dst->m_Type = src->m_Type;
+ dst->m_ParticleType = src->m_ParticleType;
+ dst->SetPosition(src->GetPosition());
+ dst->m_vecTarget = src->m_vecTarget;
+ dst->m_nFrameCounter = src->m_nFrameCounter;
+ dst->m_bRemove = src->m_bRemove;
+ dst->m_pParticle = nil;
+ dst->m_nRemoveTimer = src->m_nRemoveTimer;
+ dst->m_Color = src->m_Color;
+ dst->m_fSize = src->m_fSize;
+ dst->m_fRandVal = src->m_fRandVal;
+ dst->m_nNumEffectCycles = src->m_nNumEffectCycles;
+ dst->m_nSkipFrames = src->m_nSkipFrames;
+ dst->m_nCreationChance = src->m_nCreationChance;
+
+ i++;
+ }
+
+ return true;
+}
+
+void
+CParticleObject::RemoveAllExpireableParticleObjects(void)
+{
+ {
+ CParticleObject *pobj = pCloseListHead;
+ CParticleObject *nextpobj;
+ if ( pobj != nil )
+ {
+ do
+ {
+ nextpobj = pobj->m_pNext;
+ if ( pobj->m_nRemoveTimer != 0 )
+ {
+ MoveToList(&pCloseListHead, &pUnusedListHead, pobj);
+ pobj->m_nState = POBJECTSTATE_FREE;
+ }
+ pobj = nextpobj;
+ }
+ while ( nextpobj != nil );
+ }
+ }
+
+ {
+ CParticleObject *pobj = pFarListHead;
+ CParticleObject *nextpobj;
+ if ( pobj != nil )
+ {
+ do
+ {
+ nextpobj = pobj->m_pNext;
+ if ( pobj->m_nRemoveTimer != 0 )
+ {
+ MoveToList(&pFarListHead, &pUnusedListHead, pobj);
+ pobj->m_nState = POBJECTSTATE_FREE;
+ }
+ pobj = nextpobj;
+ }
+ while ( nextpobj != nil );
+ }
+ }
+}
+
+void
+CParticleObject::RemoveAllParticleObjects(void)
+{
+ pUnusedListHead = &gPObjectArray[0];
+
+ pCloseListHead = nil;
+ pFarListHead = nil;
+
+ for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ )
+ {
+ if ( i == 0 )
+ gPObjectArray[i].m_pPrev = nil;
+ else
+ gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1];
+
+ if ( i == MAX_PARTICLEOBJECTS-1 )
+ gPObjectArray[i].m_pNext = nil;
+ else
+ gPObjectArray[i].m_pNext = &gPObjectArray[i + 1];
+
+ gPObjectArray[i].m_nState = POBJECTSTATE_FREE;
+ }
+}
+
+void
+CParticleObject::MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj)
+{
+ ASSERT( from != nil );
+ ASSERT( to != nil );
+ ASSERT( obj != nil );
+
+ if ( obj->m_pPrev == nil )
+ {
+ *from = obj->m_pNext;
+ if ( *from )
+ (*from)->m_pPrev = nil;
+ }
+ else
+ {
+ if ( obj->m_pNext == nil )
+ obj->m_pPrev->m_pNext = nil;
+ else
+ {
+ obj->m_pNext->m_pPrev = obj->m_pPrev;
+ obj->m_pPrev->m_pNext = obj->m_pNext;
+ }
+ }
+
+ obj->m_pNext = *to;
+ obj->m_pPrev = nil;
+ *to = obj;
+
+ if ( obj->m_pNext )
+ obj->m_pNext->m_pPrev = obj;
+}
diff --git a/src/objects/ParticleObject.h b/src/objects/ParticleObject.h
new file mode 100644
index 0000000..f199e53
--- /dev/null
+++ b/src/objects/ParticleObject.h
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "AudioManager.h"
+#include "ParticleType.h"
+#include "Placeable.h"
+
+#define MAX_PARTICLEOBJECTS 70
+#define MAX_AUDIOHYDRANTS 8
+
+enum eParticleObjectType
+{
+ POBJECT_PAVEMENT_STEAM = 0,
+ POBJECT_PAVEMENT_STEAM_SLOWMOTION,
+ POBJECT_WALL_STEAM,
+ POBJECT_WALL_STEAM_SLOWMOTION,
+ POBJECT_DARK_SMOKE,
+ POBJECT_FIRE_HYDRANT,
+ POBJECT_CAR_WATER_SPLASH,
+ POBJECT_PED_WATER_SPLASH,
+ POBJECT_SPLASHES_AROUND,
+ POBJECT_SMALL_FIRE,
+ POBJECT_BIG_FIRE,
+ POBJECT_DRY_ICE,
+ POBJECT_DRY_ICE_SLOWMOTION,
+ POBJECT_WATER_FOUNTAIN_VERT,
+ POBJECT_WATER_FOUNTAIN_HORIZ,
+ POBJECT_FIRE_TRAIL,
+ POBJECT_SMOKE_TRAIL,
+ POBJECT_FIREBALL_AND_SMOKE,
+ POBJECT_ROCKET_TRAIL,
+ POBJECT_EXPLOSION_ONCE,
+ POBJECT_CATALINAS_GUNFLASH,
+ POBJECT_CATALINAS_SHOTGUNFLASH,
+};
+
+enum eParticleObjectState
+{
+ POBJECTSTATE_INITIALISED = 0,
+ POBJECTSTATE_UPDATE_CLOSE,
+ POBJECTSTATE_UPDATE_FAR,
+ POBJECTSTATE_FREE,
+};
+
+class CParticle;
+
+class CParticleObject : public CPlaceable
+{
+public:
+ CParticleObject *m_pNext;
+ CParticleObject *m_pPrev;
+ CParticle *m_pParticle;
+ uint32 m_nRemoveTimer;
+ eParticleObjectType m_Type;
+ tParticleType m_ParticleType;
+ uint8 m_nNumEffectCycles;
+ uint8 m_nSkipFrames;
+ uint16 m_nFrameCounter;
+ uint16 m_nState;
+ CVector m_vecTarget;
+ float m_fRandVal;
+ float m_fSize;
+ CRGBA m_Color;
+ uint8 m_bRemove;
+ int8 m_nCreationChance;
+
+ static CParticleObject *pCloseListHead;
+ static CParticleObject *pFarListHead;
+ static CParticleObject *pUnusedListHead;
+
+ CParticleObject();
+ ~CParticleObject();
+
+ static void Initialise(void);
+
+ static CParticleObject *AddObject(uint16 type, CVector const &pos, uint8 remove);
+ static CParticleObject *AddObject(uint16 type, CVector const &pos, float size, uint8 remove);
+ static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove);
+ static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove);
+ static CParticleObject *AddObject(tParticleType type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, uint8 numEffectCycles, uint8 skipFrames, uint16 creationChance, uint8 remove);
+
+ void RemoveObject(void);
+
+ static void UpdateAll(void);
+ void UpdateClose(void);
+ void UpdateFar(void);
+
+ static bool SaveParticle(uint8 *buffer, uint32 *length);
+ static bool LoadParticle(uint8 *buffer, uint32 length);
+
+ static void RemoveAllExpireableParticleObjects(void);
+ static void RemoveAllParticleObjects(void);
+ static void MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj);
+};
+
+extern CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS];
+
+class CAudioHydrant
+{
+public:
+ int32 AudioEntity;
+ CParticleObject *pParticleObject;
+
+ CAudioHydrant() :
+ AudioEntity(AEHANDLE_NONE),
+ pParticleObject(nil)
+ { }
+
+ static bool Add (CParticleObject *particleobject);
+ static void Remove(CParticleObject *particleobject);
+
+ static CAudioHydrant *Get(int n); // for neo screen droplets
+}; \ No newline at end of file
diff --git a/src/objects/Projectile.cpp b/src/objects/Projectile.cpp
new file mode 100644
index 0000000..fe8b0c6
--- /dev/null
+++ b/src/objects/Projectile.cpp
@@ -0,0 +1,15 @@
+#include "common.h"
+
+#include "Projectile.h"
+
+CProjectile::CProjectile(int32 model) : CObject()
+{
+ m_fMass = 1.0f;
+ m_fTurnMass = 1.0f;
+ m_fAirResistance = 0.99999f;
+ m_fElasticity = 0.75f;
+ m_fBuoyancy = GRAVITY * m_fMass * 0.1f;
+ bExplosionProof = true;
+ SetModelIndex(model);
+ ObjectCreatedBy = MISSION_OBJECT;
+}
diff --git a/src/objects/Projectile.h b/src/objects/Projectile.h
new file mode 100644
index 0000000..4b3eb4b
--- /dev/null
+++ b/src/objects/Projectile.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Object.h"
+
+class CProjectile : public CObject
+{
+public:
+ CProjectile(int32);
+};
diff --git a/src/objects/Stinger.cpp b/src/objects/Stinger.cpp
new file mode 100644
index 0000000..79730c8
--- /dev/null
+++ b/src/objects/Stinger.cpp
@@ -0,0 +1,232 @@
+#include "common.h"
+#include "Stinger.h"
+#include "CopPed.h"
+#include "ModelIndices.h"
+#include "RpAnimBlend.h"
+#include "World.h"
+#include "Automobile.h"
+#include "Bike.h"
+#include "Particle.h"
+#include "AnimBlendAssociation.h"
+#include "General.h"
+
+uint32 NumOfStingerSegments;
+
+/* -- CStingerSegment -- */
+
+CStingerSegment::CStingerSegment()
+{
+ m_fMass = 1.0f;
+ m_fTurnMass = 1.0f;
+ m_fAirResistance = 0.99999f;
+ m_fElasticity = 0.75f;
+ m_fBuoyancy = GRAVITY * m_fMass * 0.1f;
+ bExplosionProof = true;
+ SetModelIndex(MI_PLC_STINGER);
+ ObjectCreatedBy = CONTROLLED_SUB_OBJECT;
+ NumOfStingerSegments++;
+}
+
+CStingerSegment::~CStingerSegment()
+{
+ NumOfStingerSegments--;
+}
+
+/* -- CStinger -- */
+
+CStinger::CStinger()
+{
+ bIsDeployed = false;
+}
+
+void
+CStinger::Init(CPed *pPed)
+{
+ int32 i;
+
+ pOwner = pPed;
+ for (i = 0; i < NUM_STINGER_SEGMENTS; i++) {
+ pSpikes[i] = new CStingerSegment;
+ pSpikes[i]->bUsesCollision = false;
+ }
+ bIsDeployed = true;
+ m_vPos = pPed->GetPosition();
+ m_vPos.z -= 1.0f;
+ m_fMax_Z = Atan2(-pPed->GetForward().x, pPed->GetForward().y) + HALFPI;
+
+ for (i = 0; i < NUM_STINGER_SEGMENTS; i++) {
+ pSpikes[i]->SetOrientation(0.0f, 0.0f, Atan2(-pPed->GetForward().x, pPed->GetForward().y));
+ pSpikes[i]->SetPosition(m_vPos);
+ }
+
+ CVector2D fwd2d(pPed->GetForward().x, pPed->GetForward().y);
+
+ for (i = 0; i < ARRAY_SIZE(m_vPositions); i++)
+ m_vPositions[i] = fwd2d * 1.8f * Sin(DEGTORAD(i));
+
+ m_nSpikeState = STINGERSTATE_NONE;
+ m_nTimeOfDeploy = CTimer::GetTimeInMilliseconds();
+}
+
+void
+CStinger::Remove()
+{
+ if (!bIsDeployed) return;
+
+ for (int32 i = 0; i < NUM_STINGER_SEGMENTS; i++) {
+ CStingerSegment *spikeSegment = pSpikes[i];
+ if (spikeSegment->m_entryInfoList.first != nil)
+ spikeSegment->bRemoveFromWorld = true;
+ else
+ delete spikeSegment;
+ }
+ bIsDeployed = false;
+}
+
+void
+CStinger::Deploy(CPed *pPed)
+{
+ if (NumOfStingerSegments < NUM_STINGER_SEGMENTS*2 && !pPed->bInVehicle && pPed->IsPedInControl()) {
+ if (!bIsDeployed && RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_THROW_UNDER) == nil) {
+ Init(pPed);
+ pPed->SetPedState(PED_DEPLOY_STINGER);
+ CAnimManager::AddAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_THROW_UNDER);
+ }
+ }
+}
+
+void
+CStinger::CheckForBurstTyres()
+{
+ CVector firstPos = pSpikes[0]->GetPosition();
+ firstPos.z += 0.2f;
+ CVector lastPos = pSpikes[NUM_STINGER_SEGMENTS - 1]->GetPosition();
+ lastPos.z += 0.2f;
+ float dist = (lastPos - firstPos).Magnitude();
+ if (dist < 0.1f) return;
+
+ CVehicle *vehsInRange[16];
+ int16 numObjects;
+ CEntity entity;
+
+ CWorld::FindObjectsInRange((lastPos + firstPos) / 2.0f,
+ dist, true, &numObjects, 15, (CEntity**)vehsInRange,
+ false, true, false, false, false);
+
+ for (int32 i = 0; i < numObjects; i++) {
+ CAutomobile *pAutomobile = nil;
+ CBike *pBike = nil;
+
+ if (vehsInRange[i]->IsCar())
+ pAutomobile = (CAutomobile*)vehsInRange[i];
+ else if (vehsInRange[i]->IsBike())
+ pBike = (CBike*)vehsInRange[i];
+
+ if (pAutomobile == nil && pBike == nil) continue;
+
+ float maxWheelDistToSpike = sq(((CVehicleModelInfo*)CModelInfo::GetModelInfo(vehsInRange[i]->GetModelIndex()))->m_wheelScale + 0.1f);
+
+ for (int wheelId = 0; wheelId < 4; wheelId++) {
+ if ((pAutomobile != nil && pAutomobile->m_aSuspensionSpringRatioPrev[wheelId] < 1.0f) ||
+ (pBike != nil && pBike->m_aSuspensionSpringRatioPrev[wheelId] < 1.0f)) {
+ CVector vecWheelPos;
+ if (pAutomobile != nil)
+ vecWheelPos = pAutomobile->m_aWheelColPoints[wheelId].point;
+ else if (pBike != nil)
+ vecWheelPos = pBike->m_aWheelColPoints[wheelId].point;
+
+ for (int32 spike = 0; spike < NUM_STINGER_SEGMENTS; spike++) {
+ if ((pSpikes[spike]->GetPosition() - vecWheelPos).Magnitude() < maxWheelDistToSpike) {
+ if (pBike) {
+ if (wheelId < 2)
+ vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LF, true);
+ else
+ vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LR, true);
+ }
+ else {
+ switch (wheelId) {
+ case 0: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LF, true); break;
+ case 1: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LR, true); break;
+ case 2: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_RF, true); break;
+ case 3: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_RR, true); break;
+ }
+ }
+ vecWheelPos.z += 0.15f;
+ for (int j = 0; j < 4; j++)
+ CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, vecWheelPos, vehsInRange[i]->GetRight() * 0.1f);
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+CStinger::Process()
+{
+ switch (m_nSpikeState)
+ {
+ case STINGERSTATE_NONE:
+ if (pOwner != nil
+ && !pOwner->bInVehicle
+ && pOwner->GetPedState() == PED_DEPLOY_STINGER
+ && RpAnimBlendClumpGetAssociation(pOwner->GetClump(), ANIM_STD_THROW_UNDER)->currentTime > 0.39f)
+ {
+ m_nSpikeState = STINGERSTATE_DEPLOYING;
+ for (int i = 0; i < NUM_STINGER_SEGMENTS; i++)
+ CWorld::Add(pSpikes[i]);
+ pOwner->SetIdle();
+ }
+ break;
+ case STINGERSTATE_DEPLOYED:
+ if (pOwner != nil && pOwner->m_nPedType == PEDTYPE_COP)
+ ((CCopPed*)pOwner)->m_bThrowsSpikeTrap = false;
+ break;
+ case STINGERSTATE_UNDEPLOYING:
+ if (CTimer::GetTimeInMilliseconds() > m_nTimeOfDeploy + 2500)
+ m_nSpikeState = STINGERSTATE_REMOVE;
+ // no break
+ case STINGERSTATE_DEPLOYING:
+ if (m_nSpikeState == STINGERSTATE_DEPLOYING && CTimer::GetTimeInMilliseconds() > m_nTimeOfDeploy + 2500)
+ m_nSpikeState = STINGERSTATE_DEPLOYED;
+ else {
+ float progress = (CTimer::GetTimeInMilliseconds() - m_nTimeOfDeploy) / 2500.0f;
+ if (m_nSpikeState != STINGERSTATE_DEPLOYING)
+ progress = 1.0f - progress;
+
+ float degangle = progress * ARRAY_SIZE(m_vPositions);
+ float angle1 = m_fMax_Z + DEGTORAD(degangle);
+ float angle2 = m_fMax_Z - DEGTORAD(degangle);
+ int pos = clamp(degangle, 0, ARRAY_SIZE(m_vPositions)-1);
+
+ CVector2D pos2d = m_vPositions[pos];
+ CVector pos3d = m_vPos;
+ CColPoint colPoint;
+ CEntity *pEntity;
+ if (CWorld::ProcessVerticalLine(CVector(pos3d.x, pos3d.y, pos3d.z - 10.0f), pos3d.z, colPoint, pEntity, true, false, false, false, true, false, nil))
+ pos3d.z = colPoint.point.z + 0.15f;
+
+ angle1 = CGeneral::LimitRadianAngle(angle1);
+ angle2 = CGeneral::LimitRadianAngle(angle2);
+
+ for (int spike = 0; spike < NUM_STINGER_SEGMENTS; spike++) {
+ if (CWorld::TestSphereAgainstWorld(pos3d + CVector(pos2d.x, pos2d.y, 0.6f), 0.3f, nil, true, false, false, true, false, false))
+ pos2d = CVector2D(0.0f, 0.0f);
+
+ if (spike % 2 == 0) {
+ pSpikes[spike]->SetOrientation(0.0f, 0.0f, angle1);
+ pos3d.x += pos2d.x;
+ pos3d.y += pos2d.y;
+ } else {
+ pSpikes[spike]->SetOrientation(0.0f, 0.0f, angle2);
+ }
+ pSpikes[spike]->SetPosition(pos3d);
+ }
+ }
+ break;
+ case STINGERSTATE_REMOVE:
+ Remove();
+ break;
+ }
+ CheckForBurstTyres();
+} \ No newline at end of file
diff --git a/src/objects/Stinger.h b/src/objects/Stinger.h
new file mode 100644
index 0000000..250cf62
--- /dev/null
+++ b/src/objects/Stinger.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "Object.h"
+
+class CStingerSegment : public CObject
+{
+public:
+ CStingerSegment();
+ ~CStingerSegment();
+};
+
+#define NUM_STINGER_SEGMENTS (12)
+
+enum {
+ STINGERSTATE_NONE = 0,
+ STINGERSTATE_DEPLOYING,
+ STINGERSTATE_DEPLOYED,
+ STINGERSTATE_UNDEPLOYING,
+ STINGERSTATE_REMOVE,
+};
+
+class CStinger
+{
+public:
+ bool bIsDeployed;
+ uint32 m_nTimeOfDeploy;
+ CVector m_vPos;
+ float m_fMax_Z;
+ float m_fMin_Z;
+ CVector2D m_vPositions[60];
+ CStingerSegment *pSpikes[NUM_STINGER_SEGMENTS];
+ class CPed *pOwner;
+ uint8 m_nSpikeState;
+ CStinger();
+ void Init(CPed *pPed);
+ void Remove();
+ void Deploy(CPed *pPed);
+ void CheckForBurstTyres();
+ void Process();
+}; \ No newline at end of file