Vehicles/Vehicle.lua

The Vehicle class is at the heart of all vehicle-related features, together with VehicleManager. It handles all aspects that affect all vehicles (such as buying, selling or initializing vehicle scripts).

  27  
  28  Vehicle                             = Vehicle or {};
  29  local VehicleClass                  = Class(Vehicle);
  30  

Vehicle:addVehicleScript(vehicleScript)

Adds the Vehicle script class vehicleScript (a table with functions, usually) to this vehicle's scripts.

  34  function Vehicle:addVehicleScript(vehicleScript)
  35      table.insert(self.vehicleScripts, vehicleScript);
  36  end;

Vehicle:callVehicleScripts(functionName, ...)

Calls the function named functionName (string) for all vehicle scripts that are registered to this vehicle.

  39  function Vehicle:callVehicleScripts(functionName, ...)
  40      -- calls all vehicle script functions that are named the same, using the proper arguments
  41      for _, script in pairs(self.vehicleScripts) do
  42          -- ensure its not nil
  43          if script[functionName] ~= nil then
  44              script[functionName](self, ...);
  45          end;
  46      end;
  47  end;

Vehicle:callSafe(functionName, ...)

Safely calls the function named functionName (string) of this vehicle. If this function does not exist, no error will be thrown.

It makes sense to call this function e.g. if it is implemented by another vehicle script that could also be left out.

  53  function Vehicle:callSafe(functionName, ...)
  54      local func                      = self[functionName];
  55  
  56      if func ~= nil then
  57          func(self, ...);
  58      end;
  59  end;

Vehicle:load(bundleId, modName, templateName, dataTable)

Loads the newly created Vehicle instance using some parameters:

  • bundleId (int) id of the mod's asset bundle, 0 for vanilla content
  • modName (string) name of the mod, “” for vanilla content
  • templateName (string) name of the content, this will either be default.xyz or mods.yourmodname.xyz.
  • dataTable (table) the data table that was passed over to ModLoader.addContent(name, dataTable).
  67  function Vehicle:load(bundleId, modName, templateName, dataTable)
  68      
  69      -- first load our vehicle prefab
  70      local prefabName                = dataTable.prefab or "";
  71      local id                        = Utils.loadBundleGameObject(bundleId, prefabName);
  72      setPosition(id, 0,0,0);
  73      setRotation(id, 0,0,0);
  74  
  75      -- first store the main id
  76      self.id                         = id;
  77      self.bundleId                   = bundleId;
  78      
  79      -- store these two for the savegame
  80      self.modName                    = modName;
  81      self.templateName               = templateName;
  82      self.balanceSheetId             = 0;
  83      
  84      self.vehicleScripts             = {};
  85      
  86      self:loadComponents(dataTable);
  87      self:loadVehicleScripts(dataTable);
  88      
  89      VehicleManager:registerVehicle(self);
  90      
  91      -- some parameter reading
  92      self.maxWheelTorque             = dataTable.maxWheelTorque or 1200;
  93      
  94      self.maxBrakeTorque             = dataTable.maxBrakeTorque or 1200;
  95      self.parkingBrakeTorque         = dataTable.parkingBrakeTorque or 550;
  96      
  97      self.maxSteerAngle              = dataTable.steeringAngle or 30;
  98      self.steeringSteerSpeed         = dataTable.steeringSteerSpeed or 2;
  99      self.steeringReturnSpeed        = dataTable.steeringReturnSpeed or 4;
 100      self.steeringSlowdownCoeff      = dataTable.steeringSlowdownCoeff or 0.01;
 101      self.steeringMaxSlowdown        = dataTable.steeringMaxSlowdown or 0.5;
 102      
 103      -- runtime variables
 104      self.parkingBrake               = 1; -- 1 means applied, 0 released
 105      self.brakeValue                 = 0;
 106      self.throttleValue              = 0; -- handled by this script, but applied by VehicleMotor
 107      self.steerValue                 = 0;
 108      
 109      self.isEntered                  = false;
 110      
 111      local steeringWheelIndex        = dataTable.steeringWheelIndex or "";
 112      if steeringWheelIndex ~= "" then
 113          self.steeringWheelId        = getChild(self.id, steeringWheelIndex);
 114          self.steeringWheelAngle     = dataTable.steeringWheelAngle or 450;
 115          self.steeringWheelAxle      = dataTable.steeringWheelAxis or 3;
 116      end;
 117  
 118      local steeringAnimationIndex    = dataTable.steeringAnimationIndex;
 119      if steeringAnimationIndex ~= nil then
 120          self.steeringAnimationId        = getChild(self.id, steeringAnimationIndex);
 121          self.steeringAnimationLength    = Animation.getLength(self.steeringAnimationId);
 122          Animation.stop(self.steeringAnimationId);
 123          Animation.sampleTime(self.steeringAnimationId, 0.5 * self.steeringAnimationLength);
 124      end;
 125      
 126      self:loadSounds(dataTable.sounds);
 127      
 128      
 129      self:loadAxles(dataTable);
 130  
 131      -- load exhausts
 132      self.exhaustParticleSystems     = {};
 133      if dataTable.exhaustParticleSystems ~= nil then
 134          for k, v in pairs(dataTable.exhaustParticleSystems) do
 135              local id                = getChild(self.id, v.index);
 136  
 137              if id ~= 0 then
 138                  local particleSystemId  = Utils.loadBundleGameObject(self.bundleId, v.particleSystem or "$vehicles/particles/ExhaustParticles");
 139  
 140                  setParent(particleSystemId, id);
 141                  setPosition(particleSystemId, 0,0,0);
 142                  setRotation(particleSystemId, 0,0,0);
 143                  setActive(particleSystemId, false);
 144  
 145                  table.insert(self.exhaustParticleSystems, { id = particleSystemId });
 146              end;
 147          end;
 148      end;
 149      
 150      self:callVehicleScripts("load", dataTable);
 151  
 152      -- apply brake torque
 153      if not self.customWheelScriptActive then
 154          for n, axle in pairs(self.axles) do
 155              Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque);
 156              Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque);
 157          end;
 158      end;
 159  end;

Vehicle:loadVehicleScripts(dataTable)

Loads all vehicle scripts from dataTable and registers them to this vehicle.

 163  function Vehicle:loadVehicleScripts(dataTable)
 164      
 165      for i, class in pairs(dataTable.vehicleScripts) do
 166  
 167          if type(class) == "table" then
 168              self:addVehicleScript(class);
 169          else
 170              print("Error while loading vehicle " .. tostring(self.templateName) .. "'s VehicleScripts: failed to find script number " .. tostring(i) .. ". Check if it's existent within the scope of your data table or if there's a spelling mistake somewhere (case sensitive).");
 171          end;
 172      end;
 173      
 174  end;

Vehicle:loadSounds(dataTable)

Loads some basic sounds (parking brake, switch sound).

 177  function Vehicle:loadSounds(dataTable)
 178      if dataTable == nil then return end;
 179  
 180      self.parkingBrakeSound0         = self:loadSingleSound(dataTable.releaseParkingBrake);
 181      self.parkingBrakeSound1         = self:loadSingleSound(dataTable.setParkingBrake);
 182  
 183      self.switchSound0               = self:loadSingleSound(dataTable.switchTurnOff);
 184      self.switchSound1               = self:loadSingleSound(dataTable.switchTurnOn);
 185  end;

Vehicle:loadSingleSound(data, soundOnly)

Loads a sound from the configuration table given in data (table). If soundOnly is equal to true, the volume specified in data will not be applied.

This function is used several times by other classes, such as FuelTank, VehicleLighting, VehicleMotor and WarningSound. In general, it is recommended to use this function for all one-shot sounds.

 190  function Vehicle:loadSingleSound(data, soundOnly)
 191      if data == nil then
 192          return nil;
 193      end;
 194  
 195      local index                     = nil;
 196  
 197      if type(data) == "string" then
 198          index                       = data;
 199      elseif type(data) == "table" then
 200          index                       = data.prefab;
 201      end;
 202  
 203      if index == nil or index == "" then
 204          return nil;
 205      end;
 206  
 207      local id                        = Utils.loadBundleGameObject(self.bundleId, index);
 208  
 209      if id == 0 then
 210          return nil;
 211      end;
 212  
 213      -- make it a child to us
 214      setParent(id, self.id);
 215      setPosition(id, 0,0,0);
 216  
 217      if not soundOnly and type(data) == "table" then
 218          if data.volume ~= nil then
 219              AudioSource.setVolume(id, data.volume);
 220          end;
 221      end;
 222  
 223      -- and return it back to the caller
 224      return id;
 225  end;

Vehicle:playSound(sound)

Plays the specified sound with id sound (int), if existing.

 228  function Vehicle:playSound(sound)
 229      if sound ~= nil then
 230          AudioSource.play(sound);
 231      end;
 232  end;

Vehicle:stopSound(sound)

Stops playing the specified sound with id sound (int), if existing.

 235  function Vehicle:stopSound(sound)
 236      if sound ~= nil then
 237          AudioSource.stop(sound);
 238      end;
 239  end;
 240  

Vehicle:playOnOffSound(condition, onSound, offSound)

Starts or stops playing the specified sound with id sound (int), depending on condition (bool), unless that sound does not exist.

 243  function Vehicle:playOnOffSound(condition, onSound, offSound)
 244      if condition then
 245          if onSound ~= nil then
 246              AudioSource.play(onSound);
 247          end;
 248      else
 249          if offSound ~= nil then
 250              AudioSource.play(offSound);
 251          end;
 252      end;
 253  end;

Vehicle:setBalanceSheetId(balanceSheetId)

Assigns the balance sheet id balanceSheetId (id) to this vehicle. This is called after the vehicle has been bought.

Please do not modify balance sheet ids. This can distort the ecosystem and especially financial figures due to the double book-keeping system in WRS.

 258  function Vehicle:setBalanceSheetId(balanceSheetId)
 259      self.balanceSheetId     = balanceSheetId;
 260  end;

Vehicle:saveToTable()

Joins all relevant variables that shall be saved from any vehicle script into a single table and returns it.

 263  function Vehicle:saveToTable()
 264      -- write our position etc in here
 265      local px, py, pz        = getPosition(self.id);
 266      local rx, ry, rz        = getRotation(self.id);
 267      local tbl               = {
 268          modName             = self.modName,
 269          templateName        = self.templateName,
 270          balanceSheetId      = self.balanceSheetId,
 271          px = px,
 272          py = py,
 273          pz = pz,
 274          rx = rx,
 275          ry = ry,
 276          rz = rz
 277      };
 278      self:callVehicleScripts("saveToTable", tbl);
 279      return tbl;
 280  end;

Vehicle:loadFromTable(tbl)

Restores the vehicle's variables and calls all vehicle scripts to load them as well.

Please note that position and rotation are already handled by VehicleManager when the vehicle is spawned.

 285  function Vehicle:loadFromTable(tbl)
 286      if tbl == nil then return end;
 287      
 288      self:setBalanceSheetId(tbl.balanceSheetId or self.balanceSheetId);
 289      self:callVehicleScripts("loadFromTable", tbl);
 290  end;

Vehicle:getAge()

Returns the age of the vehicle in days (if possible, otherwise nil).

 293  function Vehicle:getAge()
 294      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
 295  
 296      if asset == nil then
 297          return nil;
 298      end;
 299      return asset.age;
 300  end;

Vehicle:getMaintenanceCost()

Returns the daily maintenance cost. After its acquisition, the vehicle will cost template.minMaintenance per day, while it will cost template.maxMaintenance per day once the depreciationPeriod is exceeded.

 304  function Vehicle:getMaintenanceCost()
 305      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
 306      local template                  = VehicleManager.vehicleTemplates[self.templateName];
 307      
 308      -- if there's no balance sheet item, then it is no longer in useful life
 309      local remainingUsefulLife       = 0;
 310  
 311      if asset ~= nil and template ~= nil and asset.age ~= nil then
 312          remainingUsefulLife         = map(0, template.depreciationPeriod, 1, 0, asset.age);
 313      end;
 314      
 315      return map(1, 0, template.minMaintenance, template.maxMaintenance, clamp01(remainingUsefulLife));
 316  end;

Vehicle:getSellValue()

Returns the vehicle's current sell value in the game's currency.

 319  function Vehicle:getSellValue()
 320      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
 321      local template                  = VehicleManager.vehicleTemplates[self.templateName];
 322      
 323      -- if there's no balance sheet item, then it is no longer in useful life
 324      local remainingUsefulLife       = 0;
 325  
 326      if asset ~= nil and template ~= nil and asset.age ~= nil then
 327          remainingUsefulLife         = map(0, template.depreciationPeriod, 1, 0, asset.age);
 328      end;
 329  
 330      return map(1, 0, 0.8*template.price, 0.2*template.price, clamp01(remainingUsefulLife));
 331  end;

Vehicle:sell()

Sells the vehicle, pays the money over to the player and destroys the vehicle.

 334  function Vehicle:sell()
 335      -- determine the residual value
 336      local sellValue                 = self:getSellValue();
 337  
 338      -- leave if entered
 339      if self.isEntered then
 340          self:leaveByTab();
 341      end;
 342      
 343      -- get some money
 344      g_scenario.accounting:sellAsset(self.balanceSheetId, sellValue);
 345      
 346      self:callVehicleScripts("onSell");
 347  
 348      -- next step: destroy
 349      self:destroy();
 350  end;

Vehicle:spawnAt(x,y,z, rx,ry,rz)

Teleports the vehicle to the position x,y,z (all float) with rotation rx,ry,rz (all float).

 353  function Vehicle:spawnAt(x,y,z, rx,ry,rz)
 354      -- just apply this position to our main component
 355      -- any other component has to be adjusted automatically by joints
 356      -- set the main component to kinematic
 357      if Rigidbody.isRigidbody(self.mainId) then
 358          Rigidbody.setIsKinematic(self.mainId, true);
 359      end;
 360      setWorldPosition(self.mainId, x,y,z);
 361      setWorldRotation(self.mainId, rx,ry,rz);
 362  
 363      self.resetKinematic             = 0.5; -- block vehicle for 30 physics simulations
 364  end;

Vehicle:reset()

Resets the vehicle to a reset position returned by VehicleManager:findPosition. View VehicleManager for more details.

 367  function Vehicle:reset()
 368      -- ask for a place to reset
 369      local template                  = VehicleManager.vehicleTemplates[self.templateName];
 370      local x,y,z,ry                  = VehicleManager:findPosition(template.width, template.length);
 371      
 372      if x ~= nil then
 373          -- call scripts
 374          self:callVehicleScripts("onReset");
 375  
 376          self:spawnAt(x,y,z, 0,ry,0);
 377          return true;
 378      end;
 379  
 380      return false;
 381  end;

Vehicle:loadComponents(dataTable)

Experimental feature only Loads all components if the vehicle consists of multiple components. Otherwise, it sets up the main component. This can be useful for some special vehicles.

 385  function Vehicle:loadComponents(dataTable)
 386      if dataTable.components == nil then
 387          self.mainId                 = self.id;
 388          self.components             = { main = self.id };
 389  
 390          if dataTable.massCenter ~= nil and Rigidbody.isRigidbody(self.mainId) then
 391              Rigidbody.setMassCenter(self.mainId, dataTable.massCenter.x, dataTable.massCenter.y, dataTable.massCenter.z);
 392          end;
 393  
 394      else
 395          self.components             = {};
 396  
 397          -- register all components
 398          for k, v in pairs(dataTable.components) do
 399              local id                = getChild(self.id, v.index or "");
 400              local name              = v.name or "main";
 401  
 402              if self.components[name] ~= nil then
 403                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Component name '" .. tostring(v.name) .. "' was assigned to two or more vehicle components. Please assign an individual name to each component.");
 404              else
 405                  self.components[name]   = id;
 406              end;
 407  
 408              if self.mainId == nil then
 409                  -- first component is always main component
 410                  self.mainId             = id;
 411                  self.components.main    = id;
 412              end;
 413  
 414              if v.massCenter ~= nil and Rigidboy.isRigidbody(id) then
 415                  Rigidbody.setMassCenter(id, v.massCenter.x, v.massCenter.y, v.massCenter.z);
 416              end;
 417          end;
 418      end;
 419  
 420      -- add collision ignore pairs
 421      if dataTable.ignoreCollisions ~= nil then
 422          for k, v in pairs(dataTable.ignoreCollisions) do
 423              local componentA        = self.components[v[1] or v.componentA or ""];
 424              local componentB        = self.components[v[2] or v.componentB or ""];
 425  
 426              if componentA == nil then
 427                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Invalid joint component name '" .. tostring(v.componentA) .. "' (component). Make sure this vehicle component actually exists.");
 428              elseif componentB == nil then
 429                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Invalid joint component name '" .. tostring(v.componentB) .. "' (attachTo). Make sure this vehicle component actually exists.");
 430              
 431              else
 432                  Physics.ignoreCollision(componentA, componentB, true);
 433              end;
 434          end;
 435      end;
 436  
 437      -- let's go on to joints
 438      if dataTable.componentJoints ~= nil then
 439          self.componentJoints        = {};
 440  
 441          for k, v in pairs(dataTable.componentJoints) do
 442              local component         = self.components[v.component or ""];
 443              local attachTo          = self.components[v.attachTo or ""];
 444  
 445              local attachTarget      = 0;
 446              if v.attachPosition ~= nil then
 447                  attachTarget        = getChild(self.id, v.attachPosition);
 448              end;
 449              local attachAnchor      = 0;
 450              if v.attachAnchor ~= nil then
 451                  attachAnchor        = getChild(self.id, v.attachAnchor);
 452              end;
 453  
 454              if component == nil then
 455                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Invalid joint component name '" .. tostring(v.component) .. "' (component). Make sure this vehicle component actually exists.");
 456              elseif attachTo == nil then
 457                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Invalid joint component name '" .. tostring(v.attachTo) .. "' (attachTo). Make sure this vehicle component actually exists.");
 458              elseif v.attachPosition ~= nil and attachTarget == 0 then
 459                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Failed to index joint position index '" .. tostring(v.attachPosition) .. "'. Make sure this object actually exists.");
 460              elseif v.attachAnchor ~= nil and attachAnchor == 0 then
 461                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Failed to index joint anchor index '" .. tostring(v.attachAnchor) .. "'. Make sure this object actually exists.");
 462              elseif component == attachTo then
 463                  print("Error while loading vehicle of template " .. tostring(self.templateName) .. ": Cannot connect a vehicle component to itself using a joint. Make sure there is no joint connecting to itself (components '" .. tostring(v.component) .. "' and '" .. tostring(v.attachTo) .. "').");
 464  
 465              else
 466                  -- alright, we may create this joint
 467                  local joint         = {};
 468                  joint.id            = Joint.addJoint(component, attachTo, v.axis, attachTarget or 0, attachAnchor or 0);
 469  
 470                  if v.springForce ~= nil then
 471                      Joint.setSpring(joint.id, v.springForce, v.springDamper or v.springForce * 0.01, v.targetPosition);
 472                      joint.useSpring = true;
 473                  end;
 474                  if v.limits ~= nil then
 475                      Joint.setLimits(joint.id, unpack(v.limits));
 476                      joint.useLimits = true;
 477                  end;
 478  
 479                  joint.springForce       = v.springForce or 0;
 480                  joint.springDamper      = v.springDamper or 0.01 * joint.springForce;
 481                  joint.targetPosition    = v.targetPosition or 0;
 482              end;
 483          end;
 484      end;
 485  end;

Vehicle:loadAxles(dataTable)

Loads the axles from the configuration table, and calls the function customLoadAxle() for all vehicle scripts. An example for a use is VehicleMotor:customLoadAxle() (see VehicleMotor)

 488  function Vehicle:loadAxles(dataTable)
 489      self.axles                      = {};
 490  
 491      if dataTable.axles == nil then
 492          return;
 493      end;
 494      
 495      local axleCount                 = #dataTable.axles;
 496      
 497      for i, v in pairs(dataTable.axles) do
 498          local leftWheelIndex        = v.leftWheelIndex or "";
 499          local rightWheelIndex       = v.rightWheelIndex or "";
 500          
 501          -- !!TODO!! check whether these are existing
 502          local leftWheelId           = getChild(self.id, leftWheelIndex);
 503          local rightWheelId          = getChild(self.id, rightWheelIndex);
 504          
 505          local axle                  = self:newAxle(leftWheelId, rightWheelId);
 506          self.axles[i]               = axle;
 507          
 508          -- now fill it up with some data
 509          axle.motorGear              = v.motor or axle.motorGear; -- unused as long as VehicleMotor script is not invoked
 510          axle.brake                  = v.brake or false;
 511          axle.steeringScale          = v.steeringScale or axle.steeringScale;
 512  
 513          if v.leftWheelCaliper ~= nil then
 514              axle.leftWheelCaliper   = getChild(self.id, v.leftWheelCaliper);
 515          end;
 516          if v.rightWheelCaliper ~= nil then
 517              axle.rightWheelCaliper  = getChild(self.id, v.rightWheelCaliper);
 518          end;
 519  
 520          self:callVehicleScripts("customLoadAxle", axle, v);
 521      end;
 522  end;

Vehicle:newAxle(leftId, rightId)

Creates a new axle from the specified transform ids leftId and rightId (both int) and returns it.

 525  function Vehicle:newAxle(leftId, rightId)
 526      local axle                      = {};
 527      axle.leftId                     = leftId;
 528      axle.rightId                    = rightId;
 529      axle.leftWheel                  = Wheel.getWheelId(leftId);
 530      axle.rightWheel                 = Wheel.getWheelId(rightId);
 531      
 532      if getNumOfChildren(leftId) > 0     then        axle.leftVisual     = getChildAt(leftId, 0);    end;
 533      if getNumOfChildren(rightId) > 0    then        axle.rightVisual    = getChildAt(rightId, 0);   end;
 534      
 535      axle.motorGear                  = 0; -- 0: no power attached to this wheel, 1.0: all motor power to there
 536      axle.brake                      = true; -- shall the brake be applied to this wheel?
 537      axle.steeringScale              = 0; -- coefficient defining steer angle min/max
 538      return axle;
 539  end;
 540  

Vehicle:getIsActive()

Returns whether the vehicle is active. Use this for all code blocks that shall be active when the vehicle shall be updated (e.g. lowering/lifting a snow groomer).

 543  function Vehicle:getIsActive()
 544      return self.isEntered;
 545  end;

Vehicle:getIsInputActive()

Returns whether scripts on this vehicle may handle input. ALWAYS make sure to call if self:getIsInputActive() then before any input queries. Besides some rare exceptions, input keys should never be active if Vehicle:getIsInputActive() could return false.

 548  function Vehicle:getIsInputActive()
 549      return self.isEntered;
 550  end;

Vehicle:getIsGUIActive()

Returns whether GUI elements regarding this vehicle are active and shall be updated. Currently, this is always true when the vehicle is entered, but this could change in the future (e.g. if we were to introduce new concepts for the overview menu).

 553  function Vehicle:getIsGUIActive()
 554      return self.isEntered;
 555  end;

Vehicle:update(dt)

This handles a whole lot of things that need to be checked every frame:

  • Checks whether the vehicle is active
  • Handles input (both for separate throttle/brake and for combined controls) for the commonly used statements self.throttleValue, self.brakeValue (both floats, range 0-1), self.steerValue (float, range -1 to 1), and self.parkingBrake (0 or 1)
  • Visual update of steering wheel and wheels.
 561  function Vehicle:update(dt)
 562  
 563      if self.resetKinematic ~= nil then
 564          self.resetKinematic         = self.resetKinematic - dt;
 565  
 566          if self.resetKinematic <= 0 then
 567              if Rigidbody.isRigidbody(self.mainId) then
 568                  Rigidbody.setIsKinematic(self.mainId, false);
 569              end;
 570              self.resetKinematic     = nil;
 571          end;
 572      end;
 573  
 574      self.isActive                   = self:getIsActive();
 575      
 576      if self:getIsGUIActive() then
 577          -- update UI
 578          setActive(getChild(GUI.vehicleHUD, "Icons1/Park/dark"),                 self.parkingBrake <= 0.5);
 579          setActive(getChild(GUI.vehicleHUD, "Icons1/Park/bright"),               self.parkingBrake > 0.5);
 580          
 581          -- turn indicators are controlled in VehicleLighting.lua
 582      end;
 583      
 584      if self:getIsInputActive() then
 585          
 586          if GameplaySettings.throttleBrakeSeparate then
 587              self.throttleValue          = InputMapper:getAxis(InputMapper.Axis_Vehicle_Throttle);
 588              self.brakeValue             = InputMapper:getAxis(InputMapper.Axis_Vehicle_Brake);
 589  
 590          else
 591              local inputValue            = InputMapper:getAxis(InputMapper.Axis_Vehicle_Throttle_Brake_Combined);
 592              
 593              if inputValue > 0 then
 594                  self.throttleValue      = inputValue;
 595                  self.brakeValue         = 0;
 596              else
 597                  self.throttleValue      = 0;
 598                  self.brakeValue         = -inputValue;
 599              end;
 600          end;
 601  
 602          local steerValue, fixed     = InputMapper:getAxis(InputMapper.Axis_Vehicle_Steer);
 603          -- directly write the analog value, but smoothen out digital values
 604          if fixed then
 605              self.steerValue         = steerValue;
 606  
 607          elseif steerValue ~= self.steerValue then
 608  
 609              local returnSpeed       = self.steeringSteerSpeed;
 610  
 611              if steerValue == 0 or (self.steerValue >= 0) ~= (steerValue >= 0) and self.steeringReturnSpeed > self.steeringSteerSpeed then
 612                  returnSpeed         = self.steeringReturnSpeed;
 613              end;
 614  
 615              local speedCoeff        = 1;
 616              if self.currentSpeed ~= nil then
 617                  speedCoeff          = math.abs(1 - clamp(self.currentSpeed * self.steeringSlowdownCoeff, 0, self.steeringMaxSlowdown));
 618              end;
 619  
 620              if self.steerValue < steerValue then
 621                  self.steerValue     = math.min(self.steerValue + returnSpeed * speedCoeff * dt, steerValue);
 622  
 623              else
 624                  self.steerValue     = math.max(self.steerValue - returnSpeed * speedCoeff * dt, steerValue);
 625              end;
 626          end;
 627          
 628          if self.parkingBrake > 0.5 then
 629              g_GUI:addKeyHint(InputMapper.Vehicle_ParkingBrake,      l10n.get("Input_Vehicle_ParkingBrake_release"));
 630          end;
 631          
 632          if InputMapper:getKeyDown(InputMapper.Vehicle_ParkingBrake) then
 633              self:setParkingBrake(self.parkingBrake ~= 1);
 634  
 635              -- play a sound as feedback
 636              self:playOnOffSound(self.parkingBrake == 1, self.parkingBrakeSound1, self.parkingBrakeSound0);
 637          end;
 638      end;
 639      
 640      if self.isActive then
 641          -- update wheel visuals
 642          for n, axle in pairs(self.axles) do
 643              self:wheelPositionToVisual(axle.leftWheel, axle.leftVisual, axle.leftWheelCaliper);
 644              self:wheelPositionToVisual(axle.rightWheel, axle.rightVisual, axle.rightWheelCaliper);
 645          end;
 646      
 647          -- update steering wheel
 648          if self.steeringWheelId ~= nil then
 649              if self.steeringWheelAxle == 1 then
 650                  setRotationX(self.steeringWheelId, self.steeringWheelAngle * self.steerValue);
 651                  
 652              elseif self.steeringWheelAxle == 2 then
 653                  setRotationY(self.steeringWheelId, self.steeringWheelAngle * self.steerValue);
 654                  
 655              else
 656                  setRotationZ(self.steeringWheelId, self.steeringWheelAngle * self.steerValue);
 657              end;
 658          end;
 659          if self.steeringAnimationId ~= nil then
 660              Animation.sampleTime(self.steeringAnimationId, self.steeringAnimationLength * 0.5 * (1 + self.steerValue));
 661          end;
 662      end;
 663      
 664      self:callVehicleScripts("update", dt);
 665  end;

Vehicle:fixedUpdate(dt)

This is called every time the physics system is updated. This can happen more than once per frame. Please make sure that your vehicle scripts therefore do not waste any performance in here.

 668  function Vehicle:fixedUpdate(dt)
 669      -- apply wheel torques and steering angles
 670      if not self.customWheelScriptActive and #self.axles > 0 then
 671          self:wheelsUpdate(self:getCurrentMotorTorque(), self:getCurrentBrakeTorque(), self:getCurrentSteerAngle());
 672      end;
 673  
 674      self:callVehicleScripts("fixedUpdate", dt);
 675  end;

Vehicle:wheelsUpdate(motorTorque, brakeTorque, steerAngle)

Physics update for the vehicle's wheels.

 678  function Vehicle:wheelsUpdate(motorTorque, brakeTorque, steerAngle)
 679      for n, axle in pairs(self.axles) do
 680          -- apply wheel torques
 681          Wheel.setTorques(axle.leftWheel, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0);
 682          Wheel.setTorques(axle.rightWheel, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0);
 683          
 684          -- and now steering
 685          if axle.steeringScale ~= 0 then
 686              Wheel.setSteerAngle(axle.leftWheel, steerAngle * axle.steeringScale);
 687              Wheel.setSteerAngle(axle.rightWheel, steerAngle * axle.steeringScale);
 688          end;
 689      end;
 690  end;

Vehicle:wheelPositionToVisual(wheelId, visualWheel, caliperId)

Applies the wheel's position as determined by physics to the wheel's visual. If existing, the caliper will also be adjusted accordingly.

 693  function Vehicle:wheelPositionToVisual(wheelId, visualWheel, caliperId)
 694      if visualWheel == 0 or visualWheel == nil then return; end;
 695      
 696      Wheel.applyPose(wheelId, visualWheel);
 697  
 698      if caliperId ~= nil and caliperId ~= 0 then
 699          Wheel.applyCaliperPose(wheelId, caliperId);
 700      end;
 701  end;

Vehicle:onEnter()

This calls onEnter for all vehicle scripts and handles the parking brake.

 704  function Vehicle:onEnter()
 705      VehicleManager:onEnterVehicle(self);
 706      self:callVehicleScripts("onEnter");
 707  
 708      if GameplaySettings.autoReleaseParkingBrake then
 709          self:setParkingBrake(false);
 710      end;
 711  
 712      GUI:setVehicleHUDActive(true);
 713      self:callVehicleScripts("onHUDActivate");
 714  end;

Vehicle:onLeave()

This calls onLeave for all vehicle scripts and resets several variables to their default values.

 717  function Vehicle:onLeave()
 718      VehicleManager:onLeaveVehicle(self);
 719      self:callVehicleScripts("onLeave");
 720      self:callVehicleScripts("onHUDDeactivate");
 721      GUI:setVehicleHUDActive(false);
 722      
 723      -- reset throttle and set the parking brake
 724      self.throttleValue  = 0;
 725      self.brakeValue     = 0;
 726      self:setParkingBrake(true);
 727  
 728      -- directly apply parking brake (because fixedUpdate will no longer be called)
 729      if not self.customWheelScriptActive then
 730          for n, axle in pairs(self.axles) do
 731              Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque);
 732              Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque);
 733          end;
 734      end;
 735  end;

Vehicle:enterByTab()

This is called by VehicleManager in case this vehicle shall be activated. Usually, it will be handled by the Seats vehicle script.

 738  function Vehicle:enterByTab()
 739      self:callVehicleScripts("enterByTab");
 740      -- onEnter is then called by Seats vehicle script
 741  end;

Vehicle:leaveByTab()

This is called by VehicleManager in case the player wants to leave this vehicle. Again, it will usually be handled by Seats.

 744  function Vehicle:leaveByTab()
 745      self:callVehicleScripts("leaveByTab");
 746      -- onLeave is then called by vehiclescripts
 747  end;
 748  

Vehicle:getCurrentMotorTorque()

This is a dummy function that is usually replaced by VehicleMotor.

 751  function Vehicle:getCurrentMotorTorque()
 752      -- to be replaced by a motorising vehicle script
 753      return 0;
 754  end;

Vehicle:getCurrentBrakeTorque()

Returns the current brake torque in Nm per wheel.

 757  function Vehicle:getCurrentBrakeTorque()
 758      return self.maxBrakeTorque * self.brakeValue + self.parkingBrakeTorque * self.parkingBrake;
 759  end;

Vehicle:getCurrentSteerAngle()

Returns the steering angle in degrees.

 762  function Vehicle:getCurrentSteerAngle()
 763      return self.maxSteerAngle * self.steerValue;
 764  end;
 765  

Vehicle:setGear(value)

Shifts the gear box to gear number value (ranging from 1 to the number of gears).

 768  function Vehicle:setGear(value)
 769      self.currentGear                = clamp(value, 1, #self.gears);
 770      
 771      -- avoid automatic drive interruption
 772      self.gearSwitchTimer            = -self.gearSwitchPace;
 773  end;

Vehicle:setParkingBrake(value)

Sets the vehicle's parking brake, depending on value (bool).

 776  function Vehicle:setParkingBrake(value)
 777      self.parkingBrake               = value and 1 or 0;
 778  end;

Vehicle:setIsEntered(value)

Sets whether the player has entered the vehicle. This is usually called by the Seats vehicle script.

 781  function Vehicle:setIsEntered(value)
 782      
 783      if value == self.isEntered then
 784          return;
 785      end;
 786      
 787      self.isEntered                  = value;
 788      
 789      if value then
 790          self:onEnter();
 791      else
 792          self:onLeave();
 793      end;
 794  end;

Vehicle:destroy()

Destroys the vehicle, e.g. if it is sold or if the game is closed.

 797  function Vehicle:destroy()
 798      VehicleManager:unregisterVehicle(self);
 799  
 800      self:callVehicleScripts("destroy");
 801  
 802      delete(self.id);
 803  end;
 804  

All contents of this page may be used for modding use for Winter Resort Simulator only. Any use exceeding this regulation is not permitted.

Copyright (C) HR Innoways, 2020. All Rights Reserved.