meta data for this page
Vehicles/VehicleScripts/VehicleMotor.lua
VehicleMotor implements a diesel engine together with a gearbox that powers the vehicle. This is the best option for all vehicles that are powered by any powertrain comparable to this setup, except if a very continuous speed level shall be achieved (in that case, head over to TrackedVehicle).
25 VehicleMotor = VehicleMotor or {}; 26 27 VehicleMotor.SOUNDID_DAMPING = 0; 28 VehicleMotor.SOUNDID_CRASH = 1;
VehicleMotor:load(dataTable)
Creates all functions required for VehicleMotor. As many of them require return values, only some can be created using VehicleManager:newFunction()
.
Afterwards, the function loads all relevant configuration parameters from the vehicle's data table.
34 function VehicleMotor:load(dataTable) 35 self.getMotorTorqueAtRpm = VehicleMotor.getMotorTorqueAtRpm; 36 self.getDrivingWheelsRpm = VehicleMotor.getDrivingWheelsRpm; 37 self.getCurrentMotorTorque = VehicleMotor.getCurrentMotorTorque; 38 self.getGearTransmissionRatio = VehicleMotor.getGearTransmissionRatio; 39 self.getMotorInertia = VehicleMotor.getMotorInertia; 40 self.getDrivingWheelsCount = VehicleMotor.getDrivingWheelsCount; 41 self.getShallInvertThrottle = VehicleMotor.getShallInvertThrottle; 42 self.updateWheels = VehicleMotor.updateWheels; 43 self.setAutomaticDriveActive = VehicleMotor.setAutomaticDriveActive; 44 self.calculateFriction = VehicleMotor.calculateFriction; 45 self.playSingleEventSound = VehicleMotor.playSingleEventSound; 46 self.setIsMotorOn = VehicleManager:newFunction("setIsMotorOn"); 47 self.setDrivingDirectionInverted= VehicleMotor.setDrivingDirectionInverted; 48 self.setCruiseControlValues = VehicleMotor.setCruiseControlValues; 49 50 self.motorMinRpm = dataTable.motorMinRpm or 800; 51 self.motorMaxRpm = dataTable.motorMaxRpm or 5000; 52 self.motorInertiaMoment = dataTable.motorInertiaMoment or 0.5; 53 54 local speedPointerIndex = dataTable.speedPointerIndex or ""; 55 self.speedPointerWheelRadius = dataTable.wheelRadius or 0.5; 56 self.speedPointerMaxSpeed = dataTable.maxSpeed or 100; 57 if speedPointerIndex ~= "" then 58 self.speedPointerId = getChild(self.id, speedPointerIndex); 59 self.speedPointerMaxAngle = dataTable.speedPointerMaxAngle or 90; 60 self.speedPointerLastAngle = 0; 61 end; 62 63 local rpmPointerIndex = dataTable.rpmPointerIndex or ""; 64 self.rpmPointerMaxRpm = dataTable.maxRpm or 5000; 65 if rpmPointerIndex ~= "" then 66 self.rpmPointerId = getChild(self.id, rpmPointerIndex); 67 self.rpmPointerMaxAngle = dataTable.rpmPointerMaxAngle or 90; 68 self.rpmPointerLastAngle = 0; 69 end; 70 71 self.currentGear = 2; 72 73 self.gears = dataTable.gears or {}; 74 self.gearLabels = dataTable.gearLabels or {}; 75 76 self.gearChangeTimePrev = nil; 77 self.recentlyCrashed = true; 78 self.lastPlayedCrashSound = -1; 79 80 81 if type(self.gears) ~= "table" then 82 print("Error while loading vehicle " .. tostring(self.templateName) .. "'s gears. Make sure you've set the key 'gears' within you data table to a table value containing each gear's transmission ratio."); 83 return; 84 end; 85 if type(self.gearLabels) ~= "table" then 86 print("Error while loading vehicle " .. tostring(self.templateName) .. "'s gear labels. Make sure you've set the key 'gearLabels' within you data table to a table value containing each gear's label as string value."); 87 end; 88 89 self.gearCount = #self.gears; 90 self.motorFinalDriveRatio = dataTable.motorFinalDriveRatio or 1; 91 for i, v in pairs(self.gears) do 92 if type(self.gearLabels[i]) ~= "string" then 93 self.gearLabels[i] = (i==1 and "R" or tostring(i-1)); 94 end; 95 end; 96 97 self.automaticDrive = getNoNil(dataTable.automaticDrive, true); 98 self.automaticDriveActive = self.automaticDrive; 99 self.upperLimitRpm = dataTable.automaticUpperLimitRpm or 2700; 100 self.lowerLimitRpm = dataTable.automaticLowerLimitRpm or 1600; 101 self.gearSwitchPace = dataTable.automaticGearSwitchPace or 0.3; 102 self.gearSwitchTimer = 100; 103 104 self.torqueCurve = dataTable.motorTorqueCurve; 105 self.reverseTorqueCurve = dataTable.motorReverseTorqueCurve; 106 -- !!TODO!! check if torque curve is valid 107 108 self.motorRpm = 0; 109 self.motorSmoothRpm = 0; -- smooth rpm used for digital rpm-meter 110 self.motorLastRpm = 0; 111 self.wheelRpm = 0; 112 self.currentSpeed = 0; 113 114 -- values for cruise control and the corresponding PID controller 115 self.cruiseControlRpm = 5000; 116 self.cruiseControlThrottleValue = 0.5; 117 self.cruiseControlIntegral = 0; 118 self.cruiseControlPreviousError = 0; 119 self.cruiseControlLinearCoeff = 0.005; 120 self.cruiseControlIntegralCoeff = 0.00005; 121 self.cruiseControlDerivativeCoeff = 0.00005; 122 123 -- inclineSlip: dynamic wheel friction depending on underground inclination 124 if dataTable.inclineSlip ~= nil then 125 self.inclineSlip = dataTable.inclineSlip; 126 local minAngle = dataTable.inclineSlip.minAngle or 30; 127 local maxAngle = dataTable.inclineSlip.maxAngle or 30; 128 129 self.lowerFrictionThreshold = math.cos(math.rad(minAngle)); 130 self.upperFrictionThreshold = math.cos(math.rad(maxAngle)); 131 132 self.maxForwardStiffness = dataTable.inclineSlip.maxForwardStiffness or 1; 133 self.minForwardStiffness = dataTable.inclineSlip.minForwardStiffness or 0; 134 self.maxSidewayStiffness = dataTable.inclineSlip.maxSidewayStiffness or 1; 135 self.minSidewayStiffness = dataTable.inclineSlip.minSidewayStiffness or 0; 136 end 137 138 self.isMotorOn = false; 139 140 VehicleMotor.loadMotorLoadExhaust(self, dataTable); 141 142 self.customWheelScriptActive = true; 143 self.isReversing = false; 144 145 self.realVolume = 0; 146 self.realVolumeSnow = 0; 147 148 VehicleMotor.loadMotorSounds(self, dataTable); 149 150 self.singleEventSounds = {}; 151 self.terrainLayerIndex1 = nil; 152 self.terrainLayerIndex2 = nil; 153 154 self.groundHit = false; 155 156 VehicleMotor.loadEnvironmentSounds(self, dataTable); 157 VehicleMotor.loadDampingSounds(self, dataTable); 158 VehicleMotor.loadCrashSounds(self, dataTable); 159 160 -- finally apply the brake to our axles 161 for n, axle in pairs(self.axles) do 162 Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque); 163 Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque); 164 end; 165 end;
VehicleMotor.loadMotorLoadExhaust(self, dataTable)
167 function VehicleMotor.loadMotorLoadExhaust(self, dataTable) 168 -- exhaust load scale 169 self.exhaustLoadScale = dataTable.exhaustLoadScale or 2; -- three times as much exhaust in maximum load sound weight 170 171 if self.exhaustLoadScale > 0 then 172 for k, v in pairs(self.exhaustParticleSystems) do 173 v.emissionRate = ParticleSystem.getEmissionRateOverTime(v.id); 174 end; 175 end; 176 end;
VehicleMotor.updateMotorLoadExhaust(self, motorLoad)
Updates the exhaust particle spawn rate depending on motor load. Use the factor emissionRate
to have some black smoke particles if you press the accelerator.
180 function VehicleMotor.updateMotorLoadExhaust(self, motorLoad) 181 if self.exhaustLoadScale <= 0 then 182 return; 183 end; 184 185 motorLoad = motorLoad + 1; 186 187 for k, v in pairs(self.exhaustParticleSystems) do 188 ParticleSystem.setEmissionRateOverTime(v.id, motorLoad * v.emissionRate); 189 end; 190 end;
VehicleMotor.loadMotorSounds(self, dataTable)
Loads all motor sounds used for VehicleMotor. This makes use of the Vehicle:loadSingleSound()
function (see Vehicle).
194 function VehicleMotor.loadMotorSounds(self, dataTable) 195 196 if dataTable.sounds ~= nil then 197 self.motorSoundStart = self:loadSingleSound(dataTable.sounds.motorStart); 198 self.motorSoundStop = self:loadSingleSound(dataTable.sounds.motorStop); 199 200 if self.motorSoundStart ~= nil then 201 self.motorSoundStartLength = AudioSource.getLength(self.motorSoundStart); 202 end; 203 204 self.motorSoundIdle = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorIdle); 205 self.motorSoundLoad = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorLoad); 206 self.motorSoundRun = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorRun); 207 208 if dataTable.sounds.motorConfiguration ~= nil then 209 local data = dataTable.sounds.motorConfiguration; 210 self.motorSoundMinRunRpm = data.minRunFadeRpm; 211 self.motorSoundMaxRunRpm = data.maxRunFadeRpm; 212 self.motorSoundMinRunSpeed = data.minRunFadeSpeed; 213 self.motorSoundMaxRunSpeed = data.maxRunFadeSpeed; 214 end; 215 end; 216 end;
VehicleMotor.loadEnvironmentSounds(self, dataTable)
Loads all environment-related sounds used for VehicleMotor. This makes use of the Vehicle:loadSingleSound()
function (see Vehicle).
220 function VehicleMotor.loadEnvironmentSounds(self, dataTable) 221 if dataTable.sounds ~= nil and dataTable.sounds.environmentSounds ~= nil then 222 self.environmentSounds = {}; 223 for k, sound in pairs(dataTable.sounds.environmentSounds) do 224 local environmentSound = { 225 sound = self:loadSingleSound(sound.sound), 226 layers = sound.layers, 227 volume = getNoNil(sound.volume, 1), 228 } 229 table.insert(self.environmentSounds, environmentSound); 230 end; 231 end; 232 end;
VehicleMotor.loadDampingSounds(self, dataTable)
Loads all damping sounds used for VehicleMotor. This makes use of the Vehicle:loadSingleSound()
function (see Vehicle).
236 function VehicleMotor.loadDampingSounds(self, dataTable) 237 if dataTable.sounds ~= nil and dataTable.sounds.dampingSounds ~= nil then 238 self.suspensionForce = dataTable.sounds.dampingSounds.suspensionForce or 35000; 239 self.suspensionVolume = dataTable.sounds.dampingSounds.suspensionVolume or 1; 240 self.dampingSounds = {}; 241 for k, sound in pairs(dataTable.sounds.dampingSounds.sounds) do 242 local soundvalue = self:loadSingleSound(sound); 243 AudioSource.setVolume(soundvalue, self.suspensionVolume); 244 table.insert(self.dampingSounds, soundvalue); 245 end; 246 self.singleEventSounds[VehicleMotor.SOUNDID_DAMPING] = self.dampingSounds; 247 end; 248 end;
VehicleMotor.loadCrashSounds(self, dataTable)
Loads all crash sounds used for VehicleMotor. This makes use of the Vehicle:loadSingleSound()
function (see Vehicle).
252 function VehicleMotor.loadCrashSounds(self, dataTable) 253 if dataTable.sounds ~= nil and dataTable.sounds.crashSounds ~= nil then 254 self.crashSounds = {}; 255 for k,sound in pairs(dataTable.sounds.crashSounds) do 256 local crashSound = self:loadSingleSound(sound); 257 table.insert(self.crashSounds, crashSound); 258 end; 259 self.singleEventSounds[VehicleMotor.SOUNDID_CRASH] = self.crashSounds; 260 end; 261 end
VehicleMotor.loadMotorSound(self, data)
Loads a motor sound. Note that one motor sound can consist of different samples.
264 function VehicleMotor.loadMotorSound(self, data) 265 if data == nil then 266 return nil; 267 end; 268 269 local sound = {}; 270 271 if type(data) == "table" then 272 if data[1] ~= nil then 273 for k, v in pairs(data) do 274 table.insert(sound, VehicleMotor.rawLoadMotorSound(self, v)); 275 end; 276 else 277 table.insert(sound, VehicleMotor.rawLoadMotorSound(self, data)); 278 end; 279 end; 280 281 return sound; 282 end;
VehicleMotor.rawLoadMotorSound(self, data)
Loads a single motor sound sample. Each sample can have different volume and pitch settings, as well as many others. If you are in doubt about how the parameters exactly work, please have a look at the code implementation below.
- Use
baseRpm
along withbaseRpmScale
to control the pitch based on rpm OR - Use
pitchOffset
andpitchScale
to control the pitch based on custom pitch/rpm settings. In any case, trial-and-error provide best results. volumeScale
is multiplied with rpm to determine the volume.fadeInSpeed
allows you to avoid awkward jumps in volume.minFadeInRpm
andmaxFadeInRpm
specify whether and how fast the sound shall be faded in depending on rpm. At any rpm belowminFadeInRpm
, the sound will be inactive.minFadeOutRpm
andmaxFadeOutRpm
specify whether and how fast the sound shall be faded in depending on rpm. At any rpm abovemaxFadeOutRpm
, the sound will be inactive.interieurVolumeScale
allows to influence the sound volume in interior perspective.interiorLowpass
andexterieurLowpass
specify low-pass amounts depending on perspective. Cutoff frequency will be 2500 Hz by default.
294 function VehicleMotor.rawLoadMotorSound(self, data) 295 local sound = {}; 296 297 sound.sampleId = self:loadSingleSound(data, true); 298 299 sound.minPitch = data.minPitch or 0.5; 300 sound.maxPitch = data.maxPitch or 2; 301 sound.minVolume = data.minVolume or 0.2; 302 sound.maxVolume = data.maxVolume or 1.5; 303 304 -- load additional data for our motor sound 305 if data.baseRpm ~= nil then 306 sound.baseRpm = data.baseRpm; 307 sound.baseRpmScale = data.baseRpmScale or 1; 308 else 309 sound.pitchOffset = data.pitchOffset or 1; 310 sound.pitchScale = data.pitchScale or (sound.maxPitch - sound.minPitch) / (self.motorMaxRpm - self.motorMinRpm); -- 10% more pitch for 100 more rpm 311 end; 312 313 314 if data.volumeScale ~= nil then 315 sound.volumeScale = data.volumeScale; 316 end; 317 318 if data.fadeInSpeed ~= nil then 319 sound.fadeInSpeed = data.fadeInSpeed; 320 sound.lastVolume = 0; 321 end; 322 if data.fadeOutSpeed ~= nil then 323 sound.fadeOutSpeed = data.fadeOutSpeed; 324 sound.lastVolume = 0; 325 end; 326 if data.minFadeInRpm ~= nil then 327 sound.minFadeInRpm = data.minFadeInRpm; 328 sound.maxFadeInRpm = data.maxFadeInRpm or (sound.minFadeInRpm + 300); 329 end; 330 if data.minFadeOutRpm ~= nil then 331 sound.minFadeOutRpm = data.minFadeOutRpm; 332 sound.maxFadeOutRpm = data.maxFadeOutRpm or (sound.minFadeOutRpm + 300); 333 end; 334 335 if data.interieurVolumeScale ~= nil then 336 sound.interieurVolumeScale = data.interieurVolumeScale; 337 end; 338 if data.interieurLowpass ~= nil then 339 sound.interieurLowpass = data.interieurLowpass; 340 end; 341 if data.exterieurLowpass ~= nil then 342 sound.exterieurLowpass = data.exterieurLowpass; 343 end; 344 345 return sound; 346 end;
VehicleMotor.foreachMotorSound(self, sound, call, ...)
Calls the function call
(function) on each sound sample in sound
(table) while passing over all arguments in …
.
349 function VehicleMotor.foreachMotorSound(self, sound, call, ...) 350 if sound == nil or call == nil then return end; 351 352 for k, v in pairs(sound) do 353 call(v.sampleId, ...); 354 end; 355 end;
VehicleMotor.foreachMotorSoundSelf(self, sound, call, ...)
Calls the function call
(function) on each sound sample in sound
(table) while passing over all arguments in …
, additionally also passing over self as first argument.
358 function VehicleMotor.foreachMotorSoundSelf(self, sound, call, ...) 359 if sound == nil or call == nil then return end; 360 361 for k, v in pairs(sound) do 362 call(self, v.sampleId, ...); 363 end; 364 end;
VehicleMotor:customLoadAxle(axle, v)
This hooks into Vehicle:loadAxles()
(see Vehicle for further details) to add further data to the axle configuration, which will be required later on in this script.
368 function VehicleMotor:customLoadAxle(axle, v) 369 -- read additional data from configuration 370 axle.radius = v.radius or (0.5 * (Wheel.getRadius(axle.leftWheel) + Wheel.getRadius(axle.rightWheel))); 371 axle.wheelMass = v.mass or (0.5 * (Wheel.getMass(axle.leftWheel) + Wheel.getMass(axle.rightWheel))); 372 axle.tractionControl = getNoNil(v.tractionControl, true); 373 axle.useABS = getNoNil(v.useABS, true); 374 end;
VehicleMotor:getMotorTorqueAtRpm(rpm)
Returns the motor torque at the rpm level rpm
(float) in Nm.
378 function VehicleMotor:getMotorTorqueAtRpm(rpm) 379 380 --if vehicle is in Reverse it uses reverseTorqueCurve 381 local torqueCurve = self.torqueCurve; 382 if self.reverseTorqueCurve ~= nil and self:getGearTransmissionRatio() < 0 then 383 torqueCurve = self.reverseTorqueCurve; 384 end; 385 386 if #torqueCurve < 2 then 387 return 0; 388 end; 389 390 if rpm <= torqueCurve[1].rpm then 391 return torqueCurve[1].torque; 392 393 elseif rpm >= torqueCurve[#torqueCurve].rpm then 394 return torqueCurve[#torqueCurve].torque; 395 end; 396 397 for i=1, #torqueCurve-1, 1 do 398 399 local rpm1, rpm2 = torqueCurve[i].rpm, torqueCurve[i+1].rpm; 400 401 if rpm >= rpm1 and rpm <= rpm2 then 402 -- rpm is in this range 403 local torque1, torque2 = torqueCurve[i].torque, torqueCurve[i+1].torque; 404 405 return torque1 + (torque2 - torque1) * (rpm - rpm1) / (rpm2 - rpm1); 406 end; 407 end; 408 409 -- actually we should never arrive down here 410 return 0; 411 end; 412
VehicleMotor:update(dt)
Updates the current rpm, the gearbox and some controls. This will also refresh GUI values. Finally, it also updates motor sound.
415 function VehicleMotor:update(dt) 416 if not self.isActive then 417 if not self.isMotorOn then 418 -- slowly come to a halt 419 -- skip the large update loop if this vehicle has stopped 420 if math.abs(self.currentSpeed) > 1 then 421 self.currentSpeed = 0.9 * self.currentSpeed + 0.1 * (self.wheelRpm / 30 * self.speedPointerWheelRadius * math.pi * 3.6); 422 self.motorSmoothRpm = 0.6 * self.motorSmoothRpm + 0.4 * self.motorRpm; 423 return; 424 end; 425 end; 426 end; 427 428 self.currentSpeed = 0.9 * self.currentSpeed + 0.1 * (self.wheelRpm / 30 * self.speedPointerWheelRadius * math.pi * 3.6); 429 self.motorSmoothRpm = 0.6 * self.motorSmoothRpm + 0.4 * self.motorRpm; 430 431 -- add fuel usage 432 if g_isMaster and self.isMotorOn then 433 self:callSafe("addFuelUsage", dt, self.currentSpeed * dt); 434 435 if self:getHasRunOutOfFuel() then 436 self:setIsMotorOn(false); 437 end; 438 end; 439 440 -- update gear - no longer in use in season 2 441 --[[local automaticActive = self.automaticDrive and self.automaticDriveActive; 442 if automaticActive then 443 self.gearSwitchTimer = self.gearSwitchTimer + dt; 444 445 if self.gearSwitchTimer > 0 and self.currentGear ~= 1 then 446 if self.motorSmoothRpm > self.upperLimitRpm then 447 -- switch one gear upwards 448 self:setGear(self.currentGear + 1); 449 450 elseif self.currentGear > 2 and self.motorSmoothRpm < self.lowerLimitRpm then 451 -- switch one gear upwards 452 self:setGear(self.currentGear - 1); 453 end; 454 end; 455 end; 456 457 -- make it possible to override input 458 if self:getIsInputActive() then 459 -- input help 460 461 if not self:getHasRunOutOfFuel() then 462 if not self.isMotorOn then 463 -- start motor again 464 g_GUI:addKeyHint(InputMapper.Vehicle_StartMotor, l10n.get("Input_Vehicle_StartMotor")); 465 end; 466 467 if InputMapper:getKeyDown(InputMapper.Vehicle_StartMotor) then 468 self:setIsMotorOn(not self.isMotorOn); 469 end; 470 end; 471 472 if GameplaySettings.automaticDirectionChange and not self:getIsThrottleActivated() and not self:getIsBrakeActivated() then 473 474 -- if vehicle is moving forward, let's say up a hill, and the accelerate key is released and the car starts rolling back without explicitly pressing the brake key, it goes in reverse gear 475 if self.currentGear == 2 and self.currentSpeed < -1 then 476 self:setGear(1); 477 end 478 479 -- if vehicle is moving backwards, let's say up a hill, and the accelerate backwards key is released and the car starts rolling down without explicitly pressing the brake key, it goes in forward gear 480 if self.currentGear == 1 and self.currentSpeed > 1 then 481 self:setGear(2); 482 end 483 end; 484 end; 485 486 if self:getIsActive() then 487 -- rpm pointer 488 if self.rpmPointerId ~= nil then 489 local newAngle = self.rpmPointerMaxAngle * clamp01(self.motorRpm / self.rpmPointerMaxRpm); 490 self.rpmPointerLastAngle = 0.9 * self.rpmPointerLastAngle + 0.1 * newAngle; 491 setRotationZ(self.rpmPointerId, self.rpmPointerLastAngle); 492 end; 493 if self.speedPointerId ~= nil then 494 local newAngle = self.speedPointerMaxAngle * clamp01(math.abs(self.currentSpeed / self.speedPointerMaxSpeed)); 495 self.speedPointerLastAngle = 0.95 * self.speedPointerLastAngle + 0.05 * newAngle; 496 setRotationZ(self.speedPointerId, self.speedPointerLastAngle); 497 end; 498 499 if g_isMaster then 500 -- update reverse beep 501 self.isReversing = self:getGearTransmissionRatio() < 0; 502 end; 503 end; 504 505 if self:getIsLocalPlayerEntered() then 506 -- changes the vehicle's cameras pov, according to it's speed 507 if not self.driverSeat.isInterieurCamera then 508 VehicleCamera.activeCamera.accelerationFOVFactor = clamp01(math.abs(self.currentSpeed / self.speedPointerMaxSpeed)); 509 end; 510 end; 511 512 -- refresh GUI values 513 if self:getIsGUIActive() then 514 self.vehicleHUD:showSpeed(self.currentSpeed, self.currentSpeed / self.speedPointerMaxSpeed, math.max(self.motorSmoothRpm, 900) / self.rpmPointerMaxRpm); 515 self.vehicleHUD:setIsMotorOn(self.isMotorOn); 516 self.vehicleHUD:setGear(self.gearLabels[self.currentGear]); 517 end; 518 519 -- update sounds 520 if self.isMotorOn then 521 local motorLerpValue = (self.motorSmoothRpm - self.motorMinRpm) / (self.motorMaxRpm - self.motorMinRpm); 522 local motorLoad; 523 if self.cruiseControlActive then 524 motorLoad = self.cruiseControlThrottleValue; -- [-1.5; 1] 525 else 526 motorLoad = self.throttleValue; -- [0;1] 527 end; 528 local runFade = 1; 529 530 if self.motorSoundMinRunRpm ~= nil and self.motorSoundMaxRunRpm ~= nil then 531 runFade = math.min(runFade, clamp01(inverseLerp(self.motorSoundMinRunRpm, self.motorSoundMaxRunRpm, self.motorSmoothRpm))); 532 end; 533 if self.motorSoundMinRunSpeed ~= nil and self.motorSoundMaxRunSpeed ~= nil then 534 runFade = math.min(runFade, clamp01(inverseLerp(self.motorSoundMinRunSpeed, self.motorSoundMaxRunSpeed, math.abs(self.currentSpeed)))); 535 end; 536 537 VehicleMotor.updateMotorLoadExhaust(self, motorLoad); 538 539 local isInterieurCamera = false; 540 if g_isMultiplayer then 541 isInterieurCamera = g_networkGame.localPlayer.isInterieurCamera; 542 else 543 isInterieurCamera = g_scenario.player.isInterieurCamera; 544 end; 545 546 VehicleMotor.updateMotorSound(self, self.motorSoundIdle, dt, isInterieurCamera, self.motorSmoothRpm, motorLerpValue); 547 548 VehicleMotor.updateMotorSound(self, self.motorSoundLoad, dt, isInterieurCamera, self.motorSmoothRpm, motorLerpValue, motorLoad, runFade); 549 VehicleMotor.updateMotorSound(self, self.motorSoundRun, dt, isInterieurCamera, self.motorSmoothRpm, motorLerpValue, (1-motorLoad), runFade); 550 end; 551 552 -- update snowParticles 553 if self.snowParticleSystems ~= nil then 554 local inReverse = self.currentSpeed < 0; 555 local hit, ff, sf, force = Wheel.getGroundHit(self.axles[1].leftWheel); 556 local x, y, z = getWorldPosition(self.id); 557 local offsetX, offsetY, offsetZ = GameControl.getTerrainOffset(); 558 local terrainLayer = TerrainEditor.getTerrainLayerIndexAtPosition(x-offsetX, z-offsetZ); 559 560 for k, particle in pairs(self.snowParticleSystems) do 561 if inReverse == particle.reverse then -- play only those particle systems, which go in the same direction as the car 562 if hit and terrainLayer == 0 and self.currentSpeed > 1 then 563 ParticleSystem.play(particle.id); 564 ParticleSystem.setEmissionRateOverTime(particle.id, math.abs(self.currentSpeed * particle.amount)); 565 else 566 ParticleSystem.stop(particle.id); 567 end; 568 else 569 ParticleSystem.stop(particle.id); 570 end; 571 end; 572 end; 573 end; 574
VehicleMotor.updateMotorSound(self, sound, ...)
Calls VehicleMotor.rawUpdateMotorSound
on each sample in this group of motor sounds.
578 function VehicleMotor.updateMotorSound(self, sound, ...) 579 if sound == nil then return end; 580 581 for k, v in pairs(sound) do 582 VehicleMotor.rawUpdateMotorSound(self, v, ...); 583 end; 584 end;
VehicleMotor.rawUpdateMotorSound(self, sound, dt, isInterieur, motorRpm, motorLerpValue, blendValue, blendScale)
This actually updates a motor sound, while making use of the parameters explained in VehicleMotor.rawLoadMotorSound
(above on this page).
587 function VehicleMotor.rawUpdateMotorSound(self, sound, dt, isInterieur, motorRpm, motorLerpValue, blendValue, blendScale) 588 589 local pitch, maxVolume = 1, 1; 590 local volume = 1; 591 592 if sound.baseRpm ~= nil then 593 pitch = self.motorSmoothRpm / sound.baseRpm; 594 595 if sound.baseRpmScale ~= nil then 596 pitch = 1 + (pitch - 1) * sound.baseRpmScale; 597 end; 598 else 599 pitch = sound.pitchOffset + sound.pitchScale * clamp(self.motorSmoothRpm - self.motorMinRpm, 0, self.motorMaxRpm); 600 end; 601 602 if sound.volumeScale ~= nil then 603 volume = sound.minVolume + sound.volumeScale * (self.motorSmoothRpm - self.motorMinRpm); 604 else 605 volume = lerp(sound.minVolume, sound.maxVolume, motorLerpValue); 606 end; 607 608 if blendValue ~= nil then 609 -- blend volume down to this value 610 local targetVolume = volume * blendValue; 611 612 if sound.lastVolume ~= nil then 613 if sound.lastVolume < targetVolume then 614 if sound.fadeInSpeed ~= nil then 615 volume = math.min(sound.lastVolume + sound.fadeInSpeed * dt, targetVolume); 616 else 617 volume = targetVolume; 618 end; 619 620 elseif sound.lastVolume > targetVolume then 621 if sound.fadeOutSpeed ~= nil then 622 volume = math.max(sound.lastVolume - sound.fadeOutSpeed * dt, targetVolume); 623 else 624 volume = targetVolume; 625 end; 626 else 627 volume = targetVolume; 628 end; 629 630 -- store current sound volume for next frame 631 sound.lastVolume = volume; 632 else 633 volume = targetVolume; 634 end; 635 636 else 637 -- don't clamp if blend value is applied 638 volume = clamp(volume * (blendScale or 1), sound.minVolume, sound.maxVolume); 639 end; 640 641 -- ramp volume up/down (depending on rpm) 642 if sound.minFadeInRpm ~= nil and sound.maxFadeInRpm then 643 if motorRpm < sound.minFadeInRpm then 644 volume = 0; 645 646 elseif motorRpm < sound.maxFadeInRpm then 647 volume = volume * clamp01(inverseLerp(sound.minFadeInRpm, sound.maxFadeInRpm, motorRpm)); 648 end; 649 end; 650 651 if sound.minFadeOutRpm ~= nil and sound.maxFadeOutRpm then 652 if motorRpm > sound.maxFadeOutRpm then 653 volume = 0; 654 655 elseif motorRpm > sound.minFadeOutRpm then 656 volume = volume * clamp01(inverseLerp(sound.maxFadeOutRpm, sound.minFadeOutRpm, motorRpm)); 657 end; 658 end; 659 660 if isInterieur and sound.interieurVolumeScale ~= nil then 661 volume = volume * sound.interieurVolumeScale; 662 end; 663 if sound.interieurLowpass ~= nil or sound.exterieurLowpass ~= nil then 664 if isInterieur then 665 AudioSource.setLowPass(sound.sampleId, sound.interieurLowpass or 0); 666 else 667 AudioSource.setLowPass(sound.sampleId, sound.exterieurLowpass or 0); 668 end; 669 end; 670 671 VehicleMotor.applySpatialBlend(self, sound.sampleId); 672 673 -- apply them both (but only clamped) 674 AudioSource.setPitch(sound.sampleId, clamp(pitch, sound.minPitch, sound.maxPitch)); 675 AudioSource.setVolume(sound.sampleId, volume); 676 end; 677
VehicleMotor:applySpatialBlend(sampleId)
Adjusts the spatial blend of vehicle sound depending on whether the player is sitting in it or not.
680 function VehicleMotor:applySpatialBlend(sampleId) 681 -- when local player is entered, spatial blend shall be 0 682 -- when another player (or no player) is entered, spatial blend shall be 1 683 AudioSource.setSpatialBlend(sampleId, ifelse(self:getIsLocalPlayerEntered(true), 0, 1)); 684 end;
VehicleMotor:fixedUpdate(dt)
This is called once per physics update, i.e. possibly multiple times per frame. We therefore only perform necessary calculations.
688 function VehicleMotor:fixedUpdate(dt) 689 self.soundDeltaTime = dt; 690 self:updateWheels(); 691 692 if g_isClient then 693 return; 694 end; 695 696 self.motorLastRpm = self.motorRpm; 697 self.wheelRpm = self:getDrivingWheelsRpm(); 698 self.motorRpm = 0.9 * self.motorRpm + 0.1 * self.wheelRpm * self:getGearTransmissionRatio(); 699 700 if self.isActive and self.cruiseControlActive then 701 local errorInRpms = self.cruiseControlRpm - self.motorRpm; 702 703 self.cruiseControlIntegral = self.cruiseControlIntegral + errorInRpms * dt; 704 local cruiseControlDerivative = (errorInRpms - self.cruiseControlPreviousError) / dt; 705 706 self.cruiseControlThrottleValue = clamp(self.cruiseControlLinearCoeff*errorInRpms + self.cruiseControlIntegralCoeff*self.cruiseControlIntegral + self.cruiseControlDerivativeCoeff*cruiseControlDerivative, -1.5, 1); 707 self.cruiseControlPreviousError = errorInRpms; 708 end; 709 end;
VehicleMotor:calculateFriction()
Calculates the wheels' friction index according to the vehicles normal in world space, eg steepness of the terrain.
713 function VehicleMotor:calculateFriction() 714 715 local projectedNormal = VectorUtils.transformDirection(self.mainId, Vector3.up); 716 717 if projectedNormal.y < self.lowerFrictionThreshold and projectedNormal.y > self.upperFrictionThreshold then 718 self.forwardFriction = self.minForwardStiffness + (self.maxForwardStiffness - self.minForwardStiffness) * ((projectedNormal.y - self.upperFrictionThreshold) / (self.lowerFrictionThreshold - self.upperFrictionThreshold)); 719 self.sidewayFriction = self.minSidewayStiffness + (self.maxSidewayStiffness - self.minSidewayStiffness) * ((projectedNormal.y - self.upperFrictionThreshold) / (self.lowerFrictionThreshold - self.upperFrictionThreshold)); 720 elseif projectedNormal.y > self.lowerFrictionThreshold then 721 self.forwardFriction = self.maxForwardStiffness; 722 self.sidewayFriction = self.maxSidewayStiffness; 723 else 724 self.forwardFriction = self.minForwardStiffness; 725 self.sidewayFriction = self.minSidewayStiffness; 726 end; 727 728 end
VehicleMotor:updateWheels()
Updates the wheel torques, as well as steering values. The actual updating is done by VehicleMotor.singleWheel
.
731 function VehicleMotor:updateWheels() 732 local steerAngle = self:getCurrentSteerAngle(); 733 local drivingWheelCount = self:getDrivingWheelsCount(); 734 local brakeTorque = self:getCurrentBrakeTorque(); 735 736 -- get motor torque and inertia 737 local motorTorque, motorInertia = 0, 0; 738 if g_isMaster and drivingWheelCount > 0 then 739 local a, b = self:getCurrentMotorTorque(); 740 motorTorque = a / drivingWheelCount; -- motor torque in [Nm], relative to wheel 741 motorInertia = b / drivingWheelCount; -- motor inertia in [kgm^2], relative to the wheel 742 end; 743 744 -- get the vehicles rotation to determine the wheels slip index depending on the terrain steepness if slip values are defined in the vehicles datatable 745 if g_isMaster and self.inclineSlip ~= nil then 746 self:calculateFriction(); 747 end 748 749 for n, axle in pairs(self.axles) do 750 751 if axle.steeringScale ~= 0 then 752 Wheel.setSteerAngle(axle.leftWheel, steerAngle * axle.steeringScale); 753 Wheel.setSteerAngle(axle.rightWheel, steerAngle * axle.steeringScale); 754 end; 755 756 if g_isMaster then 757 VehicleMotor.singleWheel(self, axle.leftWheel, axle, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0); 758 VehicleMotor.singleWheel(self, axle.rightWheel, axle, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0); 759 760 -- setting the slip depending on steepness of terrain if slip values for respective vehicle are defined 761 if self.inclineSlip ~= nil then 762 Wheel.setStiffness(axle.leftWheel, self.forwardFriction, self.sidewayFriction); 763 Wheel.setStiffness(axle.rightWheel, self.forwardFriction, self.sidewayFriction); 764 end; 765 766 -- add powertrain inertia 767 if axle.motorGear ~= 0 then 768 local extraMass = motorInertia / axle.radius^2; 769 Wheel.setMass(axle.leftWheel, axle.wheelMass + extraMass); 770 Wheel.setMass(axle.rightWheel, axle.wheelMass + extraMass); 771 end; 772 else 773 -- always slightly brake wheels on clients 774 Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque); 775 Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque); 776 end; 777 end; 778 end;
VehicleMotor:singleWheel(wheelId, axle, motorTorque, brakeTorque)
Updates a single wheel using ABS (anti-lock braking system) to avoid wheel lock. This also includes a traction control to avoid spinning wheels. Both ABS and traction control operate depending on forward slip (which is physically not correct, but the best option for this game for performance reasons).
782 function VehicleMotor:singleWheel(wheelId, axle, motorTorque, brakeTorque) 783 local hit, ff, sf, force = Wheel.getGroundHit(wheelId); 784 785 if hit then 786 if axle.useABS and ff < -0.6 then 787 -- brake traction control (ABS) 788 local brakeEnabled = (getTime()%1.2) < 0.6; 789 Wheel.setTorques(wheelId, motorTorque, brakeEnabled and brakeTorque or 0); 790 791 else 792 Wheel.setTorques(wheelId, motorTorque, brakeTorque); 793 end; 794 -- 795 else 796 Wheel.setTorques(wheelId, 0, brakeTorque); 797 end; 798 799 --if self:getIsLocalPlayerEntered() then 800 -- update wheel sound too 801 local x, y, z = getWorldPosition(self.id); 802 if g_isServer or g_isSingleplayer then 803 local offsetX, offsetY, offsetZ = GameControl.getTerrainOffset(); 804 self.terrainLayerIndex1, self.terrainLayerIndex2 = TerrainEditor.getTerrainLayerIndexAtPosition(x-offsetX, z-offsetZ); 805 end; 806 if self.environmentSounds ~= nil then 807 for k, sound in pairs(self.environmentSounds) do 808 local targetVolume = 0; 809 local terrainLayerFound = false; 810 811 for k1, layer in pairs(sound.layers) do 812 813 if self.terrainLayerIndex1 == layer or self.terrainLayerIndex2 == layer then 814 terrainLayerFound = true; 815 end; 816 end; 817 818 self.groundHit = hit or false; 819 820 if terrainLayerFound and hit and self.isActive then 821 targetVolume = sound.volume * math.abs(self.currentSpeed)/self.speedPointerMaxSpeed; 822 end; 823 824 self.realVolume = lerp(self.realVolume, targetVolume, self.soundDeltaTime); 825 826 -- TODO MP synchro - sound should be available on clients as well 827 if self:getIsLocalPlayerEntered() and math.abs(self.currentSpeed) > 5 then 828 AudioSource.setVolume(sound.sound, self.realVolume); 829 AudioSource.play(sound.sound); 830 else 831 AudioSource.stop(sound.sound); 832 end; 833 end; 834 end; 835 836 837 if self.isActive and self.dampingSounds ~= nil and hit then 838 if force ~= nil and force > self.suspensionForce then 839 local soundIndex = math.random(#self.dampingSounds); 840 self:playSingleEventSound(VehicleMotor.SOUNDID_DAMPING, soundIndex); 841 end; 842 end; 843 844 if self.isActive and self.hasCrashSoundsImplemented and self.hasCrashBoxImplemented then 845 if self:getIsColliding() and not self.recentlyCrashed then 846 local soundIndex = math.random(#self.crashSounds); 847 if soundIndex == self.lastPlayedCrashSound then 848 soundIndex = math.fmod(soundIndex, #self.crashSounds) + 1; 849 end; 850 self.lastPlayedCrashSound = soundIndex; 851 852 self:playSingleEventSound(VehicleMotor.SOUNDID_CRASH, soundIndex); 853 self.recentlyCrashed = true; 854 855 elseif not self:getIsColliding() then 856 self.recentlyCrashed = false; 857 end 858 end; 859 end; 860 --end;
VehicleMotor:setIsMotorOn(isOn, noEvent)
Sets the motor to on or off, depending on isOn
(bool).
864 function VehicleMotor:setIsMotorOn(isOn, noEvent) 865 if self.isMotorOn ~= isOn then 866 self.isMotorOn = isOn; 867 if self.isMotorOn then 868 if self.motorSoundStop ~= nil then AudioSource.stop(self.motorSoundStop); end; 869 870 if self.motorSoundStart ~= nil then 871 VehicleMotor.applySpatialBlend(self, self.motorSoundStart); 872 AudioSource.play(self.motorSoundStart); 873 874 if self.motorSoundIdle ~= nil then 875 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundIdle, VehicleMotor.applySpatialBlend); 876 VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.playDelayed, self.motorSoundStartLength); 877 end; 878 if self.motorSoundLoad ~= nil then 879 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundLoad, VehicleMotor.applySpatialBlend); 880 VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.playDelayed, self.motorSoundStartLength); 881 end; 882 if self.motorSoundRun ~= nil then 883 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundRun, VehicleMotor.applySpatialBlend); 884 VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.playDelayed, self.motorSoundStartLength); 885 end; 886 else 887 888 if self.motorSoundIdle ~= nil then 889 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundIdle, VehicleMotor.applySpatialBlend); 890 VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.play); 891 end; 892 if self.motorSoundLoad ~= nil then 893 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundLoad, VehicleMotor.applySpatialBlend); 894 VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.play); 895 end; 896 if self.motorSoundRun ~= nil then 897 VehicleMotor.foreachMotorSoundSelf(self, self.motorSoundRun, VehicleMotor.applySpatialBlend); 898 VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.play); 899 end; 900 end; 901 902 else 903 if self.motorSoundIdle ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.stop); end; 904 if self.motorSoundLoad ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.stop); end; 905 if self.motorSoundRun ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.stop); end; 906 907 if self.motorSoundStart ~= nil then AudioSource.stop(self.motorSoundStart); end; 908 if self.motorSoundStop ~= nil then VehicleMotor.applySpatialBlend(self, self.motorSoundStop); AudioSource.play(self.motorSoundStop); end; 909 end; 910 end; 911 912 -- update exhaust particles 913 for k, v in pairs(self.exhaustParticleSystems) do 914 setActive(v.id, self.isMotorOn or false); 915 end; 916 917 if not noEvent then 918 EventVehicleSetMotorOn:send(self, self.isMotorOn); 919 end; 920 end;
VehicleMotor:onEnter(player, isLocalPlayer)
Start the motor unless disabled in GameplaySettings.
924 function VehicleMotor:onEnter(player, isLocalPlayer) 925 if isLocalPlayer and GameplaySettings.autoStartMotor and not self:getHasRunOutOfFuel() then 926 self:setIsMotorOn(true); 927 end; 928 929 -- update snow particles 930 for k, v in pairs(self.snowParticleSystems) do 931 setActive(v.id, true); 932 end; 933 end;
VehicleMotor:onLeave(player, isLocalPlayer)
Stop the motor upon leaving. Also apply maximum braking torque to save performance on wheel physics.
937 function VehicleMotor:onLeave(player, isLocalPlayer) 938 if isLocalPlayer and GameplaySettings.autoStopMotor then 939 self:setIsMotorOn(false); 940 end; 941 942 -- update snow particles 943 for k, v in pairs(self.snowParticleSystems) do 944 setActive(v.id, false); 945 end; 946 end;
VehicleMotor:getDrivingWheelsRpm()
Returns the current wheel rpm (average of all driving wheels).
950 function VehicleMotor:getDrivingWheelsRpm() 951 local numerator = 0; 952 local denominator = 0; 953 954 for n, axle in pairs(self.axles) do 955 if axle.motorGear > 0 then 956 numerator = numerator + 0.5 * (Wheel.getRpm(axle.leftWheel) + Wheel.getRpm(axle.rightWheel)); 957 denominator = denominator + axle.motorGear; 958 end; 959 end; 960 961 return (denominator > 0 and numerator / denominator or 0); 962 end;
VehicleMotor:getDrivingWheelsCount()
Returns the number of driving wheels.
966 function VehicleMotor:getDrivingWheelsCount() 967 local sum = 0; 968 for n, axle in pairs(self.axles) do 969 sum = sum + axle.motorGear; 970 end; 971 972 return 2*sum; 973 end;
VehicleMotor:getGearTransmissionRatio()
Returns the currently activated gear transmission ratio. This will be negative for reverse gear.
977 function VehicleMotor:getGearTransmissionRatio() 978 return self.gears[self.currentGear] * self.motorFinalDriveRatio; 979 end;
VehicleMotor:playSingleEventSound(soundType, soundId, noEvent)
Plays a single sound event. There are different sound types just as 'Damping', 'Crashes'.
983 function VehicleMotor:playSingleEventSound(soundType, soundId, noEvent) 984 local soundTable = self.singleEventSounds[soundType]; 985 if soundType == nil or soundId == nil then 986 return; 987 end; 988 if soundTable == nil then 989 print("Can't find that sound type! '" .. soundType .. "'."); 990 return; 991 end; 992 local soundPrefab = soundTable[soundId]; 993 if soundPrefab ~= nil then 994 VehicleMotor.applySpatialBlend(self, soundPrefab); 995 AudioSource.play(soundPrefab); 996 end; 997 if not noEvent then 998 EventPlaySingleSound:send(self, soundType, soundId); 999 end; 1000 end; 1001
VehicleMotor:getCurrentMotorTorque()
Returns the current motor torque in Nm, depending on current transmission ratio and current motor torque (which itself depends on motorRpm and throttle value).
1004 function VehicleMotor:getCurrentMotorTorque() 1005 local transmissionRatio = self:getGearTransmissionRatio(); 1006 local motorOutputTorque = 0; 1007 if self.isMotorOn then 1008 motorOutputTorque = self:getMotorTorqueAtRpm(self.motorRpm); 1009 1010 if self.cruiseControlActive then 1011 motorOutputTorque = motorOutputTorque * self.cruiseControlThrottleValue; 1012 else 1013 motorOutputTorque = motorOutputTorque * self.throttleValue; 1014 end; 1015 end; 1016 1017 -- motor inertia is directly applied to the wheel 1018 local inertia = 2 * self.motorInertiaMoment * transmissionRatio * transmissionRatio; 1019 1020 return motorOutputTorque * transmissionRatio, math.abs(inertia); 1021 end; 1022
VehicleMotor:getShallInvertThrottle()
Returns 'true' if the vehicle is going backwards.
1026 function VehicleMotor:getShallInvertThrottle() 1027 return self:getGearTransmissionRatio() < 0; 1028 end; 1029
VehicleMotor:setDrivingDirectionInverted()
Inverts the current driving direction.
1033 function VehicleMotor:setDrivingDirectionInverted() 1034 -- not the best implementation, cause multiple gears are disabled and it's hardcoded atm 1035 if self.currentGear == 2 then 1036 self:setGear(1); 1037 elseif self.currentGear == 1 then 1038 self:setGear(2); 1039 end; 1040 end; 1041
VehicleMotor:setAutomaticDriveActive(isActive)
Enables or disables automatic drive, depending on isActive
(bool). (not used in season 2 any more)
1044 function VehicleMotor:setAutomaticDriveActive(isActive) 1045 self.automaticDriveActive = isActive; 1046 end;
VehicleMotor:setCruiseControlValues()
Is called in the exact moment when the cruise control is engaged. For more details please head over to Vehicle.
1049 function VehicleMotor:setCruiseControlValues() 1050 self.cruiseControlRpm = self.motorRpm; 1051 self.cruiseControlThrottleValue = self.throttleValue; 1052 end;
VehicleMotor:writeUpdate(isLocalPlayerEntered)
Sends a network packet each frame when the vehicle is updated. The data sent by writeUpdate
will be received by readUpdate
.
1056 function VehicleMotor:writeUpdate(isLocalPlayerEntered) 1057 if streamWriteBool( 1058 true--self:getIsActive() or (g_isServer and (self.isMotorOn or math.abs(self.currentSpeed) > 1) 1059 ) then 1060 if g_isServer then 1061 streamWriteFloat(self.motorRpm); 1062 streamWriteFloat(self.wheelRpm); 1063 streamWriteFloat(self.motorSmoothRpm); 1064 1065 -- terrain drive sound 1066 streamWriteInt8(self.terrainLayerIndex1); 1067 streamWriteInt8(self.terrainLayerIndex2); 1068 1069 -- ground hit 1070 streamWriteBool(self.groundHit); 1071 1072 elseif isLocalPlayerEntered then 1073 streamWriteUInt8(self.currentGear); 1074 end; 1075 end; 1076 end;
VehicleMotor:readUpdate(connection, networkTime, isClientEntered)
Receives a network packet each frame when the vehicle is updated. The data sent by writeUpdate
will be received by readUpdate
.
1080 function VehicleMotor:readUpdate(connection, networkTime, isClientEntered) 1081 if streamReadBool() then 1082 if g_isClient then 1083 self.motorRpm = streamReadFloat(); 1084 self.wheelRpm = streamReadFloat(); 1085 self.motorSmoothRpm = streamReadFloat(); 1086 1087 -- terrain drive sound 1088 self.terrainLayerIndex1 = streamReadInt8(); 1089 self.terrainLayerIndex2 = streamReadInt8(); 1090 1091 -- ground hit 1092 self.groundHit = streamReadBool(); 1093 1094 elseif isClientEntered then 1095 self.currentGear = streamReadUInt8(); 1096 end; 1097 end; 1098 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator - Season 2 only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2021. All Rights Reserved.