summaryrefslogtreecommitdiffstats
path: root/src/vehicles
diff options
context:
space:
mode:
Diffstat (limited to 'src/vehicles')
-rw-r--r--src/vehicles/Automobile.cpp44
-rw-r--r--src/vehicles/Bike.h15
-rw-r--r--src/vehicles/Boat.cpp693
-rw-r--r--src/vehicles/Boat.h49
-rw-r--r--src/vehicles/CarGen.cpp258
-rw-r--r--src/vehicles/CarGen.h54
-rw-r--r--src/vehicles/Cranes.cpp671
-rw-r--r--src/vehicles/Cranes.h97
-rw-r--r--src/vehicles/Floater.cpp2
-rw-r--r--src/vehicles/Vehicle.cpp49
-rw-r--r--src/vehicles/Vehicle.h5
11 files changed, 1818 insertions, 119 deletions
diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp
index e6b936f6..257c8d33 100644
--- a/src/vehicles/Automobile.cpp
+++ b/src/vehicles/Automobile.cpp
@@ -182,17 +182,17 @@ CAutomobile::CAutomobile(int32 id, uint8 CreatedBy)
m_weaponDoorTimerRight = m_weaponDoorTimerLeft;
if(GetModelIndex() == MI_DODO){
- RpAtomicSetFlags(GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
CMatrix mat1;
mat1.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
CMatrix mat2(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
mat1.GetPosition() += CVector(mat2.GetPosition().x + 0.1f, 0.0f, mat2.GetPosition().z);
mat1.UpdateRW();
}else if(GetModelIndex() == MI_MIAMI_SPARROW || GetModelIndex() == MI_MIAMI_RCRAIDER){
- RpAtomicSetFlags(GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
- RpAtomicSetFlags(GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0);
- RpAtomicSetFlags(GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
- RpAtomicSetFlags(GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
}else if(GetModelIndex() == MI_RHINO){
bExplosionProof = true;
bBulletProof = true;
@@ -253,7 +253,7 @@ CAutomobile::ProcessControl(void)
ProcessCarAlarm();
- // Scan if this car is committing a crime that the police can see
+ // Scan if this car sees the player committing any crimes
if(m_status != STATUS_ABANDONED && m_status != STATUS_WRECKED &&
m_status != STATUS_PLAYER && m_status != STATUS_PLAYER_REMOTE && m_status != STATUS_PLAYER_DISABLED){
switch(GetModelIndex())
@@ -356,7 +356,7 @@ CAutomobile::ProcessControl(void)
PruneReferences();
- if(m_status == STATUS_PLAYER && CRecordDataForChase::Status != RECORDSTATE_1)
+ if(m_status == STATUS_PLAYER && CRecordDataForChase::IsRecording())
DoDriveByShootings();
}
break;
@@ -667,7 +667,7 @@ CAutomobile::ProcessControl(void)
if(!strongGrip1 && !CVehicle::bCheat3)
gripCheat = false;
float acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat);
- acceleration /= fForceMultiplier;
+ acceleration /= m_fForceMultiplier;
// unused
if(GetModelIndex() == MI_MIAMI_RCBARON ||
@@ -718,7 +718,7 @@ CAutomobile::ProcessControl(void)
else
traction = 0.004f;
traction *= pHandling->fTractionMultiplier / 4.0f;
- traction /= fForceMultiplier;
+ traction /= m_fForceMultiplier;
if(CVehicle::bCheat3)
traction *= 4.0f;
@@ -1727,9 +1727,9 @@ CAutomobile::PreRender(void)
// bright lights
if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK && !bNoBrightHeadLights)
- CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + 4);
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT);
if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK && !bNoBrightHeadLights)
- CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + 4);
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT);
// Taillights
@@ -1798,9 +1798,9 @@ CAutomobile::PreRender(void)
// bright lights
if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 8);
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 8);
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
// Light shadows
if(!alarmOff){
@@ -1873,9 +1873,9 @@ CAutomobile::PreRender(void)
CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 4);
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT);
if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 4);
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT);
}else{
// braking
if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
@@ -1889,9 +1889,9 @@ CAutomobile::PreRender(void)
CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 8);
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
- CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + 8);
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
}
}else{
if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
@@ -2814,7 +2814,7 @@ CAutomobile::ProcessBuoyancy(void)
CVector impulse, point;
if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){
- m_flagD8 = true;
+ bTouchingWater = true;
ApplyMoveForce(impulse);
ApplyTurnForce(impulse, point);
@@ -2899,7 +2899,7 @@ CAutomobile::ProcessBuoyancy(void)
}
}else{
bIsInWater = false;
- m_flagD8 = false;
+ bTouchingWater = false;
static RwRGBA splashCol = {155, 155, 185, 196};
static RwRGBA smokeCol = {255, 255, 255, 255};
@@ -3791,7 +3791,7 @@ CAutomobile::BlowUpCar(CEntity *culprit)
}
ChangeLawEnforcerState(false);
- gFireManager.StartFire(this, culprit, 0.8f, 1); // TODO
+ gFireManager.StartFire(this, culprit, 0.8f, true);
CDarkel::RegisterCarBlownUpByPlayer(this);
if(GetModelIndex() == MI_RCBANDIT)
CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR_QUICK, GetPosition(), 0);
@@ -4054,7 +4054,7 @@ CAutomobile::HasCarStoppedBecauseOfLight(void)
if(ThePaths.m_connections[curnode->firstLink + i] == AutoPilot.m_nNextRouteNode)
break;
if(i < curnode->numLinks &&
- ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) // TODO
+ ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)
return true;
}
@@ -4064,7 +4064,7 @@ CAutomobile::HasCarStoppedBecauseOfLight(void)
if(ThePaths.m_connections[curnode->firstLink + i] == AutoPilot.m_nPrevRouteNode)
break;
if(i < curnode->numLinks &&
- ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) // TODO
+ ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)
return true;
}
diff --git a/src/vehicles/Bike.h b/src/vehicles/Bike.h
new file mode 100644
index 00000000..4e7e5a0e
--- /dev/null
+++ b/src/vehicles/Bike.h
@@ -0,0 +1,15 @@
+#pragma once
+
+// some miami bike leftovers
+
+enum eBikeNodes {
+ BIKE_NODE_NONE,
+ BIKE_CHASSIS,
+ BIKE_FORKS_FRONT,
+ BIKE_FORKS_REAR,
+ BIKE_WHEEL_FRONT,
+ BIKE_WHEEL_REAR,
+ BIKE_MUDGUARD,
+ BIKE_HANDLEBARS,
+ BIKE_NUM_NODES
+}; \ No newline at end of file
diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp
index 6d584017..0159d168 100644
--- a/src/vehicles/Boat.cpp
+++ b/src/vehicles/Boat.cpp
@@ -1,12 +1,25 @@
#include "common.h"
#include "patcher.h"
-#include "Boat.h"
+#include "General.h"
+#include "Timecycle.h"
#include "HandlingMgr.h"
+#include "CarCtrl.h"
#include "RwHelper.h"
#include "ModelIndices.h"
+#include "VisibilityPlugins.h"
+#include "DMAudio.h"
+#include "Camera.h"
+#include "Darkel.h"
+#include "Explosion.h"
+#include "Particle.h"
#include "WaterLevel.h"
-#include "Pools.h"
+#include "Floater.h"
#include "World.h"
+#include "Pools.h"
+#include "Pad.h"
+#include "Boat.h"
+
+#define INVALID_ORIENTATION (-9999.99f)
float &fShapeLength = *(float*)0x600E78;
float &fShapeTime = *(float*)0x600E7C;
@@ -19,10 +32,6 @@ float WAKE_LIFETIME = 400.0f;
CBoat * (&CBoat::apFrameWakeGeneratingBoats)[4] = *(CBoat * (*)[4])*(uintptr*)0x8620E0;
-WRAPPER void CBoat::ProcessControl() { EAXJMP(0x53EF10); }
-WRAPPER void CBoat::ProcessControlInputs(uint8) { EAXJMP(0x53EC70); }
-WRAPPER void CBoat::BlowUpCar(CEntity* ent) { EAXJMP(0x541CB0); }
-
CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner)
{
CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi);
@@ -47,35 +56,31 @@ CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner)
m_fGasPedal = 0.0f;
m_fBrakePedal = 0.0f;
- field_288 = 0.25f;
- field_28C = 0.35f;
- field_290 = 0.7f;
- field_294 = 0.998f;
- field_298 = 0.999f;
- field_29C = 0.85f;
- field_2A0 = 0.96f;
- field_2A4 = 0.96f;
+ m_fPropellerZ = 0.25f;
+ m_fPropellerY = 0.35f;
+ m_waterMoveDrag = CVector(0.7f, 0.998f, 0.999f);
+ m_waterTurnDrag = CVector(0.85f, 0.96f, 0.96f);
_unk2 = false;
- m_fTurnForceZ = 7.0f;
- field_2FC = 7.0f;
- m_vecMoveForce = CVector(0.0f, 0.0f, 0.0f);
+ m_fVolumeUnderWater = 7.0f;
+ m_fPrevVolumeUnderWater = 7.0f;
+ m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f);
- field_300 = 0;
- m_bBoatFlag1 = true;
- m_bBoatFlag2 = true;
+ m_nDeltaVolumeUnderWater = 0;
+ bBoatInWater = true;
+ bPropellerInWater = true;
bIsInWater = true;
unk1 = 0.0f;
m_bIsAnchored = true;
- field_2C4 = -9999.99f;
- m_flagD8 = true;
- field_2CC = 0.0f;
- field_2D0 = 0;
+ m_fOrientation = INVALID_ORIENTATION;
+ bTouchingWater = true;
+ m_fDamage = 0.0f;
+ m_pSetOnFireEntity = nil;
m_nNumWakePoints = 0;
- for (int16 i = 0; i < 32; i++)
+ for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++)
m_afWakePointLifeTime[i] = 0.0f;
m_nAmmoInClip = 20;
@@ -94,7 +99,542 @@ CBoat::GetComponentWorldPosition(int32 component, CVector &pos)
pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component]));
}
-RxObjSpace3DVertex KeepWaterOutVertices[4];
+void
+CBoat::ProcessControl(void)
+{
+ if(m_nZoneLevel > LEVEL_NONE && m_nZoneLevel != CCollision::ms_collisionInMemory)
+ return;
+
+ bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f;
+
+ PruneWakeTrail();
+
+ int r, g, b;
+ RwRGBA splashColor, jetColor;
+ r = 114.75f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed());
+ g = 114.75f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen());
+ b = 114.75f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue());
+ r = clamp(r, 0, 255);
+ g = clamp(g, 0, 255);
+ b = clamp(b, 0, 255);
+ splashColor.red = r;
+ splashColor.green = g;
+ splashColor.blue = b;
+ splashColor.alpha = CGeneral::GetRandomNumberInRange(128, 150);
+
+ r = 242.25f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed());
+ g = 242.25f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen());
+ b = 242.25f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue());
+ r = clamp(r, 0, 255);
+ g = clamp(g, 0, 255);
+ b = clamp(b, 0, 255);
+ jetColor.red = r;
+ jetColor.green = g;
+ jetColor.blue = b;
+ jetColor.alpha = CGeneral::GetRandomNumberInRange(96, 128);
+
+ CGeneral::GetRandomNumber(); // unused
+
+ ProcessCarAlarm();
+
+ switch(m_status){
+ case STATUS_PLAYER:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ ProcessControlInputs(0);
+ if(GetModelIndex() == MI_PREDATOR)
+ DoFixedMachineGuns();
+ break;
+ case STATUS_SIMPLE:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ CPhysical::ProcessControl();
+ bBoatInWater = true;
+ bPropellerInWater = true;
+ bIsInWater = true;
+ return;
+ case STATUS_PHYSICS:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ CCarCtrl::SteerAIBoatWithPhysics(this);
+ break;
+ case STATUS_ABANDONED:
+ case STATUS_WRECKED:
+ bBoatInWater = true;
+ bPropellerInWater = true;
+ bIsInWater = true;
+ m_fSteerAngle = 0.0;
+ bIsHandbrakeOn = false;
+ m_fBrakePedal = 0.5f;
+ m_fGasPedal = 0.0f;
+ if((GetPosition() - CWorld::Players[CWorld::PlayerInFocus].GetPos()).Magnitude() > 150.0f){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ return;
+ }
+ break;
+ }
+
+ float collisionDamage = pHandling->fCollisionDamageMultiplier * m_fDamageImpulse;
+ if(collisionDamage > 25.0f && m_status != STATUS_WRECKED && m_fHealth >= 150.0f){
+ float prevHealth = m_fHealth;
+ if(this == FindPlayerVehicle()){
+ if(bTakeLessDamage)
+ m_fHealth -= (collisionDamage-25.0f)/6.0f;
+ else
+ m_fHealth -= (collisionDamage-25.0f)/2.0f;
+ }else{
+ if(collisionDamage > 60.0f && pDriver)
+ pDriver->Say(SOUND_PED_CAR_COLLISION);
+ if(bTakeLessDamage)
+ m_fHealth -= (collisionDamage-25.0f)/12.0f;
+ else
+ m_fHealth -= (collisionDamage-25.0f)/4.0f;
+ }
+
+ if(m_fHealth <= 0.0f && prevHealth > 0.0f){
+ m_fHealth = 1.0f;
+ m_pSetOnFireEntity = m_pDamageEntity;
+ }
+ }
+
+ // Damage particles
+ if(m_fHealth <= 600.0f && m_status != STATUS_WRECKED &&
+ Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f &&
+ Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){
+ float speedSq = m_vecMoveSpeed.MagnitudeSqr();
+ CVector smokeDir = 0.8f*m_vecMoveSpeed;
+ CVector smokePos;
+ switch(GetModelIndex()){
+ case MI_SPEEDER:
+ smokePos = CVector(0.4f, -2.4f, 0.8f);
+ smokeDir += 0.05f*GetRight();
+ smokeDir.z += 0.2f*m_vecMoveSpeed.z;
+ break;
+ case MI_REEFER:
+ smokePos = CVector(2.0f, -1.0f, 0.5f);
+ smokeDir += 0.07f*GetRight();
+ break;
+ case MI_PREDATOR:
+ default:
+ smokePos = CVector(-1.5f, -0.5f, 1.2f);
+ smokeDir += -0.08f*GetRight();
+ break;
+ }
+
+ smokePos = GetMatrix() * smokePos;
+
+ // On fire
+ if(m_fHealth < 150.0f){
+ CParticle::AddParticle(PARTICLE_CARFLAME, smokePos,
+ CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(2.25f/200.0f, 0.09f)),
+ nil, 0.9f);
+ CVector smokePos2 = smokePos;
+ smokePos2.x += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
+ smokePos2.y += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
+ smokePos2.z += CGeneral::GetRandomNumberInRange(2.25f/4.0f, 2.25f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, smokePos2, CVector(0.0f, 0.0f, 0.0f));
+
+ m_fDamage += CTimer::GetTimeStepInMilliseconds();
+ if(m_fDamage > 5000.0f)
+ BlowUpCar(m_pSetOnFireEntity);
+ }
+
+ if(speedSq < 0.25f && (CTimer::GetFrameCounter() + m_randomSeed) & 1)
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, smokePos, smokeDir);
+ if(speedSq < 0.25f && m_fHealth <= 350.0f)
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir);
+ }
+
+ CPhysical::ProcessControl();
+
+ CVector buoyanceImpulse(0.0f, 0.0f, 0.0f);
+ CVector buoyancePoint(0.0f, 0.0f, 0.0f);
+ if(mod_Buoyancy.ProcessBuoyancy(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse)){
+ // Process boat in water
+ if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){
+ bBoatInWater = true;
+ bIsInWater = true;
+ }else{
+ bBoatInWater = false;
+ bIsInWater = false;
+ }
+
+ m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater;
+ m_vecBuoyancePoint = buoyancePoint;
+ ApplyMoveForce(buoyanceImpulse);
+ if(!onLand)
+ ApplyTurnForce(buoyanceImpulse, buoyancePoint);
+
+ if(!onLand && bBoatInWater && GetUp().z > 0.0f){
+ float impulse;
+ if(m_fGasPedal > 0.05f)
+ impulse = m_vecMoveSpeed.MagnitudeSqr()*pHandling->fSuspensionForceLevel*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f*m_fGasPedal;
+ else
+ impulse = 0.0f;
+ impulse = min(impulse, GRAVITY*pHandling->fSuspensionDampingLevel*m_fMass*CTimer::GetTimeStep());
+ ApplyMoveForce(impulse*GetUp());
+ ApplyTurnForce(impulse*GetUp(), buoyancePoint - pHandling->fSuspensionBias*GetForward());
+ }
+
+ // Handle boat moving forward
+ if(Abs(m_fGasPedal) > 0.05f || m_vecMoveSpeed.Magnitude() > 0.01f){
+ if(bBoatInWater)
+ AddWakePoint(GetPosition());
+
+ float steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward());
+ if(m_modelIndex == MI_GHOST)
+ steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward())*0.3f;
+ if(steerFactor < 0.0f) steerFactor = 0.0f;
+
+ CVector propeller(0.0f, -pHandling->Dimension.y*m_fPropellerY, -pHandling->Dimension.z*m_fPropellerZ);
+ propeller = Multiply3x3(GetMatrix(), propeller);
+ CVector propellerWorld = GetPosition() + propeller;
+
+ float steerSin = Sin(-m_fSteerAngle * steerFactor);
+ float steerCos = Cos(-m_fSteerAngle * steerFactor);
+ float waterLevel;
+ CWaterLevel::GetWaterLevel(propellerWorld, &waterLevel, true);
+ if(propellerWorld.z-0.5f < waterLevel){
+ float propellerDepth = waterLevel - (propellerWorld.z - 0.5f);
+ if(propellerDepth > 1.0f)
+ propellerDepth = 1.0f;
+ else
+ propellerDepth = SQR(propellerDepth);
+ bPropellerInWater = true;
+
+ if(Abs(m_fGasPedal) > 0.05f){
+ CVector forceDir = Multiply3x3(GetMatrix(), CVector(-steerSin, steerCos, -Abs(m_fSteerAngle)));
+ CVector force = propellerDepth * m_fGasPedal * 40.0f * pHandling->Transmission.fEngineAcceleration * pHandling->fMass * forceDir;
+ if(force.z > 0.2f)
+ force.z = SQR(1.2f - force.z) + 0.2f;
+ if(onLand){
+ if(m_fGasPedal < 0.0f){
+ force.x *= 5.0f;
+ force.y *= 5.0f;
+ }
+ if(force.z < 0.0f)
+ force.z = 0.0f;
+ ApplyMoveForce(force * CTimer::GetTimeStep());
+ }else{
+ ApplyMoveForce(force * CTimer::GetTimeStep());
+ ApplyTurnForce(force * CTimer::GetTimeStep(), propeller - pHandling->fTractionBias*GetUp());
+ float rightForce = DotProduct(GetRight(), force);
+ ApplyTurnForce(-rightForce*GetRight() * CTimer::GetTimeStep(), GetUp());
+ }
+
+ // Spray some particles
+ CVector jetDir = -0.04f * force;
+ if(m_fGasPedal > 0.0f){
+ if(m_status == STATUS_PLAYER){
+ bool cameraHack = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
+ TheCamera.WhoIsInControlOfTheCamera == CAMCONTROL_OBBE;
+ CVector sternPos = GetColModel()->boundingBox.min;
+ sternPos.x = 0.0f;
+ sternPos.z = 0.0f;
+ sternPos = Multiply3x3(GetMatrix(), sternPos);
+
+ CVector jetPos = GetPosition() + sternPos;
+ if(cameraHack)
+ jetPos.z = 1.0f;
+ else
+ jetPos.z = 0.0f;
+
+ CVector wakePos = GetPosition() + sternPos;
+ wakePos.z -= 0.65f;
+
+ CVector wakeDir = 0.75f * jetDir;
+
+ CParticle::AddParticle(PARTICLE_BOAT_THRUSTJET, jetPos, jetDir, nil, 0.0f, jetColor);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, jetPos, 0.25f * jetDir, nil, 1.0f, splashColor,
+ CGeneral::GetRandomNumberInRange(0, 30),
+ CGeneral::GetRandomNumberInRange(0, 90), 3);
+ if(!cameraHack)
+ CParticle::AddParticle(PARTICLE_BOAT_WAKE, wakePos, wakeDir, nil, 0.0f, jetColor);
+ }else if((CTimer::GetFrameCounter() + m_randomSeed) & 1){
+ jetDir.z = 0.018f;
+ jetDir.x *= 0.01f;
+ jetDir.y *= 0.01f;
+ propellerWorld.z += 1.5f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.5f, jetColor);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.1f * jetDir, nil, 0.5f, splashColor,
+ CGeneral::GetRandomNumberInRange(0, 30),
+ CGeneral::GetRandomNumberInRange(0, 90), 3);
+ }
+ }
+ }else if(!onLand){
+ float force = 50.0f*DotProduct(m_vecMoveSpeed, GetForward());
+ if(force > 10.0f) force = 10.0f;
+ CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f));
+ ApplyMoveForce(propellerForce * CTimer::GetTimeStep()*0.5f);
+ ApplyTurnForce(propellerForce * CTimer::GetTimeStep()*0.5f, propeller);
+ }
+ }else
+ bPropellerInWater = false;
+ }
+
+ // Slow down or push down boat as it approaches the world limits
+ m_vecMoveSpeed.x = min(m_vecMoveSpeed.x, -(GetPosition().x - 1900.0f)*0.01f); // east
+ m_vecMoveSpeed.x = max(m_vecMoveSpeed.x, -(GetPosition().x - -1515.0f)*0.01f); // west
+ m_vecMoveSpeed.y = min(m_vecMoveSpeed.y, -(GetPosition().y - 600.0f)*0.01f); // north
+ m_vecMoveSpeed.y = max(m_vecMoveSpeed.y, -(GetPosition().y - -1900.0f)*0.01f); // south
+
+ if(!onLand && bBoatInWater)
+ ApplyWaterResistance();
+
+ // No idea what exactly is going on here besides drag in YZ
+ float fx = Pow(m_waterTurnDrag.x, CTimer::GetTimeStep());
+ float fy = Pow(m_waterTurnDrag.y, CTimer::GetTimeStep());
+ float fz = Pow(m_waterTurnDrag.z, CTimer::GetTimeStep());
+ m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space
+ // TODO: figure this out
+ float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx;
+ m_vecTurnSpeed.y *= fy;
+ m_vecTurnSpeed.z *= fz;
+ float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass;
+ m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world
+ CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
+ ApplyTurnForce(CVector(0.0f, 0.0f, forceUp), com + GetForward());
+
+ m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000;
+
+ // Falling into water
+ if(!onLand && bBoatInWater && GetUp().z > 0.0f && m_nDeltaVolumeUnderWater > 200){
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, m_nDeltaVolumeUnderWater);
+
+ float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.0004f;
+ if(speedUp + m_vecMoveSpeed.z > pHandling->fBrakeDeceleration)
+ speedUp = pHandling->fBrakeDeceleration - m_vecMoveSpeed.z;
+ if(speedUp < 0.0f) speedUp = 0.0f;
+ float speedFwd = DotProduct(m_vecMoveSpeed, GetForward());
+ speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fTractionLoss;
+ CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp);
+ CVector splashImpulse = speed * m_fMass;
+ ApplyMoveForce(splashImpulse);
+ ApplyTurnForce(splashImpulse, buoyancePoint);
+ }
+
+ // Spray particles on sides of boat
+ if(m_nDeltaVolumeUnderWater > 75){
+ float speed = m_vecMoveSpeed.Magnitude();
+ float splash1Size = speed;
+ float splash2Size = m_nDeltaVolumeUnderWater * 0.005f * 0.2f;
+ float front = 0.9f * GetColModel()->boundingBox.max.y;
+ if(splash1Size > 0.75f) splash1Size = 0.75f;
+
+ CVector dir, pos;
+
+ // right
+ dir = -0.5f*m_vecMoveSpeed;
+ dir.z += 0.1f*speed;
+ dir += 0.5f*GetRight()*speed;
+ pos = front*GetForward() + 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint;
+ CWaterLevel::GetWaterLevel(pos, &pos.z, true);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor,
+ CGeneral::GetRandomNumberInRange(0, 30),
+ CGeneral::GetRandomNumberInRange(0, 90), 1);
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor);
+
+ // left
+ dir = -0.5f*m_vecMoveSpeed;
+ dir.z += 0.1f*speed;
+ dir -= 0.5f*GetRight()*speed;
+ pos = front*GetForward() - 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint;
+ CWaterLevel::GetWaterLevel(pos, &pos.z, true);
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor,
+ CGeneral::GetRandomNumberInRange(0, 30),
+ CGeneral::GetRandomNumberInRange(0, 90), 1);
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor);
+ }
+
+ m_fPrevVolumeUnderWater = m_fVolumeUnderWater;
+ }else{
+ bBoatInWater = false;
+ bIsInWater = false;
+ }
+
+ if(m_bIsAnchored){
+ m_vecMoveSpeed.x = 0.0f;
+ m_vecMoveSpeed.y = 0.0f;
+
+ if(m_fOrientation == INVALID_ORIENTATION){
+ m_fOrientation = GetForward().Heading();
+ }else{
+ // is this some inlined CPlaceable method?
+ CVector pos = GetPosition();
+ GetMatrix().RotateZ(m_fOrientation - GetForward().Heading());
+ GetPosition() = pos;
+ }
+ }
+
+ ProcessDelayedExplosion();
+}
+
+void
+CBoat::ProcessControlInputs(uint8 pad)
+{
+ m_nPadID = pad;
+ if(m_nPadID > 3)
+ m_nPadID = 3;
+
+ m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f;
+ m_fBrake = clamp(m_fBrake, 0.0f, 1.0f);
+
+ if(m_fBrake < 0.05f){
+ m_fBrake = 0.0f;
+ m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f;
+ m_fAccelerate = clamp(m_fAccelerate, 0.0f, 1.0f);
+ }else
+ m_fAccelerate = -m_fBrake*0.2f;
+
+ m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f;
+ m_fSteeringLeftRight = clamp(m_fSteeringLeftRight, -1.0f, 1.0f);
+
+ float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight);
+ m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq);
+ m_fGasPedal = m_fAccelerate;
+}
+
+void
+CBoat::ApplyWaterResistance(void)
+{
+ float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward());
+ // TODO: figure out how this works
+ float magic = (SQR(fwdSpeed) + 0.05f) * (0.001f * SQR(m_fVolumeUnderWater) * m_fMass) + 1.0f;
+ magic = Abs(magic);
+ // FRAMETIME
+ float fx = Pow(m_waterMoveDrag.x/magic, 0.5f*CTimer::GetTimeStep());
+ float fy = Pow(m_waterMoveDrag.y/magic, 0.5f*CTimer::GetTimeStep());
+ float fz = Pow(m_waterMoveDrag.z/magic, 0.5f*CTimer::GetTimeStep());
+
+ m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // invert - to local space
+ m_vecMoveSpeed.x *= fx;
+ m_vecMoveSpeed.y *= fy;
+ m_vecMoveSpeed.z *= fz;
+ float force = (fy - 1.0f) * m_vecMoveSpeed.y * m_fMass;
+ m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world
+
+ ApplyTurnForce(force*GetForward(), -GetUp());
+
+ if(m_vecMoveSpeed.z > 0.0f)
+ m_vecMoveSpeed.z *= fz;
+ else
+ m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz;
+}
+
+RwObject*
+GetBoatAtomicObjectCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ assert(RwObjectGetType(object) == rpATOMIC);
+ if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
+ *(RpAtomic**)data = atomic;
+ return object;
+
+
+}
+
+void
+CBoat::BlowUpCar(CEntity *culprit)
+{
+ RpAtomic *atomic;
+ RwFrame *frame;
+ RwMatrix *matrix;
+ CObject *obj;
+
+ if(!bCanBeDamaged)
+ return;
+
+ // explosion pushes vehicle up
+ m_vecMoveSpeed.z += 0.13f;
+ m_status = STATUS_WRECKED;
+ bRenderScorched = true;
+
+ m_fHealth = 0.0;
+ m_nBombTimer = 0;
+ TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);
+
+ if(this == FindPlayerVehicle())
+ FindPlayerPed()->m_fHealth = 0.0f; // kill player
+ if(pDriver){
+ CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION);
+ pDriver->SetDead();
+ pDriver->FlagToDestroyWhenNextProcessed();
+ }
+
+ bEngineOn = false;
+ bLightsOn = false;
+ ChangeLawEnforcerState(false);
+
+ CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0);
+ if(m_aBoatNodes[BOAT_MOVING] == nil)
+ return;
+
+ // much like CAutomobile::SpawnFlyingComponent from here on
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
+ if(atomic == nil)
+ return;
+
+ obj = new CObject();
+ if(obj == nil)
+ return;
+
+ obj->SetModelIndexNoCreate(MI_CAR_WHEEL);
+
+ // object needs base model
+ obj->RefModelInfo(GetModelIndex());
+
+ // create new atomic
+ matrix = RwFrameGetLTM(m_aBoatNodes[BOAT_MOVING]);
+ frame = RwFrameCreate();
+ atomic = RpAtomicClone(atomic);
+ *RwFrameGetMatrix(frame) = *matrix;
+ RpAtomicSetFrame(atomic, frame);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ obj->AttachToRwObject((RwObject*)atomic);
+
+ // init object
+ obj->m_fMass = 10.0f;
+ obj->m_fTurnMass = 25.0f;
+ obj->m_fAirResistance = 0.99f;
+ obj->m_fElasticity = 0.1f;
+ obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
+ obj->ObjectCreatedBy = TEMP_OBJECT;
+ obj->bIsStatic = false;
+ obj->bIsPickup = false;
+
+ // life time
+ CObject::nNoTempObjects++;
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000;
+
+ obj->m_vecMoveSpeed = m_vecMoveSpeed;
+ if(GetUp().z > 0.0f)
+ obj->m_vecMoveSpeed.z = 0.3f;
+ else
+ obj->m_vecMoveSpeed.z = 0.0f;
+
+ obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
+ obj->m_vecTurnSpeed.x = 0.5f;
+
+ // push component away from boat
+ CVector dist = obj->GetPosition() - GetPosition();
+ dist.Normalise();
+ if(GetUp().z > 0.0f)
+ dist += GetUp();
+ obj->GetPosition() += GetUp();
+
+ CWorld::Add(obj);
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
+ if(atomic)
+ RpAtomicSetFlags(atomic, 0);
+}
+
+RwIm3DVertex KeepWaterOutVertices[4];
RwImVertexIndex KeepWaterOutIndices[6];
void
@@ -102,8 +642,8 @@ CBoat::Render()
{
CMatrix matrix;
- if (m_aBoatNodes[1] != nil) {
- matrix.Attach(&m_aBoatNodes[1]->modelling);
+ if (m_aBoatNodes[BOAT_MOVING] != nil) {
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING]));
CVector pos = matrix.GetPosition();
matrix.SetRotateZ(m_fMovingHiRotation);
@@ -111,7 +651,7 @@ CBoat::Render()
matrix.UpdateRW();
if (CVehicle::bWheelsOnlyCheat) {
- RpAtomicRenderMacro((RpAtomic*)GetFirstObject(m_aBoatNodes[1]));
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING]));
}
}
m_fMovingHiRotation += 0.05f;
@@ -130,47 +670,23 @@ CBoat::Render()
KeepWaterOutIndices[5] = 3;
switch (GetModelIndex()) {
case MI_SPEEDER:
- KeepWaterOutVertices[0].objVertex.x = -1.15f;
- KeepWaterOutVertices[0].objVertex.y = 3.61f;
- KeepWaterOutVertices[0].objVertex.z = 1.03f;
- KeepWaterOutVertices[1].objVertex.x = 1.15f;
- KeepWaterOutVertices[1].objVertex.y = 3.61f;
- KeepWaterOutVertices[1].objVertex.z = 1.03f;
- KeepWaterOutVertices[2].objVertex.x = -1.15f;
- KeepWaterOutVertices[2].objVertex.y = 0.06f;
- KeepWaterOutVertices[2].objVertex.z = 1.03f;
- KeepWaterOutVertices[3].objVertex.x = 1.15f;
- KeepWaterOutVertices[3].objVertex.y = 0.06f;
- KeepWaterOutVertices[3].objVertex.z = 1.03f;
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.15f, 3.61f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.15f, 3.61f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.15f, 0.06f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.15f, 0.06f, 1.03f);
break;
case MI_REEFER:
- KeepWaterOutVertices[2].objVertex.x = -1.9f;
- KeepWaterOutVertices[2].objVertex.y = 2.83f;
- KeepWaterOutVertices[2].objVertex.z = 1.0f;
- KeepWaterOutVertices[3].objVertex.x = 1.9f;
- KeepWaterOutVertices[3].objVertex.y = 2.83f;
- KeepWaterOutVertices[3].objVertex.z = 1.0f;
- KeepWaterOutVertices[0].objVertex.x = -1.66f;
- KeepWaterOutVertices[0].objVertex.y = -4.48f;
- KeepWaterOutVertices[0].objVertex.z = 0.83f;
- KeepWaterOutVertices[1].objVertex.x = 1.66f;
- KeepWaterOutVertices[1].objVertex.y = -4.48f;
- KeepWaterOutVertices[1].objVertex.z = 0.83f;
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.9f, 2.83f, 1.0f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.9f, 2.83f, 1.0f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.66f, -4.48f, 0.83f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.66f, -4.48f, 0.83f);
break;
case MI_PREDATOR:
default:
- KeepWaterOutVertices[0].objVertex.x = -1.45f;
- KeepWaterOutVertices[0].objVertex.y = 1.9f;
- KeepWaterOutVertices[0].objVertex.z = 0.96f;
- KeepWaterOutVertices[1].objVertex.x = 1.45f;
- KeepWaterOutVertices[1].objVertex.y = 1.9f;
- KeepWaterOutVertices[1].objVertex.z = 0.96f;
- KeepWaterOutVertices[2].objVertex.x = -1.45f;
- KeepWaterOutVertices[2].objVertex.y = -3.75f;
- KeepWaterOutVertices[2].objVertex.z = 0.96f;
- KeepWaterOutVertices[3].objVertex.x = 1.45f;
- KeepWaterOutVertices[3].objVertex.y = -3.75f;
- KeepWaterOutVertices[3].objVertex.z = 0.96f;
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.45f, 1.9f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.45f, 1.9f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.45f, -3.75f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.45f, -3.75f, 0.96f);
break;
}
KeepWaterOutVertices[0].u = 0.0f;
@@ -258,10 +774,9 @@ CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat)
void
CBoat::SetupModelNodes()
{
- m_aBoatNodes[0] = nil;
- m_aBoatNodes[1] = nil;
- m_aBoatNodes[2] = nil;
- m_aBoatNodes[3] = nil;
+ int i;
+ for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++)
+ m_aBoatNodes[i] = nil;
CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes);
}
@@ -299,6 +814,43 @@ CBoat::FillBoatList()
}
}
+void
+CBoat::PruneWakeTrail(void)
+{
+ int i;
+
+ for(i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++){
+ if(m_afWakePointLifeTime[i] <= 0.0f)
+ break;
+ if(m_afWakePointLifeTime[i] <= CTimer::GetTimeStep()){
+ m_afWakePointLifeTime[i] = 0.0f;
+ break;
+ }
+ m_afWakePointLifeTime[i] -= CTimer::GetTimeStep();
+ }
+ m_nNumWakePoints = i;
+}
+
+void
+CBoat::AddWakePoint(CVector point)
+{
+ int i;
+ if(m_afWakePointLifeTime[0] > 0.0f){
+ if((CVector2D(GetPosition()) - m_avec2dWakePoints[0]).MagnitudeSqr() < SQR(1.0f)){
+ for(i = min(m_nNumWakePoints, ARRAY_SIZE(m_afWakePointLifeTime)-1); i != 0; i--){
+ m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1];
+ m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1];
+ }
+ m_avec2dWakePoints[0] = point;
+ m_afWakePointLifeTime[0] = 400.0f;
+ }
+ }else{
+ m_avec2dWakePoints[0] = point;
+ m_afWakePointLifeTime[0] = 400.0f;
+ m_nNumWakePoints = 1;
+ }
+}
+
#include <new>
class CBoat_ : public CBoat
@@ -315,4 +867,7 @@ STARTPATCHES
InjectHook(0x542370, CBoat::IsSectorAffectedByWake, PATCH_JUMP);
InjectHook(0x5424A0, CBoat::IsVertexAffectedByWake, PATCH_JUMP);
InjectHook(0x542250, CBoat::FillBoatList, PATCH_JUMP);
+ InjectHook(0x542140, &CBoat::AddWakePoint, PATCH_JUMP);
+ InjectHook(0x5420D0, &CBoat::PruneWakeTrail, PATCH_JUMP);
+ InjectHook(0x541A30, &CBoat::ApplyWaterResistance, PATCH_JUMP);
ENDPATCHES
diff --git a/src/vehicles/Boat.h b/src/vehicles/Boat.h
index d3a2ac13..f4c6a747 100644
--- a/src/vehicles/Boat.h
+++ b/src/vehicles/Boat.h
@@ -2,47 +2,41 @@
#include "Vehicle.h"
+enum eBoatNodes
+{
+ BOAT_MOVING = 1,
+ BOAT_RUDDER,
+ BOAT_WINDSCREEN
+};
+
class CBoat : public CVehicle
{
public:
// 0x288
- float field_288;
- float field_28C;
- float field_290;
- float field_294;
- float field_298;
- float field_29C;
- float field_2A0;
- float field_2A4;
+ float m_fPropellerZ;
+ float m_fPropellerY;
+ CVector m_waterMoveDrag;
+ CVector m_waterTurnDrag;
float m_fMovingHiRotation;
int32 _unk0;
RwFrame *m_aBoatNodes[4];
- uint8 m_bBoatFlag1 : 1;
- uint8 m_bBoatFlag2 : 1;
- uint8 m_bBoatFlag3 : 1;
- uint8 m_bBoatFlag4 : 1;
- uint8 m_bBoatFlag5 : 1;
- uint8 m_bBoatFlag6 : 1;
- uint8 m_bBoatFlag7 : 1;
- uint8 m_bBoatFlag8 : 1;
+ uint8 bBoatInWater : 1;
+ uint8 bPropellerInWater : 1;
bool m_bIsAnchored;
- char _pad0[2];
- float field_2C4;
+ float m_fOrientation;
int32 _unk1;
- float field_2CC;
- CEntity *field_2D0;
+ float m_fDamage;
+ CEntity *m_pSetOnFireEntity;
bool _unk2;
- char _pad1[3];
float m_fAccelerate;
float m_fBrake;
float m_fSteeringLeftRight;
uint8 m_nPadID;
- char _pad2[3];
int32 _unk3;
- float m_fTurnForceZ;
- CVector m_vecMoveForce;
- float field_2FC;
- uint16 field_300;
+ float m_fVolumeUnderWater;
+ CVector m_vecBuoyancePoint;
+ float m_fPrevVolumeUnderWater;
+ int16 m_nDeltaVolumeUnderWater;
uint16 m_nNumWakePoints;
CVector2D m_avec2dWakePoints[32];
float m_afWakePointLifeTime[32];
@@ -59,7 +53,10 @@ public:
virtual bool IsComponentPresent(int32 component) { return true; }
virtual void BlowUpCar(CEntity *ent);
+ void ApplyWaterResistance(void);
void SetupModelNodes();
+ void PruneWakeTrail(void);
+ void AddWakePoint(CVector point);
static CBoat *(&apFrameWakeGeneratingBoats)[4];
diff --git a/src/vehicles/CarGen.cpp b/src/vehicles/CarGen.cpp
new file mode 100644
index 00000000..c35005a1
--- /dev/null
+++ b/src/vehicles/CarGen.cpp
@@ -0,0 +1,258 @@
+#include "common.h"
+#include "patcher.h"
+#include "CarGen.h"
+
+#include "Automobile.h"
+#include "Boat.h"
+#include "Camera.h"
+#include "CarCtrl.h"
+#include "CutsceneMgr.h"
+#include "General.h"
+#include "Pools.h"
+#include "Streaming.h"
+#include "Timer.h"
+#include "Vehicle.h"
+#include "World.h"
+
+uint8 CTheCarGenerators::ProcessCounter;
+uint32 CTheCarGenerators::NumOfCarGenerators;
+CCarGenerator CTheCarGenerators::CarGeneratorArray[NUM_CARGENS];
+uint8 CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter;
+uint32 CTheCarGenerators::CurrentActiveCount;
+
+void CCarGenerator::SwitchOff()
+{
+ m_nUsesRemaining = 0;
+ --CTheCarGenerators::CurrentActiveCount;
+}
+
+void CCarGenerator::SwitchOn()
+{
+ m_nUsesRemaining = -1;
+ m_nTimer = CalcNextGen();
+ ++CTheCarGenerators::CurrentActiveCount;
+}
+
+uint32 CCarGenerator::CalcNextGen()
+{
+ return CTimer::GetTimeInMilliseconds() + 4;
+}
+
+void CCarGenerator::DoInternalProcessing()
+{
+ if (CheckForBlockage()) {
+ m_nTimer += 4;
+ if (m_nUsesRemaining == 0)
+ --CTheCarGenerators::CurrentActiveCount;
+ return;
+ }
+ if (CCarCtrl::NumParkedCars >= 10)
+ return;
+ CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY);
+ if (!CStreaming::HasModelLoaded(m_nModelIndex))
+ return;
+ if (CModelInfo::IsBoatModel(m_nModelIndex)){
+ CBoat* pBoat = new CBoat(m_nModelIndex, PARKED_VEHICLE);
+ pBoat->bIsStatic = false;
+ pBoat->bEngineOn = false;
+ CVector pos = m_vecPos;
+ if (pos.z <= -100.0f)
+ pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y);
+ pos.z += pBoat->GetDistanceFromCentreOfMassToBaseOfModel();
+ pBoat->GetPosition() = pos;
+ pBoat->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle));
+ pBoat->m_status = STATUS_ABANDONED;
+ pBoat->m_nDoorLock = CARLOCK_UNLOCKED;
+ CWorld::Add(pBoat);
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm)
+ pBoat->m_nAlarmState = -1;
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock)
+ pBoat->m_nDoorLock = CARLOCK_LOCKED;
+ if (m_nColor1 != -1 && m_nColor2){
+ pBoat->m_currentColour1 = m_nColor1;
+ pBoat->m_currentColour2 = m_nColor2;
+ }
+ m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pBoat);
+ }else{
+ bool groundFound = false;
+ CVector pos = m_vecPos;
+ if (pos.z > -100.0f){
+ pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &groundFound);
+ }else{
+ CColPoint cp;
+ CEntity* pEntity;
+ groundFound = CWorld::ProcessVerticalLine(CVector(pos.x, pos.y, 1000.0f), -1000.0f,
+ cp, pEntity, true, false, false, false, false, false, nil);
+ if (groundFound)
+ pos.z = cp.point.z;
+ }
+ if (!groundFound) {
+ debug("CCarGenerator::DoInternalProcessing - can't find ground z for new car x = %f y = %f \n", m_vecPos.x, m_vecPos.y);
+ }else{
+ CAutomobile* pCar = new CAutomobile(m_nModelIndex, PARKED_VEHICLE);
+ pCar->bIsStatic = false;
+ pCar->bEngineOn = false;
+ pos.z += pCar->GetDistanceFromCentreOfMassToBaseOfModel();
+ pCar->GetPosition() = pos;
+ pCar->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle));
+ pCar->m_status = STATUS_ABANDONED;
+ pCar->bLightsOn = false;
+ pCar->m_nDoorLock = CARLOCK_UNLOCKED;
+ CWorld::Add(pCar);
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm)
+ pCar->m_nAlarmState = -1;
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock)
+ pCar->m_nDoorLock = CARLOCK_LOCKED;
+ if (m_nColor1 != -1 && m_nColor2) {
+ pCar->m_currentColour1 = m_nColor1;
+ pCar->m_currentColour2 = m_nColor2;
+ }
+ m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pCar);
+ }
+ }
+ if (m_nUsesRemaining < -1) /* I don't think this is a correct comparasion */
+ --m_nUsesRemaining;
+ m_nTimer = CalcNextGen();
+ if (m_nUsesRemaining == 0)
+ --CTheCarGenerators::CurrentActiveCount;
+}
+
+void CCarGenerator::Process()
+{
+ if (m_nVehicleHandle == -1 &&
+ (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter || CTimer::GetTimeInMilliseconds() >= m_nTimer) &&
+ m_nUsesRemaining != 0 && CheckIfWithinRangeOfAnyPlayer())
+ DoInternalProcessing();
+ if (m_nVehicleHandle == -1)
+ return;
+ CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_nVehicleHandle);
+ if (!pVehicle){
+ m_nVehicleHandle = -1;
+ return;
+ }
+ if (pVehicle->m_status != STATUS_PLAYER)
+ return;
+ m_nTimer += 60000;
+ m_nVehicleHandle = -1;
+ m_bIsBlocking = true;
+ pVehicle->bExtendedRange = false;
+}
+
+void CCarGenerator::Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
+{
+ CMatrix m1, m2, m3; /* Unused but present on stack, so I'll leave them. */
+ m_vecPos = CVector(x, y, z);
+ m_fAngle = angle;
+ m_nModelIndex = mi;
+ m_nColor1 = color1;
+ m_nColor2 = color2;
+ m_bForceSpawn = force;
+ m_nAlarm = alarm;
+ m_nDoorlock = lock;
+ m_nMinDelay = min_delay;
+ m_nMaxDelay = max_delay;
+ m_nVehicleHandle = -1;
+ m_nTimer = CTimer::GetTimeInMilliseconds() + 1;
+ m_nUsesRemaining = 0;
+ m_bIsBlocking = false;
+ m_vecInf = CModelInfo::GetModelInfo(m_nModelIndex)->GetColModel()->boundingBox.min;
+ m_vecSup = CModelInfo::GetModelInfo(m_nModelIndex)->GetColModel()->boundingBox.max;
+ m_fSize = max(m_vecInf.Magnitude(), m_vecSup.Magnitude());
+}
+
+bool CCarGenerator::CheckForBlockage()
+{
+ int16 entities;
+ CWorld::FindObjectsKindaColliding(CVector(m_vecPos), m_fSize, 1, &entities, 2, nil, false, true, true, false, false);
+ return entities > 0;
+}
+
+bool CCarGenerator::CheckIfWithinRangeOfAnyPlayer()
+{
+ CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos;
+ float distance = direction.Magnitude();
+ float farclip = 120.0f * TheCamera.GenerationDistMultiplier;
+ float nearclip = farclip - 20.0f;
+ if (distance >= farclip){
+ if (m_bIsBlocking)
+ m_bIsBlocking = false;
+ return false;
+ }
+ if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter)
+ return true;
+ if (m_bIsBlocking)
+ return false;
+ if (distance < nearclip)
+ return false;
+ return DotProduct2D(direction, FindPlayerSpeed()) <= 0;
+}
+
+void CTheCarGenerators::Process()
+{
+ if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing())
+ return;
+ if (++CTheCarGenerators::ProcessCounter == 4)
+ CTheCarGenerators::ProcessCounter = 0;
+ for (uint32 i = ProcessCounter; i < NumOfCarGenerators; i += 4)
+ CTheCarGenerators::CarGeneratorArray[i].Process();
+ if (GenerateEvenIfPlayerIsCloseCounter)
+ GenerateEvenIfPlayerIsCloseCounter--;
+}
+
+int32 CTheCarGenerators::CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
+{
+ CarGeneratorArray[NumOfCarGenerators].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay);
+ return NumOfCarGenerators++;
+}
+
+void CTheCarGenerators::Init()
+{
+ GenerateEvenIfPlayerIsCloseCounter = 0;
+ NumOfCarGenerators = 0;
+ ProcessCounter = 0;
+ CurrentActiveCount = 0;
+}
+
+void CTheCarGenerators::SaveAllCarGenerators(uint8 *buffer, uint32 *size)
+{
+ const uint32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
+ *size = sizeof(int) + nGeneralDataSize + sizeof(uint32) + sizeof(CarGeneratorArray) + SAVE_HEADER_SIZE;
+INITSAVEBUF
+ WriteSaveHeader(buffer, 'C','G','N','\0', *size - SAVE_HEADER_SIZE);
+
+ WriteSaveBuf(buffer, nGeneralDataSize);
+ WriteSaveBuf(buffer, NumOfCarGenerators);
+ WriteSaveBuf(buffer, CurrentActiveCount);
+ WriteSaveBuf(buffer, ProcessCounter);
+ WriteSaveBuf(buffer, GenerateEvenIfPlayerIsCloseCounter);
+ WriteSaveBuf(buffer, (int16)0); // alignment
+ WriteSaveBuf(buffer, sizeof(CarGeneratorArray));
+ for (int i = 0; i < NUM_CARGENS; i++)
+ WriteSaveBuf(buffer, CarGeneratorArray[i]);
+VALIDATESAVEBUF(*size)
+}
+
+void CTheCarGenerators::LoadAllCarGenerators(uint8* buffer, uint32 size)
+{
+ const int32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
+ Init();
+INITSAVEBUF
+ CheckSaveHeader(buffer, 'C','G','N','\0', size - SAVE_HEADER_SIZE);
+ assert(ReadSaveBuf<uint32>(buffer) == nGeneralDataSize);
+ NumOfCarGenerators = ReadSaveBuf<uint32>(buffer);
+ CurrentActiveCount = ReadSaveBuf<uint32>(buffer);
+ ProcessCounter = ReadSaveBuf<uint8>(buffer);
+ GenerateEvenIfPlayerIsCloseCounter = ReadSaveBuf<uint8>(buffer);
+ ReadSaveBuf<int16>(buffer); // alignment
+ assert(ReadSaveBuf<uint32>(buffer) == sizeof(CarGeneratorArray));
+ for (int i = 0; i < NUM_CARGENS; i++)
+ CarGeneratorArray[i] = ReadSaveBuf<CCarGenerator>(buffer);
+VALIDATESAVEBUF(size)
+}
+
+STARTPATCHES
+InjectHook(0x543020, CTheCarGenerators::Init, PATCH_JUMP);
+InjectHook(0x542F40, CTheCarGenerators::Process, PATCH_JUMP);
+InjectHook(0x543050, CTheCarGenerators::SaveAllCarGenerators, PATCH_JUMP);
+InjectHook(0x5431E0, CTheCarGenerators::LoadAllCarGenerators, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/vehicles/CarGen.h b/src/vehicles/CarGen.h
new file mode 100644
index 00000000..9d645318
--- /dev/null
+++ b/src/vehicles/CarGen.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "common.h"
+#include "config.h"
+
+enum {
+ CARGEN_MAXACTUALLIMIT = 100
+};
+
+class CCarGenerator
+{
+ int32 m_nModelIndex;
+ CVector m_vecPos;
+ float m_fAngle;
+ int16 m_nColor1;
+ int16 m_nColor2;
+ uint8 m_bForceSpawn;
+ uint8 m_nAlarm;
+ uint8 m_nDoorlock;
+ int16 m_nMinDelay;
+ int16 m_nMaxDelay;
+ uint32 m_nTimer;
+ int32 m_nVehicleHandle;
+ uint16 m_nUsesRemaining;
+ bool m_bIsBlocking;
+ CVector m_vecInf;
+ CVector m_vecSup;
+ float m_fSize;
+public:
+ void SwitchOff();
+ void SwitchOn();
+ uint32 CalcNextGen();
+ void DoInternalProcessing();
+ void Process();
+ void Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
+ bool CheckForBlockage();
+ bool CheckIfWithinRangeOfAnyPlayer();
+ void SetUsesRemaining(uint16 uses) { m_nUsesRemaining = uses; }
+};
+
+class CTheCarGenerators
+{
+public:
+ static uint8 ProcessCounter;
+ static uint32 NumOfCarGenerators;
+ static CCarGenerator CarGeneratorArray[NUM_CARGENS];
+ static uint8 GenerateEvenIfPlayerIsCloseCounter;
+ static uint32 CurrentActiveCount;
+
+ static void Process();
+ static int32 CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
+ static void Init();
+ static void SaveAllCarGenerators(uint8 *, uint32 *);
+ static void LoadAllCarGenerators(uint8 *, uint32);
+};
diff --git a/src/vehicles/Cranes.cpp b/src/vehicles/Cranes.cpp
new file mode 100644
index 00000000..dbc3c340
--- /dev/null
+++ b/src/vehicles/Cranes.cpp
@@ -0,0 +1,671 @@
+#include "common.h"
+#include "patcher.h"
+#include "Cranes.h"
+
+#include "Camera.h"
+#include "DMAudio.h"
+#include "Garages.h"
+#include "General.h"
+#include "Entity.h"
+#include "ModelIndices.h"
+#include "Replay.h"
+#include "Object.h"
+#include "World.h"
+
+#define MAX_DISTANCE_TO_FIND_CRANE (10.0f)
+#define CRANE_UPDATE_RADIUS (300.0f)
+#define CRANE_MOVEMENT_PROCESSING_RADIUS (150.0f)
+#define CRUSHER_Z (-0.951f)
+#define MILITARY_Z (10.7862f)
+#define DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE (5.0f)
+#define DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT (0.5f)
+#define CAR_REWARD_MILITARY_CRANE (1500)
+#define CAR_MOVING_SPEED_THRESHOLD (0.01f)
+#define CRANE_SLOWDOWN_MULTIPLIER (0.3f)
+
+#define OSCILLATION_SPEED (0.002f)
+#define CAR_ROTATION_SPEED (0.0035f)
+#define CRANE_MOVEMENT_SPEED (0.001f)
+#define HOOK_ANGLE_MOVEMENT_SPEED (0.004f)
+#define HOOK_OFFSET_MOVEMENT_SPEED (0.1f)
+#define HOOK_HEIGHT_MOVEMENT_SPEED (0.06f)
+
+#define MESSAGE_SHOW_DURATION (4000)
+
+#define MAX_DISTANCE (99999.9f)
+#define MIN_VALID_POSITION (-10000.0f)
+#define DEFAULT_OFFSET (20.0f)
+
+uint32 TimerForCamInterpolation;
+
+uint32 CCranes::CarsCollectedMilitaryCrane;
+int32 CCranes::NumCranes;
+CCrane CCranes::aCranes[NUM_CRANES];
+
+void CCranes::InitCranes(void)
+{
+ CarsCollectedMilitaryCrane = 0;
+ NumCranes = 0;
+ for (int i = 0; i < NUMSECTORS_X; i++) {
+ for (int j = 0; j < NUMSECTORS_Y; j++) {
+ for (CPtrNode* pNode = CWorld::GetSector(i, j)->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) {
+ CEntity* pEntity = (CEntity*)pNode->item;
+ if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_2 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_3 == pEntity->GetModelIndex())
+ AddThisOneCrane(pEntity);
+ }
+ }
+ }
+ for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL).first; pNode; pNode = pNode->next) {
+ CEntity* pEntity = (CEntity*)pNode->item;
+ if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_2 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_3 == pEntity->GetModelIndex())
+ AddThisOneCrane(pEntity);
+ }
+}
+
+void CCranes::AddThisOneCrane(CEntity* pEntity)
+{
+ pEntity->GetMatrix().ResetOrientation();
+ if (NumCranes >= NUM_CRANES)
+ return;
+ CCrane* pCrane = &aCranes[NumCranes];
+ pCrane->Init();
+ pCrane->m_pCraneEntity = (CBuilding*)pEntity;
+ pCrane->m_nCraneStatus = CCrane::NONE;
+ pCrane->m_fHookAngle = NumCranes; // lol wtf
+ while (pCrane->m_fHookAngle > TWOPI)
+ pCrane->m_fHookAngle -= TWOPI;
+ pCrane->m_fHookOffset = DEFAULT_OFFSET;
+ pCrane->m_fHookHeight = DEFAULT_OFFSET;
+ pCrane->m_nTimeForNextCheck = 0;
+ pCrane->m_nCraneState = CCrane::IDLE;
+ pCrane->m_bWasMilitaryCrane = false;
+ pCrane->m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[NumCranes]);
+ if (pCrane->m_nAudioEntity >= 0)
+ DMAudio.SetEntityStatus(pCrane->m_nAudioEntity, 1);
+ pCrane->m_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex());
+ // Is this used to avoid military crane?
+ if (pCrane->m_bIsTop || pEntity->GetPosition().y > 0.0f) {
+ CObject* pHook = new CObject(MI_MAGNET, false);
+ pHook->ObjectCreatedBy = MISSION_OBJECT;
+ pHook->bUsesCollision = false;
+ pHook->bExplosionProof = true;
+ pHook->bAffectedByGravity = false;
+ pCrane->m_pHook = pHook;
+ pCrane->CalcHookCoordinates(&pCrane->m_vecHookCurPos.x, &pCrane->m_vecHookCurPos.y, &pCrane->m_vecHookCurPos.z);
+ pCrane->SetHookMatrix();
+ }
+ else
+ pCrane->m_pHook = nil;
+ NumCranes++;
+}
+
+void CCranes::ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY)
+{
+ float fMinDistance = MAX_DISTANCE;
+ float X = fPosX, Y = fPosY;
+ if (X <= MIN_VALID_POSITION || Y <= MIN_VALID_POSITION) {
+ X = fDropOffX;
+ Y = fDropOffY;
+ }
+ int index = 0;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
+ fMinDistance = distance;
+ index = i;
+ }
+ }
+#ifdef FIX_BUGS // classic
+ if (fMinDistance == MAX_DISTANCE)
+ return;
+#endif
+ CCrane* pCrane = &aCranes[index];
+ pCrane->m_fPickupX1 = fInfX;
+ pCrane->m_fPickupX2 = fSupX;
+ pCrane->m_fPickupY1 = fInfY;
+ pCrane->m_fPickupY2 = fSupY;
+ pCrane->m_vecDropoffTarget.x = fDropOffX;
+ pCrane->m_vecDropoffTarget.y = fDropOffY;
+ pCrane->m_vecDropoffTarget.z = fDropOffZ;
+ pCrane->m_nCraneStatus = CCrane::ACTIVATED;
+ pCrane->m_pVehiclePickedUp = nil;
+ pCrane->m_nVehiclesCollected = 0;
+ pCrane->m_fDropoffHeading = fHeading;
+ pCrane->m_bIsCrusher = bIsCrusher;
+ pCrane->m_bIsMilitaryCrane = bIsMilitary;
+ bool military = true;
+ if (!bIsMilitary && !pCrane->m_bWasMilitaryCrane)
+ military = false;
+ pCrane->m_bWasMilitaryCrane = military;
+ pCrane->m_nTimeForNextCheck = 0;
+ pCrane->m_nCraneState = CCrane::IDLE;
+ float Z;
+ if (bIsCrusher)
+ Z = CRUSHER_Z;
+ else if (bIsMilitary)
+ Z = MILITARY_Z;
+ else
+ Z = CWorld::FindGroundZForCoord((fInfX + fSupX) / 2, (fInfY + fSupY) / 2);
+ pCrane->FindParametersForTarget((fInfX + fSupX) / 2, (fInfY + fSupY) / 2, Z, &pCrane->m_fPickupAngle, &pCrane->m_fPickupDistance, &pCrane->m_fPickupHeight);
+ pCrane->FindParametersForTarget(fDropOffX, fDropOffY, fDropOffZ, &pCrane->m_fDropoffAngle, &pCrane->m_fDropoffDistance, &pCrane->m_fDropoffHeight);
+}
+
+void CCranes::DeActivateCrane(float X, float Y)
+{
+ float fMinDistance = MAX_DISTANCE;
+ int index = 0;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
+ fMinDistance = distance;
+ index = i;
+ }
+ }
+#ifdef FIX_BUGS // classic
+ if (fMinDistance == MAX_DISTANCE)
+ return;
+#endif
+ aCranes[index].m_nCraneStatus = CCrane::DEACTIVATED;
+ aCranes[index].m_nCraneState = CCrane::IDLE;
+}
+
+bool CCranes::IsThisCarPickedUp(float X, float Y, CVehicle* pVehicle)
+{
+ int index = 0;
+ bool result = false;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < MAX_DISTANCE_TO_FIND_CRANE && aCranes[i].m_pVehiclePickedUp == pVehicle) {
+ if (aCranes[i].m_nCraneStatus == CCrane::LIFTING_TARGET || aCranes[i].m_nCraneStatus == CCrane::ROTATING_TARGET)
+ result = true;
+ }
+ }
+ return true;
+}
+
+void CCranes::UpdateCranes(void)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (aCranes[i].m_bIsTop || aCranes[i].m_bIsCrusher ||
+ (TheCamera.GetPosition().x + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().x &&
+ TheCamera.GetPosition().x - CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().x &&
+ TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().y &&
+ TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().y))
+ aCranes[i].Update();
+ }
+}
+
+void CCrane::Update(void)
+{
+ if (CReplay::IsPlayingBack())
+ return;
+ if (((m_nCraneStatus == ACTIVATED || m_nCraneStatus == DEACTIVATED) &&
+ Abs(TheCamera.GetGameCamPosition().x - m_pCraneEntity->GetPosition().x) < CRANE_MOVEMENT_PROCESSING_RADIUS &&
+ Abs(TheCamera.GetGameCamPosition().y - m_pCraneEntity->GetPosition().y) < CRANE_MOVEMENT_PROCESSING_RADIUS) ||
+ m_nCraneState != IDLE) {
+ switch (m_nCraneState) {
+ case IDLE:
+ if (GoTowardsTarget(m_fPickupAngle, m_fPickupDistance, GetHeightToPickup()) &&
+ CTimer::GetTimeInMilliseconds() > m_nTimeForNextCheck) {
+ CWorld::AdvanceCurrentScanCode();
+#ifdef FIX_BUGS
+ int xstart = max(0, CWorld::GetSectorIndexX(m_fPickupX1));
+ int xend = min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fPickupX2));
+ int ystart = max(0, CWorld::GetSectorIndexY(m_fPickupY1));
+ int yend = min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fPickupY2));
+#else
+ int xstart = CWorld::GetSectorIndexX(m_fPickupX1);
+ int xend = CWorld::GetSectorIndexX(m_fPickupX2);
+ int ystart = CWorld::GetSectorIndexY(m_fPickupY1);
+ int yend = CWorld::GetSectorIndexY(m_fPickupY1);
+#endif
+ assert(xstart <= xend);
+ assert(ystart <= yend);
+ for (int i = xstart; i <= xend; i++) {
+ for (int j = ystart; j <= yend; j++) {
+ FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES]);
+ FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
+ }
+ }
+ }
+ break;
+ case GOING_TOWARDS_TARGET:
+ if (m_pVehiclePickedUp){
+ if (m_pVehiclePickedUp->GetPosition().x < m_fPickupX1 ||
+ m_pVehiclePickedUp->GetPosition().x > m_fPickupX2 ||
+ m_pVehiclePickedUp->GetPosition().y < m_fPickupY1 ||
+ m_pVehiclePickedUp->GetPosition().y > m_fPickupY2 ||
+ m_pVehiclePickedUp->pDriver ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().x) > CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().y) > CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().z) > CAR_MOVING_SPEED_THRESHOLD ||
+ FindPlayerPed()->GetPedState() == PED_ENTER_CAR && // TODO: fix carjack bug
+ FindPlayerPed()->m_pSeekTarget == m_pVehiclePickedUp) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ else {
+ float fAngle, fOffset, fHeight;
+ FindParametersForTarget(
+ m_pVehiclePickedUp->GetPosition().x,
+ m_pVehiclePickedUp->GetPosition().y,
+ m_pVehiclePickedUp->GetPosition().z + m_pVehiclePickedUp->GetColModel()->boundingBox.max.z,
+ &fAngle, &fOffset, &fHeight);
+ if (GoTowardsTarget(fAngle, fOffset, fHeight)) {
+ CVector distance = m_pVehiclePickedUp->GetPosition() - m_vecHookCurPos;
+ distance.z += m_pVehiclePickedUp->GetColModel()->boundingBox.max.z;
+ if (distance.MagnitudeSqr() < SQR(DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT)) {
+ m_nCraneState = GOING_TOWARDS_TARGET_ONLY_HEIGHT;
+ m_vecHookVelocity *= 0.4f;
+ m_pVehiclePickedUp->bLightsOn = false;
+ m_pVehiclePickedUp->bUsesCollision = false;
+ if (m_bIsCrusher)
+ m_pVehiclePickedUp->bCollisionProof = true;
+ DMAudio.PlayOneShot(m_nAudioEntity, SOUND_CRANE_PICKUP, 0.0f);
+ }
+ }
+ }
+ }
+ else
+ m_nCraneState = IDLE;
+ break;
+ case LIFTING_TARGET:
+ RotateCarriedCarProperly();
+ if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoff(), CRANE_SLOWDOWN_MULTIPLIER))
+ m_nCraneState = ROTATING_TARGET;
+ if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ break;
+ case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ RotateCarriedCarProperly();
+ if (GoTowardsHeightTarget(GetHeightToPickupHeight(), CRANE_SLOWDOWN_MULTIPLIER))
+ m_nCraneState = LIFTING_TARGET;
+ TimerForCamInterpolation = CTimer::GetTimeInMilliseconds();
+ if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ break;
+ case ROTATING_TARGET:
+ {
+ bool bRotateFinished = RotateCarriedCarProperly();
+ bool bMovementFinished = GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, m_fDropoffHeight, 0.3f);
+ if (bMovementFinished && bRotateFinished) {
+ float fDistanceFromPlayer = m_pVehiclePickedUp ? ((CVector2D)FindPlayerCoors() - (CVector2D)m_pVehiclePickedUp->GetPosition()).Magnitude() : 0.0f;
+ if (fDistanceFromPlayer > DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE || !m_bWasMilitaryCrane) {
+ m_nCraneState = DROPPING_TARGET;
+ if (m_pVehiclePickedUp) {
+ m_pVehiclePickedUp->bUsesCollision = true;
+ m_pVehiclePickedUp->m_nStaticFrames = 0;
+ ++m_nVehiclesCollected;
+ if (m_bIsMilitaryCrane) {
+ CCranes::RegisterCarForMilitaryCrane(m_pVehiclePickedUp->GetModelIndex());
+ if (!CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()) {
+ CWorld::Players[CWorld::PlayerInFocus].m_nMoney += CAR_REWARD_MILITARY_CRANE;
+ CGarages::TriggerMessage("GA_10", CAR_REWARD_MILITARY_CRANE, MESSAGE_SHOW_DURATION, -1);
+ }
+ CWorld::Remove(m_pVehiclePickedUp);
+ delete m_pVehiclePickedUp;
+ }
+ }
+ m_pVehiclePickedUp = nil;
+ }
+ }
+ break;
+ }
+ case DROPPING_TARGET:
+ if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoffHeight(), CRANE_SLOWDOWN_MULTIPLIER)) {
+ m_nCraneState = IDLE;
+ m_nTimeForNextCheck = CTimer::GetTimeInMilliseconds() + 10000;
+ }
+ break;
+ default:
+ break;
+ }
+ CVector vecHook;
+ CalcHookCoordinates(&vecHook.x, &vecHook.y, &vecHook.z);
+ m_vecHookVelocity += ((CVector2D)vecHook - (CVector2D)m_vecHookCurPos) * CTimer::GetTimeStep() * CRANE_MOVEMENT_SPEED;
+ m_vecHookVelocity *= Pow(0.98f, CTimer::GetTimeStep());
+ m_vecHookCurPos.x += m_vecHookVelocity.x * CTimer::GetTimeStep();
+ m_vecHookCurPos.y += m_vecHookVelocity.y * CTimer::GetTimeStep();
+ m_vecHookCurPos.z = vecHook.z;
+ switch (m_nCraneState) {
+ case LIFTING_TARGET:
+ case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ case ROTATING_TARGET:
+ if (m_pVehiclePickedUp) {
+ m_pVehiclePickedUp->GetPosition() = CVector(m_vecHookCurPos.x, m_vecHookCurPos.y, m_vecHookCurPos.z - m_pVehiclePickedUp->GetColModel()->boundingBox.max.z);
+ m_pVehiclePickedUp->SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ CVector up(vecHook.x - m_vecHookCurPos.x, vecHook.y - m_vecHookCurPos.y, 20.0f);
+ up.Normalise();
+ m_pVehiclePickedUp->GetRight() = CrossProduct(m_pVehiclePickedUp->GetForward(), up);
+ m_pVehiclePickedUp->GetForward() = CrossProduct(up, m_pVehiclePickedUp->GetRight());
+ m_pVehiclePickedUp->GetUp() = up;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ int16 rnd = (m_pCraneEntity->m_randomSeed + (CTimer::GetTimeInMilliseconds() >> 11)) & 0xF;
+ // 16 options, lasting 2048 ms each
+ // a bit awkward: why there are 4 periods for -= and 6 for +=? is it a bug?
+ if (rnd < 4) {
+ m_fHookAngle -= OSCILLATION_SPEED * CTimer::GetTimeStep();
+ if (m_fHookAngle < 0.0f)
+ m_fHookAngle += TWOPI;
+ }
+ else if (rnd > 5 && rnd < 12) {
+ m_fHookAngle += OSCILLATION_SPEED * CTimer::GetTimeStep();
+ if (m_fHookAngle > TWOPI)
+ m_fHookAngle -= TWOPI;
+ }
+ CalcHookCoordinates(&m_vecHookCurPos.x, &m_vecHookCurPos.y, &m_vecHookCurPos.z);
+ m_vecHookVelocity.x = m_vecHookVelocity.y = 0.0f;
+ }
+ float fCos = Cos(m_fHookAngle);
+ float fSin = Sin(m_fHookAngle);
+ m_pCraneEntity->GetRight().x = fCos;
+ m_pCraneEntity->GetForward().y = fCos;
+ m_pCraneEntity->GetRight().y = fSin;
+ m_pCraneEntity->GetForward().x = -fSin;
+ m_pCraneEntity->GetMatrix().UpdateRW();
+ m_pCraneEntity->UpdateRwFrame();
+ SetHookMatrix();
+}
+
+bool CCrane::RotateCarriedCarProperly()
+{
+ if (m_fDropoffHeading <= 0.0f)
+ return true;
+ if (!m_pVehiclePickedUp)
+ return true;
+ float fAngleDelta = m_fDropoffHeading - CGeneral::GetATanOfXY(m_pVehiclePickedUp->GetForward().x, m_pVehiclePickedUp->GetForward().y);
+ while (fAngleDelta < -HALFPI)
+ fAngleDelta += PI;
+ while (fAngleDelta > HALFPI)
+ fAngleDelta -= PI;
+ float fDeltaThisFrame = CAR_ROTATION_SPEED * CTimer::GetTimeStep();
+ if (Abs(fAngleDelta) <= fDeltaThisFrame) // no rotation is actually applied?
+ return true;
+ m_pVehiclePickedUp->GetMatrix().RotateZ(Abs(fDeltaThisFrame));
+ return false;
+}
+
+void CCrane::FindCarInSectorList(CPtrList* pList)
+{
+ CPtrNode* node;
+ for (node = pList->first; node; node = node->next) {
+ CVehicle* pVehicle = (CVehicle*)node->item;
+ if (pVehicle->m_scanCode == CWorld::GetCurrentScanCode())
+ continue;
+ pVehicle->m_scanCode = CWorld::GetCurrentScanCode();
+ if (pVehicle->GetPosition().x < m_fPickupX1 || pVehicle->GetPosition().x > m_fPickupX2 ||
+ pVehicle->GetPosition().y < m_fPickupY1 || pVehicle->GetPosition().y > m_fPickupY2)
+ continue;
+ if (pVehicle->pDriver)
+ continue;
+ if (Abs(pVehicle->GetMoveSpeed().x) >= CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(pVehicle->GetMoveSpeed().y) >= CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(pVehicle->GetMoveSpeed().z) >= CAR_MOVING_SPEED_THRESHOLD)
+ continue;
+ if (!pVehicle->IsCar() || pVehicle->m_status == STATUS_WRECKED || pVehicle->m_fHealth < 250.0f)
+ continue;
+ if (!DoesCranePickUpThisCarType(pVehicle->GetModelIndex()) ||
+ m_bIsMilitaryCrane && CCranes::DoesMilitaryCraneHaveThisOneAlready(pVehicle->GetModelIndex())) {
+ if (!pVehicle->bCraneMessageDone) {
+ pVehicle->bCraneMessageDone = true;
+ if (!m_bIsMilitaryCrane)
+ CGarages::TriggerMessage("CR_1", -1, MESSAGE_SHOW_DURATION, -1); // Crane cannot lift this vehicle.
+ else if (DoesCranePickUpThisCarType(pVehicle->GetModelIndex()))
+ CGarages::TriggerMessage("GA_20", -1, MESSAGE_SHOW_DURATION, -1); // We got more of these than we can shift. Sorry man, no deal.
+ else
+ CGarages::TriggerMessage("GA_19", -1, MESSAGE_SHOW_DURATION, -1); // We're not interested in that model.
+ }
+ }
+ else {
+ m_pVehiclePickedUp = pVehicle;
+ pVehicle->RegisterReference((CEntity**)&m_pVehiclePickedUp);
+ m_nCraneState = GOING_TOWARDS_TARGET;
+ }
+ }
+}
+
+bool CCrane::DoesCranePickUpThisCarType(uint32 mi)
+{
+ if (m_bIsCrusher) {
+ return mi != MI_FIRETRUCK &&
+ mi != MI_TRASH &&
+#ifndef FIX_BUGS // why
+ mi != MI_BLISTA &&
+#endif
+ mi != MI_SECURICA &&
+ mi != MI_BUS &&
+ mi != MI_DODO &&
+ mi != MI_RHINO;
+ }
+ if (m_bIsMilitaryCrane) {
+ return mi == MI_FIRETRUCK ||
+ mi == MI_AMBULAN ||
+ mi == MI_ENFORCER ||
+ mi == MI_FBICAR ||
+ mi == MI_RHINO ||
+ mi == MI_BARRACKS ||
+ mi == MI_POLICE;
+ }
+ return true;
+}
+
+bool CCranes::DoesMilitaryCraneHaveThisOneAlready(uint32 mi)
+{
+ switch (mi) {
+ case MI_FIRETRUCK: return (CarsCollectedMilitaryCrane & 1);
+ case MI_AMBULAN: return (CarsCollectedMilitaryCrane & 2);
+ case MI_ENFORCER: return (CarsCollectedMilitaryCrane & 4);
+ case MI_FBICAR: return (CarsCollectedMilitaryCrane & 8);
+ case MI_RHINO: return (CarsCollectedMilitaryCrane & 0x10);
+ case MI_BARRACKS: return (CarsCollectedMilitaryCrane & 0x20);
+ case MI_POLICE: return (CarsCollectedMilitaryCrane & 0x40);
+ default: break;
+ }
+ return false;
+}
+
+void CCranes::RegisterCarForMilitaryCrane(uint32 mi)
+{
+ switch (mi) {
+ case MI_FIRETRUCK: CarsCollectedMilitaryCrane |= 1; break;
+ case MI_AMBULAN: CarsCollectedMilitaryCrane |= 2; break;
+ case MI_ENFORCER: CarsCollectedMilitaryCrane |= 4; break;
+ case MI_FBICAR: CarsCollectedMilitaryCrane |= 8; break;
+ case MI_RHINO: CarsCollectedMilitaryCrane |= 0x10; break;
+ case MI_BARRACKS: CarsCollectedMilitaryCrane |= 0x20; break;
+ case MI_POLICE: CarsCollectedMilitaryCrane |= 0x40; break;
+ default: break;
+ }
+}
+
+bool CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()
+{
+ return (CarsCollectedMilitaryCrane & 0x7F) == 0x7F;
+}
+
+bool CCrane::GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier)
+{
+ bool bAngleMovementFinished, bOffsetMovementFinished, bHeightMovementFinished;
+ float fHookAngleDelta = fAngleToTarget - m_fHookAngle;
+ while (fHookAngleDelta > PI)
+ fHookAngleDelta -= TWOPI;
+ while (fHookAngleDelta < -PI)
+ fHookAngleDelta += TWOPI;
+ float fHookAngleChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_ANGLE_MOVEMENT_SPEED;
+ if (Abs(fHookAngleDelta) < fHookAngleChangeThisFrame) {
+ m_fHookAngle = fAngleToTarget;
+ bAngleMovementFinished = true;
+ } else {
+ if (fHookAngleDelta < 0.0f) {
+ m_fHookAngle -= fHookAngleChangeThisFrame;
+ if (m_fHookAngle < 0.0f)
+ m_fHookAngle += TWOPI;
+ }
+ else {
+ m_fHookAngle += fHookAngleChangeThisFrame;
+ if (m_fHookAngle > TWOPI)
+ m_fHookAngle -= TWOPI;
+ }
+ bAngleMovementFinished = false;
+ }
+ float fHookOffsetDelta = fDistanceToTarget - m_fHookOffset;
+ float fHookOffsetChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_OFFSET_MOVEMENT_SPEED;
+ if (Abs(fHookOffsetDelta) < fHookOffsetChangeThisFrame) {
+ m_fHookOffset = fDistanceToTarget;
+ bOffsetMovementFinished = true;
+ } else {
+ if (fHookOffsetDelta < 0.0f)
+ m_fHookOffset -= fHookOffsetChangeThisFrame;
+ else
+ m_fHookOffset += fHookOffsetChangeThisFrame;
+ bOffsetMovementFinished = false;
+ }
+ float fHookHeightDelta = fTargetHeight - m_fHookHeight;
+ float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
+ if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
+ m_fHookHeight = fTargetHeight;
+ bHeightMovementFinished = true;
+ } else {
+ if (fHookHeightDelta < 0.0f)
+ m_fHookHeight -= fHookHeightChangeThisFrame;
+ else
+ m_fHookHeight += fHookHeightChangeThisFrame;
+ bHeightMovementFinished = false;
+ }
+ return bAngleMovementFinished && bOffsetMovementFinished && bHeightMovementFinished;
+}
+
+bool CCrane::GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier)
+{
+ bool bHeightMovementFinished;
+ float fHookHeightDelta = fTargetHeight - m_fHookHeight;
+ float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
+ if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
+ m_fHookHeight = fTargetHeight;
+ bHeightMovementFinished = true;
+ } else {
+ if (fHookHeightDelta < 0.0f)
+ m_fHookHeight -= fHookHeightChangeThisFrame;
+ else
+ m_fHookHeight += fHookHeightChangeThisFrame;
+ bHeightMovementFinished = false;
+ }
+ return bHeightMovementFinished;
+}
+
+void CCrane::FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight)
+{
+ *pAngle = CGeneral::GetATanOfXY(X - m_pCraneEntity->GetPosition().x, Y - m_pCraneEntity->GetPosition().y);
+ *pDistance = ((CVector2D(X, Y) - (CVector2D)m_pCraneEntity->GetPosition())).Magnitude();
+ *pHeight = Z;
+}
+
+void CCrane::CalcHookCoordinates(float* pX, float* pY, float* pZ)
+{
+ *pX = Cos(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().x;
+ *pY = Sin(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().y;
+ *pZ = m_fHookHeight;
+}
+
+void CCrane::SetHookMatrix()
+{
+ if (m_pHook == nil)
+ return;
+ m_pHook->GetPosition() = m_vecHookCurPos;
+ CVector up(m_vecHookInitPos.x - m_vecHookCurPos.x, m_vecHookInitPos.y - m_vecHookCurPos.y, 20.0f);
+ up.Normalise();
+ m_pHook->GetRight() = CrossProduct(CVector(0.0f, 1.0f, 0.0f), up);
+ m_pHook->GetForward() = CrossProduct(up, m_pHook->GetRight());
+ m_pHook->GetUp() = up;
+ m_pHook->SetOrientation(0.0f, 0.0f, -HALFPI);
+ m_pHook->GetMatrix().UpdateRW();
+ m_pHook->UpdateRwFrame();
+ CWorld::Remove(m_pHook);
+ CWorld::Add(m_pHook);
+}
+
+bool CCranes::IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (pVehicle == aCranes[i].m_pVehiclePickedUp) {
+ switch (aCranes[i].m_nCraneState) {
+ case CCrane::GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ case CCrane::LIFTING_TARGET:
+ case CCrane::ROTATING_TARGET:
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+bool CCranes::IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (pVehicle == aCranes[i].m_pVehiclePickedUp)
+ return true;
+ }
+ return false;
+}
+
+void CCranes::Save(uint8* buf, uint32* size)
+{
+ INITSAVEBUF
+
+ *size = 2 * sizeof(uint32) + sizeof(aCranes);
+ WriteSaveBuf(buf, NumCranes);
+ WriteSaveBuf(buf, CarsCollectedMilitaryCrane);
+ for (int i = 0; i < NUM_CRANES; i++) {
+ CCrane *pCrane = WriteSaveBuf(buf, aCranes[i]);
+ if (pCrane->m_pCraneEntity != nil)
+ pCrane->m_pCraneEntity = (CBuilding*)(CPools::GetBuildingPool()->GetJustIndex(pCrane->m_pCraneEntity) + 1);
+ if (pCrane->m_pHook != nil)
+ pCrane->m_pHook = (CObject*)(CPools::GetObjectPool()->GetJustIndex(pCrane->m_pHook) + 1);
+ if (pCrane->m_pVehiclePickedUp != nil)
+ pCrane->m_pVehiclePickedUp = (CVehicle*)(CPools::GetVehiclePool()->GetJustIndex(pCrane->m_pVehiclePickedUp) + 1);
+ }
+
+ VALIDATESAVEBUF(*size);
+}
+
+void CCranes::Load(uint8* buf, uint32 size)
+{
+ INITSAVEBUF
+
+ NumCranes = ReadSaveBuf<int32>(buf);
+ CarsCollectedMilitaryCrane = ReadSaveBuf<uint32>(buf);
+ for (int i = 0; i < NUM_CRANES; i++)
+ aCranes[i] = ReadSaveBuf<CCrane>(buf);
+ for (int i = 0; i < NUM_CRANES; i++) {
+ CCrane *pCrane = &aCranes[i];
+ if (pCrane->m_pCraneEntity != nil)
+ pCrane->m_pCraneEntity = CPools::GetBuildingPool()->GetSlot((uint32)pCrane->m_pCraneEntity - 1);
+ if (pCrane->m_pHook != nil)
+ pCrane->m_pHook = CPools::GetObjectPool()->GetSlot((uint32)pCrane->m_pHook - 1);
+ if (pCrane->m_pVehiclePickedUp != nil)
+ pCrane->m_pVehiclePickedUp = CPools::GetVehiclePool()->GetSlot((uint32)pCrane->m_pVehiclePickedUp - 1);
+ }
+ for (int i = 0; i < NUM_CRANES; i++) {
+ aCranes[i].m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[i]);
+ if (aCranes[i].m_nAudioEntity != 0)
+ DMAudio.SetEntityStatus(aCranes[i].m_nAudioEntity, 1);
+ }
+
+ VALIDATESAVEBUF(size);
+}
diff --git a/src/vehicles/Cranes.h b/src/vehicles/Cranes.h
new file mode 100644
index 00000000..c0502638
--- /dev/null
+++ b/src/vehicles/Cranes.h
@@ -0,0 +1,97 @@
+#pragma once
+#include "common.h"
+
+#include "World.h"
+
+class CVehicle;
+class CEntity;
+class CObject;
+class CBuilding;
+
+class CCrane
+{
+public:
+ enum CraneState : uint8 {
+ IDLE = 0,
+ GOING_TOWARDS_TARGET = 1,
+ LIFTING_TARGET = 2,
+ GOING_TOWARDS_TARGET_ONLY_HEIGHT = 3,
+ ROTATING_TARGET = 4,
+ DROPPING_TARGET = 5
+ };
+ enum CraneStatus : uint8 {
+ NONE = 0,
+ ACTIVATED = 1,
+ DEACTIVATED = 2
+ };
+ CBuilding *m_pCraneEntity;
+ CObject *m_pHook;
+ int32 m_nAudioEntity;
+ float m_fPickupX1;
+ float m_fPickupX2;
+ float m_fPickupY1;
+ float m_fPickupY2;
+ CVector m_vecDropoffTarget;
+ float m_fDropoffHeading;
+ float m_fPickupAngle;
+ float m_fDropoffAngle;
+ float m_fPickupDistance;
+ float m_fDropoffDistance;
+ float m_fPickupHeight;
+ float m_fDropoffHeight;
+ float m_fHookAngle;
+ float m_fHookOffset;
+ float m_fHookHeight;
+ CVector m_vecHookInitPos;
+ CVector m_vecHookCurPos;
+ CVector2D m_vecHookVelocity;
+ CVehicle *m_pVehiclePickedUp;
+ uint32 m_nTimeForNextCheck;
+ CraneStatus m_nCraneStatus;
+ CraneState m_nCraneState;
+ uint8 m_nVehiclesCollected;
+ bool m_bIsCrusher;
+ bool m_bIsMilitaryCrane;
+ bool m_bWasMilitaryCrane;
+ bool m_bIsTop;
+
+ void Init(void) { memset(this, 0, sizeof(*this)); }
+ void Update(void);
+ bool RotateCarriedCarProperly(void);
+ void FindCarInSectorList(CPtrList* pList);
+ bool DoesCranePickUpThisCarType(uint32 mi);
+ bool GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier = 1.0f);
+ bool GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier = 1.0f);
+ void FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight);
+ void CalcHookCoordinates(float* pX, float* pY, float* pZ);
+ void SetHookMatrix(void);
+
+ float GetHeightToPickup() { return 4.0f + m_fPickupHeight + (m_bIsCrusher ? 4.5f : 0.0f); };
+ float GetHeightToDropoff() { return m_bIsCrusher ? (2.0f + m_fDropoffHeight + 3.0f) : (2.0f + m_fDropoffHeight); }
+ float GetHeightToPickupHeight() { return m_fPickupHeight + (m_bIsCrusher ? 7.0f : 4.0f); }
+ float GetHeightToDropoffHeight() { return m_fDropoffHeight + (m_bIsCrusher ? 7.0f : 2.0f); }
+};
+
+static_assert(sizeof(CCrane) == 128, "CCrane: error");
+
+class CCranes
+{
+public:
+ static void InitCranes(void);
+ static void AddThisOneCrane(CEntity* pCraneEntity);
+ static void ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY);
+ static void DeActivateCrane(float fX, float fY);
+ static bool IsThisCarPickedUp(float fX, float fY, CVehicle* pVehicle);
+ static void UpdateCranes(void);
+ static bool DoesMilitaryCraneHaveThisOneAlready(uint32 mi);
+ static void RegisterCarForMilitaryCrane(uint32 mi);
+ static bool HaveAllCarsBeenCollectedByMilitaryCrane(void);
+ static bool IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle);
+ static bool IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle);
+ static void Save(uint8* buf, uint32* size);
+ static void Load(uint8* buf, uint32 size); // on mobile it's CranesLoad outside of the class
+
+ static uint32 CarsCollectedMilitaryCrane;
+ static int32 NumCranes;
+ static CCrane aCranes[NUM_CRANES];
+};
diff --git a/src/vehicles/Floater.cpp b/src/vehicles/Floater.cpp
index 6b8bf755..62d55925 100644
--- a/src/vehicles/Floater.cpp
+++ b/src/vehicles/Floater.cpp
@@ -26,7 +26,7 @@ cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVec
{
m_numSteps = 2.0f;
- if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->m_flagD8))
+ if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater))
return false;
m_matrix = phys->GetMatrix();
diff --git a/src/vehicles/Vehicle.cpp b/src/vehicles/Vehicle.cpp
index adeba19e..f47fd131 100644
--- a/src/vehicles/Vehicle.cpp
+++ b/src/vehicles/Vehicle.cpp
@@ -482,6 +482,55 @@ CVehicle::InflictDamage(CEntity* damagedBy, eWeaponType weaponType, float damage
}
void
+CVehicle::DoFixedMachineGuns(void)
+{
+ if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){
+ if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 150){
+ CVector source, target;
+ float dx, dy, len;
+
+ dx = GetForward().x;
+ dy = GetForward().y;
+ len = Sqrt(SQR(dx) + SQR(dy));
+ if(len < 0.1f) len = 0.1f;
+ dx /= len;
+ dy /= len;
+
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds();
+
+ source = GetMatrix() * CVector(2.0f, 2.5f, 1.0f);
+ target = source + CVector(dx, dy, 0.0f)*60.0f;
+ target += CVector(
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f);
+ CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
+ FireOneInstantHitRound(&source, &target, 15);
+
+ source = GetMatrix() * CVector(-2.0f, 2.5f, 1.0f);
+ target = source + CVector(dx, dy, 0.0f)*60.0f;
+ target += CVector(
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
+ ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f);
+ CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
+ FireOneInstantHitRound(&source, &target, 15);
+
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+
+ m_nAmmoInClip--;
+ if(m_nAmmoInClip == 0){
+ m_nAmmoInClip = 20;
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds() + 1400;
+ }
+ }
+ }else{
+ if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400)
+ m_nAmmoInClip = 20;
+ }
+}
+
+void
CVehicle::ExtinguishCarFire(void)
{
m_fHealth = max(m_fHealth, 300.0f);
diff --git a/src/vehicles/Vehicle.h b/src/vehicles/Vehicle.h
index 4639f3e1..f9becda0 100644
--- a/src/vehicles/Vehicle.h
+++ b/src/vehicles/Vehicle.h
@@ -130,7 +130,8 @@ public:
int8 m_nGettingInFlags;
int8 m_nGettingOutFlags;
uint8 m_nNumMaxPassengers;
- char field_1CD[19];
+ char field_1CD[3];
+ float field_1D0[4];
CEntity *m_pCurGroundEntity;
CFire *m_pCarFire;
float m_fSteerAngle;
@@ -238,6 +239,7 @@ public:
bool IsTrain(void) { return m_vehType == VEHICLE_TYPE_TRAIN; }
bool IsHeli(void) { return m_vehType == VEHICLE_TYPE_HELI; }
bool IsPlane(void) { return m_vehType == VEHICLE_TYPE_PLANE; }
+ bool IsBike(void) { return m_vehType == VEHICLE_TYPE_BIKE; }
void FlyingControl(eFlightModel flightModel);
void ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
@@ -268,6 +270,7 @@ public:
bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius);
bool ShufflePassengersToMakeSpace(void);
void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage);
+ void DoFixedMachineGuns(void);
bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1; }
CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); }