meta data for this page

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).

      26  
      27  Vehicle                             = Vehicle or {};
      28  local VehicleClass                  = Class(Vehicle, NetworkEntity);
      29  

Vehicle:addVehicleScript(vehicleScript)

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

      38  function Vehicle:addVehicleScript(vehicleScript)
      39      table.insert(self.vehicleScripts, vehicleScript);
      40  end;
      41  

Vehicle:callVehicleScripts(functionName, ...)

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

      44  function Vehicle:callVehicleScripts(functionName, ...)
      45      -- calls all vehicle script functions that are named the same, using the proper arguments
      46      for _, script in ipairs(self.vehicleScripts) do
      47          -- ensure its not nil
      48          if script[functionName] ~= nil then
      49              script[functionName](self, ...);
      50          end;
      51      end;
      52  end;
      53  

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.

      59  function Vehicle:callSafe(functionName, ...)
      60      local func                      = self[functionName];
      61  
      62      if func ~= nil then
      63          func(self, ...);
      64      end;
      65  end;
      66  

Vehicle:load(bundleId, vehicleType, dataTable, isDummy, addedColors)

Loads the newly created Vehicle instance using some parameters:

  • bundleId (int) id of the mod's asset bundle, 0 for vanilla content
  • vehicleType (string) name of the vehicle type, this will either be default.xyz or mods.yourmodname.xyz.
  • dataTable (table) the data table that was passed over to ModLoader.addContent(name, dataTable).
  • isDummy (optional bool) is set true if the spawned vehicle should be a dummy(not registered). Example placing mode.
      74  function Vehicle:load(bundleId, vehicleType, dataTable, isDummy, addedColors)
      75      -- call NetworkEntity's load function
      76      Vehicle:parentClass().load(self);
      77  
      78      -- first load our vehicle prefab
      79      local prefabName                = dataTable.prefab or "";
      80      local id                        = Utils.loadBundleGameObject(bundleId, prefabName);
      81      setPosition(id, 0,0,0);
      82      setRotation(id, 0,0,0);
      83  
      84      -- first store the main id
      85      self.id                         = id;
      86      self.bundleId                   = bundleId;
      87      self.loadComplete               = false;
      88  
      89      -- store these two for the savegame
      90      self.vehicleType                = vehicleType;
      91      self.dataTable                  = dataTable;
      92  
      93      -- seat id system is a part of Vehicle.lua, because it is better if supported by all vehicles
      94      self.lastSeatId                 = 0;
      95      self.seatsById                  = {};
      96  
      97      self.balanceSheetId             = 0;
      98      self.isDummy                    = getNoNil(isDummy, false);
      99      self.customColors               = dataTable.customColors;
     100      if addedColors ~= nil then
     101          for k, color in pairs(addedColors) do
     102              if color ~= nil then
     103                  self.customColors[k].value      = color.value;
     104              end;
     105          end;
     106      end;
     107  
     108      -- filmmaking feature
     109      self.colorCountIndex            = 0;
     110  
     111      self.vehicleScripts             = {};
     112  
     113      self:loadComponents(dataTable);
     114      self:loadVehicleScripts(dataTable);
     115  
     116      if g_isMaster then
     117          -- out of bounds callback
     118          setOutOfBoundsCallback(self.id, function() VehicleManager:vehicleOutOfBounds(self); end);
     119      end;
     120  
     121      -- some parameter reading
     122      self.maxWheelTorque             = dataTable.maxWheelTorque or 1200;
     123  
     124      self.maxBrakeTorque             = dataTable.maxBrakeTorque or 1200;
     125      self.parkingBrakeTorque         = dataTable.parkingBrakeTorque or 550;
     126  
     127      self.maxSteerAngle              = dataTable.steeringAngle or 30;
     128      self.steeringSteerSpeed         = dataTable.steeringSteerSpeed or 2;
     129      self.steeringReturnSpeed        = dataTable.steeringReturnSpeed or 4;
     130      self.steeringSlowdownCoeff      = dataTable.steeringSlowdownCoeff or 0.01;
     131      self.steeringMaxSlowdown        = dataTable.steeringMaxSlowdown or 0.5;
     132  
     133      self.width                      = dataTable.width;
     134      self.length                     = dataTable.length;
     135  
     136      self.hasCrashSoundsImplemented  = false;
     137      self.hasCrashBoxImplemented     = false;
     138      self:loadCrashBox(dataTable);
     139  
     140      if dataTable.sounds ~= nil then
     141          if dataTable.sounds.crashSounds ~= nil then
     142              self.hasCrashSoundsImplemented  = true;
     143          end;
     144      end;
     145  
     146      -- runtime variables
     147      self.parkingBrake               = 1; -- 1 means applied, 0 released
     148      self.brakeValue                 = 0;
     149      self.throttleValue              = 0; -- handled by this script, but applied by VehicleMotor
     150      self.steerValue                 = 0; -- steer value for wheels
     151      self.rawSteerValue              = 0; -- raw input from user
     152      self.rawSteerValueFixed         = false; -- whether raw input from user is from joystick (true) or keyboard (false)
     153  
     154      self.anyPlayerEntered           = false;
     155  
     156      local steeringWheelIndex        = dataTable.steeringWheelIndex or "";
     157      if steeringWheelIndex ~= "" then
     158          self.steeringWheelId        = getChild(self.id, steeringWheelIndex);
     159          self.steeringWheelAngle     = getNoNil(dataTable.steeringWheelAngle, 450);
     160          self.steeringWheelAngleExterior = getNoNil(dataTable.steeringWheelAngleExterior, self.steeringWheelAngle);
     161          self.steeringWheelAxle      = getNoNil(dataTable.steeringWheelAxis, 3);
     162      end;
     163  
     164      local steeringAnimationIndex    = dataTable.steeringAnimationIndex;
     165      if steeringAnimationIndex ~= nil then
     166          self.steeringAnimationId        = getChild(self.id, steeringAnimationIndex);
     167          self.steeringAnimationLength    = Animation.getLength(self.steeringAnimationId);
     168          Animation.stop(self.steeringAnimationId);
     169          Animation.sampleTime(self.steeringAnimationId, 0.5 * self.steeringAnimationLength);
     170      end;
     171  
     172      -- read drag values
     173      self.dragControlMinSpeed            = dataTable.dragControlMinSpeed;
     174      self.dragControlMaxSpeed            = dataTable.dragControlMaxSpeed;
     175      self.dragControlMaxDrag             = dataTable.dragControlMaxDrag;
     176      self.dragControlMinDrag             = dataTable.dragControlMinDrag;
     177  
     178      self:loadSounds(dataTable.sounds);
     179  
     180      self:loadAxles(dataTable);
     181  
     182      self.driveDirectionChangeTimePrev   = 0;
     183  
     184      -- cruise control values
     185      self.cruiseControlActive            = false;
     186  
     187      -- load exhausts
     188      self.exhaustParticleSystems     = {};
     189      if dataTable.exhaustParticleSystems ~= nil then
     190          for k, v in pairs(dataTable.exhaustParticleSystems) do
     191              local id                = getChild(self.id, v.index);
     192  
     193              if id ~= 0 then
     194                  local particleSystemId  = Utils.loadBundleGameObject(self.bundleId, v.particleSystem or "$vehicles/particles/ExhaustParticles");
     195  
     196                  setParent(particleSystemId, id);
     197                  setPosition(particleSystemId, 0,0,0);
     198                  setRotation(particleSystemId, 0,0,0);
     199                  setActive(particleSystemId, false);
     200  
     201                  table.insert(self.exhaustParticleSystems, { id = particleSystemId });
     202              end;
     203          end;
     204      end;
     205  
     206      -- load snow particles
     207      self.snowParticleSystems        = {};
     208      if dataTable.snowParticleSystems ~= nil then
     209          for k, v in pairs(dataTable.snowParticleSystems) do
     210              local id                = getChild(self.id, v.index);
     211              if id ~= 0 then
     212                  local particleSystemId  = Utils.loadBundleGameObject(self.bundleId, v.particleSystem); -- or "$vehicles/particles/SnowCrawler");
     213  
     214                  setParent(particleSystemId, id);
     215                  setPosition(particleSystemId, 0,0,0);
     216                  setRotation(particleSystemId, 0,0,0);
     217                  setActive(particleSystemId, false);
     218  
     219                  table.insert(self.snowParticleSystems, { id = particleSystemId, amount = getNoNil(v.particleAmount, 100), reverse = getNoNil(v.reverse, false) });
     220              end;
     221          end;
     222      end;
     223  
     224      -- load ik hand targets
     225      if dataTable.steeringWheelHands ~= nil then
     226          if dataTable.steeringWheelHands.handLeft ~= nil and dataTable.steeringWheelHands.handLeft ~= "" then
     227              self.anchorSteeringLeftIK       = getChild(self.id, dataTable.steeringWheelHands.handLeft);
     228          end;
     229  
     230          if dataTable.steeringWheelHands.handRight ~= nil and dataTable.steeringWheelHands.handRight ~= "" then
     231              self.anchorSteeringRightIK      = getChild(self.id, dataTable.steeringWheelHands.handRight);
     232          end;
     233      end;
     234  
     235      -- set up a default HUD class
     236      self.vehicleHUDClass            = VehicleHUD;
     237  
     238      self:callVehicleScripts("load", dataTable);
     239  
     240      -- apply brake torque
     241      if not self.customWheelScriptActive then
     242          for n, axle in pairs(self.axles) do
     243              Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque);
     244              Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque);
     245          end;
     246      end;
     247  
     248      self.loadComplete               = true;
     249  
     250      -- finally register the vehicle (only if this is not a dummy)
     251      if not self.isDummy then
     252          VehicleManager:registerVehicle(self);
     253  
     254          -- this is called by vehicle manager (not by Vehicle itself any more)
     255          --self:register();
     256      end;
     257  end;
     258  

Vehicle:loadVehicleScripts(dataTable)

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

     262  function Vehicle:loadVehicleScripts(dataTable)
     263  
     264      -- make sure that the vehicle scripts table exists
     265      if type(dataTable.vehicleScripts) ~= "table" then
     266          print("Error while loading vehicle type " .. tostring(self.vehicleType) .. ": vehicleScripts is a " .. type(dataTable.vehicleScripts) .. " value (table expected).");
     267          return;
     268      end;
     269  
     270      for i, class in pairs(dataTable.vehicleScripts) do
     271  
     272          if type(class) == "table" then
     273              self:addVehicleScript(class);
     274          else
     275              print("Error while loading vehicle type " .. tostring(self.vehicleType) .. "'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).");
     276          end;
     277      end;
     278  end;
     279  

Vehicle:loadSounds(dataTable)

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

     282  function Vehicle:loadSounds(dataTable)
     283      if dataTable == nil then return end;
     284  
     285      self.parkingBrakeSound0         = self:loadSingleSound(dataTable.releaseParkingBrake);
     286      self.parkingBrakeSound1         = self:loadSingleSound(dataTable.setParkingBrake);
     287  
     288      self.switchSound0               = self:loadSingleSound(dataTable.switchTurnOff);
     289      self.switchSound1               = self:loadSingleSound(dataTable.switchTurnOn);
     290  end;
     291  

Vehicle:loadCrashBox(dataTable)

Loads the crashbox if there is one specified. Crashbox is needed for crashSounds etc.

     294  function Vehicle:loadCrashBox(dataTable)
     295      if dataTable.crashbox ~= nil then
     296          self.crashbox       = {};
     297          for k,v in pairs(dataTable.crashbox) do
     298              local entry =  {
     299                  origin       = getChild(self.id, v.origin or ""),
     300                  dimensions   = v.dimensions,
     301                  componentnumber = v.componentnumber,
     302              };
     303              table.insert(self.crashbox, entry);
     304          end;
     305          self.hasCrashBoxImplemented = true;
     306      end;
     307  end;
     308  

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.

     313  function Vehicle:loadSingleSound(data, soundOnly)
     314      if data == nil then
     315          return nil;
     316      end;
     317  
     318      local index                     = nil;
     319  
     320      if type(data) == "string" then
     321          index                       = data;
     322      elseif type(data) == "table" then
     323          index                       = data.prefab;
     324      end;
     325  
     326      if index == nil or index == "" then
     327          return nil;
     328      end;
     329  
     330      local id                        = Utils.loadBundleGameObject(self.bundleId, index);
     331  
     332      if id == 0 then
     333          return nil;
     334      end;
     335  
     336      -- make it a child to us
     337  
     338      setParent(id, self.mainId);
     339      setPosition(id, 0,0,0);
     340  
     341      if not soundOnly and type(data) == "table" then
     342          if data.volume ~= nil then
     343              AudioSource.setVolume(id, data.volume);
     344          end;
     345      end;
     346  
     347      -- and return it back to the caller
     348      return id;
     349  end;
     350  

Vehicle:playSound(sound)

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

     353  function Vehicle:playSound(sound)
     354      if sound ~= nil then
     355          AudioSource.play(sound);
     356      end;
     357  end;
     358  

Vehicle:stopSound(sound)

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

     361  function Vehicle:stopSound(sound)
     362      if sound ~= nil then
     363          AudioSource.stop(sound);
     364      end;
     365  end;
     366  

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.

     369  function Vehicle:playOnOffSound(condition, onSound, offSound)
     370      if condition then
     371          if onSound ~= nil then
     372              AudioSource.play(onSound);
     373          end;
     374      else
     375          if offSound ~= nil then
     376              AudioSource.play(offSound);
     377          end;
     378      end;
     379  end;

Vehicle:registerNewSeat(seat)

Assigns a new (entity, seatId) combination to the VehicleSeat seat.

     382  function Vehicle:registerNewSeat(seat)
     383      self.lastSeatId                 = self.lastSeatId + 1;
     384      self.seatsById[self.lastSeatId] = seat;
     385      seat:assignSeatId(self, self.lastSeatId);
     386  end;

Vehicle:getSeatById(seatId)

Returns the seat that has beem assigned the given seatId to. (Used for MP synchronisation)

     390  function Vehicle:getSeatById(seatId)
     391      return self.seatsById[seatId];
     392  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.

     397  function Vehicle:setBalanceSheetId(balanceSheetId)
     398      self.balanceSheetId     = balanceSheetId;
     399  end;

Vehicle:saveToTable()

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

     402  function Vehicle:saveToTable()
     403      -- write our position etc in here
     404      local px, py, pz        = getPosition(self.id);
     405      local rx, ry, rz        = getRotation(self.id);
     406  
     407      --- if there are more components than 1, apply the use the position and rotation from the first component
     408      if #self.components > 1 then
     409          px, py, pz      = getPosition(self.mainId);
     410          rx, ry, rz      = getRotation(self.mainId);
     411      end;
     412  
     413      local tbl               = {
     414          vehicleType         = self.vehicleType,
     415          balanceSheetId      = self.balanceSheetId,
     416          customizationName   = self.dataTable.customizationName,
     417          customColors        = self.customColors;
     418          px = px,
     419          py = py,
     420          pz = pz,
     421          rx = rx,
     422          ry = ry,
     423          rz = rz,
     424      };
     425      self:callVehicleScripts("saveToTable", tbl);
     426      return tbl;
     427  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.

     432  function Vehicle:loadFromTable(tbl)
     433      if tbl == nil then return end;
     434  
     435      self:setBalanceSheetId(tbl.balanceSheetId or self.balanceSheetId);
     436      self:callVehicleScripts("loadFromTable", tbl);
     437  end;

Vehicle:getAge()

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

     440  function Vehicle:getAge()
     441      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
     442  
     443      if asset == nil then
     444          return nil;
     445      end;
     446      return asset.age;
     447  end;

Vehicle:getMaintenanceCost()

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

     451  function Vehicle:getMaintenanceCost()
     452      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
     453  
     454      -- if there's no balance sheet item, then it is no longer in useful life
     455      local remainingUsefulLife       = 0;
     456  
     457      if asset ~= nil and asset.age ~= nil then
     458          remainingUsefulLife         = map(0, self.dataTable.depreciationPeriod, 1, 0, asset.age);
     459      end;
     460  
     461      return map(1, 0, self.dataTable.minMaintenance, self.dataTable.maxMaintenance, clamp01(remainingUsefulLife));
     462  end;

Vehicle:getSellValue()

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

     465  function Vehicle:getSellValue()
     466      local asset                     = g_scenario.accounting:getBalanceSheetEntry(self.balanceSheetId);
     467  
     468      -- if there's no balance sheet item, then it is no longer in useful life
     469      local remainingUsefulLife       = 0;
     470  
     471      if asset ~= nil and asset.age ~= nil then
     472          -- a placeable vehicle that has been bought and sold today shall not deduct any money
     473          if self.dataTable.isPlaceable and asset.age == 0 then
     474              return self.dataTable.price;
     475          end;
     476  
     477          remainingUsefulLife         = map(0, self.dataTable.depreciationPeriod, 1, 0, asset.age);
     478      end;
     479  
     480      -- use sell price of data table if customizable
     481      local acquisitionPrice          = self.dataTable.price;
     482  
     483      return map(1, 0, 0.8*acquisitionPrice, 0.2*acquisitionPrice, clamp01(remainingUsefulLife));
     484  end;

Vehicle:sell()

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

     487  function Vehicle:sell()
     488      assert(g_isMaster, "Selling is only allowed on servers");
     489  
     490      -- determine the residual value
     491      local sellValue                 = self:getSellValue();
     492  
     493      -- get some money
     494      g_scenario.accounting:sellAsset(self.balanceSheetId, sellValue);
     495  
     496      self:callVehicleScripts("onSell");
     497  
     498      -- next step: trigger destroying (this will also call destroy)
     499      self:onDestroy();
     500      return sellValue;
     501  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).

     504  function Vehicle:spawnAt(x,y,z, rx,ry,rz)
     505      -- just apply this position to our main component
     506      -- any other component has to be adjusted automatically by joints
     507      -- set the main component to kinematic
     508      if Rigidbody.isRigidbody(self.mainId) then
     509          self:setVehicleKinematic(true);
     510      end;
     511  
     512      setWorldPosition(self.mainId, x,y,z);
     513      setWorldRotation(self.mainId, rx,ry,rz);
     514  
     515      if g_isMaster and not self.dataTable.isKinematic then
     516          -- block vehicle for 0.5 seconds of physics simulation
     517          self.resetKinematic         = 0.5;
     518      end;
     519  
     520      -- set the colors of the vehicle
     521      self:applyVehicleColors(self.customColors);
     522  end;

Vehicle:reset()

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

     525  function Vehicle:reset()
     526      -- ask for a place to reset
     527      local x,y,z,ry                  = VehicleManager:findPosition(self.dataTable.width, self.dataTable.length);
     528  
     529      if x ~= nil then
     530          -- call scripts
     531          self:callVehicleScripts("onReset");
     532  
     533          self:spawnAt(x,y,z, 0,ry,0);
     534          return true;
     535      end;
     536  
     537      return false;
     538  end;

Vehicle:loadComponents(dataTable)

Loads all components if the vehicle consists of multiple components. Otherwise, it sets up the main component. This is useful for some special vehicles such as the Marmotta Vattenmask.

     541  function Vehicle:loadComponents(dataTable)
     542      if g_isClient then
     543          self.canInterpolate             = false;
     544          self.interpolationWeight        = 2;
     545          self.interpolationDuration      = 1;
     546      end;
     547  
     548      self.components             = {};
     549  
     550      local dataTable_components  = dataTable.components;
     551      if dataTable_components == nil then
     552          -- add some fake data if this section is empty in the vehicle's data table
     553          dataTable_components    = {
     554              {
     555                  index           = "",
     556                  massCenter      = dataTable.massCenter,
     557              },
     558          };
     559      end;
     560  
     561      -- register all components
     562      for k, v in ipairs(dataTable_components) do
     563          local id                = getChild(self.id, v.index or "");
     564          if not self.isDummy then
     565              VehicleManager:registerVehicleRigidBody(self, id);
     566          end;
     567  
     568          local component         = {
     569              id                  = id,
     570          };
     571  
     572          if g_isClient then
     573              component.lastPosition      = Vector3.zero:clone();
     574              component.currentPosition   = Vector3.zero:clone();
     575              component.targetPosition    = Vector3.zero:clone();
     576  
     577              -- use quaternions to avoid Gimbal Lock
     578              component.lastRotation      = Quaternion.zero:clone();
     579              component.currentRotation   = Quaternion.zero:clone();
     580              component.targetRotation    = Quaternion.zero:clone();
     581  
     582              -- remove rigidbody component!
     583              if Rigidbody.isRigidbody(component.id) then
     584                  Rigidbody.setIsKinematic(component.id, true);
     585                  --Rigidbody.removeRigidbody(component.id);
     586              end;
     587  
     588          elseif g_isServer then
     589              component.lastSentPosition  = Vector3.zero:clone();
     590              component.lastSentRotation  = Vector3.zero:clone();
     591          end;
     592  
     593          table.insert(self.components, component);
     594  
     595          if self.mainId == nil then
     596              -- first component is always main component
     597              self.mainId             = id;
     598          end;
     599  
     600          if v.massCenter ~= nil and Rigidbody.isRigidbody(id) then
     601              Rigidbody.setMassCenter(id, v.massCenter.x, v.massCenter.y, v.massCenter.z);
     602          end;
     603      end;
     604  
     605      -- add collision ignore pairs
     606      if dataTable.ignoreCollisions ~= nil then
     607          for k, v in pairs(dataTable.ignoreCollisions) do
     608              local componentA        = getChild(self.id, v[1]);
     609              local componentB        = getChild(self.id, v[2]);
     610  
     611              if componentA ~= nil and componentB ~= nil then
     612                  if Rigidbody.hasCollider(componentA) and Rigidbody.hasCollider(componentB) then
     613                      Physics.ignoreCollision(componentA, componentB, true);
     614  
     615                  elseif Rigidbody.hasCollider(componentA) then
     616                      for k1, child in getChildren(componentB) do
     617                          if Rigidbody.hasCollider(child) then
     618                              Physics.ignoreCollision(componentA, child, true);
     619                          end
     620                      end;
     621  
     622                  elseif Rigidbody.hasCollider(componentB) then
     623                      for k1, child in getChildren(componentA) do
     624                          if Rigidbody.hasCollider(child) then
     625                              Physics.ignoreCollision(componentB, child, true);
     626                          end
     627                      end;
     628  
     629                  else
     630                      for k1, child1 in getChildren(componentA) do
     631                          if Rigidbody.hasCollider(child1) then
     632                              for k2, child2 in getChildren(componentB) do
     633                                  if Rigidbody.hasCollider(child2) then
     634                                      Physics.ignoreCollision(child1, child2, true);
     635                                  end
     636                              end;
     637                          end
     638                      end;
     639                  end;
     640              end;
     641          end;
     642      end;
     643  
     644      -- let's go on to joints
     645      if dataTable.componentJoints ~= nil then
     646          self.componentJoints        = {};
     647  
     648          for k, v in pairs(dataTable.componentJoints) do
     649              local component         = self.components[v.component or 1];
     650              local attachTo          = self.components[v.attachTo or 1];
     651  
     652              local attachTarget      = 0;
     653              if v.attachPosition ~= nil then
     654                  attachTarget        = getChild(self.id, v.attachPosition);
     655              end;
     656              local attachAnchor      = 0;
     657              if v.attachAnchor ~= nil then
     658                  attachAnchor        = getChild(self.id, v.attachAnchor);
     659              end;
     660  
     661              if component == nil then
     662                  print("Error while loading vehicle type '" .. tostring(self.vehicleType) .. "': Invalid joint component name '" .. tostring(v.component) .. "' (component). Make sure this vehicle component actually exists.");
     663              elseif attachTo == nil then
     664                  print("Error while loading vehicle type '" .. tostring(self.vehicleType) .. "': Invalid joint component name '" .. tostring(v.attachTo) .. "' (attachTo). Make sure this vehicle component actually exists.");
     665              elseif v.attachPosition ~= nil and attachTarget == 0 then
     666                  print("Error while loading vehicle type '" .. tostring(self.vehicleType) .. "': Failed to index joint position index '" .. tostring(v.attachPosition) .. "'. Make sure this object actually exists.");
     667              elseif v.attachAnchor ~= nil and attachAnchor == 0 then
     668                  print("Error while loading vehicle type '" .. tostring(self.vehicleType) .. "': Failed to index joint anchor index '" .. tostring(v.attachAnchor) .. "'. Make sure this object actually exists.");
     669              elseif component == attachTo then
     670                  print("Error while loading vehicle type '" .. tostring(self.vehicleType) .. "': 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) .. "').");
     671              else
     672                  -- alright, we may create this joint
     673                  local joint         = {};
     674                  joint.id            = Joint.addJoint(component.id, attachTo.id, v.axis, attachTarget or 0, attachAnchor or 0);
     675              end;
     676          end;
     677      end;
     678  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)

     681  function Vehicle:loadAxles(dataTable)
     682      self.axles                      = {};
     683  
     684      if dataTable.axles == nil then
     685          return;
     686      end;
     687  
     688      local axleCount                 = #dataTable.axles;
     689  
     690      for i, v in pairs(dataTable.axles) do
     691          local leftWheelIndex        = v.leftWheelIndex or "";
     692          local rightWheelIndex       = v.rightWheelIndex or "";
     693  
     694          -- !!TODO!! check whether these are existing
     695          local leftWheelId           = getChild(self.id, leftWheelIndex);
     696          local rightWheelId          = getChild(self.id, rightWheelIndex);
     697  
     698          local axle                  = self:newAxle(leftWheelId, rightWheelId);
     699          self.axles[i]               = axle;
     700  
     701          -- now fill it up with some data
     702          axle.motorGear              = v.motor or axle.motorGear; -- unused as long as VehicleMotor script is not invoked
     703          axle.brake                  = v.brake or false;
     704          axle.steeringScale          = v.steeringScale or axle.steeringScale;
     705  
     706          if v.leftWheelCaliper ~= nil then
     707              axle.leftWheelCaliper   = getChild(self.id, v.leftWheelCaliper);
     708          end;
     709          if v.rightWheelCaliper ~= nil then
     710              axle.rightWheelCaliper  = getChild(self.id, v.rightWheelCaliper);
     711          end;
     712  
     713          if g_isClient then
     714              axle.lastClientRpmLeft      = 0;
     715              axle.lastClientRpmRight     = 0;
     716              axle.targetClientRpmLeft    = 0;
     717              axle.targetClientRpmRight   = 0;
     718              axle.clientRpmLeft          = 0;
     719              axle.clientRpmRight         = 0;
     720  
     721              axle.lastRotationLeft       = 0;
     722              axle.lastRotationRight      = 0;
     723          end;
     724  
     725          self:callVehicleScripts("customLoadAxle", axle, v);
     726      end;
     727  end;

Vehicle:newAxle(leftId, rightId)

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

     730  function Vehicle:newAxle(leftId, rightId)
     731      local axle                      = {};
     732      axle.leftId                     = leftId;
     733      axle.rightId                    = rightId;
     734      axle.leftWheel                  = Wheel.getWheelId(leftId);
     735      axle.rightWheel                 = Wheel.getWheelId(rightId);
     736  
     737      if getNumOfChildren(leftId) > 0     then        axle.leftVisual     = getChildAt(leftId, 0);    end;
     738      if getNumOfChildren(rightId) > 0    then        axle.rightVisual    = getChildAt(rightId, 0);   end;
     739  
     740      axle.motorGear                  = 0; -- 0: no power attached to this wheel, 1.0: all motor power to there
     741      axle.brake                      = true; -- shall the brake be applied to this wheel?
     742      axle.steeringScale              = 0; -- coefficient defining steer angle min/max
     743      return axle;
     744  end;
     745  

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).

     748  function Vehicle:getIsActive()
     749      -- isActive gets true as soon as anybody has entered the vehicle
     750      return self.currentUser ~= nil;
     751  end;
     752  
     753  function Vehicle:getIsLocalPlayerEntered(includePassengers)
     754      if self.currentUser == g_scenario.player then
     755          return true;
     756      end;
     757  
     758      if includePassengers then
     759          return self:getIsEnteredAsPassenger(g_scenario.player);
     760      end;
     761  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.

     764  function Vehicle:getIsInputActive()
     765      return g_scenario ~= nil and self.currentUser == g_scenario.player and not EscapeMenu.isActive and not g_GUI:getIsKeyInputForGUI();
     766  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).

     769  function Vehicle:getIsGUIActive()
     770      return g_scenario ~= nil and self.currentUser == g_scenario.player and self.vehicleHUD ~= nil;
     771  end;

Vehicle:getIsColliding()

Returns whether there is a collision taking place between the crashbox and some external objects

     774  function Vehicle:getIsColliding()
     775      -- consider appropriate layers only
     776      local mask                  = -(2^30+2^19+2^23+2^2+1);
     777      if self.hasCrashBoxImplemented then
     778          for k,v in pairs(self.crashbox) do
     779          local alpha, beta, gamma    = getRotation(self.components[v.componentnumber].id);
     780              if Utils.checkBox(VectorUtils.getWorldPosition(v.origin), Vector3:new(v.dimensions.x, v.dimensions.y, v.dimensions.z), beta, mask) then
     781                  return true;
     782              end;
     783          end;
     784          return false;
     785      else
     786          --print("getIsColliding() returns nil because Crashbox is not fully implemented, dimensions or crashbox origin is missing");
     787          return nil;
     788      end;
     789  end;
     790  

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.
     796  function Vehicle:update(dt)
     797  
     798      if self.resetKinematic ~= nil then
     799          self.resetKinematic         = self.resetKinematic - dt;
     800  
     801          if self.resetKinematic <= 0 then
     802              self.resetKinematic     = nil;
     803              if g_isMaster and not self.dataTable.isKinematic and Rigidbody.isRigidbody(self.mainId) then
     804                  self:setVehicleKinematic(false);
     805              end;
     806          end;
     807      end;
     808  
     809      self.isActive                   = self:getIsActive();
     810  
     811      if self:getIsGUIActive() then
     812          -- update UI
     813          self.vehicleHUD:setParkingBrake(self.parkingBrake > 0.5);
     814          
     815          -- turn indicators are controlled in VehicleLighting.lua
     816      end;
     817      
     818      if self:getIsInputActive() then
     819  
     820          local throttleValueInverter = 1;
     821          if self:getShallInvertThrottle() then
     822              throttleValueInverter = throttleValueInverter * -1;
     823          end;
     824  
     825          if GameplaySettings.throttleBrakeSeparate then
     826  
     827              self.throttleValue          =  InputMapper:getAxis(InputMapper.Axis_Vehicle_Throttle, nil, true);
     828              self.brakeValue             =  InputMapper:getAxis(InputMapper.Axis_Vehicle_Brake, nil, true);
     829  
     830              if self:getShallInvertThrottle() and GameplaySettings.automaticDirectionChange then
     831                  self.throttleValue, self.brakeValue = self.brakeValue, self.throttleValue;
     832              end;        
     833          else
     834              local inputValue            = InputMapper:getAxis(InputMapper.Axis_Vehicle_Throttle_Brake_Combined, nil, true) * throttleValueInverter;
     835  
     836              if inputValue > 0 then
     837                  self.throttleValue      = inputValue;
     838                  self.brakeValue         = 0;
     839                  if math.abs(self.throttleValue) < 0.05 then
     840                      self.throttleValue = 0;
     841                  end
     842              else
     843                  self.throttleValue      = 0;
     844                  self.brakeValue         = -inputValue;
     845                  if math.abs(self.brakeValue) < 0.05 then
     846                      self.brakeValue = 0;
     847                  end
     848              end;
     849          end;
     850  
     851          -- gear switching. Converted from vehicle motor and tradcked vehicle into one function
     852          if not GameplaySettings.automaticDirectionChange then
     853              
     854              g_GUI:addKeyHint(InputMapper.Vehicle_InverseDirection,  l10n.get("Input_Vehicle_InverseDirection"));
     855              
     856              -- inverse direction
     857              if InputMapper:getKeyDown(InputMapper.Vehicle_InverseDirection) then
     858                  self:setDrivingDirectionInverted();
     859                  self:setCruiseControlEnabled(false);
     860              end;
     861  
     862          else
     863              if not self:getShallInvertThrottle() and self.currentSpeed > -1 and self.currentSpeed < 1    then
     864                  if self:getIsBrakeActivated() then
     865                      if self.driveDirectionChangeTimePrev == nil then
     866                          self.driveDirectionChangeTimePrev               = getTime();
     867                      else
     868                          local driveDirectionChangeTime              = getTime();
     869                          if driveDirectionChangeTime > self.driveDirectionChangeTimePrev + GameplaySettings.driveDirectionChangeTime then
     870                              self:setDrivingDirectionInverted();
     871                              self.driveDirectionChangeTimePrev           = nil;
     872                          end;
     873                      end;
     874                  end;
     875              end;
     876              if self:getShallInvertThrottle() and self.currentSpeed > -1 and self.currentSpeed < 1  then
     877                  if self:getIsThrottleActivated() then
     878                      if self.driveDirectionChangeTimePrev == nil then
     879                          self.driveDirectionChangeTimePrev               = getTime();
     880                      else 
     881                          local driveDirectionChangeTime              = getTime();
     882                          if driveDirectionChangeTime > self.driveDirectionChangeTimePrev +  GameplaySettings.driveDirectionChangeTime  then
     883                              self:setDrivingDirectionInverted();
     884                              self.driveDirectionChangeTimePrev           = nil;
     885                          end;
     886                      end;    
     887                  end;
     888              end;
     889          end;
     890  
     891          if InputMapper:getKeyDown(InputMapper.Vehicle_EnableCruiseControl) and self:getIsLocalPlayerEntered() then
     892              self:setCruiseControlEnabled(not self.cruiseControlActive);
     893          end;
     894      
     895          if self.cruiseControlActive and (self.throttleValue > 0.05 or self.brakeValue > 0.05 or self.parkingBrake > 0.1) then
     896              self:setCruiseControlEnabled(false);
     897          end;
     898          
     899  
     900          local steerValue, fixed         = InputMapper:getAxis(InputMapper.Axis_Vehicle_Steer, nil, true);
     901          self.rawSteerValue              = steerValue;
     902          self.rawSteerValueFixed         = fixed;
     903  
     904  
     905          if self.parkingBrake > 0.5 then
     906              g_GUI:addKeyHint(InputMapper.Vehicle_ParkingBrake,      l10n.get("Input_Vehicle_ParkingBrake_release"));
     907          end;
     908  
     909          if InputMapper:getKeyDown(InputMapper.Vehicle_ParkingBrake) then
     910              self:setParkingBrake(self.parkingBrake ~= 1);
     911  
     912              -- play a sound as feedback
     913              self:playOnOffSound(self.parkingBrake == 1, self.parkingBrakeSound1, self.parkingBrakeSound0);
     914          end;
     915      end;
     916  
     917      if self.isActive then
     918          
     919          -- just for debug, turnon if you want to know if vehicle is colliding
     920          --local coll = self:getIsColliding();
     921          --print(tostring(coll));
     922  
     923          -- update steering
     924          -- directly write the analog value, but smoothen out digital values
     925          if self.rawSteerValueFixed then
     926              self.steerValue         = self.rawSteerValue;
     927  
     928          elseif self.rawSteerValue ~= self.steerValue then
     929  
     930              local returnSpeed       = self.steeringSteerSpeed;
     931  
     932              if self.rawSteerValue == 0 or (self.steerValue >= 0) ~= (self.rawSteerValue >= 0) and self.steeringReturnSpeed > self.steeringSteerSpeed then
     933                  returnSpeed         = self.steeringReturnSpeed;
     934              end;
     935  
     936              local speedCoeff        = 1;
     937              if self.currentSpeed ~= nil then
     938                  speedCoeff          = math.abs(1 - clamp(self.currentSpeed * self.steeringSlowdownCoeff, 0, self.steeringMaxSlowdown));
     939              end;
     940  
     941              if self.steerValue < self.rawSteerValue then
     942                  self.steerValue     = math.min(self.steerValue + returnSpeed * speedCoeff * dt, self.rawSteerValue);
     943  
     944              else
     945                  self.steerValue     = math.max(self.steerValue - returnSpeed *  speedCoeff *dt, self.rawSteerValue);
     946              end;
     947          end;
     948  
     949          -- calculate the simulated drag according the current speed and the defined speed ranges in the datatable
     950          if g_isMaster and self.dragControlMinSpeed ~= nil and self.dragControlMaxSpeed ~= nil and self.dragControlMinDrag ~= nil and self.dragControlMaxDrag ~= nil then
     951              local drag                  = 0;
     952              if self.currentSpeed < self.dragControlMinSpeed then
     953                  drag                    = self.dragControlMinDrag;
     954              elseif self.currentSpeed > self.dragControlMaxSpeed then
     955                  drag                    = self.dragControlMaxDrag;
     956              else
     957                  drag                    = map(0, self.dragControlMaxSpeed - self.dragControlMinSpeed, self.dragControlMaxDrag, self.dragControlMinDrag, self.dragControlMaxSpeed - self.currentSpeed);
     958              end;
     959              -- for vehicles with more than one component
     960              for k, v in pairs(self.components) do
     961                  if Rigidbody.isRigidbody(v.id) then
     962                      Rigidbody.setDrag(v.id, drag);
     963                  end;
     964              end;
     965          end;
     966      end;
     967  
     968      if self.isActive --[[or math.abs(self.currentSpeed) > 1.5
     969   then
     970  
     971          -- update wheel visuals
     972          for n, axle in pairs(self.axles) do
     973              self:wheelPositionToVisual(axle.leftWheel, axle.leftVisual, axle.leftWheelCaliper);
     974              self:wheelPositionToVisual(axle.rightWheel, axle.rightVisual, axle.rightWheelCaliper);
     975  
     976              -- manually add some wheel rotation on clients
     977              if g_isClient then
     978                  axle.clientRpmLeft          = 0.8 * (axle.clientRpmLeft or 0)       + 0.2 * (axle.targetClientRpmLeft or 0);
     979                  axle.clientRpmRight         = 0.8 * (axle.clientRpmRight or 0)      + 0.2 * (axle.targetClientRpmRight or 0);
     980              
     981                  -- times 6 because we want the result in degrees - rpm to sec^-1 is / 60, but sec^-1 to degree^-1 is * 360 => *6 in total
     982                  axle.lastRotationLeft       = (axle.lastRotationLeft or 0)      + axle.clientRpmLeft    * 6 * dt;
     983                  axle.lastRotationRight      = (axle.lastRotationRight or 0)     + axle.clientRpmRight   * 6 * dt;
     984  
     985                  if axle.leftVisual ~= nil then
     986                      rotate(axle.leftVisual,     axle.lastRotationLeft,  0,0, false);
     987                  end;
     988                  if axle.rightVisual ~= nil then
     989                      rotate(axle.rightVisual,    axle.lastRotationRight, 0,0, false);
     990                  end;
     991              end;
     992          end;
     993  
     994          -- update steering wheel
     995          if self.steeringWheelId ~= nil then
     996              local steeringWheelAngle    = self:getMaxVisualSteerAngle();
     997              
     998              if self.steeringWheelAxle == 1 then
     999                  setRotationX(self.steeringWheelId, steeringWheelAngle * self.steerValue);
    1000  
    1001              elseif self.steeringWheelAxle == 2 then
    1002                  setRotationY(self.steeringWheelId, steeringWheelAngle * self.steerValue);
    1003  
    1004              else
    1005                  setRotationZ(self.steeringWheelId, steeringWheelAngle * self.steerValue);
    1006              end;
    1007          end;
    1008          if self.steeringAnimationId ~= nil then
    1009              Animation.sampleTime(self.steeringAnimationId, self.steeringAnimationLength * 0.5 * (1 + self.steerValue));
    1010          end;
    1011      end;
    1012  
    1013      self:callVehicleScripts("update", dt);
    1014  end;
    1015  

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.

    1017  function Vehicle:fixedUpdate(dt)
    1018      -- apply wheel torques and steering angles
    1019      if g_isMaster then
    1020          if not self.customWheelScriptActive and #self.axles > 0 then
    1021              self:wheelsUpdate(self:getCurrentMotorTorque(), self:getCurrentBrakeTorque(), self:getCurrentSteerAngle());
    1022          end;
    1023      end;
    1024  
    1025      -- do the client-side interpolation
    1026      if g_isClient then
    1027          if self.canInterpolate then
    1028              local maxExtrapolation          = 1.3;
    1029              self.interpolationWeight        = self.interpolationWeight + dt / self.interpolationDuration;
    1030  
    1031              if self.interpolationWeight > maxExtrapolation then
    1032                  self.interpolationWeight    = maxExtrapolation;
    1033                  self.canInterpolate         = false;
    1034              end;
    1035  
    1036              for k, component in pairs(self.components) do
    1037                  -- interpolate position and rotation
    1038                  component.currentPosition   = Vector3.lerp(component.lastPosition,                  component.targetPosition,   self.interpolationWeight);
    1039                  component.currentRotation   = Quaternion.lerpShortestPath(component.lastRotation,   component.targetRotation,   self.interpolationWeight);
    1040  
    1041                  -- apply them
    1042                  VectorUtils.setPosition(component.id,           component.currentPosition);
    1043                  VectorUtils.setRotationQuaternion(component.id, component.currentRotation);
    1044              end;
    1045  
    1046          else
    1047              -- cannot interpolate any more => keep position and rotation fixed
    1048              for k, component in pairs(self.components) do
    1049                  VectorUtils.setPosition(component.id,           component.currentPosition);
    1050                  VectorUtils.setRotationQuaternion(component.id, component.currentRotation);
    1051              end;
    1052          end;
    1053      end;
    1054  
    1055      self:callVehicleScripts("fixedUpdate", dt);
    1056  
    1057      -- set dirty whenever the vehicle is active
    1058      self:setDirty(self:getIsActive());
    1059  
    1060      if g_isServer and not self.isDirty then
    1061          -- not yet dirty => check whether we need to sync the position
    1062          for n, component in pairs(self.components) do
    1063              local px,py,pz          = getWorldPosition(component.id);
    1064              local qx,qy,qz,qw       = getWorldRotationQuaternion(component.id);
    1065  
    1066              local lastPos, lastRot  = component.lastSentPosition, component.lastSentRotation;
    1067              if
    1068                  math.abs(px - lastPos.x) > 0.1 or
    1069                  math.abs(py - lastPos.y) > 0.08 or
    1070                  math.abs(pz - lastPos.z) > 0.1 or
    1071  
    1072                  math.abs(qx - lastRot.x) > 0.02 or
    1073                  math.abs(qy - lastRot.y) > 0.02 or
    1074                  math.abs(qz - lastRot.z) > 0.02 or
    1075                  math.abs(qw - lastRot.w) > 0.02
    1076              then
    1077                  -- mark vehicle as dirty
    1078                  self:setDirty(true);
    1079                  break;
    1080              end;
    1081          end;
    1082      end;
    1083  end;

Vehicle:wheelsUpdate(motorTorque, brakeTorque, steerAngle)

Physics update for the vehicle's wheels.

    1086  function Vehicle:wheelsUpdate(motorTorque, brakeTorque, steerAngle)
    1087      for n, axle in pairs(self.axles) do
    1088          -- apply wheel torques
    1089          Wheel.setTorques(axle.leftWheel, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0);
    1090          Wheel.setTorques(axle.rightWheel, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0);
    1091  
    1092          -- and now steering
    1093          if axle.steeringScale ~= 0 then
    1094              Wheel.setSteerAngle(axle.leftWheel, steerAngle * axle.steeringScale);
    1095              Wheel.setSteerAngle(axle.rightWheel, steerAngle * axle.steeringScale);
    1096          end;
    1097      end;
    1098  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.

    1101  function Vehicle:wheelPositionToVisual(wheelId, visualWheel, caliperId)
    1102      if visualWheel == 0 or visualWheel == nil then return; end;
    1103  
    1104      Wheel.applyPose(wheelId, visualWheel);
    1105  
    1106      if caliperId ~= nil and caliperId ~= 0 then
    1107          Wheel.applyCaliperPose(wheelId, caliperId);
    1108      end;
    1109  end;

Vehicle:getMayEnter(player)

Returns whether the player player may enter the vehicle on the driver seat.

    1112  function Vehicle:getMayEnter(player)
    1113      -- vehicle must have a driver seat and noone else can sit in there
    1114      return self.driverSeat ~= nil and (self.currentUser == nil or self.currentUser == player);
    1115  end;

Vehicle:getMayEnterOnAnySeat(player)

Returns whether the player player may enter the vehicle on any seat (not just on the driver seat).

    1118  function Vehicle:getMayEnterOnAnySeat(player)
    1119      for k, seat in ipairs(self.seatsById) do
    1120          if seat:getMayEnter(player) then
    1121              return true;
    1122          end;
    1123      end;
    1124  
    1125      return false;
    1126  end;

Vehicle:getIsEntered(player)

Returns whether the player player is entered in this vehicle.

    1129  function Vehicle:getIsEntered(player)
    1130      return self.driverSeat ~= nil and self.currentUser == player;
    1131  end;

Vehicle:getIsEnteredAsPassenger(player)

Returns whether the player player is entered as passenger.

    1134  function Vehicle:getIsEnteredAsPassenger(player)
    1135      if self.seatsById == nil then
    1136          return false;
    1137      end;
    1138  
    1139      for k, seat in ipairs(self.seatsById) do
    1140          if seat ~= self.driverSeat and seat.currentUser == player then
    1141              return true;
    1142          end;
    1143      end;
    1144      return false;
    1145  end;

Vehicle:getShallInvertThrottle()

Returns whether the throttle input shall be inverted, i.e. whether the W and S key shall be inverted.

    1148  function Vehicle:getShallInvertThrottle()
    1149      return false;
    1150  end;

Vehicle:getIsThrottleActivated()

Returns whether motor throttle is currently activated (after considering inverted throttle).

    1153  function Vehicle:getIsThrottleActivated()
    1154      return self.throttleValue > 0.05 or (self.brakeValue > 0.05 and self:getShallInvertThrottle());
    1155  end;

Vehicle:getIsBrakeActivated()

Returns whether the brake is currently activated (after considering inverted throttle).

    1158  function Vehicle:getIsBrakeActivated()
    1159      return self.brakeValue > 0.05 or (self.throttleValue > 0.05 and self:getShallInvertThrottle());
    1160  end;
    1161  

Vehicle:setDrivingDirectionInverted()

Inverts the current driving direction.

    1165  function Vehicle:setDrivingDirectionInverted()
    1166      -- call vehicleMotor or trackedVehicle function
    1167  end;
    1168  

Vehicle:handleEnterRequest(player)

Master (= server or singleplayer) function if a client wants to enter a vehicle.

    1171  function Vehicle:handleEnterRequest(player)
    1172      assert(player ~= nil,   "Vehicle:handleEnterRequest may not be called with nil player");
    1173      assert(g_isMaster,      "Function may not be called on clients!");
    1174  
    1175      -- handle event from client
    1176  
    1177      if not self:getMayEnter(player) then
    1178          return false;
    1179      end;
    1180  
    1181      -- nobody is currently sitting in this vehicle => client may join
    1182      self:setCurrentUser(player);
    1183  
    1184      -- BUT: the player controller itself is no longer active
    1185      --player:setCurrentUser(nil);
    1186  
    1187      return true;
    1188  end;

Vehicle:tryEnter(player)

Master (= server or singleplayer) function used for vehicle switching. The function tries to let player enter this vehicle and returns true if successful, otherwise false.

    1191  function Vehicle:tryEnter(player)
    1192      assert(g_isMaster,      "Function may not be called on clients!");
    1193  
    1194      if self:getMayEnter(player) then
    1195          self:handleEnterRequest(player);
    1196          return true;
    1197      end;
    1198      
    1199      for k, seat in ipairs(self.seatsById) do
    1200          if seat ~= self.driverSeat and seat:getMayEnter(player) then
    1201              -- we may enter!
    1202              seat:enterRequest(player);
    1203              return true;
    1204          end;
    1205      end;
    1206      return false;
    1207  end;
    1208  

Vehicle:handleLeave(player, isEnteringOtherVehicle)

Called whenever the player wants to leave this vehicle. If called on a client, this will call the exact same function on the server with the same arguments.

    1211  function Vehicle:handleLeave(player, isEnteringOtherVehicle)
    1212      --assert(player == self.currentUser, "Cannot handle leaving of a player that is not sitting in here");
    1213      if g_isClient then
    1214          -- on client: inform server that we've left
    1215          EventLeaveVehicle:send(self, isEnteringOtherVehicle);
    1216  
    1217      else
    1218          -- on server: just apply the change
    1219          --self:setCurrentUser(nil);
    1220          assert(player, "Must be called with a valid player on it");
    1221          -- TODO check if that player is actually entered in here
    1222  
    1223          -- reenable player if he/she is not entering another vehicle
    1224          if not isEnteringOtherVehicle then
    1225              player:setCurrentUser(player);
    1226          end;
    1227      end;
    1228  end;

Vehicle:setCurrentUser(newPlayer, noEvent)

This function is a core part of the network entity system as it handles entering/leaving vehicles as special case of setting an entity's user.

    1231  function Vehicle:setCurrentUser(newPlayer, noEvent)
    1232      local oldPlayer                 = self.currentUser;
    1233  
    1234      if oldPlayer ~= nil and newPlayer ~= nil then
    1235          if oldPlayer == newPlayer then
    1236              -- same player => don't do anything
    1237              return;
    1238          end;
    1239  
    1240          -- first kick out the old player if this is on a client
    1241          -- on a server its not allowed though
    1242          if g_isClient then
    1243              self:setCurrentUser(nil, true);
    1244  
    1245          else
    1246              self:setCurrentUser(nil);
    1247              error("currentUser must be nil before assigning a new user on a server");
    1248          end;
    1249      end;
    1250  
    1251      if g_isClient and self.currentUser == newPlayer then
    1252          -- we already have the correct state (i.e. when leaving the vehicle)
    1253          return;
    1254      end;
    1255  
    1256      local player                    = newPlayer or oldPlayer;
    1257      local isEntering                = newPlayer ~= nil;
    1258  
    1259      Vehicle:parentClass().setCurrentUser(self, newPlayer, noEvent);
    1260  
    1261      if oldPlayer ~= nil then
    1262          self:onLeave(oldPlayer, oldPlayer:getIsLocalPlayer());
    1263      end;
    1264  
    1265      if newPlayer ~= nil then
    1266          self:onEnter(newPlayer, newPlayer:getIsLocalPlayer());
    1267      end;
    1268  end;
    1269  

Vehicle:onEnter(player, isLocalPlayer)

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

    1272  function Vehicle:onEnter(player, isLocalPlayer)
    1273      self.anyPlayerEntered           = true;
    1274  
    1275      if isLocalPlayer then
    1276          -- first enter the vehicle (and leave the currently entered one)
    1277          VehicleManager:onEnterVehicle(self);
    1278      end;
    1279  
    1280      -- then call onEnter (do this always)
    1281      self:callVehicleScripts("onEnter", player, isLocalPlayer);
    1282  
    1283      if isLocalPlayer then
    1284          if GameplaySettings.autoReleaseParkingBrake then
    1285              self:setParkingBrake(false);
    1286          end;
    1287  
    1288          self.vehicleHUD             = g_GUI:showHUD(self.vehicleHUDClass);
    1289          self:callVehicleScripts("onHUDActivate");
    1290      end;
    1291  end;

Vehicle:onLeave(player, isLocalPlayer)

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

    1294  function Vehicle:onLeave(player, isLocalPlayer)
    1295      self.anyPlayerEntered           = false;
    1296      self:callVehicleScripts("onLeave", player, isLocalPlayer);
    1297  
    1298      if isLocalPlayer then
    1299          VehicleManager:onLeaveVehicle(self);
    1300  
    1301          self:callVehicleScripts("onHUDDeactivate");
    1302  
    1303          if self.vehicleHUD ~= nil then
    1304              -- destroy reference to hud before calling close (important in case close throws an error)
    1305              local hud               = self.vehicleHUD;
    1306              self.vehicleHUD         = nil;
    1307              hud:close();
    1308          end;
    1309      end;
    1310      if g_isMaster then
    1311          -- reset throttle and set the parking brake
    1312          self.throttleValue  = 0;
    1313          self.brakeValue     = 0;
    1314          self:setParkingBrake(true);
    1315          self:setCruiseControlEnabled(false);
    1316      end;
    1317  end;

Vehicle:getMinimapPositionAndRotation()

This function is called by MinimapHUD. It returns where on the map the vehicle is currently located at and in which direction it is oriented.

    1320  function Vehicle:getMinimapPositionAndRotation()
    1321      local x,y,z                     = getWorldPosition(self.mainId);
    1322      local _a,ry,_b                  = getWorldRotation(self.mainId);
    1323      return x,y,z, ry;
    1324  end;
    1325  

Vehicle:getCurrentMotorTorque()

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

    1328  function Vehicle:getCurrentMotorTorque()
    1329      -- to be replaced by a motorising vehicle script
    1330      return 0;
    1331  end;

Vehicle:getCurrentBrakeTorque()

Returns the current brake torque in Nm per wheel.

    1334  function Vehicle:getCurrentBrakeTorque()
    1335      return self.maxBrakeTorque * self.brakeValue + self.parkingBrakeTorque * self.parkingBrake;
    1336  end;

Vehicle:getCurrentSteerAngle()

Returns the steering angle in degrees.

    1339  function Vehicle:getCurrentSteerAngle()
    1340      return self.maxSteerAngle * self.steerValue;
    1341  end;
    1342  

Vehicle:getMaxVisualSteerAngle()

Returns the maximum steering angle to be displayed on the driver's steering wheel in degrees.

    1345  function Vehicle:getMaxVisualSteerAngle()
    1346      if self:getIsLocalPlayerEntered() and self.driverSeat.isInterieurCamera then
    1347          return self.steeringWheelAngle;
    1348      end;
    1349      return self.steeringWheelAngleExterior;
    1350  end;
    1351  

Vehicle:setGear(value)

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

    1354  function Vehicle:setGear(value)
    1355      self.currentGear                = clamp(value, 1, #self.gears);
    1356  
    1357      -- avoid automatic drive interruption
    1358      self.gearSwitchTimer            = -self.gearSwitchPace;
    1359  end;

Vehicle:setParkingBrake(value)

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

    1362  function Vehicle:setParkingBrake(value)
    1363      self.parkingBrake               = value and 1 or 0;
    1364  end;

Vehicle:onDestroy()

Starts destroying the vehicle. onDestroy is the function to call when you want to destroy a vehicle, destroy will only be called as a result of it.

    1367  function Vehicle:onDestroy()
    1368      -- leave if entered
    1369      if self:getIsActive() then
    1370          self:setCurrentUser(nil);
    1371      end;
    1372      
    1373      -- kick out all passengers
    1374      if self.seatsById ~= nil then
    1375          for k, seat in ipairs(self.seatsById) do
    1376              if seat ~= self.driverSeat and seat.currentUser ~= nil then
    1377                  seat:leaveRequest(seat.currentUser, false);
    1378              end;
    1379          end;
    1380      end;
    1381  
    1382      Vehicle:parentClass().onDestroy(self);
    1383  end;

Vehicle:destroy()

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

    1386  function Vehicle:destroy()
    1387      Vehicle:parentClass().destroy(self);
    1388  
    1389      -- if vehicle is dummy, skip unregister
    1390      if not self.isDummy then
    1391          VehicleManager:unregisterVehicle(self);
    1392          
    1393          for k, v in pairs(self.components) do
    1394              VehicleManager:unregisterVehicleRigidBody(v);
    1395          end;
    1396      end;
    1397      self:callVehicleScripts("destroy");
    1398  
    1399      delete(self.id);
    1400  end;

Vehicle:setVehicleKinematic(isKinematic)

Sets the vehicle's kinematic value to false or true.

    1403  function Vehicle:setVehicleKinematic(isKinematic)
    1404      -- if vehicle is dummy, skip unregister
    1405      Rigidbody.setIsKinematic(self.mainId, isKinematic);
    1406  
    1407      self:callVehicleScripts("setVehicleKinematic", isKinematic);
    1408  end;

Vehicle:applyVehicleColors(customColors)

Sets the color of the different vehicle parts.

    1411  function Vehicle:applyVehicleColors(customColors)
    1412      -- set custom colors
    1413      if customColors ~= nil then
    1414          -- go through the different color channels
    1415          for k, color in pairs(customColors) do
    1416              local channel                       = color.colorChannel;
    1417              -- convert color from hex to rgb
    1418              local r, g, b                       = EscapeMenu:unpackHexColor(color.value);
    1419              if color.vehicleParts ~= nil then
    1420                  for k1, vehiclePart in pairs(color.vehicleParts) do
    1421                      -- iterate through all objects the color should be applied to!
    1422                      local index                 = vehiclePart;
    1423                      local materialSlot          = color.materialSlot;
    1424  
    1425                      -- the entry into "vehicleParts" could well be a table (and this way specify its own material slot)
    1426                      if type(vehiclePart) == "table" then
    1427                          index                   = vehiclePart.index         or vehiclePart[1];
    1428                          materialSlot            = vehiclePart.colorSlot     or vehiclePart[2];
    1429                      end;
    1430                      
    1431                      -- index object & create a new property block for it
    1432                      -- note that materialSlot may well also be nil!
    1433                      local objectToRecolor       = getChild(self.id, index);
    1434                      local propertyBlock         = Material.newPropertyBlock(objectToRecolor, materialSlot);
    1435  
    1436                      -- set color of channel, and apply it to the property!
    1437                      if channel == "" or channel == nil then
    1438                          channel                 = "_Color";
    1439                      end;
    1440  
    1441                      -- finally apply colors
    1442                      Material.setPropertyBlockColor(propertyBlock, channel, r, g, b, 255);
    1443                      Material.applyPropertyBlock(objectToRecolor, propertyBlock, materialSlot);
    1444                  end;
    1445              end;
    1446          end;
    1447      end;
    1448  end;

Vehicle:setCruiseControlEnabled(isEnabled, noEvent)

Turns cruise control on or off, depending on isEnabled. If turned on, setCruiseControlValues will be called to e.g. store the current motor rpm value.

    1451  function Vehicle:setCruiseControlEnabled(isEnabled, noEvent)
    1452      self.cruiseControlActive                    = isEnabled;
    1453      if self.vehicleHUD ~= nil then
    1454          self.vehicleHUD:setIsCruiseControlOn(self.cruiseControlActive);
    1455      end;
    1456  
    1457      if g_isClient and not noEvent then
    1458          EventVehicleCruiseControlEnable:send(self, isEnabled);
    1459          return;
    1460      end;
    1461  
    1462      if self.cruiseControlActive then
    1463          self:setCruiseControlValues();
    1464      end;
    1465  end;

Vehicle:setCruiseControlValues()

Called whenever the cruise control is engaged. Can be overridden by VehicleScripts such as VehicleMotor.

    1468  function Vehicle:setCruiseControlValues()
    1469      -- implemented in vehicleMotor
    1470  end;
    1471  

Vehicle:writeResync()

Resynchronizes all variables when a new player joins the game. The data sent by writeResync will be received by readResync.

    1475  function Vehicle:writeResync()
    1476      Vehicle:parentClass().writeResync(self);
    1477  
    1478      local x,y,z                     = getWorldPosition(self.mainId);
    1479      local rx,ry,rz                  = getWorldRotation(self.mainId);
    1480  
    1481      streamWriteFloat(x);
    1482      streamWriteFloat(y);
    1483      streamWriteFloat(z);
    1484      streamWriteFloat(rx);
    1485      streamWriteFloat(ry);
    1486      streamWriteFloat(rz);
    1487  
    1488      streamWriteString(self.vehicleType);
    1489      streamWriteString(self.dataTable.customizationName or "");
    1490  
    1491      -- write the colors
    1492      if self.customColors ~= nil then
    1493          local colorLength               = #self.customColors;
    1494          streamWriteUInt8(colorLength);
    1495          for k,v in ipairs(self.customColors) do
    1496              streamWriteString(v.value);
    1497          end;
    1498      else
    1499          -- send length
    1500          streamWriteUInt8(0);
    1501      end;
    1502  
    1503      self:writeComponentPositions();
    1504  
    1505      self:callVehicleScripts("writeResync");
    1506      --[[print("Syncing " .. tostring(self.vehicleType));
    1507      local functionName              = "writeResync";
    1508      for _, script in pairs(self.vehicleScripts) do
    1509          -- ensure its not nil
    1510          if script[functionName] ~= nil then
    1511              script[functionName](self);
    1512              print("Script " .. getGlobalName(script) .. " offset " .. streamGetWriteOffset());
    1513          end;
    1514      end;
 
    1515  
    1516      Vehicle:parentClass().finishWriteResync(self);
    1517  
    1518      -- set as dirty for the next frame
    1519      self:setDirty(true);
    1520  end;

Vehicle:readResync()

Resynchronizes all variables when a new player joins the game. The data sent by writeResync will be received by readResync.

    1524  function Vehicle:readResync()
    1525      Vehicle:parentClass().readResync(self);
    1526  
    1527      local x,y,z                     = streamReadFloat(), streamReadFloat(), streamReadFloat();
    1528      local rx,ry,rz                  = streamReadFloat(), streamReadFloat(), streamReadFloat();
    1529  
    1530      -- next read the template name
    1531      local vehicleType               = streamReadString();
    1532      local customizationName         = streamReadString();
    1533  
    1534      local colorLenght               = streamReadUInt8();
    1535      local addedColors               = nil;
    1536      if colorLenght > 0 then
    1537          addedColors             = {};
    1538          for i = 1, colorLenght, 1 do
    1539              addedColors[i]          = {value        = streamReadString()};
    1540          end;
    1541      end;
    1542      if customizationName == "" then
    1543          customizationName           = nil;
    1544      end;
    1545      local isDummy                   = false;
    1546  
    1547      local successful, params        = VehicleManager:getVehicleParams(vehicleType, customizationName, isDummy, addedColors);
    1548  
    1549      -- if the vehicle was not found, we need to return a nil value
    1550      if not successful then
    1551          print("Error while resynchronizing vehicles: Vehicle type '" .. tostring(vehicleType) .. "' is apparently used by the host, but not registered on this client.");
    1552          return nil;
    1553      end;
    1554  
    1555      self:load(unpack(params));
    1556      self:spawnAt(x,y,z, rx,ry,rz);
    1557  
    1558      -- this is only ever on clients
    1559      self.interpolationDuration      = 1;
    1560      self.interpolationWeight        = 0;
    1561      self.canInterpolate             = false;
    1562  
    1563      -- move all components to the correct position
    1564      -- and apply target, last AND current position
    1565      for k, component in ipairs(self.components) do
    1566          component.targetPosition    = Vector3:new(streamReadFloat(), streamReadFloat(), streamReadFloat());
    1567          component.targetRotation    = Quaternion:new(streamReadFloat(), streamReadFloat(), streamReadFloat(), streamReadFloat());
    1568  
    1569          component.lastPosition      = component.targetPosition;
    1570          component.lastRotation      = component.targetRotation;
    1571          component.currentPosition   = component.targetPosition;
    1572          component.currentRotation   = component.targetRotation;
    1573      end;
    1574  
    1575      -- sync all transforms to apply the new position
    1576      Physics.syncTransforms();
    1577  
    1578      self:callVehicleScripts("readResync");
    1579      --[[print("Read Syncing " .. tostring(self.vehicleType));
    1580      local functionName              = "readResync";
    1581      for _, script in pairs(self.vehicleScripts) do
    1582          -- ensure its not nil
    1583          if script[functionName] ~= nil then
    1584              script[functionName](self);
    1585              print("Script " .. getGlobalName(script) .. " offset " .. streamGetReadOffset());
    1586          end;
    1587      end;
 
    1588  
    1589      Vehicle:parentClass().finishReadResync(self);
    1590  end;
    1591  
    1592  function Vehicle:postWriteResync()
    1593      assert(g_isServer, "Only allowed on server");
    1594  
    1595      self:callVehicleScripts("postWriteResync");
    1596  end;

Vehicle:writeUpdate()

Sends a network packet each frame when the vehicle is updated. The data sent by writeUpdate will be received by readUpdate.

    1600  function Vehicle:writeUpdate()
    1601      local isLocalPlayerEntered      = self:getIsLocalPlayerEntered();
    1602  
    1603      if g_isServer then
    1604          self:writeComponentPositions();
    1605          self:writeWheelRpms();
    1606          -- write extra information from server to client
    1607          streamWriteFloat(self.rawSteerValue);
    1608      else
    1609          -- write from client -> server
    1610          if streamWriteBool(isLocalPlayerEntered) then
    1611              -- write input to server
    1612              streamWriteFloat(self.throttleValue);
    1613              streamWriteFloat(self.brakeValue);
    1614              streamWriteFloat(self.rawSteerValue);
    1615              streamWriteBool(self.rawSteerValueFixed);
    1616              streamWriteBool(self.parkingBrake > 0.5);
    1617          end;
    1618      end;
    1619  
    1620      self:callVehicleScripts("writeUpdate", isLocalPlayerEntered);
    1621  end;

Vehicle:writeComponentPositions()

Writes this vehicle's component positions to the stream.

    1625  function Vehicle:writeComponentPositions()
    1626      assert(g_isServer, "Only allowed on server");
    1627  
    1628      for k, component in ipairs(self.components) do
    1629          local px,py,pz          = getWorldPosition(component.id);
    1630          local qx,qy,qz,qw       = getWorldRotationQuaternion(component.id);
    1631  
    1632          -- write all of them to stream
    1633          streamWriteFloat(px);   streamWriteFloat(py);   streamWriteFloat(pz);
    1634          streamWriteFloat(qx);   streamWriteFloat(qy);   streamWriteFloat(qz);   streamWriteFloat(qw);
    1635  
    1636          component.lastSentPosition.x    = px;
    1637          component.lastSentPosition.y    = py;
    1638          component.lastSentPosition.z    = pz;
    1639  
    1640          component.lastSentRotation.x    = qx;
    1641          component.lastSentRotation.y    = qy;
    1642          component.lastSentRotation.z    = qz;
    1643          component.lastSentRotation.w    = qw;
    1644      end;
    1645  end;

Vehicle:writeWheelRpms()

Writes this vehicle's wheel rpms to the stream.

    1648  function Vehicle:writeWheelRpms()
    1649      assert(g_isServer, "Only allowed on server");
    1650  
    1651      for n, axle in ipairs(self.axles) do
    1652          streamWriteFloat(Wheel.getRpm(axle.leftWheel));
    1653          streamWriteFloat(Wheel.getRpm(axle.rightWheel));
    1654      end;
    1655  end;

Vehicle:readWheelRpms()

Reads this vehicle's wheel rpms from the stream.

    1658  function Vehicle:readWheelRpms()
    1659      assert(g_isClient, "Only allowed on client");
    1660  
    1661      for n, axle in ipairs(self.axles) do
    1662          axle.targetClientRpmLeft        = streamReadFloat();
    1663          axle.targetClientRpmRight       = streamReadFloat();
    1664      end;
    1665  end;

Vehicle:readUpdate(connection, networkTime)

Receives a network packet each frame when the vehicle is updated. The data sent by writeUpdate will be received by readUpdate.

    1669  function Vehicle:readUpdate(connection, networkTime)
    1670      local isClientEntered               = false;
    1671      if g_isClient then
    1672  
    1673          local minDelay                  = 0.05;
    1674          local maxDelay                  = 0.6;
    1675          local expectedDelay             = 0.1;
    1676  
    1677          local deltaTime;
    1678  
    1679          if self.lastPacketTime == nil then
    1680              deltaTime                   = NetworkGame.TICK_RATE;
    1681          elseif networkTime < self.lastPacketTime then
    1682              return;
    1683          else
    1684              deltaTime                   = networkTime - self.lastPacketTime;
    1685          end;
    1686  
    1687          self.lastPacketTime             = networkTime;
    1688  
    1689  
    1690          local timeLeft                  = self.interpolationDuration * (1 - self.interpolationWeight);
    1691          local targetDelay               = expectedDelay;
    1692          if timeLeft > 0 then
    1693              targetDelay                 = lerp(timeLeft, expectedDelay, 0.1);
    1694          end;
    1695  
    1696          self.interpolationDuration      = clamp(targetDelay + deltaTime, minDelay, maxDelay);
    1697          self.interpolationWeight        = 0;
    1698  
    1699          for k, component in ipairs(self.components) do
    1700              component.lastPosition      = component.currentPosition;
    1701              component.lastRotation      = component.currentRotation;
    1702  
    1703              component.targetPosition    = Vector3:new(streamReadFloat(), streamReadFloat(), streamReadFloat());
    1704              component.targetRotation    = Quaternion:new(streamReadFloat(), streamReadFloat(), streamReadFloat(), streamReadFloat());
    1705          end;
    1706  
    1707          self:readWheelRpms();
    1708          
    1709          self.rawSteerValue              = streamReadFloat();
    1710          -- we can interpolate again
    1711          self.canInterpolate             = true;
    1712      else
    1713  
    1714          -- read from client => server
    1715          -- only read from the client if he is still the user
    1716          isClientEntered                 = streamReadBool();
    1717  
    1718          if isClientEntered then
    1719  
    1720              if self.currentUser == nil or self.currentUser.connection ~= connection then
    1721                  -- client has sent an update for an entity that he is not entitled to use => ignore it
    1722                  if g_debugNetworking then
    1723                      --print("Warning: Ignoring input from client who is not the user of this entity");
    1724                  end;
    1725  
    1726                  -- abort this update stream
    1727                  return;
    1728              else
    1729  
    1730                  -- handle input
    1731                  self.throttleValue      = streamReadFloat();
    1732                  self.brakeValue         = streamReadFloat();
    1733                  self.rawSteerValue      = streamReadFloat();
    1734                  self.rawSteerValueFixed = streamReadBool();
    1735                  self.parkingBrake       = streamReadBool() and 1 or 0;
    1736              end;
    1737          end;    
    1738      end;
    1739  
    1740      self:callVehicleScripts("readUpdate", connection, networkTime, isClientEntered);
    1741  end;

Vehicle:onGUI()

Can be used for any IMGUI calls.

    1744  function Vehicle:onGUI()
    1745      self:callVehicleScripts("onGUI");
    1746  
    1747      if g_isDevelopmentVersion and g_isUnityEditor then
    1748          -- render debug info
    1749          IMGUI.renderLabel(20, 70, 400, 40, string.format("[EDITOR ONLY] motor torque %.2f\nbrake torque %.2f\nsteer angle %.2f", self:getCurrentMotorTorque(), self:getCurrentBrakeTorque(), self:getCurrentSteerAngle()))
    1750      end;
    1751  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.