#include "common.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 "Floater.h" #include "World.h" #include "Pools.h" #include "Pad.h" #include "Boat.h" #define INVALID_ORIENTATION (-9999.99f) float fShapeLength = 0.4f; float fShapeTime = 0.05f; float fRangeMult = 0.75f; //0.6f; // 0.75f gta 3 float fTimeMult; float MAX_WAKE_LENGTH = 50.0f; float MIN_WAKE_INTERVAL = 1.0f; float WAKE_LIFETIME = 400.0f; CBoat *CBoat::apFrameWakeGeneratingBoats[4]; const uint32 CBoat::nSaveStructSize = #ifdef COMPATIBLE_SAVES 1156; #else sizeof(CBoat); #endif CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner) { CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi); m_vehType = VEHICLE_TYPE_BOAT; m_fAccelerate = 0.0f; m_fBrake = 0.0f; m_fSteeringLeftRight = 0.0f; m_nPadID = 0; m_fMovingHiRotation = 0.0f; SetModelIndex(mi); pHandling = mod_HandlingManager.GetHandlingData((eHandlingId)minfo->m_handlingId); minfo->ChooseVehicleColour(m_currentColour1, m_currentColour2); m_fMass = pHandling->fMass; m_fTurnMass = pHandling->fTurnMass / 2.0f; m_vecCentreOfMass = pHandling->CentreOfMass; m_fAirResistance = pHandling->Dimension.x * pHandling->Dimension.z / m_fMass; m_fElasticity = 0.1f; m_fBuoyancy = pHandling->fBuoyancy; m_fSteerAngle = 0.0f; m_fGasPedal = 0.0f; m_fBrakePedal = 0.0f; 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_fVolumeUnderWater = 7.0f; m_fPrevVolumeUnderWater = 7.0f; m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f); m_nDeltaVolumeUnderWater = 0; bBoatInWater = true; bPropellerInWater = true; bIsInWater = true; unk1 = 0.0f; m_bIsAnchored = true; m_fOrientation = INVALID_ORIENTATION; bTouchingWater = true; m_fDamage = 0.0f; m_pSetOnFireEntity = nil; m_nNumWakePoints = 0; for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++) m_afWakePointLifeTime[i] = 0.0f; m_nAmmoInClip = 20; } void CBoat::SetModelIndex(uint32 id) { CEntity::SetModelIndex(id); SetupModelNodes(); } void CBoat::GetComponentWorldPosition(int32 component, CVector &pos) { pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component])); } void CBoat::ProcessControl(void) { 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(GetStatus()){ 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 && GetStatus() != 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 && GetStatus() != 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 (GetModelIndex() == 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(GetStatus() == 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; #ifdef PC_PARTICLE CVector wakePos = GetPosition() + sternPos; wakePos.z -= 0.65f; #else CVector wakePos = GetPosition() + sternPos; wakePos.z = -0.3f; #endif CVector wakeDir = 0.75f * jetDir; CParticle::AddParticle(PARTICLE_BOAT_THRUSTJET, jetPos, jetDir, nil, 0.0f, jetColor); #ifdef PC_PARTICLE CParticle::AddParticle(PARTICLE_CAR_SPLASH, jetPos, 0.25f * jetDir, nil, 1.0f, splashColor, CGeneral::GetRandomNumberInRange(0, 30), CGeneral::GetRandomNumberInRange(0, 90), 3); #endif if(!cameraHack) CParticle::AddParticle(PARTICLE_BOAT_WAKE, wakePos, wakeDir, nil, 0.0f, jetColor); }else if((CTimer::GetFrameCounter() + m_randomSeed) & 1){ #ifdef PC_PARTICLE 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); #else jetDir.z = 0.018f; jetDir.x *= 0.03f; jetDir.y *= 0.03f; propellerWorld.z += 1.0f; CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 0.0f, jetColor); #endif #ifdef PC_PARTICLE CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.1f * jetDir, nil, 0.5f, splashColor, CGeneral::GetRandomNumberInRange(0, 30), CGeneral::GetRandomNumberInRange(0, 90), 3); #endif } } }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 - (WORLD_MAX_X-50.0f))*0.01f); // east m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - (WORLD_MIN_X+50.0f))*0.01f); // west m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MAX_Y-50.0f))*0.01f); // north m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MIN_Y+50.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->fBrakeBias; 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 #ifdef PC_PARTICLE if(m_nDeltaVolumeUnderWater > 75) #else if(m_nDeltaVolumeUnderWater > 120) #endif { float speed = m_vecMoveSpeed.Magnitude(); float splash1Size = speed; float splash2Size = float(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 #ifdef PC_PARTICLE 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); #else dir = 0.3f*m_vecMoveSpeed; dir.z += 0.05f*speed; dir += 0.5f*GetRight()*speed; pos = (GetPosition() + m_vecBuoyancePoint) + (1.5f*GetRight()); #endif #ifdef PC_PARTICLE 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); #else CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); #endif // left #ifdef PC_PARTICLE 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); #else dir = 0.3f*m_vecMoveSpeed; dir.z += 0.05f*speed; dir -= 0.5f*GetRight()*speed; pos = (GetPosition() + m_vecBuoyancePoint) - (1.5f*GetRight()); #endif #ifdef PC_PARTICLE 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); #else CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); #endif } 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()); GetMatrix().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; SetStatus(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->GetMatrix().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 CBoat::Render() { CMatrix matrix; if (m_aBoatNodes[BOAT_MOVING] != nil) { matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING])); CVector pos = matrix.GetPosition(); matrix.SetRotateZ(m_fMovingHiRotation); matrix.Translate(pos); matrix.UpdateRW(); if (CVehicle::bWheelsOnlyCheat) { RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING])); } } m_fMovingHiRotation += 0.05f; ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2); if (!CVehicle::bWheelsOnlyCheat) CEntity::Render(); RwIm3DVertexSetRGBA(&KeepWaterOutVertices[0], 255, 255, 255, 255); KeepWaterOutIndices[0] = 0; RwIm3DVertexSetRGBA(&KeepWaterOutVertices[1], 255, 255, 255, 255); KeepWaterOutIndices[1] = 2; RwIm3DVertexSetRGBA(&KeepWaterOutVertices[2], 255, 255, 255, 255); KeepWaterOutIndices[2] = 1; RwIm3DVertexSetRGBA(&KeepWaterOutVertices[3], 255, 255, 255, 255); KeepWaterOutIndices[3] = 1; KeepWaterOutIndices[4] = 2; KeepWaterOutIndices[5] = 3; switch (GetModelIndex()) { case MI_SPEEDER: 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: 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: 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; KeepWaterOutVertices[0].v = 0.0f; KeepWaterOutVertices[1].u = 1.0f; KeepWaterOutVertices[1].v = 0.0f; KeepWaterOutVertices[2].u = 0.0f; KeepWaterOutVertices[2].v = 1.0f; KeepWaterOutVertices[3].u = 1.0f; KeepWaterOutVertices[3].v = 1.0f; RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpWaterRaster); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) { RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6); RwIm3DEnd(); } RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); } void CBoat::Teleport(CVector v) { CWorld::Remove(this); SetPosition(v); SetOrientation(0.0f, 0.0f, 0.0f); SetMoveSpeed(0.0f, 0.0f, 0.0f); SetTurnSpeed(0.0f, 0.0f, 0.0f); CWorld::Add(this); } bool CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats) { uint8 numVerts = 0; if ( apFrameWakeGeneratingBoats[0] == NULL ) return false; for ( int32 i = 0; i < 4; i++ ) { CBoat *pBoat = apFrameWakeGeneratingBoats[i]; if ( !pBoat ) break; for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ ) { float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize; if ( Abs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist && Abs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist ) { apBoats[numVerts] = pBoat; numVerts = 1; // += ? break; } } } return numVerts != 0; } float CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat) { for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ ) { float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength; CVector2D vecDist = pBoat->m_avec2dWakePoints[i] - CVector2D(vecVertex); float fDist = vecDist.MagnitudeSqr(); if ( fDist < SQR(fMaxDist) ) return 1.0f - Min(fRangeMult * Sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f); } return 0.0f; } void CBoat::SetupModelNodes() { int i; for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++) m_aBoatNodes[i] = nil; CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes); } void CBoat::FillBoatList() { int16 frameId = 0; apFrameWakeGeneratingBoats[0] = nil; apFrameWakeGeneratingBoats[1] = nil; apFrameWakeGeneratingBoats[2] = nil; apFrameWakeGeneratingBoats[3] = nil; for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) { CBoat *boat = (CBoat *)(CPools::GetVehiclePool()->GetSlot(i)); if (boat && boat->m_vehType == VEHICLE_TYPE_BOAT) { int16 nNumWakePoints = boat->m_nNumWakePoints; if (nNumWakePoints != 0) { if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) { int16 frameId2 = -1; for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) { if (apFrameWakeGeneratingBoats[j]->m_nNumWakePoints < nNumWakePoints) { frameId2 = j; nNumWakePoints = apFrameWakeGeneratingBoats[j]->m_nNumWakePoints; } } if (frameId2 != -1) apFrameWakeGeneratingBoats[frameId2] = boat; } else { apFrameWakeGeneratingBoats[frameId++] = boat; } } } } } 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; } } #ifdef COMPATIBLE_SAVES void CBoat::Save(uint8*& buf) { CVehicle::Save(buf); SkipSaveBuf(buf, 1156 - 648); } void CBoat::Load(uint8*& buf) { CVehicle::Load(buf); SkipSaveBuf(buf, 1156 - 648); } #endif