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 with baseRpmScale to control the pitch based on rpm OR
  • Use pitchOffset and pitchScale 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 and maxFadeInRpm specify whether and how fast the sound shall be faded in depending on rpm. At any rpm below minFadeInRpm, the sound will be inactive.
  • minFadeOutRpm and maxFadeOutRpm specify whether and how fast the sound shall be faded in depending on rpm. At any rpm above maxFadeOutRpm, the sound will be inactive.
  • interieurVolumeScale allows to influence the sound volume in interior perspective.
  • interiorLowpass and exterieurLowpass 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;

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.