LUADOC - Farming Simulator 17

Printable Version

Vehicle

Description
Base class for all vehicles
Functions

registerInteractionFlag

Description
Register interaction flag
Definition
registerInteractionFlag(string name)
Arguments
stringnamename of flag
Code
49function Vehicle.registerInteractionFlag(name)
50 local key = "INTERACTION_FLAG_"..string.upper(name);
51 if Vehicle[key] == nil then
52 Vehicle.NUM_INTERACTION_FLAGS = Vehicle.NUM_INTERACTION_FLAGS+1;
53 Vehicle[key] = Vehicle.NUM_INTERACTION_FLAGS;
54 end
55end

new

Description
Creating vehicle object
Definition
new(boolean isServer, boolean isClient, table customMt)
Arguments
booleanisServeris server
booleanisClientis client
tablecustomMtcustom metatable
Return Values
tableinstanceInstance of object
Code
63function Vehicle:new(isServer, isClient, customMt)
64
65 if Vehicle_mt == nil then
66 Vehicle_mt = Class(Vehicle, Object);
67 end;
68
69 local mt = customMt;
70 if mt == nil then
71 mt = Vehicle_mt;
72 end;
73
74 local self = Object:new(isServer, isClient, mt);
75 self.isAddedToMission = false;
76 self.isDeleted = false;
77 self.updateLoopIndex = -1;
78 return self;
79end;

load

Description
Load vehicle
Definition
load(table vehicleData, function asyncCallbackFunction, table asyncCallbackObject, table asyncCallbackArguments)
Arguments
tablevehicleDatadata from vehicle
functionasyncCallbackFunctionasyncron callback function
tableasyncCallbackObjectasyncron callback object
tableasyncCallbackArgumentsasyncron callback arguments
Return Values
integervehicleLoadStatevehicle load state
Code
88function Vehicle:load(vehicleData, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments)
89
90 local modName, baseDirectory = Utils.getModNameAndBaseDirectory(vehicleData.filename);
91
92 self.configFileName = vehicleData.filename;
93 self.baseDirectory = baseDirectory;
94 self.customEnvironment = modName;
95 self.typeName = vehicleData.typeName;
96 self.isVehicleSaved = Utils.getNoNil(vehicleData.isVehicleSaved, true);
97 self.configurations = Utils.getNoNil(vehicleData.configurations, {});
98 self.boughtConfigurations = Utils.getNoNil(vehicleData.boughtConfigurations, {});
99 local typeDef = VehicleTypeUtil.vehicleTypes[self.typeName];
100 self.specializations = typeDef.specializations;
101 self.specializationNames = typeDef.specializationNames;
102 self.xmlFile = loadXMLFile("TempConfig", vehicleData.filename);
103 self.isAddedToPhysics = false
104
105 local data = {};
106 data[1] = {posX=vehicleData.posX, posY=vehicleData.posY, posZ=vehicleData.posZ, yOffset=vehicleData.yOffset, isAbsolute=vehicleData.isAbsolute};
107 data[2] = {rotX=vehicleData.rotX, rotY=vehicleData.rotY, rotZ=vehicleData.rotZ};
108 data[3] = vehicleData.isVehicleSaved;
109 data[4] = vehicleData.propertyState;
110 data[5] = vehicleData.price;
111 data[6] = vehicleData.savegame;
112 data[7] = asyncCallbackFunction;
113 data[8] = asyncCallbackObject;
114 data[9] = asyncCallbackArguments;
115
116 for i=1, table.getn(self.specializations) do
117 if self.specializations[i].preLoad ~= nil then
118 local vehicleLoadState = self.specializations[i].preLoad(self, vehicleData.savegame);
119 if vehicleLoadState ~= nil and vehicleLoadState ~= BaseMission.VEHICLE_LOAD_OK then
120 print("Error: " .. self.specializationNames[i] .. "-specialization 'preLoad' failed");
121 if asyncCallbackFunction ~= nil then
122 asyncCallbackFunction(asyncCallbackObject, nil, vehicleLoadState, asyncCallbackArguments);
123 end;
124 return vehicleLoadState;
125 end;
126 end;
127 end;
128
129 self.i3dFilename = getXMLString(self.xmlFile, "vehicle.filename");
130
131 if asyncCallbackFunction ~= nil then
132 Utils.loadSharedI3DFile(self.i3dFilename, baseDirectory, true, false, true, self.loadFinished, self, data);
133 else
134 local i3dNode = Utils.loadSharedI3DFile(self.i3dFilename, baseDirectory, true, false, true);
135 return self:loadFinished(i3dNode, data)
136 end
137end

loadFinished

Description
Load finished
Definition
loadFinished(integer i3dNode, table arguments)
Arguments
integeri3dNodei3d node id
tableargumentsarguments
Return Values
integervehicleLoadStatevehicle load state
Code
144function Vehicle:loadFinished(i3dNode, arguments)
145
146 local vehicleLoadState = BaseMission.VEHICLE_LOAD_OK;
147
148 local position, rotation, isSave, propertyState, price, savegame, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments = unpack(arguments);
149
150 if i3dNode == 0 then
151 vehicleLoadState = BaseMission.VEHICLE_LOAD_ERROR;
152 if asyncCallbackFunction ~= nil then
153 asyncCallbackFunction(asyncCallbackObject, nil, vehicleLoadState, asyncCallbackArguments);
154 end;
155 return vehicleLoadState;
156 end
157
158 if savegame ~= nil then
159 local i = 0;
160 while true do
161 local key = string.format(savegame.key..".boughtConfiguration(%d)", i);
162 if not hasXMLProperty(savegame.xmlFile, key) then
163 break;
164 end;
165 local name = getXMLString(savegame.xmlFile, key.."#name");
166 local id = getXMLInt(savegame.xmlFile, key.."#id");
167 self:addBoughtConfiguration(name, id)
168 i = i + 1;
169 end;
170
171 self.tourId = nil;
172 local tourId = getXMLString(savegame.xmlFile, savegame.key.."#tourId");
173 if tourId ~= nil then
174 self.tourId = tourId;
175 if g_currentMission ~= nil then
176 g_currentMission.tourVehicles[self.tourId] = self;
177 end;
178 end;
179 end;
180
181 -- check if one of the configurations is not set - e.g. if new configurations are available but not in savegame
182 local item = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()];
183 if item ~= nil and item.configurations ~= nil then
184 for configName, _ in pairs(item.configurations) do
185 local defaultConfigId = StoreItemsUtil.getDefaultConfigId(item, configName)
186 if self.configurations[configName] == nil then
187 self:setConfiguration(configName, defaultConfigId)
188 end;
189 -- base configuration is always included
190 self:addBoughtConfiguration(configName, defaultConfigId)
191 end;
192 -- check if currently used configurations are still available
193 for configName, value in pairs(self.configurations) do
194 if item.configurations[configName] == nil then
195 print("Warning: " .. configName .. "Configurations are not present in '" .. self.configFileName .. "' anymore! Ignoring this configuration.")
196 self.configurations[configName] = nil
197 self.boughtConfigurations[configName][value] = nil
198 else
199 local defaultConfigId = StoreItemsUtil.getDefaultConfigId(item, configName)
200 if #item.configurations[configName] < value then
201 print("Warning: " .. configName .. "Configuration with index " .. value .. " is not present in '" .. self.configFileName .. "' anymore! Using default configuration instead.")
202 self.boughtConfigurations[configName][value] = nil
203 self:setConfiguration(configName, defaultConfigId)
204 else
205 self:addBoughtConfiguration(configName, value)
206 end
207 end
208 end
209 end;
210
211 self.propertyState = propertyState;
212 self.price = price;
213 if self.price == 0 or self.price == nil then
214 self.price = StoreItemsUtil.getDefaultPrice(item, self.configurations);
215 end
216 self.age = 0;
217
218 self.rootNode = getChildAt(i3dNode, 0)
219
220 self.components = {};
221 local numComponents = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.components#count"), 1);
222
223 local maxNumComponents = getNumOfChildren(i3dNode);
224 if numComponents > maxNumComponents then
225 print("Error: Invalid components count '"..numComponents.."' in '".. self.configFileName.. "'");
226 numComponents = maxNumComponents;
227 end;
228
229 self.vehicleNodes = {};
230
231 local rootPosition = {0,0,0};
232 for i=1, numComponents do
233 local namei = string.format("vehicle.components.component%d", i);
234 if not hasXMLProperty(self.xmlFile, namei) then
235 print("Warning: " .. namei .. " not found in '"..self.configFileName.."'");
236 end;
237
238 local component = {node=getChildAt(i3dNode, 0)};
239 local success = self:loadComponentFromXML(component, self.xmlFile, namei, rootPosition, i);
240 if success then
241 table.insert(self.components, component);
242 end;
243 end;
244 self.interpolationAlpha = 0;
245 self.interpolationDuration = 50+30;
246 self.positionIsDirty = false;
247
248 delete(i3dNode);
249
250 -- wheel setup
251 local wheelId = Utils.getNoNil(self.configurations["wheel"], 1);
252 local configKey = string.format("vehicle.wheelConfigurations.wheelConfiguration(%d)", wheelId-1);
253 local key = configKey..".wheels";
254 if self.configurations["wheel"] ~= nil and not hasXMLProperty(self.xmlFile, key) then
255 print("Warning: Invalid wheelId '"..tostring(self.configurations["wheel"]).."'. Using default wheel settings instead!");
256 end;
257
258 ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.wheelConfigurations.wheelConfiguration", wheelId, self.components, self);
259
260 local rimColorStr = getXMLString(self.xmlFile, "vehicle.rimColor")
261 if rimColorStr ~= nil then
262 self.rimColor = Vehicle.getColorFromString(rimColorStr)
263 elseif getXMLBool(self.xmlFile, "vehicle.rimColor#useBaseColor") then
264 self.rimColor = Vehicle.getColorByConfigId(self, "baseColor", self.configurations["baseColor"])
265 end
266
267 local axisColorStr = getXMLString(self.xmlFile, "vehicle.axisColor")
268 if axisColorStr ~= nil then
269 self.axisColor = Vehicle.getColorFromString(axisColorStr)
270 elseif getXMLBool(self.xmlFile, "vehicle.axisColor#useBaseColor") then
271 self.axisColor = Vehicle.getColorByConfigId(self, "baseColor", self.configurations["baseColor"])
272 elseif getXMLBool(self.xmlFile, "vehicle.axisColor#useRimColor") then
273 self.axisColor = Utils.getNoNil(Vehicle.getColorByConfigId(self, "rimColor", self.configurations["rimColor"]), self.rimColor)
274 end
275
276 self.driveGroundParticleSystems = {};
277
278 -- load wheels
279 local fallbackOldKey = "vehicle.wheels";
280
281 self.maxRotTime = 0;
282 self.minRotTime = 0;
283 self.autoRotateBackSpeed = Vehicle.getConfigurationValue(self.xmlFile, key, "", "#autoRotateBackSpeed", getXMLFloat, 1.0, nil, fallbackOldKey);
284 self.differentialIndex = Vehicle.getConfigurationValue(self.xmlFile, key, "", "#differentialIndex", getXMLInt, nil, nil, fallbackOldKey);
285 self.ackermannSteeringIndex = Vehicle.getConfigurationValue(self.xmlFile, key, "", "#ackermannSteeringIndex", getXMLInt, nil, nil, fallbackOldKey);
286 self.wheelSmoothAccumulation = 0;
287 self.wheels = {};
288 local i = 0;
289 while true do
290 local wheelnamei = string.format(".wheel(%d)", i);
291 local reprStr = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#repr", getXMLString, nil, nil, fallbackOldKey);
292 if reprStr == nil then
293 break;
294 end;
295 local wheel = {};
296 wheel.repr = Utils.indexToObject(self.components, reprStr);
297 if wheel.repr == nil then
298 print("Error: invalid wheel repr " .. reprStr);
299 else
300 wheel.xmlIndex = i;
301 local driveNodeStr = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#driveNode", getXMLString, nil, nil, fallbackOldKey);
302 wheel.driveNode = Utils.indexToObject(self.components, driveNodeStr);
303 if wheel.driveNode == nil then
304 wheel.driveNode = wheel.repr;
305 end;
306 wheel.linkNode = Utils.indexToObject(self.components, Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#linkNode", getXMLString, nil, nil, fallbackOldKey));
307 if wheel.linkNode == nil then
308 wheel.linkNode = wheel.driveNode;
309 end;
310
311 wheel.isSynchronized = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#isSynchronized", getXMLBool, true, nil, fallbackOldKey);
312
313 wheel.createTipOcclusionArea = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#createTipOcclusionArea", getXMLBool, true, nil, fallbackOldKey);
314 wheel.tipOcclusionAreaGroupId = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#tipOcclusionAreaGroupId", getXMLInt, i+1, nil, fallbackOldKey);
315
316 -- look for top parent component node
317 wheel.node = self:getParentComponent(wheel.repr);
318 if wheel.node == 0 then
319 print("Invalid wheel-repr. Needs to be a child of a collision");
320 end;
321 wheel.positionX, wheel.positionY, wheel.positionZ = localToLocal(wheel.driveNode, wheel.node, 0,0,0);
322 wheel.useReprDirection = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#useReprDirection", getXMLBool, false, nil, fallbackOldKey)
323 wheel.useDriveNodeDirection = Vehicle.getConfigurationValue(self.xmlFile, key, wheelnamei, "#useDriveNodeDirection", getXMLBool, false, nil, fallbackOldKey)
324 if wheel.useReprDirection then
325 wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.repr, wheel.node, 0,-1,0);
326 wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.repr, wheel.node, 1,0,0);
327 elseif wheel.useDriveNodeDirection then
328 wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.driveNode, wheel.node, 0,-1,0);
329 wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.driveNode, wheel.node, 1,0,0);
330 else
331 wheel.directionX, wheel.directionY, wheel.directionZ = 0,-1,0;
332 wheel.axleX, wheel.axleY, wheel.axleZ = 1,0,0;
333 end
334 wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = 0,0,0;
335 if wheel.repr ~= wheel.driveNode then
336 wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = localToLocal(wheel.repr, wheel.driveNode, 0, 0, 0)
337 end
338
339 wheel.startPositionX, wheel.startPositionY, wheel.startPositionZ = getTranslation(wheel.repr);
340 wheel.dirtAmount = 0;
341 wheel.xDriveOffset = 0
342 wheel.lastColor = {0,0,0,0};
343 wheel.lastTerrainAttribute = 0;
344 wheel.contact = Vehicle.WHEEL_NO_CONTACT;
345 wheel.steeringAngle = 0;
346 wheel.hasGroundContact = false;
347 wheel.hasHandbrake = true;
348 local vehicleNode = self.vehicleNodes[wheel.node];
349 if vehicleNode ~= nil and vehicleNode.component ~= nil and vehicleNode.component.motorized == nil then
350 vehicleNode.component.motorized = true;
351 end
352
353 self:loadDynamicWheelDataFromXML(self.xmlFile, key, wheelnamei, wheel);
354
355 table.insert(self.wheels, wheel);
356 end;
357 i = i+1;
358 end;
359 local attacherJointModifiers = {};
360 local i = 0;
361 while true do
362 local key = string.format(configKey..".attacherJointModifier(%d)", i);
363 if not hasXMLProperty(self.xmlFile, key) then
364 break;
365 end;
366
367 local index = getXMLInt(self.xmlFile, key.."#index");
368 if index ~= nil then
369 local lowerDistanceToGroundOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#lowerDistanceToGroundOffset"), 0);
370 local upperDistanceToGroundOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#upperDistanceToGroundOffset"), 0);
371 attacherJointModifiers[index] = {lowerDistanceToGroundOffset=lowerDistanceToGroundOffset, upperDistanceToGroundOffset=upperDistanceToGroundOffset};
372 end;
373 i = i + 1;
374 end;
375
376 self:loadWheelsSteeringDataFromXML(self.xmlFile, self.ackermannSteeringIndex);
377
378 self.tipOcclusionAreas = {};
379 local i=0;
380 while true do
381 local key = string.format("vehicle.tipOcclusionAreas.tipOcclusionArea(%d)", i);
382 if not hasXMLProperty(self.xmlFile, key) then
383 break;
384 end
385 local entry = {};
386 entry.start = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#start"));
387 entry.width = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#width"));
388 entry.height = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#height"));
389 if entry.start ~= nil and entry.width ~= nil and entry.height ~= nil then
390 table.insert(self.tipOcclusionAreas, entry);
391 end
392 i = i + 1;
393 end
394
395 getWheelsWithTipOcclisionAreaGroupId = function(wheels, id)
396 local returnWheels = {};
397 for _,wheel in pairs(wheels) do
398 if wheel.tipOcclusionAreaGroupId == id then
399 table.insert(returnWheels, wheel);
400 end
401 end
402 return returnWheels;
403 end
404
405 local createdTipOcclusionAreaGroupIds = {};
406 for _,wheel in pairs(self.wheels) do
407 if wheel.createTipOcclusionArea then
408 local doCreate = true;
409 for _,groupId in pairs(createdTipOcclusionAreaGroupIds) do
410 if groupId == wheel.tipOcclusionAreaGroupId then
411 doCreate = false;
412 break;
413 end
414 end
415 if doCreate then
416 local start = createTransformGroup(string.format("tipOcclusionAreaGroupId%d",wheel.tipOcclusionAreaGroupId));
417 local width = createTransformGroup(string.format("tipOcclusionAreaGroupId%d",wheel.tipOcclusionAreaGroupId));
418 local height = createTransformGroup(string.format("tipOcclusionAreaGroupId%d",wheel.tipOcclusionAreaGroupId));
419 link(wheel.node, start);
420 link(wheel.node, width);
421 link(wheel.node, height);
422
423 local xMax = -math.huge;
424 local xMin = math.huge;
425 local zMax = -math.huge;
426 local zMin = math.huge;
427
428 local usedWheels = getWheelsWithTipOcclisionAreaGroupId(self.wheels, wheel.tipOcclusionAreaGroupId);
429
430 for _,usedWheel in pairs(usedWheels) do
431 local x,_,z = localToLocal(usedWheel.repr, usedWheel.node, 0.5*usedWheel.width,0,-usedWheel.radius);
432 xMax = math.max(x, xMax);
433 zMin = math.min(z, zMin);
434 local x,_,z = localToLocal(usedWheel.repr, usedWheel.node, -0.5*usedWheel.width,0,usedWheel.radius);
435 xMin = math.min(x, xMin);
436 zMax = math.max(z, zMax);
437 end
438
439 setTranslation(start, xMax,0,zMin);
440 setTranslation(width, xMin,0,zMin);
441 setTranslation(height, xMax,0,zMax);
442
443 table.insert(createdTipOcclusionAreaGroupIds, wheel.tipOcclusionAreaGroupId);
444 table.insert(self.tipOcclusionAreas, {start=start, width=width, height=height});
445 end
446 end
447 end
448
449 self.aiSteeringSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.ai.steeringSpeed"), 1)*0.001;
450 self.aiMinTurningRadius = getXMLFloat(self.xmlFile, "vehicle.ai.minTurningRadius#value");
451
452 self.aiLeftMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.areaMarkers#leftIndex"));
453 self.aiRightMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.areaMarkers#rightIndex"));
454 self.aiBackMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.areaMarkers#backIndex"));
455 self.aiTrafficCollisionTrigger = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.trafficCollisionTrigger#index"));
456 if self.aiTrafficCollisionTrigger ~= nil then
457 local rigidBodyType = getRigidBodyType(self.aiTrafficCollisionTrigger);
458 if rigidBodyType ~= "Kinematic" then
459 print("Warning: 'aiTrafficCollisionTrigger' is not a kinematic body type");
460 end
461 end
462
463 self.aiLookAheadSize = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.aiLookAheadSize#value"), 2);
464
465 self.aiSizeLeftMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.sizeMarkers#leftIndex"));
466 self.aiSizeRightMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.sizeMarkers#rightIndex"));
467 self.aiSizeBackMarker = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.sizeMarkers#backIndex"));
468
469 self.terrainDetailRequiredValueRanges = {};
470 self.terrainDetailProhibitValueRanges = {};
471 self.aiRequiredFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
472 self.aiRequiredMinGrowthState = nil;
473 self.aiRequiredMaxGrowthState = nil;
474 self.aiRequiredFruitType2 = FruitUtil.FRUITTYPE_UNKNOWN;
475 self.aiProhibitedFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
476 self.aiProhibitedMinGrowthState = 0;
477 self.aiProhibitedMaxGrowthState = 0;
478 self.aiUseDensityHeightMap = false;
479 self.aiUseWindrowFruitType = false;
480
481 self.vehicleMovingDirection = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.vehicleMovingDirection#value"), 1);
482 self.movingDirection = 0;
483
484 self.steeringAxleNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.steeringAxleNode#index"));
485 if self.steeringAxleNode == nil then
486 self.steeringAxleNode = self.components[1].node;
487 end;
488
489 self.sizeWidth = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.size#width"), Vehicle.defaultWidth);
490 self.sizeLength = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.size#length"), Vehicle.defaultLength);
491 self.widthOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.size#widthOffset"), 0);
492 self.lengthOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.size#lengthOffset"), 0);
493
494 self.typeDesc = Utils.getXMLI18N(self.xmlFile, "vehicle.typeDesc", "", "TypeDescription", self.customEnvironment, true);
495
496 self.serverMass = 0;
497
498 local objectChanges = {};
499 ObjectChangeUtil.loadObjectChangeFromXML(self.xmlFile, "vehicle.objectChanges", objectChanges, self.components, self);
500 ObjectChangeUtil.setObjectChanges(objectChanges, true);
501
502 if self.isClient then
503 self.sampleHydraulic = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.hydraulicSound", nil, self.baseDirectory);
504 end;
505
506 self.componentJoints = {};
507
508 local componentJointI = 0;
509 while true do
510 local key = string.format("vehicle.components.joint(%d)", componentJointI);
511 local index1 = getXMLInt(self.xmlFile, key.."#component1");
512 local index2 = getXMLInt(self.xmlFile, key.."#component2");
513 local jointIndexStr = getXMLString(self.xmlFile, key.."#index");
514 if index1 == nil or index2 == nil or jointIndexStr == nil then
515 break;
516 end;
517 local jointNode = Utils.indexToObject(self.components, jointIndexStr);
518 if jointNode ~= nil and jointNode ~= 0 then
519 local jointDesc = {};
520 if self:loadComponentJointFromXML(jointDesc, self.xmlFile, key, componentJointI, jointNode, index1, index2) then
521 table.insert(self.componentJoints, jointDesc);
522 end;
523 end;
524 componentJointI = componentJointI +1;
525 end;
526
527 local collisionPairI = 0;
528 self.collisionPairs = {}
529 while true do
530 local key = string.format("vehicle.components.collisionPair(%d)", collisionPairI);
531 if not hasXMLProperty(self.xmlFile, key) then
532 break;
533 end;
534 local enabled = getXMLBool(self.xmlFile, key.."#enabled");
535 local index1 = getXMLInt(self.xmlFile, key.."#component1");
536 local index2 = getXMLInt(self.xmlFile, key.."#component2");
537 if index1 ~= nil and index2 ~= nil and enabled ~= nil then
538 local component1 = self.components[index1+1];
539 local component2 = self.components[index2+1];
540 if component1 ~= nil and component2 ~= nil then
541 if not enabled then
542 table.insert(self.collisionPairs, {component1=component1, component2=component2, enabled=enabled})
543 end;
544 end;
545 end;
546 collisionPairI = collisionPairI +1;
547 end;
548
549 self:loadSchemaOverlay(self.xmlFile);
550
551 self.dynamicallyLoadedWheels = {};
552 local i=0;
553 while true do
554 local baseName = string.format("vehicle.dynamicallyLoadedWheels.dynamicallyLoadedWheel(%d)", i);
555 if not hasXMLProperty(self.xmlFile, baseName) then
556 break;
557 end;
558 local dynamicallyLoadedWheel = {};
559 if self:loadDynamicallyWheelsFromXML(dynamicallyLoadedWheel, self.xmlFile, baseName) then
560 table.insert(self.dynamicallyLoadedWheels, dynamicallyLoadedWheel);
561 end;
562 i = i + 1;
563 end;
564
565 self.dynamicallyLoadedParts = {};
566 local i=0;
567 while true do
568 local baseName = string.format("vehicle.dynamicallyLoadedParts.dynamicallyLoadedPart(%d)", i);
569 if not hasXMLProperty(self.xmlFile, baseName) then
570 break;
571 end;
572 local dynamicallyLoadedPart = {};
573 if self:loadDynamicallyPartsFromXML(dynamicallyLoadedPart, self.xmlFile, baseName) then
574 table.insert(self.dynamicallyLoadedParts, dynamicallyLoadedPart);
575 end;
576 i = i + 1;
577 end;
578
579 if hasXMLProperty(self.xmlFile, "vehicle.driveGroundParticleSystems") then
580 print("Warning: vehicle.driveGroundParticleSystems are not supported anymore., use vehicle.wheels.wheel#hasParticles resp. vehicle.wheelConfigurations.wheelConfiguration.wheels.wheel#hasParticles instead!");
581 end
582
583 self.maximalAirConsumptionPerFullStop = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.maximalAirConsumptionPerFullStop#value"), 0);
584
585 self.hasWheelGroundContact = false;
586 self.requiredDriveMode = 1;
587 self.steeringAxleAngle = 0;
588 self.steeringAxleTargetAngle = 0;
589 self.rotatedTime = 0;
590 self.firstTimeRun = false;
591 self.lastPosition = nil; -- = {0,0,0};
592 self.lastSpeed = 0;
593 self.lastSpeedReal = 0;
594 self.lastMovedDistance = 0;
595 self.lastSpeedAcceleration = 0;
596 self.speedDisplayDt = 0;
597 self.speedDisplayScale = 1;
598 self.isBroken = false;
599 self.lastMoveTime = -10000;
600 self.forcePtoUpdate = true;
601 self.lastSoundSpeed = 0;
602 self.tipErrorMessageTime = 0;
603 self.tipErrorMessage = "";
604 self.showDetachingNotAllowedTime = 0;
605 self.time = 0;
606 self.forceIsActive = false;
607 self.operatingTime = 0;
608 self.vehicleDirtyFlag = self:getNextDirtyFlag();
609 self.updateWheelsTime = 0
610
611 self.componentsVisibility = true;
612
613 self.ikChains = {};
614 local i = 0;
615 while true do
616 local key = string.format("vehicle.ikChains.ikChain(%d)", i);
617 if not hasXMLProperty(self.xmlFile, key) then
618 break;
619 end;
620 IKUtil.loadIKChain(self.xmlFile, key, self.components, self.components, self.ikChains, self.getParentComponent, self);
621 i = i + 1;
622 end;
623 IKUtil.updateAlignNodes(self.ikChains, self.getParentComponent, self, nil)
624
625
626 self.conflictCheckedInputs = {};
627 for i=1, table.getn(self.specializations) do
628 local vehicleLoadState = self.specializations[i].load(self, savegame);
629 if vehicleLoadState ~= nil and vehicleLoadState ~= BaseMission.VEHICLE_LOAD_OK then
630 print("Error: " .. self.specializationNames[i] .. "-specialization 'load' failed");
631 if asyncCallbackFunction ~= nil then
632 asyncCallbackFunction(asyncCallbackObject, nil, vehicleLoadState, asyncCallbackArguments);
633 end;
634 return vehicleLoadState;
635 end;
636 end;
637
638 -- apply design
639 if self.configurations["design"] ~= nil then
640 self:applyDesign(self.xmlFile, self.configurations["design"]);
641 end;
642
643 if self.configurations["vehicleType"] ~= nil then
644 ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.vehicleTypeConfigurations.vehicleTypeConfiguration", self.configurations["vehicleType"], self.components, self);
645 end
646
647 if self.applyInitialAnimation ~= nil then
648 self:applyInitialAnimation();
649 end
650
651 for i=1, table.getn(self.specializations) do
652 if self.specializations[i].postLoad ~= nil then
653 local vehicleLoadState = self.specializations[i].postLoad(self, savegame);
654 if vehicleLoadState ~= nil and vehicleLoadState ~= BaseMission.VEHICLE_LOAD_OK then
655 print("Error: " .. self.specializationNames[i] .. "-specialization 'postLoad' failed");
656 if asyncCallbackFunction ~= nil then
657 asyncCallbackFunction(asyncCallbackObject, nil, vehicleLoadState, asyncCallbackArguments);
658 end;
659 return vehicleLoadState;
660 end;
661 end;
662 end;
663
664 local speedLimit = math.huge;
665 for i=1, table.getn(self.specializations) do
666 if self.specializations[i].getDefaultSpeedLimit ~= nil then
667 local limit = self.specializations[i].getDefaultSpeedLimit(self);
668 speedLimit = math.min(limit, speedLimit);
669 end;
670 end;
671
672 self.checkSpeedLimit = speedLimit == math.huge;
673 self.speedLimit = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.speedLimit#value"), speedLimit);
674
675 -- do coloring
676 if self.configurations["baseColor"] ~= nil then
677 self:setColor(self.xmlFile, "baseColor", self.configurations["baseColor"]);
678 end;
679
680 -- do coloring
681 if self.configurations["designColor"] ~= nil then
682 self:setColor(self.xmlFile, "designColor", self.configurations["designColor"]);
683 end;
684
685 -- close vehicle xml file
686 delete(self.xmlFile);
687 self.xmlFile = nil;
688
689 if savegame ~= nil then
690 self.age = Utils.getNoNil(getXMLFloat(savegame.xmlFile, savegame.key.."#age"), 0);
691 self.price = Utils.getNoNil(getXMLInt(savegame.xmlFile, savegame.key.."#price"), self.price);
692 self.propertyState = Utils.getNoNil(getXMLInt(savegame.xmlFile, savegame.key.."#propertyState"), self.propertyState);
693 local operatingTime = Utils.getNoNil(getXMLFloat(savegame.xmlFile, savegame.key .. "#operatingTime"), self.operatingTime) * 1000;
694 self:setOperatingTime(operatingTime, true);
695 local findPlace = savegame.resetVehicles;
696 if not findPlace then
697 local isAbsolute = Utils.getNoNil(getXMLBool(savegame.xmlFile, savegame.key.."#isAbsolute"), false);
698 if isAbsolute then
699 local componentPosition = {};
700 local i = 1;
701 while true do
702 local componentKey = string.format(savegame.key..".component%d", i);
703 if not hasXMLProperty(savegame.xmlFile, componentKey) then
704 break;
705 end;
706 local x,y,z = Utils.getVectorFromString(getXMLString(savegame.xmlFile, componentKey.."#position"));
707 local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(savegame.xmlFile, componentKey.."#rotation"));
708 if x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil then
709 findPlace = true;
710 break;
711 end;
712 table.insert(componentPosition, {x=x, y=y, z=z, xRot=xRot, yRot=yRot, zRot=zRot});
713 i = i + 1;
714 end;
715 if #componentPosition == #self.components then
716 for i=1, #self.components do
717 local p = componentPosition[i];
718 self:setWorldPosition(p.x,p.y,p.z, p.xRot,p.yRot,p.zRot, i, true);
719 end
720 else
721 findPlace = true;
722 print("Warning: Invalid savegame component count. Ignoring savegame position");
723 end;
724 else
725 local yOffset = getXMLFloat(savegame.xmlFile, savegame.key.."#yOffset");
726 local xPosition = getXMLFloat(savegame.xmlFile, savegame.key.."#xPosition");
727 local zPosition = getXMLFloat(savegame.xmlFile, savegame.key.."#zPosition");
728 local yRotation = getXMLFloat(savegame.xmlFile, savegame.key.."#yRotation");
729 if yOffset == nil or xPosition == nil or zPosition == nil or yRotation == nil then
730 findPlace = true;
731 else
732 self:setRelativePosition(xPosition, yOffset, zPosition, math.rad(yRotation));
733 end;
734 end;
735 end;
736 if findPlace then
737 if savegame.resetVehicles then
738 local x, _, z, place, width, offset = PlacementUtil.getPlace(g_currentMission.loadSpawnPlaces, self.sizeWidth, self.sizeLength, self.widthOffset, self.lengthOffset, g_currentMission.usedLoadPlaces);
739 if x ~= nil then
740 local yRot = Utils.getYRotationFromDirection(place.dirPerpX, place.dirPerpZ);
741 PlacementUtil.markPlaceUsed(g_currentMission.usedLoadPlaces, place, width);
742 self:setRelativePosition(x, offset, z, yRot);
743 else
744 vehicleLoadState = BaseMission.VEHICLE_LOAD_ERROR;
745 end;
746 else
747 vehicleLoadState = BaseMission.VEHICLE_LOAD_DELAYED;
748 end;
749 end;
750 else
751 local posY = position.posY;
752 if posY == nil then
753 -- vehicle position based on yOffset
754 local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, position.posX, 300, position.posZ);
755 posY = terrainHeight + Utils.getNoNil(position.yOffset, 0);
756 end;
757
758 local tempRootNode = createTransformGroup("tempRootNode");
759 setTranslation(tempRootNode, position.posX, posY, position.posZ);
760 setRotation(tempRootNode, rotation.rotX, rotation.rotY, rotation.rotZ);
761
762 -- now move the objects to the scene root node
763 for i, component in pairs(self.components) do
764 local x,y,z = localToWorld(tempRootNode, getWorldTranslation(component.node));
765 local rx,ry,rz = localRotationToWorld(tempRootNode, getWorldRotation(component.node));
766 self:setWorldPosition(x,y,z, rx,ry,rz, i, true);
767 end
768 delete(tempRootNode);
769 end;
770
771 if g_currentMission ~= nil then
772 g_currentMission.environment:addDayChangeListener(self)
773 end
774
775 self:addToPhysics();
776
777 if asyncCallbackFunction ~= nil then
778 asyncCallbackFunction(asyncCallbackObject, self, vehicleLoadState, asyncCallbackArguments);
779 else
780 return vehicleLoadState;
781 end;
782end;

updateWheelBase

Description
Updates wheel base
Definition
updateWheelBase(table wheel)
Arguments
tablewheelwheel to update
Code
787function Vehicle:updateWheelBase(wheel)
788
789 if self.isServer and self.isAddedToPhysics then
790 -- TODO take direction into account
791 local positionY = wheel.positionY+wheel.deltaY;
792
793 local collisionMask = 255 - 4; -- all up to bit 8, except bit 2 which is set by the players kinematic object
794 wheel.wheelShape = createWheelShape(wheel.node, wheel.positionX, positionY, wheel.positionZ, wheel.radius, wheel.suspTravel, wheel.spring, wheel.damperCompressionLowSpeed, wheel.damperCompressionHighSpeed, wheel.damperCompressionLowSpeedThreshold, wheel.damperRelaxationLowSpeed, wheel.damperRelaxationHighSpeed, wheel.damperRelaxationLowSpeedThreshold, wheel.mass, collisionMask, wheel.wheelShape);
795
796 local forcePointY = positionY - wheel.radius * wheel.forcePointRatio;
797 local steeringX, steeringY, steeringZ = localToLocal(getParent(wheel.repr), wheel.node, wheel.startPositionX, wheel.startPositionY+wheel.deltaY, wheel.startPositionZ)
798 setWheelShapeForcePoint(wheel.node, wheel.wheelShape, wheel.positionX, forcePointY, wheel.positionZ);
799 setWheelShapeSteeringCenter(wheel.node, wheel.wheelShape, steeringX, steeringY, steeringZ);
800 setWheelShapeDirection(wheel.node, wheel.wheelShape, wheel.directionX, wheel.directionY, wheel.directionZ, wheel.axleX, wheel.axleY, wheel.axleZ);
801
802 if wheel.raycastOffsets ~= nil then
803 setWheelShapeRaycastOffsets(wheel.node, wheel.wheelShape, #wheel.raycastOffsets, unpack(wheel.raycastOffsets));
804 end
805
806 if wheel.driveGroundParticleSystem ~= nil then
807 local ps = wheel.driveGroundParticleSystem
808 setTranslation(ps.emitterShape, wheel.positionX + ps.offsets[1], wheel.positionY - wheel.radius + ps.offsets[2], wheel.positionZ + ps.offsets[3]);
809 end
810 end;
811end

updateWheelTireFriction

Description
Updates wheel tire friction
Definition
updateWheelTireFriction(table wheel)
Arguments
tablewheelwheel to update tire friction
Code
816function Vehicle:updateWheelTireFriction(wheel)
817 if self.isServer and self.isAddedToPhysics then
818 setWheelShapeTireFriction(wheel.node, wheel.wheelShape, wheel.maxLongStiffness, wheel.maxLatStiffness, wheel.maxLatStiffnessLoad, wheel.frictionScale*wheel.tireGroundFrictionCoeff);
819 end;
820end

loadDynamicWheelDataFromXML

Description
Loading dynamic wheel data from xml
Definition
loadDynamicWheelDataFromXML(integer xmlFile, string key, string wheelnamei, table wheel)
Arguments
integerxmlFileid of xml object
stringkeykey to load
stringwheelnameiwheel name with index
tablewheelwheel table to write in
Code
828function Vehicle:loadDynamicWheelDataFromXML(xmlFile, key, wheelnamei, wheel)
829 local fallbackOldKey = "vehicle.wheels";
830
831 wheel.showSteeringAngle = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#showSteeringAngle", getXMLBool, true, nil, fallbackOldKey);
832 wheel.suspTravel = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#suspTravel", getXMLFloat, 0.01, nil, fallbackOldKey);
833 local initialCompression = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#initialCompression", getXMLFloat, nil, nil, fallbackOldKey);
834 if initialCompression ~= nil then
835 wheel.deltaY = (1-initialCompression*0.01)*wheel.suspTravel;
836 else
837 wheel.deltaY = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#deltaY", getXMLFloat, 0.0, nil, fallbackOldKey);
838 end
839 wheel.spring = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#spring", getXMLFloat, 0, nil, fallbackOldKey)*Vehicle.springScale;
840
841 wheel.brakeFactor = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#brakeFactor", getXMLFloat, 1, nil, fallbackOldKey)
842
843 wheel.damperCompressionLowSpeed = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperCompressionLowSpeed", getXMLFloat, nil, nil, fallbackOldKey);
844 wheel.damperRelaxationLowSpeed = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperRelaxationLowSpeed", getXMLFloat, nil, nil, fallbackOldKey);
845 if wheel.damperRelaxationLowSpeed == nil then
846 wheel.damperRelaxationLowSpeed = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damper", getXMLFloat, Utils.getNoNil(wheel.damperCompressionLowSpeed, 0), nil, fallbackOldKey);
847 end
848 -- by default, the high speed relaxation damper is set to 90% of the low speed relaxation damper
849 wheel.damperRelaxationHighSpeed = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperRelaxationHighSpeed", getXMLFloat, wheel.damperRelaxationLowSpeed * 0.7, nil, fallbackOldKey);
850
851 -- by default, we set the low speed compression damper to 90% of the low speed relaxation damper
852 if wheel.damperCompressionLowSpeed == nil then
853 wheel.damperCompressionLowSpeed = wheel.damperRelaxationLowSpeed * 0.9;
854 end
855 -- by default, the high speed compression damper is set to 20% of the low speed compression damper
856 wheel.damperCompressionHighSpeed = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperCompressionHighSpeed", getXMLFloat, wheel.damperCompressionLowSpeed * 0.2, nil, fallbackOldKey);
857 wheel.damperCompressionLowSpeedThreshold = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperCompressionLowSpeedThreshold", getXMLFloat, 0.1016, nil, fallbackOldKey); -- default 4 inch / s
858 wheel.damperRelaxationLowSpeedThreshold = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#damperRelaxationLowSpeedThreshold", getXMLFloat, 0.1524, nil, fallbackOldKey); -- default 6 inch / s
859
860 wheel.forcePointRatio = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#forcePointRatio", getXMLFloat, 0, nil, fallbackOldKey);
861 wheel.driveMode = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#driveMode", getXMLInt, 0, nil, fallbackOldKey);
862 wheel.xOffset = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#xOffset", getXMLFloat, 0, nil, fallbackOldKey);
863 wheel.steeringAxleScale = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringAxleScale", getXMLFloat, 0, nil, fallbackOldKey);
864 wheel.steeringAxleRotMax = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringAxleRotMax", getXMLFloat, 0, nil, fallbackOldKey));
865 wheel.steeringAxleRotMin = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringAxleRotMin", getXMLFloat, -0, nil, fallbackOldKey));
866 local raycastOffsets = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#raycastOffsets", getXMLString, nil, nil, fallbackOldKey)
867 if raycastOffsets ~= nil then
868 wheel.raycastOffsets = {Utils.getVectorFromString(raycastOffsets)}
869 end
870 wheel.ignoreDefaultRaycast = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#ignoreDefaultRaycast", getXMLBool, false, nil, fallbackOldKey);
871 wheel.rotationDamping = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#rotationDamping", getXMLFloat, 0.0, nil, fallbackOldKey);
872
873 local colorStr = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#color", getXMLString, nil, nil, fallbackOldKey);
874 if colorStr ~= nil then
875 wheel.color = Vehicle.getColorFromString(colorStr);
876 end
877
878 local axisColorStr = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#axisColor", getXMLString, nil, nil, fallbackOldKey);
879 if axisColorStr ~= nil then
880 wheel.axisColor = Vehicle.getColorFromString(axisColorStr);
881 end
882
883 local additionalColorStr = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#additionalColor", getXMLString, nil, nil, fallbackOldKey);
884 if additionalColorStr ~= nil then
885 wheel.additionalColor = Vehicle.getColorFromString(additionalColorStr);
886 end
887
888 local wheelXmlFilename = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#filename", getXMLString, nil, nil, fallbackOldKey);
889 if wheelXmlFilename ~= nil and wheelXmlFilename ~= "" then
890 local wheelConfigIndex = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#configIndex", getXMLString, "basic", nil, fallbackOldKey);
891 local isLeft = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#isLeft", getXMLBool, true, nil, fallbackOldKey);
892 local xRotOffset = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#xRotOffset", getXMLFloat, 0, nil, fallbackOldKey);
893 self:loadWheelDataFromXML(wheel, wheelXmlFilename, wheelConfigIndex, isLeft, true, xRotOffset);
894 end;
895
896 wheel.mass = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#mass", getXMLFloat, wheel.mass, nil, fallbackOldKey);
897 wheel.radius = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#radius", getXMLFloat, Utils.getNoNil(wheel.radius, 0.5), nil, fallbackOldKey);
898 wheel.width = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#width", getXMLFloat, Utils.getNoNil(wheel.width, 0.6), nil, fallbackOldKey);
899 wheel.restLoad = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#restLoad", getXMLFloat, Utils.getNoNil(wheel.restLoad, 1.0), nil, fallbackOldKey); -- [t]
900 wheel.maxLongStiffness = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#maxLongStiffness", getXMLFloat, Utils.getNoNil(wheel.maxLongStiffness, 30.0), nil, fallbackOldKey); -- [t / rad]
901 wheel.maxLatStiffness = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#maxLatStiffness", getXMLFloat, Utils.getNoNil(wheel.maxLatStiffness, 40.0), nil, fallbackOldKey); -- xml is ratio to restLoad [1/rad], final value is [t / rad]
902 wheel.maxLatStiffnessLoad = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#maxLatStiffnessLoad", getXMLFloat, Utils.getNoNil(wheel.maxLatStiffnessLoad, 2), nil, fallbackOldKey); -- xml is ratio to restLoad, final value is [t]
903 wheel.frictionScale = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#frictionScale", getXMLFloat, Utils.getNoNil(wheel.frictionScale, 1.5), nil, fallbackOldKey);
904 wheel.tireGroundFrictionCoeff = 1.0; -- This will be changed dynamically based on the tire-ground pair
905
906 local tireTypeName = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#tireType", getXMLString, "mud", nil, fallbackOldKey);
907 wheel.tireType = WheelsUtil.getTireType(tireTypeName);
908 if wheel.tireType == nil then
909 print("Warning: Failed to find tire type '"..tireTypeName.."'. Defaulting to 'mud'.");
910 wheel.tireType = WheelsUtil.getTireType("mud");
911 end
912 if Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#tyreTrackAtlasIndex", getXMLInt, nil, nil, fallbackOldKey) ~= nil then
913 print("Warning: 'tyreTrackAtlasIndex' is not supported anymore. Please use 'tireTrackAtlasIndex' instead!");
914 end;
915 wheel.tireTrackAtlasIndex = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#tireTrackAtlasIndex", getXMLInt, Utils.getNoNil(wheel.tireTrackAtlasIndex, 0), nil, fallbackOldKey);
916 if Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#hasTyreTracks", getXMLBool, nil, nil, fallbackOldKey) ~= nil then
917 print("Warning: 'hasTyreTracks' is not supported anymore. Please use 'hasTireTracks' instead!");
918 end;
919 wheel.smoothGroundRadius = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#smoothGroundRadius", getXMLFloat, Utils.getNoNil(wheel.smoothGroundRadius, math.max(0.6, wheel.width*0.75)), nil, fallbackOldKey);
920 wheel.versatileYRot = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#versatileYRot", getXMLBool, false, nil, fallbackOldKey);
921 wheel.forceVersatility = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#forceVersatility", getXMLBool, false, nil, fallbackOldKey);
922 wheel.hasTireTracks = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#hasTireTracks", getXMLBool, false, nil, fallbackOldKey);
923 wheel.hasParticles = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#hasParticles", getXMLBool, false, nil, fallbackOldKey);
924 wheel.steeringNode = Utils.indexToObject(self.components, Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringNode", getXMLString, nil, nil, fallbackOldKey));
925 wheel.steeringRotNode = Utils.indexToObject(self.components, Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringRotNode", getXMLString, nil, nil, fallbackOldKey));
926 wheel.steeringNodeMinTransX = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringNodeMinTransX", getXMLFloat, nil, nil, fallbackOldKey);
927 wheel.steeringNodeMaxTransX = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringNodeMaxTransX", getXMLFloat, nil, nil, fallbackOldKey);
928 wheel.steeringNodeMinRotY = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringNodeMinRotY", getXMLFloat, nil, nil, fallbackOldKey));
929 wheel.steeringNodeMaxRotY = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#steeringNodeMaxRotY", getXMLFloat, nil, nil, fallbackOldKey));
930
931 wheel.fenderNode = Utils.indexToObject(self.components, Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#fenderNode", getXMLString, nil, nil, fallbackOldKey));
932 wheel.fenderRotMax = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#fenderRotMax", getXMLFloat, nil, nil, fallbackOldKey);
933 wheel.fenderRotMin = Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#fenderRotMin", getXMLFloat, nil, nil, fallbackOldKey);
934
935 wheel.rotSpeed = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#rotSpeed", getXMLFloat, nil, nil, fallbackOldKey));
936 wheel.rotSpeedNeg = Utils.getNoNilRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#rotSpeedNeg", getXMLFloat, nil, nil, fallbackOldKey), nil);
937 wheel.rotMax = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#rotMax", getXMLFloat, nil, nil, fallbackOldKey));
938 wheel.rotMin = Utils.degToRad(Vehicle.getConfigurationValue(xmlFile, key, wheelnamei, "#rotMin", getXMLFloat, nil, nil, fallbackOldKey));
939
940 if wheel.mass == nil then
941 print("Warning: Missing '#mass' for wheel '"..wheel.xmlIndex.."' in '"..self.configFileName.."'. Using default '0.1'!");
942 wheel.mass = 0.1;
943 end;
944
945 if g_currentMission.tireTrackSystem ~= nil and wheel.hasTireTracks then
946 wheel.tireTrackIndex = g_currentMission.tireTrackSystem:createTrack(wheel.width, wheel.tireTrackAtlasIndex);
947 end;
948
949 if wheel.hasParticles then
950 self:loadWheelParticleSystem(wheel, xmlFile, key .. wheelnamei);
951 end
952
953 local baseWheel = wheel
954 local totalOffset = 0
955 local key, _ = Vehicle.getXMLConfigurationKey(xmlFile, self.configurations["wheel"], "vehicle.wheelConfigurations.wheelConfiguration", "vehicle", "wheels");
956 key = key .. ".wheels";
957 local i = 0;
958 while true do
959 local additionalWheelKey = string.format(key..wheelnamei..".additionalWheel(%d)", i);
960 if not hasXMLProperty(xmlFile, additionalWheelKey) then
961 break;
962 end;
963
964 local wheelXmlFilename = getXMLString(xmlFile, additionalWheelKey.."#filename");
965 if wheelXmlFilename ~= nil and wheelXmlFilename ~= "" then
966 if wheel.additionalWheels == nil then
967 wheel.additionalWheels = {};
968 end;
969 local additionalWheel = {};
970 additionalWheel.linkNode = wheel.linkNode;
971
972 local wheelConfigIndex = Utils.getNoNil(getXMLString(xmlFile, additionalWheelKey.."#configIndex"), "basic");
973 local isLeft = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#isLeft"), true);
974 local xRotOffset = Utils.getNoNilRad(getXMLFloat(xmlFile, additionalWheelKey.."#xRotOffset"), 0);
975 additionalWheel.color = Utils.getNoNil(Vehicle.getColorFromString(getXMLString(xmlFile, additionalWheelKey.."#color")), wheel.color)
976
977 self:loadWheelDataFromXML(additionalWheel, wheelXmlFilename, wheelConfigIndex, isLeft, false, xRotOffset);
978
979 additionalWheel.addRaycast = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#addRaycast"), false);
980 additionalWheel.hasParticles = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#hasParticles"), false);
981 additionalWheel.hasTireTracks = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#hasTireTracks"), false);
982 if g_currentMission.tireTrackSystem ~= nil and additionalWheel.hasTireTracks then
983 additionalWheel.tireTrackIndex = g_currentMission.tireTrackSystem:createTrack(additionalWheel.width, additionalWheel.tireTrackAtlasIndex);
984 end;
985
986 local offsetDir = 1
987 if not wheel.isLeft then
988 offsetDir = -1
989 end
990 local offset = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey.."#offset"), 0);
991 local connectorFilename = getXMLString(xmlFile, additionalWheelKey..".connector#filename");
992 if connectorFilename ~= nil and connectorFilename ~= "" then
993 local i3dNode = Utils.loadSharedI3DFile(connectorFilename, self.baseDirectory, false, false, false);
994 if i3dNode ~= 0 then
995 local index = Utils.getNoNil(getXMLString(xmlFile, additionalWheelKey..".connector#index"), "0|0")
996 local connectorUseWidthAndDiam = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey..".connector#useWidthAndDiam"), false)
997 local connectorUsePosAndScale = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey..".connector#usePosAndScale"), false)
998 local node = Utils.indexToObject(i3dNode, index);
999 if node ~= nil then
1000 local connector = {};
1001 connector.filename = connectorFilename;
1002 connector.node = node;
1003 connector.linkNode = baseWheel.wheelTire;
1004
1005 link(connector.linkNode, connector.node)
1006
1007 local baseWheelWidth = baseWheel.width * 39.3701
1008 local dualWheelWidth = additionalWheel.width * 39.3701
1009 local diameter = 0
1010 local wheelDistance = offset * 39.3701 -- meter to inch
1011
1012 if baseWheel.outerRimWidthAndDiam ~= nil then
1013 baseWheelWidth = baseWheel.outerRimWidthAndDiam[1]
1014 diameter = baseWheel.outerRimWidthAndDiam[2]
1015 end
1016 if additionalWheel.outerRimWidthAndDiam ~= nil then
1017 dualWheelWidth = additionalWheel.outerRimWidthAndDiam[1]
1018 end
1019
1020 diameter = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".connector#diameter"), diameter)
1021 local width = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".connector#width"), wheelDistance)
1022 local additionalOffset = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".connector#offset"), 0)
1023
1024 local connectorOffset = totalOffset + offsetDir*(((0.5*baseWheelWidth + 0.5*wheelDistance) * 0.0254) + additionalOffset) -- in meters
1025 totalOffset = totalOffset + offsetDir*(( 0.5*baseWheelWidth + wheelDistance + 0.5*dualWheelWidth ) * 0.0254) -- in meters
1026
1027 if not connectorUseWidthAndDiam then
1028 if getHasShaderParameter(connector.node, "connectorPos") then
1029 baseWheelWidth = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".connector#width"), baseWheelWidth)
1030 wheelDistance = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".connector#distance"), wheelDistance)
1031 setShaderParameter(connector.node, "connectorPos", 0, baseWheelWidth, wheelDistance, dualWheelWidth, false)
1032 end
1033 else
1034 setTranslation(connector.node, connectorOffset, 0, 0)
1035 setShaderParameter(connector.node, "widthAndDiam", width, diameter, 0, 0, false)
1036 end
1037 if connectorUsePosAndScale and getHasShaderParameter(connector.node, "connectorPosAndScale") then
1038 local _,_,_,w = getShaderParameter(connector.node, "connectorPosAndScale")
1039 local startPos = getXMLFloat(xmlFile, additionalWheelKey..".connector#startPos")
1040 local endPos = getXMLFloat(xmlFile, additionalWheelKey..".connector#endPos")
1041 local scale = getXMLFloat(xmlFile, additionalWheelKey..".connector#uniformScale")
1042 setShaderParameter(connector.node, "connectorPosAndScale", startPos, endPos, scale, w, false)
1043 end
1044 if getHasShaderParameter(connector.node, "numberOfStaticsAndDiam") then
1045 local x,_,z,w = getShaderParameter(connector.node, "numberOfStaticsAndDiam")
1046 setShaderParameter(connector.node, "numberOfStaticsAndDiam", x, diameter, z, w, false)
1047 end
1048 local useAxisColor = getXMLBool(xmlFile, additionalWheelKey..".connector#useAxisColor");
1049 local color = nil
1050 if useAxisColor then
1051 color = Utils.getNoNil(wheel.axisColor, self.axisColor);
1052 else
1053 color = Utils.getNoNil(Utils.getNoNil(Utils.getNoNil(Vehicle.getColorFromString(getXMLString(xmlFile, additionalWheelKey..".connector#color")), Vehicle.getColorByConfigId(self, "rimColor", self.configurations["rimColor"])), wheel.color), self.rimColor);
1054 end
1055 if color ~= nil and getHasShaderParameter(connector.node, "colorScale") then
1056 setShaderParameter(connector.node, "colorScale", color[1], color[2], color[3], color[4], false);
1057 end
1058
1059 additionalWheel.connector = connector
1060 end
1061
1062 delete(i3dNode);
1063 end
1064 end
1065
1066 local hubFilename = getXMLString(xmlFile, additionalWheelKey..".hub#filename")
1067 if hubFilename ~= nil and hubFilename ~= "" then
1068 local i3dNode = Utils.loadSharedI3DFile(hubFilename, self.baseDirectory, false, false, false);
1069 if i3dNode ~= 0 then
1070 local index = Utils.getNoNil(getXMLString(xmlFile, additionalWheelKey..".hub#index"), "0|0")
1071 local node = Utils.indexToObject(i3dNode, index);
1072 if node ~= nil then
1073 local hub = {};
1074 hub.filename = hubFilename;
1075 hub.node = node;
1076 hub.linkNode = additionalWheel.wheelTire;
1077 link(hub.linkNode, hub.node)
1078
1079 local x,y,z = Utils.getVectorFromString(Utils.getNoNil(getXMLString(xmlFile, additionalWheelKey..".hub#scale"), "1 1 1"))
1080 setScale(hub.node, x,y,z)
1081 local hubOffset = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey..".hub#offset"), 0)
1082 setTranslation(hub.node, offsetDir*hubOffset, 0, 0)
1083
1084 local color = Utils.getNoNil(Vehicle.getColorFromString(getXMLString(xmlFile, additionalWheelKey..".hub#color")), self.axisColor)
1085 if color ~= nil then
1086 if getHasShaderParameter(hub.node, "colorScale") then
1087 setShaderParameter(hub.node, "colorScale", color[1], color[2], color[3], color[4], false);
1088 end
1089 end
1090
1091 additionalWheel.hub = hub
1092 end
1093 delete(i3dNode);
1094 end
1095 end
1096
1097 if additionalWheel.connector == nil then
1098 -- fallback if no connector is set
1099 totalOffset = totalOffset + offset*offsetDir
1100 end
1101
1102 local x,y,z = getTranslation(additionalWheel.wheelTire);
1103 setTranslation(additionalWheel.wheelTire, x+totalOffset,y,z);
1104
1105 if additionalWheel.addRaycast then
1106 if wheel.raycastOffsets == nil then
1107 wheel.raycastOffsets = {}
1108 end
1109
1110 if #wheel.raycastOffsets < Vehicle.MAX_NUM_WHEEL_RAYCAST_OFFSETS then
1111 table.insert(wheel.raycastOffsets, x+totalOffset)
1112 else
1113 print("Warning: max number of wheel raycast offsets is " .. Vehicle.MAX_NUM_WHEEL_RAYCAST_OFFSETS .. ". Ignoring additional wheel raycast offset!")
1114 end
1115 end
1116
1117 if additionalWheel.hasParticles then
1118 self:loadWheelParticleSystem(wheel, xmlFile, additionalWheelKey, totalOffset);
1119 end
1120
1121 table.insert(wheel.additionalWheels, additionalWheel);
1122
1123 wheel.mass = wheel.mass + additionalWheel.mass;
1124 wheel.maxLatStiffness = wheel.maxLatStiffness + additionalWheel.maxLatStiffness;
1125 wheel.maxLongStiffness = wheel.maxLongStiffness + additionalWheel.maxLongStiffness;
1126 baseWheel = additionalWheel
1127 end;
1128
1129 i = i + 1;
1130 end;
1131
1132 if wheel.raycastOffsets ~= nil and #wheel.raycastOffsets > 0 then
1133 if not wheel.ignoreDefaultRaycast then
1134 table.insert(wheel.raycastOffsets, 0, 1)
1135 end
1136
1137 if #wheel.raycastOffsets > Vehicle.MAX_NUM_WHEEL_RAYCAST_OFFSETS then
1138 print("Warning: max number of wheel raycast offsets is " .. Vehicle.MAX_NUM_WHEEL_RAYCAST_OFFSETS .. ". Ignoring other offsets!")
1139 wheel.raycastOffsets = {wheel.raycastOffsets[1], wheel.raycastOffsets[2], wheel.raycastOffsets[3], wheel.raycastOffsets[4]}
1140 end
1141 end
1142
1143 wheel.maxLatStiffness = wheel.maxLatStiffness*wheel.restLoad;
1144 wheel.maxLatStiffnessLoad = wheel.maxLatStiffnessLoad*wheel.restLoad;
1145
1146
1147 local positionY = wheel.positionY+wheel.deltaY;
1148 wheel.netInfo = {};
1149 wheel.netInfo.xDrive = 0;
1150 wheel.netInfo.x = wheel.positionX;
1151 wheel.netInfo.y = positionY;
1152 wheel.netInfo.z = wheel.positionZ;
1153 wheel.netInfo.suspensionLength = wheel.suspTravel*0.5;
1154
1155 -- The suspension elongates by 20% of the specified susp travel
1156 wheel.netInfo.sync = {yMin = -5, yRange = 10};
1157 wheel.netInfo.yMin = positionY-1.2*wheel.suspTravel;
1158
1159 if wheel.driveGroundParticleSystem ~= nil then
1160 local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode));
1161 setTranslation(wheel.driveGroundParticleSystem.rootNode, wx + wheel.driveGroundParticleSystem.offsets[1], wy - wheel.radius + wheel.driveGroundParticleSystem.offsets[2], wz + wheel.driveGroundParticleSystem.offsets[3]);
1162 end;
1163
1164 wheel.wheelShape = 0;
1165end

loadWheelParticleSystem

Description
Load wheel particle system
Definition
loadWheelParticleSystem(table wheel, integer xmlFile, string wheelKey, float xOffset)
Arguments
tablewheelwheel
integerxmlFileid of xml object
stringwheelKeywheek key
floatxOffsetx offset of particleSystem
Code
1173function Vehicle:loadWheelParticleSystem(wheel, xmlFile, wheelKey, xOffset)
1174 local ps = {};
1175 ParticleUtil.loadParticleSystem(xmlFile, ps, wheelKey, self.components, false, "$data/particleSystems/shared/drivingParticleSystem.i3d", self.baseDirectory);
1176
1177 ps.offsets = Utils.getVectorNFromString("0 0 0", 3);
1178 if xOffset ~= nil then
1179 ps.offsets[1] = xOffset;
1180 end
1181
1182 local additionalOffsets = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, wheelKey.."#psOffset"), "0 0 0"), 3);
1183 ps.offsets[1] = ps.offsets[1] + additionalOffsets[1];
1184 ps.offsets[2] = ps.offsets[2] + additionalOffsets[2];
1185 ps.offsets[3] = ps.offsets[3] + additionalOffsets[3];
1186
1187 link(wheel.node, ps.emitterShape);
1188 local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode));
1189 setTranslation(ps.emitterShape, wx + ps.offsets[1], wy - wheel.radius + ps.offsets[2], wz + ps.offsets[3]);
1190 ps.wheel = wheel;
1191 ps.rootNode = ps.emitterShape;
1192 ps.minSpeed = Utils.getNoNil(getXMLFloat(xmlFile, wheelKey.."#minSpeed"), 5)/3600;
1193 ps.maxSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, wheelKey.."#maxSpeed"), 50)/3600;
1194 ps.minScale = Utils.getNoNil(getXMLFloat(self.xmlFile, wheelKey.."#minScale"), 0.1);
1195 ps.maxScale = Utils.getNoNil(getXMLFloat(self.xmlFile, wheelKey.."#maxScale"), 1);
1196 ps.direction = Utils.getNoNil(getXMLFloat(self.xmlFile, wheelKey.."#direction"), 0);
1197 ps.onlyActiveOnGroundContact = Utils.getNoNil(getXMLBool(self.xmlFile, wheelKey.."#onlyActiveOnGroundContact"), true);
1198 wheel.driveGroundParticleSystem = ps;
1199 table.insert(self.driveGroundParticleSystems, ps);
1200end

loadWheelDataFromXML

Description
Loading wheel data from xml
Definition
loadWheelDataFromXML(table wheel, string xmlFilename, integer wheelConfigIndex, boolean isLeft, boolean isRealWheel, float xRotOffset)
Arguments
tablewheelwheel
stringxmlFilenamexml file name
integerwheelConfigIndexwheel config index
booleanisLeftis left
booleanisRealWheelis real wheel
floatxRotOffsetx rotation offset
Code
1210function Vehicle:loadWheelDataFromXML(wheel, xmlFilename, wheelConfigIndex, isLeft, isRealWheel, xRotOffset)
1211 local xmlFilename = Utils.getFilename(xmlFilename, self.baseDirectory);
1212 local xmlFile = loadXMLFile("TempConfig", xmlFilename);
1213
1214 if xmlFile ~= nil then
1215 local i = 0;
1216 local wheelConfigFound = false
1217 while true do
1218 local configKey = string.format("wheel.configurations.configuration(%d)", i)
1219 if not hasXMLProperty(xmlFile, configKey) then
1220 break;
1221 end;
1222 if getXMLString(xmlFile, configKey.."#index") == wheelConfigIndex then
1223 wheelConfigFound = true
1224
1225 local key = "indexLeft"
1226 if not isLeft then
1227 key = "indexRight"
1228 end
1229 wheel.isLeft = isLeft
1230
1231 wheel.tireFilename = getXMLString(xmlFile, configKey..".tire#filename")
1232 wheel.tireIndex = Utils.getNoNil(getXMLString(xmlFile, configKey..".tire#"..key), "0|0")
1233 wheel.outerRimFilename = getXMLString(xmlFile, configKey..".outerRim#filename")
1234 wheel.outerRimIndex = Utils.getNoNil(getXMLString(xmlFile, configKey..".outerRim#index"), "0|0")
1235 wheel.outerRimWidthAndDiam = Utils.getVectorNFromString(getXMLString(xmlFile, configKey..".outerRim#widthAndDiam"), 2)
1236 wheel.outerRimScale = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, configKey..".outerRim#scale"), "1 1 1"), 3)
1237 wheel.innerRimFilename = getXMLString(xmlFile, configKey..".innerRim#filename")
1238 wheel.innerRimIndex = Utils.getNoNil(getXMLString(xmlFile, configKey..".innerRim#"..key), "0|0")
1239 wheel.innerRimWidthAndDiam = Utils.getVectorNFromString(getXMLString(xmlFile, configKey..".innerRim#widthAndDiam"), 2)
1240 wheel.innerRimOffset = Utils.getNoNil(getXMLFloat(xmlFile, configKey..".innerRim#offset"), 0)
1241 wheel.innerRimScale = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, configKey..".innerRim#scale"), "1 1 1"), 3)
1242 wheel.hubFilename = getXMLString(xmlFile, configKey..".hub#filename")
1243 wheel.hubIndex = Utils.getNoNil(getXMLString(xmlFile, configKey..".hub#"..key), "0|0")
1244 wheel.hubOffset = Utils.getNoNil(getXMLFloat(xmlFile, configKey..".hub#offset"), 0)
1245 wheel.hubScale = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, configKey..".hub#scale"), "1 1 1"), 3)
1246 wheel.hubWidthAndDiam = Utils.getVectorNFromString(getXMLString(xmlFile, configKey..".hub#widthAndDiam"), 2)
1247 wheel.additionalFilename = getXMLString(xmlFile, configKey..".additional#filename")
1248 wheel.additionalIndex = Utils.getNoNil(getXMLString(xmlFile, configKey..".additional#"..key), "0|0")
1249 wheel.additionalOffset = Utils.getNoNil(getXMLFloat(xmlFile, configKey..".additional#offset"), 0)
1250 wheel.additionalScale = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, configKey..".additional#scale"), "1 1 1"), 3)
1251 wheel.additionalMass = Utils.getNoNil(getXMLFloat(xmlFile, configKey..".additional#mass"), 0)
1252 wheel.additionalWidthAndDiam = Utils.getVectorNFromString(getXMLString(xmlFile, configKey..".additional#widthAndDiam"), 2)
1253 break;
1254 end
1255 i = i + 1;
1256 end
1257
1258 if not wheelConfigFound then
1259 print("Error: wheelConfigIndex '" .. wheelConfigIndex .. "' not found in '" .. xmlFilename .. "'")
1260 return
1261 end
1262
1263 wheel.radius = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.size#radius"), 0.5);
1264 wheel.width = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.size#width"), wheel.radius * 0.8);
1265 wheel.xOffset = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.size#xOffset"), 0);
1266 wheel.maxDeformation = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.size#maxDeformation"), 0);
1267
1268 wheel.mass = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#mass"), wheel.mass) + wheel.additionalMass
1269 wheel.maxLongStiffness = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#maxLongStiffness"), wheel.maxLongStiffness); -- [t / rad]
1270 wheel.maxLatStiffness = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#maxLatStiffness"), wheel.maxLatStiffness); -- xml is ratio to restLoad [1/rad], final value is [t / rad]
1271
1272 wheel.smoothGroundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#smoothGroundRadius"), math.max(0.6, wheel.width*0.75));
1273
1274 if isRealWheel then
1275 wheel.frictionScale = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#frictionScale"), wheel.frictionScale);
1276 local tireTypeName = getXMLString(xmlFile, "wheel.tire#type");
1277 if tireTypeName ~= nil then
1278 local tireType = WheelsUtil.getTireType(tireTypeName);
1279 if tireType ~= nil then
1280 wheel.tireType = tireType;
1281 else
1282 print("Warning: Failed to find tire type '"..tireTypeName.."'.");
1283 end
1284 end;
1285
1286 wheel.maxLatStiffnessLoad = Utils.getNoNil(getXMLFloat(xmlFile, "wheel.physics#maxLatStiffnessLoad"), wheel.maxLatStiffnessLoad); -- xml is ratio to restLoad, final value is [t]
1287 end;
1288
1289 wheel.tireTrackAtlasIndex = Utils.getNoNil(getXMLInt(xmlFile, "wheel.tire#atlasIndex"), 0);
1290
1291 local loadWheelPart = function(wheel, parent, name, filename, index, offset, widthAndDiam, scale)
1292 if filename == nil then
1293 return
1294 end
1295 local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false);
1296 if i3dNode ~= 0 then
1297 wheel[name] = Utils.indexToObject(i3dNode, index);
1298 link(parent, wheel[name]);
1299 delete(i3dNode);
1300
1301 if offset ~= 0 then
1302 local dir = 1
1303 if not wheel.isLeft then
1304 dir = -1
1305 end
1306 setTranslation(wheel[name], offset*dir, 0, 0)
1307 end
1308 if scale ~= nil then
1309 setScale(wheel[name], scale[1], scale[2], scale[3])
1310 end
1311 if widthAndDiam ~= nil then
1312 if getHasShaderParameter(wheel[name], "widthAndDiam") then
1313 setShaderParameter(wheel[name], "widthAndDiam", widthAndDiam[1], widthAndDiam[2], 0, 0, false)
1314 end
1315 end
1316 end;
1317 end
1318
1319 loadWheelPart(wheel, wheel.linkNode, "wheelTire", wheel.tireFilename, wheel.tireIndex, 0, nil, nil)
1320 loadWheelPart(wheel, wheel.wheelTire, "wheelOuterRim", wheel.outerRimFilename, wheel.outerRimIndex, 0, wheel.outerRimWidthAndDiam, wheel.outerRimScale)
1321 loadWheelPart(wheel, wheel.wheelTire, "wheelInnerRim", wheel.innerRimFilename, wheel.innerRimIndex, wheel.innerRimOffset, wheel.innerRimWidthAndDiam, wheel.innerRimScale)
1322 loadWheelPart(wheel, wheel.wheelTire, "wheelHub", wheel.hubFilename, wheel.hubIndex, wheel.hubOffset, wheel.hubWidthAndDiam, wheel.hubScale)
1323 loadWheelPart(wheel, wheel.wheelTire, "wheelAdditional", wheel.additionalFilename, wheel.additionalIndex, wheel.additionalOffset, wheel.additionalWidthAndDiam, wheel.additionalScale)
1324
1325 setRotation(wheel.wheelTire, xRotOffset, 0, 0)
1326
1327 local x, y, z, _ = getShaderParameter(wheel.wheelTire, "morphPosition");
1328 setShaderParameter(wheel.wheelTire, "morphPosition", x, y, z, 0, false);
1329
1330 local color = Utils.getNoNil(Utils.getNoNil(wheel.color, Vehicle.getColorByConfigId(self, "rimColor", self.configurations["rimColor"])), self.rimColor);
1331 if color ~= nil then
1332 local r,g,b,a = unpack(color);
1333 if wheel.wheelOuterRim ~= nil then
1334 setShaderParameter(wheel.wheelOuterRim, "colorScale", r, g, b, a, false);
1335 end
1336 if wheel.wheelInnerRim ~= nil then
1337 setShaderParameter(wheel.wheelInnerRim, "colorScale", r, g, b, a, false);
1338 end
1339 end
1340
1341 local additionalColor = Utils.getNoNil(wheel.additionalColor, color);
1342 if wheel.wheelAdditional ~= nil and additionalColor ~= nil then
1343 local r,g,b,a = unpack(additionalColor);
1344 setShaderParameter(wheel.wheelAdditional, "colorScale", r, g, b, a, false);
1345 end
1346
1347 local wheelAxisColor = Utils.getNoNil(wheel.axisColor, self.axisColor)
1348 if wheelAxisColor ~= nil then
1349 if wheel.wheelHub ~= nil then
1350 local r,g,b,a = unpack(wheelAxisColor);
1351 setShaderParameter(wheel.wheelHub, "colorScale", r, g, b, a, false);
1352 end
1353 end
1354
1355 delete(xmlFile);
1356 end;
1357end;

loadWheelsSteeringDataFromXML

Description
Loading wheel steering data from xml
Definition
loadWheelsSteeringDataFromXML(integer xmlFile, integer ackermannSteeringIndex)
Arguments
integerxmlFileid of xml object
integerackermannSteeringIndexackermann steering index
Code
1363function Vehicle:loadWheelsSteeringDataFromXML(xmlFile, ackermannSteeringIndex)
1364 local key, _ = Vehicle.getXMLConfigurationKey(xmlFile, ackermannSteeringIndex, "vehicle.ackermannSteeringConfigurations.ackermannSteering", "vehicle.ackermannSteering", "ackermann");
1365
1366 self.steeringCenterNode = nil;
1367 local rotSpeed = getXMLFloat(xmlFile, key.."#rotSpeed");
1368 local rotMax = getXMLFloat(xmlFile, key.."#rotMax");
1369 local centerX;
1370 local centerZ;
1371 local rotCenterWheel1 = getXMLInt(xmlFile, key.."#rotCenterWheel1");
1372 if rotCenterWheel1 ~= nil and self.wheels[rotCenterWheel1+1] ~= nil then
1373 local wheel = self.wheels[rotCenterWheel1+1];
1374 centerX, _, centerZ = localToLocal(wheel.node, self.components[1].node, wheel.positionX, wheel.positionY, wheel.positionZ);
1375
1376 local rotCenterWheel2 = getXMLInt(xmlFile, key.."#rotCenterWheel2");
1377 if rotCenterWheel2 ~= nil and self.wheels[rotCenterWheel2+1] ~= nil then
1378 local wheel = self.wheels[rotCenterWheel2+1];
1379 local x, _, z = localToLocal(wheel.node, self.components[1].node, wheel.positionX, wheel.positionY, wheel.positionZ);
1380 centerX, centerZ = 0.5*(centerX + x), 0.5*(centerZ + z);
1381 end
1382 else
1383 local centerNode, _ = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#rotCenterNode"));
1384 if centerNode ~= nil then
1385 centerX, _, centerZ = localToLocal(centerNode, self.components[1].node, 0,0,0);
1386 self.steeringCenterNode = centerNode;
1387 else
1388 local p = Utils.getVectorNFromString(getXMLString(xmlFile, key.."#rotCenter", 2));
1389 if p ~= nil then
1390 centerX = p[1];
1391 centerZ = p[2];
1392 end
1393 end
1394 end
1395 if self.steeringCenterNode == nil then
1396 self.steeringCenterNode = createTransformGroup("steeringCenterNode");
1397 link(self.components[1].node, self.steeringCenterNode);
1398 if centerX ~= nil and centerZ ~= nil then
1399 setTranslation(self.steeringCenterNode, centerX, 0, centerZ);
1400 end
1401 end
1402
1403 if rotSpeed ~= nil and rotMax ~= nil and centerX ~= nil then
1404 rotSpeed = math.abs(math.rad(rotSpeed));
1405 rotMax = math.abs(math.rad(rotMax));
1406
1407 -- find the wheel that should get the maximum steering (the one that results in the maximum turnign radius)
1408 local maxTurningRadius = 0;
1409 local maxTurningRadiusWheel = 0;
1410 for i, wheel in ipairs(self.wheels) do
1411 if wheel.rotSpeed ~= 0 then
1412 local diffX, _, diffZ = localToLocal(wheel.node, self.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ);
1413 local turningRadius = math.abs(diffZ)/math.tan(rotMax) + math.abs(diffX);
1414 if turningRadius >= maxTurningRadius then
1415 maxTurningRadius = turningRadius;
1416 maxTurningRadiusWheel = i;
1417 end
1418 end
1419 end
1420 self.maxRotation = math.max(Utils.getNoNil(self.maxRotation, 0), rotMax);
1421 self.maxTurningRadius = maxTurningRadius;
1422 self.maxTurningRadiusWheel = maxTurningRadiusWheel;
1423 self.wheelSteeringDuration = rotMax / rotSpeed;
1424
1425 if maxTurningRadiusWheel > 0 then
1426 for _, wheel in ipairs(self.wheels) do
1427 if wheel.rotSpeed ~= 0 then
1428 local diffX, _, diffZ = localToLocal(wheel.node, self.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ);
1429
1430 local rotMaxI = math.atan(diffZ/(maxTurningRadius-diffX));
1431 local rotMinI = -math.atan(diffZ/(maxTurningRadius+diffX));
1432
1433
1434 local switchMaxMin = rotMinI > rotMaxI
1435 if switchMaxMin then
1436 rotMaxI, rotMinI = rotMinI, rotMaxI;
1437 end
1438
1439 wheel.rotMax = rotMaxI;
1440 wheel.rotMin = rotMinI;
1441 wheel.rotSpeed = rotMaxI/self.wheelSteeringDuration;
1442 wheel.rotSpeedNeg = -rotMinI/self.wheelSteeringDuration;
1443
1444 if switchMaxMin then
1445 wheel.rotSpeed, wheel.rotSpeedNeg = -wheel.rotSpeedNeg, -wheel.rotSpeed
1446 end
1447 end
1448 end
1449 end
1450 end
1451
1452 for _, wheel in ipairs(self.wheels) do
1453 if wheel.rotSpeed ~= 0 then
1454 -- if both speed and rot have the same sign, we can reach it with the positive time
1455 if (wheel.rotMax >= 0) == (wheel.rotSpeed >= 0) then
1456 self.maxRotTime = math.max(wheel.rotMax/wheel.rotSpeed, self.maxRotTime);
1457 end
1458 if (wheel.rotMin >= 0) == (wheel.rotSpeed >= 0) then
1459 self.maxRotTime = math.max(wheel.rotMin/wheel.rotSpeed, self.maxRotTime);
1460 end
1461
1462 -- if speed and rot have a different sign, we can reach it with the negative time
1463 local rotSpeedNeg = wheel.rotSpeedNeg;
1464 if rotSpeedNeg == nil then
1465 rotSpeedNeg = wheel.rotSpeed;
1466 end
1467 if (wheel.rotMax >= 0) ~= (rotSpeedNeg >= 0) then
1468 self.minRotTime = math.min(wheel.rotMax/rotSpeedNeg, self.minRotTime);
1469 end
1470 if (wheel.rotMin >= 0) ~= (rotSpeedNeg >= 0) then
1471 self.minRotTime = math.min(wheel.rotMin/rotSpeedNeg, self.minRotTime);
1472 end
1473 end
1474
1475 wheel.fenderRotMax = Utils.getNoNilRad(wheel.fenderRotMax, wheel.rotMax);
1476 wheel.fenderRotMin = Utils.getNoNilRad(wheel.fenderRotMin, wheel.rotMin);
1477 wheel.steeringNodeMaxRot = math.max(wheel.rotMax, wheel.steeringAxleRotMax);
1478 wheel.steeringNodeMinRot = math.min(wheel.rotMin, wheel.steeringAxleRotMin);
1479 end;
1480end

delete

Description
Deleting vehicle
Definition
delete()
Code
1484function Vehicle:delete()
1485 if g_currentMission ~= nil then
1486 g_currentMission.environment:removeDayChangeListener(self)
1487 end
1488
1489 for i=table.getn(self.specializations), 1, -1 do
1490 if self.specializations[i].preDelete ~= nil then
1491 self.specializations[i].preDelete(self);
1492 end
1493 end;
1494
1495 for i=table.getn(self.specializations), 1, -1 do
1496 self.specializations[i].delete(self);
1497 end;
1498
1499 if self.schemaOverlay ~= nil then
1500 if self.schemaOverlay.overlaySelectedTurnedOn ~= self.schemaOverlay.overlayTurnedOn then
1501 self.schemaOverlay.overlaySelectedTurnedOn:delete();
1502 end
1503 if self.schemaOverlay.overlayTurnedOn ~= self.schemaOverlay.overlay then
1504 self.schemaOverlay.overlayTurnedOn:delete();
1505 end
1506 if self.schemaOverlay.overlaySelected ~= self.schemaOverlay.overlay then
1507 self.schemaOverlay.overlaySelected:delete();
1508 end
1509 self.schemaOverlay.overlay:delete();
1510 end
1511
1512 ParticleUtil.deleteParticleSystems(self.driveGroundParticleSystems)
1513
1514 if self.isClient then
1515 SoundUtil.deleteSample(self.sampleAttach);
1516 SoundUtil.deleteSample(self.sampleHydraulic);
1517 end;
1518
1519 if self.isServer then
1520 for _,v in pairs(self.componentJoints) do
1521 if v.jointIndex ~= 0 then
1522 removeJoint(v.jointIndex);
1523 end
1524 end;
1525 end;
1526
1527
1528 for _,wheel in pairs(self.wheels) do
1529 self:deleteVisualWheel(wheel)
1530
1531 if g_currentMission.tireTrackSystem ~= nil and wheel.tireTrackIndex ~= nil then
1532 g_currentMission.tireTrackSystem:destroyTrack(wheel.tireTrackIndex);
1533 end
1534
1535 if wheel.additionalWheels ~= nil then
1536 for _, additionalWheel in pairs(wheel.additionalWheels) do
1537 self:deleteVisualWheel(additionalWheel)
1538
1539 if g_currentMission.tireTrackSystem ~= nil and additionalWheel.tireTrackIndex ~= nil then
1540 g_currentMission.tireTrackSystem:destroyTrack(additionalWheel.tireTrackIndex);
1541 end;
1542 end;
1543 end;
1544 end;
1545
1546 for _, dynamicallyLoadedWheel in pairs(self.dynamicallyLoadedWheels) do
1547 self:deleteVisualWheel(dynamicallyLoadedWheel)
1548 end;
1549
1550 for _, dynamicallyLoadedPart in pairs(self.dynamicallyLoadedParts) do
1551 if dynamicallyLoadedPart.filename ~= nil then
1552 Utils.releaseSharedI3DFile(dynamicallyLoadedPart.filename, self.baseDirectory, true);
1553 end;
1554 end;
1555
1556 if self.aiTrafficCollisionTrigger ~= nil then
1557 removeTrigger(self.aiTrafficCollisionTrigger);
1558 end
1559
1560 for _,v in pairs(self.components) do
1561 delete(v.node);
1562 end;
1563
1564 Utils.releaseSharedI3DFile(self.i3dFilename, self.baseDirectory, true);
1565
1566 Vehicle:superClass().delete(self);
1567
1568 self.isDeleted = true;
1569end;

deleteVisualWheel

Description
Deleting visual wheel
Definition
deleteVisualWheel(table wheel)
Arguments
tablewheelwheel
Code
1574function Vehicle:deleteVisualWheel(wheel)
1575 if wheel.tireFilename ~= nil then
1576 Utils.releaseSharedI3DFile(wheel.tireFilename, self.baseDirectory, true);
1577 end
1578 if wheel.innerRimFilename ~= nil then
1579 Utils.releaseSharedI3DFile(wheel.innerRimFilename, self.baseDirectory, true);
1580 end
1581 if wheel.outerRimFilename ~= nil then
1582 Utils.releaseSharedI3DFile(wheel.outerRimFilename, self.baseDirectory, true);
1583 end
1584 if wheel.hubFilename ~= nil then
1585 Utils.releaseSharedI3DFile(wheel.hubFilename, self.baseDirectory, true);
1586 end
1587 if wheel.additionalFilename ~= nil then
1588 Utils.releaseSharedI3DFile(wheel.additionalFilename, self.baseDirectory, true);
1589 end
1590 if wheel.connector ~= nil then
1591 Utils.releaseSharedI3DFile(wheel.connector.filename, self.baseDirectory, true);
1592 end
1593end

readStream

Description
Called on client side on join
Definition
readStream(integer streamId, table connection)
Arguments
integerstreamIdstream ID
tableconnectionconnection
Code
1599function Vehicle:readStream(streamId, connection)
1600 Vehicle:superClass().readStream(self, streamId);
1601 local configFile = Utils.convertFromNetworkFilename(streamReadString(streamId));
1602 local typeName = streamReadString(streamId);
1603
1604 local configurations = {};
1605 local numConfigs = streamReadUIntN(streamId, ConfigurationUtil.sendNumBits);
1606 for i=1, numConfigs do
1607 local configNameId = streamReadUIntN(streamId, ConfigurationUtil.sendNumBits);
1608 local configId = streamReadUInt16(streamId);
1609
1610 local configName = ConfigurationUtil.intToConfigurationName[configNameId+1];
1611 if configName ~= nil then
1612 configurations[configName] = configId+1;
1613 end;
1614 end;
1615
1616 local boughtConfigurations = {}
1617 local numConfigs = streamReadUIntN(streamId, ConfigurationUtil.sendNumBits);
1618 for i=1, numConfigs do
1619 local configNameId = streamReadUIntN(streamId, ConfigurationUtil.sendNumBits);
1620 local configName = ConfigurationUtil.intToConfigurationName[configNameId+1];
1621 boughtConfigurations[configName] = {}
1622 local numBoughtConfigIds = streamReadUInt16(streamId)
1623 for j=1, numBoughtConfigIds do
1624 local boughtConfigId = streamReadUInt16(streamId)
1625 boughtConfigurations[configName][boughtConfigId + 1] = true
1626 end
1627 end
1628
1629 if self.configFileName == nil then
1630 local vehicleData = {};
1631 vehicleData.filename = configFile;
1632 vehicleData.isAbsolute = false;
1633 vehicleData.typeName = typeName;
1634 vehicleData.posX = 0;
1635 vehicleData.posY = nil;
1636 vehicleData.posZ = 0;
1637 vehicleData.yOffset = 0;
1638 vehicleData.rotX = 0;
1639 vehicleData.rotY = 0;
1640 vehicleData.rotZ = 0;
1641 vehicleData.isVehicleSaved = true;
1642 vehicleData.price = 0;
1643 vehicleData.propertyState = Vehicle.PROPERTY_STATE_NONE;
1644 vehicleData.isLeased = 0;
1645 vehicleData.configurations = configurations;
1646 vehicleData.boughtConfigurations = boughtConfigurations;
1647 self:load(vehicleData);
1648 end;
1649
1650 local paramsXZ = g_currentMission.vehicleXZPosCompressionParams;
1651 local paramsY = g_currentMission.vehicleYPosCompressionParams;
1652 for i=1, table.getn(self.components) do
1653 local x = Utils.readCompressedWorldPosition(streamId, paramsXZ);
1654 local y = Utils.readCompressedWorldPosition(streamId, paramsY);
1655 local z = Utils.readCompressedWorldPosition(streamId, paramsXZ);
1656 local x_rot = Utils.readCompressedAngle(streamId);
1657 local y_rot = Utils.readCompressedAngle(streamId);
1658 local z_rot = Utils.readCompressedAngle(streamId);
1659
1660 local x_rot,y_rot,z_rot,w_rot = mathEulerToQuaternion(x_rot,y_rot,z_rot);
1661 self:setWorldPositionQuaternion(x,y,z, x_rot,y_rot,z_rot,w_rot, i, true);
1662 end
1663 self.interpolationAlpha = 0;
1664 self.positionIsDirty = false;
1665 for i=1, table.getn(self.wheels) do
1666 local wheel = self.wheels[i];
1667 wheel.netInfo.x = streamReadFloat32(streamId);
1668 wheel.netInfo.y = streamReadFloat32(streamId);
1669 wheel.netInfo.z = streamReadFloat32(streamId);
1670 wheel.netInfo.xDrive = streamReadFloat32(streamId);
1671 wheel.netInfo.suspensionLength = streamReadFloat32(streamId);
1672 if wheel.tireTrackIndex ~= nil then
1673 wheel.contact = streamReadUIntN(streamId, 2);
1674 end;
1675 if wheel.versatileYRot then
1676 local yRot = streamReadUIntN(streamId, 9);
1677 wheel.steeringAngle = yRot / 511 * math.pi*2;
1678 end
1679 end;
1680
1681 self.serverMass = streamReadFloat32(streamId);
1682 self.age = streamReadUInt16(streamId);
1683 self:setOperatingTime(streamReadFloat32(streamId), true);
1684 self.price = streamReadInt32(streamId)
1685 self.propertyState = streamReadUIntN(streamId, 2);
1686
1687 for _,v in pairs(self.specializations) do
1688 if v.readStream ~= nil then
1689 v.readStream(self, streamId, connection);
1690 end;
1691 end;
1692end;

writeStream

Description
Called on server side on join
Definition
writeStream(integer streamId, table connection)
Arguments
integerstreamIdstream ID
tableconnectionconnection
Code
1698function Vehicle:writeStream(streamId, connection)
1699 Vehicle:superClass().writeStream(self, streamId);
1700 streamWriteString(streamId, Utils.convertToNetworkFilename(self.configFileName));
1701 streamWriteString(streamId, self.typeName);
1702
1703 local numConfigs = 0;
1704 for _,_ in pairs(self.configurations) do
1705 numConfigs = numConfigs + 1;
1706 end
1707
1708 streamWriteUIntN(streamId, numConfigs, ConfigurationUtil.sendNumBits);
1709 for configName, configId in pairs(self.configurations) do
1710 local configNameId = ConfigurationUtil.configurationNameToInt[configName];
1711 streamWriteUIntN(streamId, configNameId-1, ConfigurationUtil.sendNumBits);
1712 streamWriteUInt16(streamId, configId-1);
1713 end
1714
1715 local numBoughtConfigs = 0
1716 for _,_ in pairs(self.boughtConfigurations) do
1717 numBoughtConfigs = numBoughtConfigs + 1
1718 end
1719
1720 streamWriteUIntN(streamId, numBoughtConfigs, ConfigurationUtil.sendNumBits);
1721 for configName, configIds in pairs(self.boughtConfigurations) do
1722 local numBoughtConfigIds = 0
1723 for _,_ in pairs(configIds) do
1724 numBoughtConfigIds = numBoughtConfigIds + 1
1725 end
1726 local configNameId = ConfigurationUtil.configurationNameToInt[configName];
1727 streamWriteUIntN(streamId, configNameId-1, ConfigurationUtil.sendNumBits);
1728 streamWriteUInt16(streamId, numBoughtConfigIds)
1729 for id, _ in pairs(configIds) do
1730 streamWriteUInt16(streamId, id-1);
1731 end
1732 end
1733
1734 local paramsXZ = g_currentMission.vehicleXZPosCompressionParams;
1735 local paramsY = g_currentMission.vehicleYPosCompressionParams;
1736 for i=1, table.getn(self.components) do
1737 local x,y,z=getWorldTranslation(self.components[i].node)
1738 local x_rot,y_rot,z_rot = getWorldRotation(self.components[i].node);
1739 Utils.writeCompressedWorldPosition(streamId, x, paramsXZ);
1740 Utils.writeCompressedWorldPosition(streamId, y, paramsY);
1741 Utils.writeCompressedWorldPosition(streamId, z, paramsXZ);
1742 Utils.writeCompressedAngle(streamId, x_rot);
1743 Utils.writeCompressedAngle(streamId, y_rot);
1744 Utils.writeCompressedAngle(streamId, z_rot);
1745 end;
1746 for i=1, table.getn(self.wheels) do
1747 local wheel = self.wheels[i];
1748 streamWriteFloat32(streamId, wheel.netInfo.x);
1749 streamWriteFloat32(streamId, wheel.netInfo.y);
1750 streamWriteFloat32(streamId, wheel.netInfo.z);
1751 streamWriteFloat32(streamId, wheel.netInfo.xDrive);
1752 streamWriteFloat32(streamId, wheel.netInfo.suspensionLength);
1753 if wheel.tireTrackIndex ~= nil then
1754 streamWriteUIntN(streamId, wheel.contact, 2);
1755 end;
1756 if wheel.versatileYRot then
1757 local yRot = wheel.steeringAngle % (math.pi*2);
1758 streamWriteUIntN(streamId, Utils.clamp(math.floor(yRot / (math.pi*2) * 511), 0, 511), 9);
1759 end
1760 end;
1761
1762 streamWriteFloat32(streamId, self:getTotalMass());
1763 streamWriteUInt16(streamId, self.age);
1764 streamWriteFloat32(streamId, self.operatingTime);
1765 streamWriteInt32(streamId, self.price);
1766 streamWriteUIntN(streamId, self.propertyState, 2);
1767
1768 for _,v in pairs(self.specializations) do
1769 if v.writeStream ~= nil then
1770 v.writeStream(self, streamId, connection);
1771 end;
1772 end;
1773end;

readUpdateStream

Description
Called on client side on update
Definition
readUpdateStream(integer streamId, integer timestamp, table connection)
Arguments
integerstreamIdstream ID
integertimestamptimestamp
tableconnectionconnection
Code
1780function Vehicle:readUpdateStream(streamId, timestamp, connection)
1781 if connection.isServer then
1782 local hasUpdate = streamReadBool(streamId);
1783 if hasUpdate then
1784
1785 -- We need to use g_packetPhysicsNetworkTime instead of timestamp, because we need to use the exact same time stepping as we used when simulating
1786 -- Due to the async physics simulation, we always see the results of the dt of one frame behind, so physicsDt ~= dt
1787
1788 -- Interpolate to target from currently interpolated position
1789
1790 local deltaTime = g_client.tickDuration;
1791 if self.lastPhysicsNetworkTime ~= nil then
1792 deltaTime = math.min(g_packetPhysicsNetworkTime - self.lastPhysicsNetworkTime, 3*g_client.tickDuration);
1793 end
1794 self.lastPhysicsNetworkTime = g_packetPhysicsNetworkTime;
1795
1796 -- interpTimeLeft is the time we would've continued interpolating. This should always be about g_clientInterpDelay.
1797 -- If we extrapolated, reset to the full delay
1798 local interpTimeLeft = g_clientInterpDelay;
1799 if self.positionIsDirty and self.interpolationAlpha < 1 then
1800 interpTimeLeft = (1-self.interpolationAlpha) * self.interpolationDuration;
1801 interpTimeLeft = interpTimeLeft * 0.95 + g_clientInterpDelay * 0.05; -- slighly blend towards the expected delay
1802 interpTimeLeft = math.min(interpTimeLeft, 3*g_clientInterpDelay); -- clamp to some reasonable maximum
1803 end
1804 self.interpolationDuration = interpTimeLeft + deltaTime;
1805 self.interpolationAlpha = 0;
1806
1807 local paramsXZ = g_currentMission.vehicleXZPosCompressionParams;
1808 local paramsY = g_currentMission.vehicleYPosCompressionParams;
1809 for i=1, table.getn(self.components) do
1810 local x = Utils.readCompressedWorldPosition(streamId, paramsXZ);
1811 local y = Utils.readCompressedWorldPosition(streamId, paramsY);
1812 local z = Utils.readCompressedWorldPosition(streamId, paramsXZ);
1813 local x_rot = Utils.readCompressedAngle(streamId);
1814 local y_rot = Utils.readCompressedAngle(streamId);
1815 local z_rot = Utils.readCompressedAngle(streamId);
1816
1817 local x_rot,y_rot,z_rot,w_rot = mathEulerToQuaternion(x_rot,y_rot,z_rot);
1818 local targetTranslation = self.components[i].targetTranslation;
1819 local targetRotation = self.components[i].targetRotation;
1820 targetTranslation[1] = x;
1821 targetTranslation[2] = y;
1822 targetTranslation[3] = z;
1823 targetRotation[1] = x_rot; targetRotation[2] = y_rot; targetRotation[3] = z_rot; targetRotation[4] = w_rot;
1824
1825 local trans = self.components[i].curTranslation;
1826 local rot = self.components[i].curRotation;
1827 local lastTranslation = self.components[i].lastTranslation;
1828 local lastRotation = self.components[i].lastRotation;
1829 lastTranslation[1] = trans[1];
1830 lastTranslation[2] = trans[2];
1831 lastTranslation[3] = trans[3];
1832 lastRotation[1] = rot[1]; lastRotation[2] = rot[2]; lastRotation[3] = rot[3]; lastRotation[4] = rot[4];
1833 end;
1834 self.positionIsDirty = true;
1835
1836 -- read netinfo
1837 for i=1, table.getn(self.wheels) do
1838 local wheel = self.wheels[i];
1839 if wheel.isSynchronized then
1840 local y = streamReadUIntN(streamId, 8);
1841 wheel.netInfo.y = y / 255 * wheel.netInfo.sync.yRange + wheel.netInfo.sync.yMin;
1842 local xDrive = streamReadUIntN(streamId, 9);
1843 wheel.netInfo.xDrive = xDrive / 511 * math.pi*2;
1844 local suspLength = streamReadUIntN(streamId, 7)
1845 wheel.netInfo.suspensionLength = suspLength / 100
1846 if wheel.tireTrackIndex ~= nil then
1847 wheel.contact = streamReadUIntN(streamId, 2);
1848 end;
1849 if wheel.versatileYRot then
1850 local yRot = streamReadUIntN(streamId, 9);
1851 wheel.steeringAngle = yRot / 511 * math.pi*2;
1852 end
1853 end;
1854 end;
1855 local rotatedTimeRange = math.max(self.maxRotTime - self.minRotTime, 0.001);
1856 local rotatedTime = streamReadUIntN(streamId, 8);
1857 self.rotatedTime = rotatedTime / 255 * rotatedTimeRange + self.minRotTime;
1858 -- set to 0 due to inaccuracy
1859 if math.abs(self.rotatedTime) < 0.001 then
1860 self.rotatedTime = 0;
1861 end;
1862 self.hasWheelGroundContact = streamReadBool(streamId);
1863 end;
1864 end;
1865
1866 for _,v in pairs(self.specializations) do
1867 if v.readUpdateStream ~= nil then
1868 v.readUpdateStream(self, streamId, timestamp, connection);
1869 end;
1870 end;
1871end;

writeUpdateStream

Description
Called on server side on update
Definition
writeUpdateStream(integer streamId, table connection, integer dirtyMask)
Arguments
integerstreamIdstream ID
tableconnectionconnection
integerdirtyMaskdirty mask
Code
1878function Vehicle:writeUpdateStream(streamId, connection, dirtyMask)
1879 if not connection.isServer then
1880 if streamWriteBool(streamId, bitAND(dirtyMask, self.vehicleDirtyFlag) ~= 0) then
1881
1882 local paramsXZ = g_currentMission.vehicleXZPosCompressionParams;
1883 local paramsY = g_currentMission.vehicleYPosCompressionParams;
1884 for i=1, table.getn(self.components) do
1885 local x,y,z=getWorldTranslation(self.components[i].node)
1886 --local x_rot,y_rot,z_rot,w_rot=getQuaternion(self.components[i].node);
1887 local x_rot,y_rot,z_rot = getWorldRotation(self.components[i].node);
1888
1889 Utils.writeCompressedWorldPosition(streamId, x, paramsXZ);
1890 Utils.writeCompressedWorldPosition(streamId, y, paramsY);
1891 Utils.writeCompressedWorldPosition(streamId, z, paramsXZ);
1892 Utils.writeCompressedAngle(streamId, x_rot);
1893 Utils.writeCompressedAngle(streamId, y_rot);
1894 Utils.writeCompressedAngle(streamId, z_rot);
1895 end;
1896 for i=1, table.getn(self.wheels) do
1897 local wheel = self.wheels[i];
1898 if wheel.isSynchronized then
1899 streamWriteUIntN(streamId, Utils.clamp(math.floor((wheel.netInfo.y - wheel.netInfo.sync.yMin) / wheel.netInfo.sync.yRange * 255), 0, 255), 8);
1900 local xDrive = wheel.netInfo.xDrive % (math.pi*2);
1901 streamWriteUIntN(streamId, Utils.clamp(math.floor(xDrive / (math.pi*2) * 511), 0, 511), 9);
1902 streamWriteUIntN(streamId, Utils.clamp(wheel.netInfo.suspensionLength*100, 0, 128), 7)
1903 if wheel.tireTrackIndex ~= nil then
1904 streamWriteUIntN(streamId, wheel.contact, 2);
1905 end;
1906 if wheel.versatileYRot then
1907 local yRot = wheel.steeringAngle % (math.pi*2);
1908 streamWriteUIntN(streamId, Utils.clamp(math.floor(yRot / (math.pi*2) * 511), 0, 511), 9);
1909 end
1910 end;
1911 end;
1912 local rotatedTimeRange = math.max(self.maxRotTime - self.minRotTime, 0.001);
1913 local rotatedTime = Utils.clamp(math.floor((self.rotatedTime - self.minRotTime)/rotatedTimeRange * 255), 0, 255);
1914 streamWriteUIntN(streamId, rotatedTime, 8);
1915 streamWriteBool(streamId, self.hasWheelGroundContact);
1916 end;
1917 end;
1918
1919 for _,v in pairs(self.specializations) do
1920 if v.writeUpdateStream ~= nil then
1921 v.writeUpdateStream(self, streamId, connection, dirtyMask);
1922 end;
1923 end;
1924end;

testScope

Description
Test if vehicle is in scope
Definition
testScope(float x, float y, float z, float coeff)
Arguments
floatxx world position
floatyy world position
floatzz world position
floatcoeffcoeff
Return Values
booleanisInScopeis in scope
Code
1933function Vehicle:testScope(x,y,z, coeff)
1934 local vx,vy,vz = getWorldTranslation(self.components[1].node);
1935 local distanceSq = Utils.vector3LengthSq(vx-x, vy-y, vz-z);
1936 for _,v in pairs(self.specializations) do
1937 if v.testScope ~= nil then
1938 if not v.testScope(self, x,y,z, coeff, distanceSq) then
1939 return false;
1940 end;
1941 end;
1942 end;
1943
1944 return true;
1945end;

getUpdatePriority

Description
Test if vehicle is in scope
Definition
getUpdatePriority(float skipCount, float x, float y, float z, float coeff, table connection)
Arguments
floatskipCountskip count
floatxx world position
floatyy world position
floatzz world position
floatcoeffcoeff
tableconnectionconnection
Return Values
floatprioritypriority
Code
1956function Vehicle:getUpdatePriority(skipCount, x, y, z, coeff, connection)
1957 if self:getOwner() == connection then
1958 return 50;
1959 end;
1960 local x1, y1, z1 = getWorldTranslation(self.components[1].node);
1961 local dist = Utils.vector3Length(x1-x, y1-y, z1-z);
1962 local clipDist = getClipDistance(self.components[1].node)*coeff;
1963 return (1-dist/clipDist)* 0.8 + 0.5*skipCount * 0.2;
1964end;

onGhostRemove

Description
Called on ghost remove
Definition
onGhostRemove()
Code
1968function Vehicle:onGhostRemove()
1969 for _,v in pairs(self.specializations) do
1970 if v.onGhostRemove ~= nil then
1971 v.onGhostRemove(self);
1972 end;
1973 end;
1974end;

onGhostAdd

Description
Called on ghost add
Definition
onGhostAdd()
Code
1978function Vehicle:onGhostAdd()
1979 for _,v in pairs(self.specializations) do
1980 if v.onGhostAdd ~= nil then
1981 v.onGhostAdd(self);
1982 end;
1983 end;
1984end;

getSaveAttributesAndNodes

Description
Get Save attributes and nodes
Definition
getSaveAttributesAndNodes(string nodeIdent, boolean usedModNames)
Arguments
stringnodeIdentnode ident
booleanusedModNamesused mod names
Return Values
stringattributesattributes
stringnodesnodes
Code
1992function Vehicle:getSaveAttributesAndNodes(nodeIdent, usedModNames)
1993 local attributes = 'isAbsolute="true" age="'..tostring(self.age)..'" price="'..tostring(self.price)..'" propertyState="'..tostring(self.propertyState)..'" operatingTime="' .. tostring((self.operatingTime / 1000)) ..'" ';
1994
1995 if self.tourId ~= nil then
1996 attributes = attributes .. ' tourId="' .. self.tourId .. '"';
1997 end;
1998
1999 local nodes = "";
2000 if not self.isBroken then
2001 for i=1, table.getn(self.components) do
2002 if i>1 then
2003 nodes = nodes.."\n";
2004 end;
2005 local node = self.components[i].node;
2006 local x,y,z = getWorldTranslation(node);
2007 local xRot,yRot,zRot = getWorldRotation(node);
2008 nodes = nodes.. nodeIdent..'<component'..i..' position="'..x..' '..y..' '..z..'" rotation="'..xRot..' '..yRot..' '..zRot..'" />';
2009 end;
2010 end;
2011 for k, c in pairs(self.configurations) do
2012 nodes = nodes.. "\n" .. nodeIdent..'<configuration name="'..k..'" id="'..c..'" />';
2013 end;
2014
2015 for k, configIds in pairs(self.boughtConfigurations) do
2016 for c,_ in pairs(configIds) do
2017 nodes = nodes.. "\n" .. nodeIdent..'<boughtConfiguration name="'..k..'" id="'..c..'" />';
2018 end
2019 end;
2020
2021 for _,v in pairs(self.specializations) do
2022 if v.getSaveAttributesAndNodes ~= nil then
2023 local specAttributes, specNodes = v.getSaveAttributesAndNodes(self, nodeIdent, usedModNames);
2024 if specAttributes ~= nil and specAttributes ~= "" then
2025 attributes = attributes.." "..specAttributes;
2026 end;
2027 if specNodes ~= nil and specNodes ~= "" then
2028 nodes = nodes.."\n"..specNodes;
2029 end;
2030 end;
2031 end;
2032 return attributes, nodes;
2033end;

getXMLStatsAttributes

Description
Get xml states attributes
Definition
getXMLStatsAttributes()
Return Values
stringattributesattributes
Code
2038function Vehicle:getXMLStatsAttributes()
2039 if self.isDeleted or not self.isVehicleSaved or self.nonTabbable then
2040 return nil;
2041 end
2042 local name = "Unknown";
2043 local category = "unknown";
2044 local storeItem = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()];
2045 if storeItem ~= nil then
2046 if storeItem.name ~= nil then
2047 name = tostring(storeItem.name);
2048 end
2049 if storeItem.category ~= nil and storeItem.category ~= "" then
2050 category = tostring(storeItem.category);
2051 end
2052 end
2053 local attrString = "";
2054 if self.components[1] ~= nil and self.components[1].node ~= 0 then
2055 local x,y,z = getWorldTranslation(self.components[1].node);
2056 attrString = attrString..string.format(' x="%.3f" y="%.3f" z="%.3f"', x,y,z);
2057 end
2058
2059 local attributes = string.format('name="%s" category="%s" type="%s"%s', Utils.encodeToHTML(name), Utils.encodeToHTML(category), Utils.encodeToHTML(tostring(self.typeName)), attrString);
2060 for _,v in pairs(self.specializations) do
2061 if v.getXMLStatsAttributes ~= nil then
2062 local specAttributes = v.getXMLStatsAttributes(self);
2063 if specAttributes ~= nil and specAttributes ~= "" then
2064 attributes = attributes.." "..specAttributes;
2065 end;
2066 end;
2067 end;
2068 return attributes;
2069end;

setRelativePosition

Description
Set relative position of vehicle
Definition
setRelativePosition(float positionX, float offsetY, float positionZ, float yRot)
Arguments
floatpositionXx position
floatoffsetYy offset
floatpositionZz position
floatyRoty rotation
Code
2077function Vehicle:setRelativePosition(positionX, offsetY, positionZ, yRot)
2078 -- position the vehicle
2079 local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, positionX, 300, positionZ);
2080
2081 local tempRootNode = createTransformGroup("tempRootNode");
2082 setTranslation(tempRootNode, positionX, terrainHeight+offsetY, positionZ);
2083 setRotation(tempRootNode, 0, yRot, 0);
2084
2085 -- now move the objects to the scene root node
2086 for i, component in pairs(self.components) do
2087 local x,y,z = localToWorld(tempRootNode, unpack(component.originalTranslation));
2088 local rx,ry,rz = localRotationToWorld(tempRootNode, unpack(component.originalRotation));
2089 self:setWorldPosition(x,y,z, rx,ry,rz, i, true);
2090 end
2091 delete(tempRootNode);
2092
2093 self.interpolationAlpha = 0;
2094 self.positionIsDirty = false;
2095
2096 for _,v in pairs(self.specializations) do
2097 if v.setRelativePosition ~= nil then
2098 v.setRelativePosition(self, positionX, offsetY, positionZ, yRot);
2099 end;
2100 end;
2101end;

setWorldPosition

Description
Set world position and rotation of component
Definition
setWorldPosition(float x, float y, float z, float xRot, float yRot, float zRot, integer i, boolean changeInterp)
Arguments
floatxx position
floatyy position
floatzz position
floatxRotx rotation
floatyRoty rotation
floatzRotz rotation
integeriindex if component
booleanchangeInterpchange interpolation
Code
2113function Vehicle:setWorldPosition(x,y,z, xRot,yRot,zRot, i, changeInterp)
2114 local component = self.components[i];
2115 setWorldTranslation(component.node, x,y,z);
2116 setWorldRotation(component.node, xRot,yRot,zRot);
2117
2118 local qx, qy, qz, qw = getWorldQuaternion(component.node);
2119 local a;
2120 a = component.curTranslation; a[1],a[2],a[3] = x,y,z;
2121 a = component.curRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2122 if changeInterp then
2123 a = component.lastTranslation; a[1],a[2],a[3] = x,y,z;
2124 a = component.lastRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2125 a = component.targetTranslation; a[1],a[2],a[3] = x,y,z;
2126 a = component.targetRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2127 end
2128end;

setWorldPositionQuaternion

Description
Set world position and quaternion rotation of component
Definition
setWorldPositionQuaternion(float x, float y, float z, float qx, float qy, float qz, float qw, integer i, boolean changeInterp)
Arguments
floatxx position
floatyy position
floatzz position
floatqxx rotation
floatqyy rotation
floatqzz rotation
floatqww rotation
integeriindex if component
booleanchangeInterpchange interpolation
Code
2141function Vehicle:setWorldPositionQuaternion(x,y,z, qx,qy,qz,qw, i, changeInterp)
2142 local component = self.components[i];
2143 setWorldTranslation(component.node, x,y,z);
2144 setWorldQuaternion(component.node, qx,qy,qz,qw);
2145
2146 local a;
2147 a = component.curTranslation; a[1],a[2],a[3] = x,y,z;
2148 a = component.curRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2149 if changeInterp then
2150 a = component.lastTranslation; a[1],a[2],a[3] = x,y,z;
2151 a = component.lastRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2152 a = component.targetTranslation; a[1],a[2],a[3] = x,y,z;
2153 a = component.targetRotation; a[1],a[2],a[3],a[4] = qx,qy,qz,qw;
2154 end
2155end;

addNodeVehicleMapping

Description
Add component nodes to list
Definition
addNodeVehicleMapping(table list)
Arguments
tablelistlist
Code
2160function Vehicle:addNodeVehicleMapping(list)
2161 for _,v in pairs(self.components) do
2162 list[v.node] = self;
2163 end;
2164
2165 for _,v in pairs(self.specializations) do
2166 if v.addNodeVehicleMapping ~= nil then
2167 v.addNodeVehicleMapping(self, list);
2168 end;
2169 end;
2170end;

removeNodeVehicleMapping

Description
Remove component nodes from list
Definition
removeNodeVehicleMapping(table list)
Arguments
tablelistlist
Code
2175function Vehicle:removeNodeVehicleMapping(list)
2176 for _,v in pairs(self.components) do
2177 list[v.node] = nil;
2178 end;
2179
2180 for _,v in pairs(self.specializations) do
2181 if v.removeNodeVehicleMapping ~= nil then
2182 v.removeNodeVehicleMapping(self, list);
2183 end;
2184 end;
2185end;

mouseEvent

Description
Mouse event
Definition
mouseEvent(float posX, float posY, boolean isDown, boolean isUp, integer button)
Arguments
floatposXmouse position x (0-1)
floatposYmouse position y (0-1)
booleanisDownbutton is down
booleanisUpbutton is up
integerbuttonacting button
Code
2194function Vehicle:mouseEvent(posX, posY, isDown, isUp, button)
2195
2196 if self.isEntered or self.selectedImplement == nil then
2197 for _,v in pairs(self.specializations) do
2198 v.mouseEvent(self, posX, posY, isDown, isUp, button);
2199 end;
2200 end;
2201
2202 if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then
2203 self.selectedImplement.object:mouseEvent(posX, posY, isDown, isUp, button);
2204 end;
2205end;

keyEvent

Description
Key event
Definition
keyEvent(integer unicode, integer sym, integer modifier, boolean isDown)
Arguments
integerunicodeunicode
integersymsym
integermodifiermodifier
booleanisDownbutton is down
Code
2213function Vehicle:keyEvent(unicode, sym, modifier, isDown)
2214
2215 if self.isEntered or self.selectedImplement == nil then
2216 for _,v in pairs(self.specializations) do
2217 v.keyEvent(self, unicode, sym, modifier, isDown);
2218 end;
2219 end;
2220
2221 if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then
2222 self.selectedImplement.object:keyEvent(unicode, sym, modifier, isDown);
2223 end;
2224end;

update

Description
Update
Definition
update(float dt)
Arguments
floatdttime since last call in ms
Code
2229function Vehicle:update(dt)
2230
2231 if not self.isServer and self.positionIsDirty then
2232 local maxIntrpAlpha = 1.2;
2233 local interpolationAlpha = self.interpolationAlpha + g_physicsDtUnclamped / self.interpolationDuration;
2234 --local interpolationAlpha = math.max((g_physicsNetworkTime-g_clientInterpDelay - self.lastPoseTime) / (self.targetPoseTime - self.lastPoseTime), 0);
2235 --self.curPoseTime = g_physicsNetworkTime-g_clientInterpDelay;
2236 --[[local index1 = math.max(table.getn(self.networkPosesTimes)-1, 1);
2237 local index2 = table.getn(self.networkPosesTimes);
2238 for i=1, table.getn(self.networkPosesTimes) do
2239 if self.networkPosesTimes[i] >= g_physicsNetworkTime-g_clientInterpDelay then
2240 index2 = i;
2241 index1 = math.max(i-1, 1);
2242 break;
2243 end
2244 end
2245 if index1 ~= index2 then
2246 self.interpolationAlpha = math.max((g_physicsNetworkTime-g_clientInterpDelay - self.networkPosesTimes[index1]) / (self.networkPosesTimes[index2] - self.networkPosesTimes[index1]), 0);
2247 else
2248 self.interpolationAlpha = maxIntrpAlpha;
2249 end
2250 if self.isEntered then
2251 local component = self.components[1];
2252 local dist = Utils.vector3Length(component.networkPoses[index2][1]-component.networkPoses[index1][1], component.networkPoses[index2][2]-component.networkPoses[index1][2], component.networkPoses[index2][3]-component.networkPoses[index1][3]);
2253 local dt = (self.networkPosesTimes[index2] - self.networkPosesTimes[index1]);
2254 print(string.format("Interp %.2f: %d@%.2fms -> %d@%.2fms: %.2f speed: %.2f in %.2fms", g_physicsNetworkTime-g_clientInterpDelay, index1, self.networkPosesTimes[index1], index2, self.networkPosesTimes[index2], self.interpolationAlpha, dist/(dt*0.001), dt));
2255 end]]
2256 if interpolationAlpha >= maxIntrpAlpha then
2257 interpolationAlpha = maxIntrpAlpha;
2258 self.positionIsDirty = false;
2259 end;
2260 self.interpolationAlpha = interpolationAlpha;
2261 for i, component in pairs(self.components) do
2262 local lastTranslation = component.lastTranslation;
2263 local targetTranslation = component.targetTranslation;
2264 local x = lastTranslation[1] + interpolationAlpha * (targetTranslation[1] - lastTranslation[1]);
2265 local y = lastTranslation[2] + interpolationAlpha * (targetTranslation[2] - lastTranslation[2]);
2266 local z = lastTranslation[3] + interpolationAlpha * (targetTranslation[3] - lastTranslation[3]);
2267 local rot1 = component.lastRotation;
2268 local rot2 = component.targetRotation;
2269 local qx,qy,qz,qw = Utils.nlerpQuaternionShortestPath(rot1[1], rot1[2], rot1[3], rot1[4], rot2[1], rot2[2], rot2[3], rot2[4], interpolationAlpha);
2270 self:setWorldPositionQuaternion(x,y,z, qx,qy,qz,qw, i, false);
2271 end;
2272 end;
2273
2274 self.isActive = self:getIsActive();
2275
2276 self.updateLoopIndex = g_updateLoopIndex;
2277
2278 if self.isServer then
2279 local vx, vy, vz = worldDirectionToLocal(self.components[1].node, getLinearVelocity(self.components[1].node));
2280 local movingDirection = 0;
2281 if vz > 0.001 then
2282 movingDirection = 1;
2283 elseif vz < -0.001 then
2284 movingDirection = -1;
2285 end;
2286 local lastSpeedReal = Utils.vector3Length(vx, vy, vz)*0.001;
2287 local lastMovedDistance = lastSpeedReal*dt;
2288
2289 self.lastSpeedAcceleration = (lastSpeedReal*movingDirection - self.lastSpeedReal*self.movingDirection)/dt;
2290 self.lastSpeed = self.lastSpeed*0.5 + lastSpeedReal*0.5;
2291 self.lastSpeedReal = lastSpeedReal;
2292 self.movingDirection = movingDirection;
2293 self.lastMovedDistance = lastMovedDistance;
2294 else
2295 local newX, newY, newZ = getWorldTranslation(self.components[1].node);
2296 if self.lastPosition == nil then
2297 self.lastPosition = {newX, newY, newZ};
2298 end;
2299 local lastMovingDirection = self.movingDirection;
2300 local dx, dy, dz = worldDirectionToLocal(self.components[1].node, newX-self.lastPosition[1], newY-self.lastPosition[2], newZ-self.lastPosition[3]);
2301 if dz > 0.001 then
2302 self.movingDirection = 1;
2303 elseif dz < -0.001 then
2304 self.movingDirection = -1;
2305 else
2306 self.movingDirection = 0;
2307 end;
2308 self.lastMovedDistance = Utils.vector3Length(dx, dy, dz);
2309 local lastLastSpeedReal = self.lastSpeedReal;
2310 self.lastSpeedReal = (self.lastMovedDistance/dt);
2311 self.lastSpeedAcceleration = (self.lastSpeedReal*self.movingDirection - lastLastSpeedReal*lastMovingDirection)/dt;
2312 self.lastSpeed = self.lastSpeed * 0.95 + self.lastSpeedReal * 0.05;
2313 self.lastPosition[1], self.lastPosition[2], self.lastPosition[3] = newX, newY, newZ;
2314 end
2315
2316 self.updateWheelsTime = self.updateWheelsTime - dt
2317 if self.isActive then
2318 self.updateWheelsTime = 3000
2319 end
2320
2321 if self.updateWheelsTime > 0 then
2322
2323 IKUtil.updateIKChains(self.ikChains);
2324
2325 if self.firstTimeRun then
2326 WheelsUtil.updateWheelsGraphics(self, dt);
2327
2328 -- tire tracks and ground sounds
2329 if self.isEntered and g_currentMission.surfaceSounds ~= nil then
2330 for _,surfaceSound in pairs(g_currentMission.surfaceSounds) do
2331 surfaceSound.wheelCount = 0;
2332 end
2333 end
2334
2335 local wheelSmoothAmount = 0;
2336 if self.lastSpeedReal > 0.0002 and next(self.wheels) ~= nil then -- start smoothing if driving faster than 0.7km/h
2337 wheelSmoothAmount = self.wheelSmoothAccumulation + math.max(self.lastMovedDistance * 1.2, 0.0003*dt); -- smooth 1.2m per meter driving or at least 0.3m/s
2338 local rounded = TipUtil.getRoundedHeightValue(wheelSmoothAmount);
2339 self.wheelSmoothAccumulation = wheelSmoothAmount - rounded;
2340 else
2341 self.wheelSmoothAccumulation = 0;
2342 end
2343
2344 for _,wheel in ipairs(self.wheels) do
2345 local r, g, b, a, t = nil, nil, nil, nil, nil;
2346 local surfaceSound = nil;
2347 local wheelSpeed = 0;
2348 -- using netinfo couse of tire deformation
2349 local wx, wy, wz = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z;
2350 wy = wy - wheel.radius;
2351 wx = wx + wheel.xOffset;
2352 wx, wy, wz = localToWorld(wheel.node, wx,wy,wz);
2353
2354 if self.isServer and self.isAddedToPhysics then
2355 wheelSpeed = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape);
2356 local contactObject, contactSubShapeIndex = getWheelShapeContactObject(wheel.node, wheel.wheelShape);
2357 if contactObject == g_currentMission.terrainRootNode then
2358 if contactSubShapeIndex <= 0 then
2359 wheel.contact = Vehicle.WHEEL_GROUND_CONTACT;
2360 else
2361 wheel.contact = Vehicle.WHEEL_GROUND_HEIGHT_CONTACT;
2362 end
2363 elseif wheel.hasGroundContact and contactObject ~= 0 and getRigidBodyType(contactObject) == "Static" and getUserAttribute(contactObject, "noTireTracks") ~= true then
2364 wheel.contact = Vehicle.WHEEL_OBJ_CONTACT;
2365 else
2366 wheel.contact = Vehicle.WHEEL_NO_CONTACT;
2367 end;
2368 else
2369 if self.movingDirection > 0 then
2370 wheelSpeed = 1;
2371 elseif self.movingDirection < 0 then
2372 wheelSpeed = -1;
2373 end
2374 end;
2375
2376 local isOnField = false;
2377 if wheel.contact == Vehicle.WHEEL_GROUND_CONTACT then
2378 local densityBits = getDensityAtWorldPos(g_currentMission.terrainDetailId, wx, wy, wz);
2379 isOnField = densityBits ~= 0;
2380
2381 if isOnField then
2382 r, g, b, a = Utils.getTireTrackColorFromDensityBits(densityBits)
2383 t = 1
2384 if self.isEntered and g_currentMission.surfaceNameToSurfaceSound ~= nil then
2385 surfaceSound = g_currentMission.surfaceNameToSurfaceSound.field;
2386 end
2387 else
2388 r, g, b, a, t = getTerrainAttributesAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz, true, true, true, true, false);
2389 if self.isEntered and g_currentMission.surfaceNameToSurfaceSound ~= nil then
2390 surfaceSound = g_currentMission.materialIdToSurfaceSound[t];
2391 end
2392 end
2393
2394 wheel.dirtAmount = 1;
2395 wheel.lastColor[1] = r;
2396 wheel.lastColor[2] = g;
2397 wheel.lastColor[3] = b;
2398 wheel.lastColor[4] = a;
2399 wheel.lastTerrainAttribute = t;
2400 elseif wheel.contact == Vehicle.WHEEL_OBJ_CONTACT then
2401 if wheel.dirtAmount > 0 then
2402 local maxTrackLength = 30 * (1 + g_currentMission.environment.groundWetness);
2403 local speedFactor = math.min(self:getLastSpeed(), 20) / 20;
2404 maxTrackLength = maxTrackLength * (2 - speedFactor);
2405 wheel.dirtAmount = math.max(wheel.dirtAmount - self.lastMovedDistance/maxTrackLength, 0);
2406 r, g, b = wheel.lastColor[1], wheel.lastColor[2], wheel.lastColor[3];
2407 a = 0; -- no depth to tyre tracks on road etc.
2408 end;
2409 if self.isEntered and g_currentMission.surfaceNameToSurfaceSound ~= nil then
2410 surfaceSound = g_currentMission.surfaceNameToSurfaceSound.asphalt;
2411 end
2412 elseif wheel.contact == Vehicle.WHEEL_GROUND_HEIGHT_CONTACT then
2413 if self.isServer then
2414 if wheel.smoothGroundRadius > 0 and wheelSmoothAmount > 0 then
2415 local smoothYOffset = -0.1;
2416 local heightType = TipUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, wheel.smoothGroundRadius)
2417 if heightType ~= nil and heightType.allowsSmoothing then
2418 local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz);
2419 local physicsDeltaHeight = wy - terrainHeight;
2420 local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale;
2421 deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset)
2422 deltaHeight = math.max(deltaHeight + smoothYOffset, 0);
2423 local internalHeight = terrainHeight + deltaHeight;
2424 smoothDensityMapHeightAtWorldPos(TipUtil.terrainDetailHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, wheel.smoothGroundRadius, wheel.smoothGroundRadius + 1.2);
2425 if Vehicle.debugRendering then
2426 DebugUtil.drawDebugCircle(wx,internalHeight,wz, wheel.smoothGroundRadius, 10);
2427 end
2428 end
2429 if wheel.additionalWheels ~= nil then
2430 for _, additionalWheel in pairs(wheel.additionalWheels) do
2431 local refNode = wheel.node;
2432 if wheel.repr ~= wheel.driveNode then
2433 refNode = wheel.repr;
2434 end
2435 local xShift,yShift,zShift = localToLocal(additionalWheel.wheelTire, refNode, additionalWheel.xOffset,0,0);
2436 local wx,wy,wz = localToWorld(refNode, xShift, yShift-additionalWheel.radius, zShift);
2437 local heightType = TipUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, additionalWheel.smoothGroundRadius)
2438 if heightType ~= nil and heightType.allowsSmoothing then
2439 local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz);
2440 local physicsDeltaHeight = wy - terrainHeight;
2441 local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale;
2442 deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset)
2443 deltaHeight = math.max(deltaHeight + smoothYOffset, 0);
2444 local internalHeight = terrainHeight + deltaHeight;
2445 smoothDensityMapHeightAtWorldPos(TipUtil.terrainDetailHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, additionalWheel.smoothGroundRadius, additionalWheel.smoothGroundRadius + 1.2);
2446 if Vehicle.debugRendering then
2447 DebugUtil.drawDebugCircle(wx,internalHeight,wz, additionalWheel.smoothGroundRadius, 10);
2448 end
2449 end
2450 end
2451 end
2452 end
2453 end
2454 end;
2455
2456 if self.isServer and wheel.contact ~= Vehicle.WHEEL_NO_CONTACT then
2457 local groundType = WheelsUtil.getGroundType(isOnField, wheel.contact ~= Vehicle.WHEEL_GROUND_CONTACT, a);
2458 local coeff = WheelsUtil.getTireFriction(wheel.tireType, groundType, g_currentMission.environment.groundWetness);
2459 if coeff ~= wheel.tireGroundFrictionCoeff then
2460 wheel.tireGroundFrictionCoeff = coeff;
2461 self:updateWheelTireFriction(wheel);
2462 end
2463 end
2464
2465 if wheel.tireTrackIndex ~= nil then
2466 if r ~= nil then
2467 local ux,uy,uz = localDirectionToWorld(self.rootNode, 0,1,0);
2468 -- we are using dirtAmount as alpha value -> realistic dirt fadeout
2469
2470 g_currentMission.tireTrackSystem:addTrackPoint(wheel.tireTrackIndex, wx, wy, wz, ux, uy, uz, r, g, b, wheel.dirtAmount, a, wheelSpeed);
2471 if wheel.additionalWheels ~= nil then
2472 for _, additionalWheel in pairs(wheel.additionalWheels) do
2473 if additionalWheel.tireTrackIndex ~= nil then
2474 local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(additionalWheel.wheelTire));
2475 wy = wy - wheel.radius;
2476 wx = wx + wheel.xOffset;
2477 wx, wy, wz = localToWorld(wheel.node, wx,wy,wz);
2478 wy = math.max(wy, getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz))
2479 g_currentMission.tireTrackSystem:addTrackPoint(additionalWheel.tireTrackIndex, wx, wy, wz, ux, uy, uz, r, g, b, wheel.dirtAmount, a, wheelSpeed);
2480 end
2481 end;
2482 end;
2483 else
2484 g_currentMission.tireTrackSystem:cutTrack(wheel.tireTrackIndex);
2485 if wheel.additionalWheels ~= nil then
2486 for _, additionalWheel in pairs(wheel.additionalWheels) do
2487 if additionalWheel.tireTrackIndex ~= nil then
2488 g_currentMission.tireTrackSystem:cutTrack(additionalWheel.tireTrackIndex);
2489 end
2490 end;
2491 end;
2492 end;
2493 end
2494
2495
2496 if surfaceSound ~= nil then
2497 surfaceSound.wheelCount = surfaceSound.wheelCount + 1;
2498 end
2499
2500 end;
2501
2502 -- adjust volume of ground sounds, only in entered steerable vehicle
2503 if self.isEntered and g_currentMission.surfaceSounds ~= nil then
2504
2505 local wheelCount = table.getn(self.wheels);
2506 local noSound = math.abs(self:getLastSpeed()) < 1;
2507
2508 local debugString = "";
2509
2510 for _,surfaceSound in pairs(g_currentMission.surfaceSounds) do
2511 if surfaceSound.sample.sample ~= nil then
2512
2513 local currentVolume = surfaceSound.currentVolume;
2514 local targetVolume = surfaceSound.sample.volume * (surfaceSound.wheelCount/wheelCount);
2515
2516 if currentVolume < targetVolume then
2517 currentVolume = math.min(targetVolume, currentVolume + surfaceSound.sample.volume * dt/125);
2518 elseif currentVolume > targetVolume then
2519 currentVolume = math.max(targetVolume, currentVolume - surfaceSound.sample.volume * dt/125);
2520 end
2521
2522 if noSound then
2523 currentVolume = 0;
2524 end
2525
2526 if currentVolume > 0 then
2527 if not SoundUtil.isSamplePlaying(surfaceSound.sample) then
2528 SoundUtil.playSample(surfaceSound.sample, 0, 0, 0);
2529 end
2530 if surfaceSound.pitchScale ~= nil then
2531 local pitch = surfaceSound.sample.pitchOffset + self:getLastSpeed()*surfaceSound.pitchScale;
2532 SoundUtil.setSamplePitch(surfaceSound.sample, pitch);
2533 end
2534 elseif SoundUtil.isSamplePlaying(surfaceSound.sample) then
2535 SoundUtil.stopSample(surfaceSound.sample);
2536 end
2537
2538 if currentVolume ~= surfaceSound.currentVolume then
2539 SoundUtil.setSampleVolume(surfaceSound.sample, currentVolume);
2540 end
2541
2542 surfaceSound.currentVolume = currentVolume;
2543
2544 debugString = debugString .. string.format("%s=%.1f%% ", tostring(surfaceSound.name), 100*currentVolume/surfaceSound.sample.volume);
2545 end
2546 end
2547 --print(debugString);
2548 if self.isActive and Vehicle.debugRendering then
2549 renderText(0.2, 0.02, getCorrectTextSize(0.02), "volumes: " .. debugString);
2550 end
2551
2552 end
2553
2554
2555 if Vehicle.debugMoveAttacherJoints then
2556 if self.selectedImplement ~= nil then
2557 local jointDescIndex = self.selectedImplement.jointDescIndex;
2558 local jointDesc = self.attacherJoints[jointDescIndex];
2559 if jointDesc.rotationNode ~= nil then
2560 local moveUpper, _ = InputBinding.getInputAxis(InputBinding.AXIS_FRONTLOADER_ARM);
2561 local moveLower, _ = InputBinding.getInputAxis(InputBinding.AXIS_FRONTLOADER_TOOL);
2562 if moveUpper ~= 0 then
2563 jointDesc.upperRotation[1] = jointDesc.upperRotation[1] + math.rad(moveUpper*(2/1000)*dt);
2564 jointDesc.moveAlpha = jointDesc.moveAlpha - 0.001;
2565 print("upperRotation: " ..math.deg(jointDesc.upperRotation[1]));
2566 end;
2567 if moveLower ~= 0 then
2568 jointDesc.lowerRotation[1] = jointDesc.lowerRotation[1] + math.rad(moveLower*(2/1000)*dt);
2569 jointDesc.moveAlpha = jointDesc.moveAlpha - 0.001;
2570 print("lowerRotation: " ..math.deg(jointDesc.lowerRotation[1]));
2571 end;
2572 end;
2573 end
2574 end
2575
2576 end;
2577 if self.showDetachingNotAllowedTime > 0 then
2578 self.showDetachingNotAllowedTime = self.showDetachingNotAllowedTime - dt;
2579 end;
2580 else
2581 self.showDetachingNotAllowedTime = 0;
2582 if g_currentMission.tireTrackSystem ~= nil then
2583 for _,wheel in ipairs(self.wheels) do
2584 if wheel.tireTrackIndex ~= nil then
2585 g_currentMission.tireTrackSystem:cutTrack(wheel.tireTrackIndex);
2586 end
2587 if wheel.additionalWheels ~= nil then
2588 for _, additionalWheel in pairs(wheel.additionalWheels) do
2589 if additionalWheel.tireTrackIndex ~= nil then
2590 g_currentMission.tireTrackSystem:cutTrack(additionalWheel.tireTrackIndex);
2591 end
2592 end;
2593 end;
2594 end;
2595 end
2596 end;
2597
2598 for _,v in pairs(self.specializations) do
2599 v.update(self, dt);
2600 end;
2601
2602 for _,v in ipairs(self.specializations) do
2603 if v.postUpdate ~= nil then
2604 v.postUpdate(self, dt);
2605 end;
2606 end;
2607
2608 if Vehicle.debugAttributeRendering then
2609 Vehicle.drawDebugAttributeRendering(self);
2610 end;
2611
2612 self.firstTimeRun = true;
2613end;

updateTick

Description
updateTick
Definition
updateTick(float dt)
Arguments
floatdttime since last call in ms
Code
2618function Vehicle:updateTick(dt)
2619 self.wasTooFast = false;
2620 if self.isServer then
2621 local hasOwner = self:getOwner() ~= nil;
2622 for i=1, table.getn(self.components) do
2623 local x,y,z = getWorldTranslation(self.components[i].node);
2624 local x_rot,y_rot,z_rot=getWorldRotation(self.components[i].node);
2625 local sentTranslation = self.components[i].sentTranslation;
2626 local sentRotation = self.components[i].sentRotation;
2627 if hasOwner or
2628 math.abs(x-sentTranslation[1]) > 0.005 or
2629 math.abs(y-sentTranslation[2]) > 0.005 or
2630 math.abs(z-sentTranslation[3]) > 0.005 or
2631 math.abs(x_rot-sentRotation[1]) > 0.1 or
2632 math.abs(y_rot-sentRotation[2]) > 0.1 or
2633 math.abs(z_rot-sentRotation[3]) > 0.1
2634 then
2635 self:raiseDirtyFlags(self.vehicleDirtyFlag);
2636 sentTranslation[1] = x; sentTranslation[2] = y; sentTranslation[3] = z;
2637 sentRotation[1] = x_rot; sentRotation[2] = y_rot; sentRotation[3] = z_rot;
2638 self.lastMoveTime = g_currentMission.time;
2639
2640 local paramsXZ = g_currentMission.vehicleXZPosCompressionParams;
2641 local paramsY = g_currentMission.vehicleYPosCompressionParams;
2642 if not Utils.getIsWorldPositionInCompressionRange(x, paramsXZ) or
2643 not Utils.getIsWorldPositionInCompressionRange(z, paramsXZ) or
2644 not Utils.getIsWorldPositionInCompressionRange(y, paramsY)
2645 then
2646 -- leave the vehicle first if we want to reset the controlled vehicle
2647 --[[if not g_currentMission.controlPlayer and g_currentMission.controlledVehicle ~= nil and self == g_currentMission.controlledVehicle then
2648 g_currentMission:onLeaveVehicle(g_currentMission.playerStartX, g_currentMission.playerStartY, g_currentMission.playerStartZ);
2649 end
2650 g_client:getServerConnection():sendEvent(ResetVehicleEvent:new(self));
2651 return;]]
2652 end
2653 end;
2654 end;
2655 end;
2656
2657 if self.isActive then
2658 if self.isClient then
2659 for _, ps in pairs(self.driveGroundParticleSystems) do
2660 local scale = self:getDriveGroundParticleSystemsScale(ps);
2661 -- interpolate between different ground colors to avoid unrealisitic particle color changes
2662 if ps.lastColor == nil then
2663 ps.lastColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]};
2664 ps.targetColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]};
2665 ps.currentColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]};
2666 ps.alpha = 1;
2667 end;
2668
2669 if ps.alpha ~= 1 then
2670 ps.alpha = math.min(ps.alpha + dt/1000, 1);
2671 ps.currentColor = {Utils.vector3ArrayLerp(ps.lastColor, ps.targetColor, ps.alpha)};
2672 if ps.alpha == 1 then
2673 ps.lastColor = {ps.currentColor[1], ps.currentColor[2], ps.currentColor[3]};
2674 end;
2675 end;
2676
2677 if ps.alpha == 1 and ps.wheel.lastColor[1] ~= ps.targetColor[1] and ps.wheel.lastColor[2] ~= ps.targetColor[2] and ps.wheel.lastColor[3] ~= ps.targetColor[3] then
2678 ps.alpha = 0;
2679 ps.targetColor = {ps.wheel.lastColor[1], ps.wheel.lastColor[2], ps.wheel.lastColor[3]};
2680 end;
2681
2682 if scale > 0 and g_currentMission.environment.groundWetness < 0.1 then
2683 ParticleUtil.setEmittingState(ps, true);
2684 ParticleUtil.setEmitCountScale(ps, scale);
2685 setShaderParameter(ps.shape, "psColor", ps.currentColor[1], ps.currentColor[2], ps.currentColor[3], 1, false);
2686 else
2687 ParticleUtil.setEmittingState(ps, false);
2688 end;
2689 end;
2690 if self.tipErrorMessageTime > 0 then
2691 self.tipErrorMessageTime = self.tipErrorMessageTime - dt;
2692 end;
2693 end;
2694 else
2695 self.tipErrorMessageTime = 0;
2696 if self.isClient then
2697 for _, ps in pairs(self.driveGroundParticleSystems) do
2698 ParticleUtil.setEmittingState(ps, false);
2699 end;
2700 end;
2701 end;
2702
2703 if self:getIsOperating() then
2704 self:setOperatingTime(self.operatingTime + dt);
2705 end
2706
2707 for _,v in pairs(self.specializations) do
2708 if v.updateTick ~= nil then
2709 v.updateTick(self, dt);
2710 end;
2711 end;
2712
2713 for _,v in ipairs(self.specializations) do
2714 if v.postUpdateTick ~= nil then
2715 v.postUpdateTick(self, dt);
2716 end;
2717 end;
2718end;

drawUIInfo

Description
Draw UI info
Definition
drawUIInfo()
Code
2722function Vehicle:drawUIInfo()
2723 if g_showVehicleDistance then
2724 local x,y,z = getWorldTranslation(self.rootNode);
2725 local x1,y1,z1 = getWorldTranslation(getCamera())
2726 local dist = Utils.vector3Length(x-x1,y-y1,z-z1);
2727 if dist <= 350 then
2728 Utils.renderTextAtWorldPosition(x,y+1,z, string.format("%.0f", dist), getCorrectTextSize(0.02), 0)
2729 end;
2730 end;
2731end;

draw

Description
Draw
Definition
draw()
Code
2735function Vehicle:draw()
2736 if self.isEntered or self.selectedImplement == nil then
2737 for _,v in pairs(self.specializations) do
2738 v.draw(self);
2739 end;
2740 if self.showDetachingNotAllowedTime > 0 then
2741 local alpha = (math.sin(self.showDetachingNotAllowedTime/100)+1)*0.5;
2742 g_currentMission:enableHudIcon("detachingNotAllowed", 6, alpha);
2743 end;
2744 end;
2745 if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then
2746 self.selectedImplement.object:draw();
2747 end;
2748 if self.tipErrorMessageTime > 0 then
2749 g_currentMission:showBlinkingWarning(self.tipErrorMessage);
2750 end;
2751
2752 if Vehicle.debugRendering and self.isEntered then
2753 self.isSelectable = true;
2754 if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then
2755 Vehicle.drawDebugRendering(self.selectedImplement.object);
2756 else
2757 Vehicle.drawDebugRendering(self);
2758 end
2759 end
2760end;

getDriveGroundParticleSystemsScale

Description
Get drive ground particle system scale
Definition
getDriveGroundParticleSystemsScale(table particleSystem)
Arguments
tableparticleSystemparticleSystem
Return Values
floatscalescale
Code
2766function Vehicle:getDriveGroundParticleSystemsScale(particleSystem)
2767 local wheel = particleSystem.wheel;
2768 if wheel ~= nil then
2769 if particleSystem.onlyActiveOnGroundContact and wheel.contact ~= Vehicle.WHEEL_GROUND_CONTACT then
2770 return 0;
2771 end;
2772 if not GroundSoundUtil.terrainAttributeEmitsParticles[wheel.lastTerrainAttribute] then
2773 return 0;
2774 end;
2775 end;
2776 local minSpeed = particleSystem.minSpeed;
2777 local direction = particleSystem.direction;
2778 if self.lastSpeedReal > minSpeed and (direction == 0 or (direction > 0) == (self.movingDirection > 0)) then
2779 local maxSpeed = particleSystem.maxSpeed;
2780 local alpha = math.min((self.lastSpeedReal - minSpeed) / (maxSpeed - minSpeed), 1);
2781 local scale = Utils.lerp(particleSystem.minScale, particleSystem.maxScale, alpha);
2782 return scale;
2783 end;
2784 return 0;
2785end;

getSpeedLimit

Description
Get speed limit
Definition
getSpeedLimit(boolean onlyIfWorking)
Arguments
booleanonlyIfWorkingonly if working
Return Values
floatlimitlimit
booleandoCheckSpeedLimitdo check speed limit
Code
2792function Vehicle:getSpeedLimit(onlyIfWorking)
2793 local limit = math.huge;
2794 local doCheckSpeedLimit = self:doCheckSpeedLimit();
2795 if onlyIfWorking == nil or (onlyIfWorking and doCheckSpeedLimit) then
2796 limit = self.speedLimit;
2797 end;
2798 for _, implement in pairs(self.attachedImplements) do
2799 if implement.object ~= nil then
2800 local speed, implementDoCheckSpeedLimit = implement.object:getSpeedLimit(onlyIfWorking);
2801 if onlyIfWorking == nil or (onlyIfWorking and implementDoCheckSpeedLimit) then
2802 limit = math.min(limit, speed);
2803 end;
2804 doCheckSpeedLimit = doCheckSpeedLimit or implementDoCheckSpeedLimit;
2805 end
2806 end;
2807
2808 return limit, doCheckSpeedLimit;
2809end;

getAttachedTrailersFillLevelAndCapacity

Description
Get fill level and capacity of attached trailers
Definition
getAttachedTrailersFillLevelAndCapacity()
Return Values
floatfillLevelfill level
floatcapacitycapacity
Code
2819function Vehicle:getAttachedTrailersFillLevelAndCapacity()
2820 local fillLevel = 0;
2821 local capacity = 0;
2822 local hasTrailer = false;
2823
2824 if self.fillLevel ~= nil and self.getCapacity ~= nil then
2825 fillLevel = fillLevel + self.fillLevel;
2826 capacity = capacity + self:getCapacity();
2827 hasTrailer = true;
2828 end;
2829
2830 for _, implement in pairs(self.attachedImplements) do
2831 if implement.object ~= nil then
2832 local f, c = implement.object:getAttachedTrailersFillLevelAndCapacity();
2833 if f ~= nil and c ~= nil then
2834 fillLevel = fillLevel + f;
2835 capacity = capacity + c;
2836 hasTrailer = true;
2837 end;
2838 end
2839 end;
2840 if hasTrailer then
2841 return fillLevel, capacity;
2842 end;
2843 return nil;
2844end;

getFillLevelInformation

Description
Get fill level information
Definition
getFillLevelInformation(table fillLevelInformations)
Arguments
tablefillLevelInformationsfill level informations
Code
2849function Vehicle:getFillLevelInformation(fillLevelInformations)
2850
2851 if self.fillUnits ~= nil then
2852 for _,fillUnit in pairs(self.fillUnits) do
2853 if fillUnit.capacity > 0 and fillUnit.showOnHud then
2854 local added = false;
2855 for _,fillLevelInformation in pairs(fillLevelInformations) do
2856 if fillLevelInformation.fillType == fillUnit.currentFillType then
2857 fillLevelInformation.fillLevel = fillLevelInformation.fillLevel + fillUnit.fillLevel;
2858 fillLevelInformation.capacity = fillLevelInformation.capacity + fillUnit.capacity;
2859 added = true;
2860 break;
2861 end
2862 end
2863 if not added then
2864 table.insert(fillLevelInformations, {fillType=fillUnit.currentFillType, fillLevel=fillUnit.fillLevel, capacity=fillUnit.capacity});
2865 end
2866 end
2867 end
2868 end
2869
2870 for _, implement in pairs(self.attachedImplements) do
2871 if implement.object ~= nil then
2872 implement.object:getFillLevelInformation(fillLevelInformations);
2873 end
2874 end
2875
2876end

handleToggleTipStateEvent

Description
Handle toggle tip state event
Definition
handleToggleTipStateEvent()
Code
2880function Vehicle:handleToggleTipStateEvent()
2881 if self == g_currentMission.controlledVehicle and g_currentMission.trailerInTipRange ~= nil then
2882 -- check if current fruit type is accepted by the station
2883 if g_currentMission.currentTipTrigger ~= nil then
2884 if g_currentMission.currentTipTriggerIsAllowed then
2885 self.tipErrorMessageTime = 0;
2886 g_currentMission.trailerInTipRange:toggleTipState(g_currentMission.currentTipTrigger, g_currentMission.currentTipReferencePointIndex);
2887 else
2888 local text = g_currentMission.currentTipTrigger:getNotAllowedText(g_currentMission.trailerInTipRange, TipTrigger.TOOL_TYPE_TRAILER);
2889 if text ~= nil and text ~= "" then
2890 self.tipErrorMessageTime = 3000;
2891 self.tipErrorMessage = text;
2892 end
2893 end;
2894 end;
2895 end
2896end

handleAttachEvent

Description
Handle attach event
Definition
handleAttachEvent()
Code
2900function Vehicle:handleAttachEvent()
2901 if self:handleAttachAttachableEvent() then
2902 --
2903 elseif self:handleDetachAttachableEvent() then
2904 --
2905 end
2906end;

handleAttachAttachableEvent

Description
Handle attach attachable event
Definition
handleAttachAttachableEvent()
Return Values
booleansuccesssuccess
Code
2911function Vehicle:handleAttachAttachableEvent()
2912 if g_currentMission.attachableInMountRange ~= nil then
2913 if g_currentMission.attachableInMountRangeVehicle == self then
2914 if self.attacherJoints[g_currentMission.attachableInMountRangeIndex].jointIndex == 0 then
2915 self:attachImplement(g_currentMission.attachableInMountRange, g_currentMission.attachableInMountRangeJointIndex, g_currentMission.attachableInMountRangeIndex);
2916 return true;
2917 end;
2918 else
2919 for _,implement in ipairs(self.attachedImplements) do
2920 if implement.object ~= nil and implement.object:handleAttachAttachableEvent() then
2921 return true;
2922 end;
2923 end;
2924 end;
2925 end;
2926 return false;
2927end;

handleDetachAttachableEvent

Description
Handle detach attachable event
Definition
handleDetachAttachableEvent()
Return Values
booleansuccesssuccess
Code
2932function Vehicle:handleDetachAttachableEvent()
2933 local vehicle = self;
2934
2935 local rootAttacherVehicle = self:getRootAttacherVehicle()
2936 if rootAttacherVehicle ~= self then
2937 vehicle = rootAttacherVehicle;
2938 end;
2939
2940 if vehicle.selectedImplement ~= nil then
2941 local object = vehicle.selectedImplement.object;
2942 if object ~= nil and object.attacherVehicle ~= nil then
2943 if object.isDetachAllowed == nil or object:isDetachAllowed() then
2944 local implementIndex = object.attacherVehicle:getImplementIndexByObject(object);
2945 if implementIndex ~= nil then
2946 object.attacherVehicle:detachImplement(implementIndex);
2947 return true;
2948 end
2949 else
2950 vehicle.showDetachingNotAllowedTime = 2000;
2951 return false;
2952 end;
2953 end;
2954 end;
2955 return false;
2956end;

handleLowerImplementEvent

Description
Handle lower implement event
Definition
handleLowerImplementEvent()
Code
2960function Vehicle:handleLowerImplementEvent()
2961 if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil and self.selectedImplement.object.attacherVehicle ~= nil then
2962 local object = self.selectedImplement.object;
2963 local jointDesc = object.attacherVehicle.attacherJoints[self.selectedImplement.jointDescIndex];
2964 if object.attacherJoint.allowsLowering and jointDesc.allowsLowering then
2965 object.attacherVehicle:setJointMoveDown(self.selectedImplement.jointDescIndex, not jointDesc.moveDown, false);
2966 end;
2967 end;
2968end;

getIsActiveForInput

Description
Returns true if active for input
Definition
getIsActiveForInput(boolean onlyTrueIfSelected, boolean activeIfIngameMessageShown)
Arguments
booleanonlyTrueIfSelectedreturn only true if vehicle is selected
booleanactiveIfIngameMessageShownis active if ingame message shown
Return Values
booleanisActiveForInputis active for input
Code
2975function Vehicle:getIsActiveForInput(onlyTrueIfSelected, activeIfIngameMessageShown)
2976 if g_gui:getIsGuiVisible() or g_currentMission.isPlayerFrozen or self.isHired then
2977 return false;
2978 end;
2979
2980 if activeIfIngameMessageShown == nil or activeIfIngameMessageShown == false then
2981 if g_currentMission.inGameMessage:getIsVisible() then
2982 return false;
2983 end
2984 end
2985
2986 if self.isEntered then
2987 if onlyTrueIfSelected == nil or onlyTrueIfSelected then
2988 return self.selectedImplement == nil;
2989 else
2990 return true;
2991 end;
2992 end;
2993
2994 if self.attacherVehicle ~= nil then
2995 if onlyTrueIfSelected == nil or onlyTrueIfSelected then
2996 return self.isSelected and self.attacherVehicle:getIsActiveForInput(false, activeIfIngameMessageShown);
2997 else
2998 return self.attacherVehicle:getIsActiveForInput(false, activeIfIngameMessageShown);
2999 end;
3000 end;
3001 return false;
3002end;

getIsActiveForSound

Description
Returns true if active for sound
Definition
getIsActiveForSound()
Return Values
booleanisActiveForSoundis active for sound
Code
3007function Vehicle:getIsActiveForSound()
3008 if self.isClient then
3009 if self.isEntered then
3010 return true;
3011 end;
3012 if self.attacherVehicle ~= nil then
3013 return self.attacherVehicle:getIsActiveForSound();
3014 end;
3015 end
3016 return false;
3017end;

getIsOperating

Description
Returns true if is operating
Definition
getIsOperating()
Return Values
booleanisOperatingis operating
Code
3022function Vehicle:getIsOperating()
3023 return false;
3024end

getIsActive

Description
Returns true if is active
Definition
getIsActive()
Return Values
booleanisActiveis active
Code
3029function Vehicle:getIsActive()
3030 if self.isBroken then
3031 return false;
3032 end;
3033 if self.isEntered or self.isControlled or self.forceIsActive or self.isMotorStarted then
3034 return true;
3035 end;
3036 if self.attacherVehicle ~= nil then
3037 return self.attacherVehicle:getIsActive();
3038 end;
3039 return false;
3040end;

hasInputConflictWithSelection

Description
Returns true if input conflict with selection
Definition
hasInputConflictWithSelection(table inputs)
Arguments
tableinputsinputs
Return Values
booleanhasConflicthas conflict
Code
3046function Vehicle:hasInputConflictWithSelection(inputs)
3047 if inputs == nil then
3048 inputs = self.conflictCheckedInputs;
3049 end
3050 local rootAttacherVehicle = self:getRootAttacherVehicle();
3051 local curVehicle = rootAttacherVehicle;
3052 if rootAttacherVehicle.selectedImplement ~= nil then
3053 curVehicle = rootAttacherVehicle.selectedImplement.object;
3054 end
3055 -- check selected implement and all of its attachers (up to our vehicle or root) for conflicts
3056 -- implements have a higher priority if they have a smaller distance to the selected implement in the tree
3057 while curVehicle ~= nil and curVehicle ~= self do
3058 if curVehicle:hasInputConflict(inputs, false) then
3059 return true;
3060 end
3061 curVehicle = curVehicle.attacherVehicle;
3062 end
3063 -- no conflcit if we are between the selection and the root and have no conflict with implements closer to the selection
3064 if curVehicle == self then
3065 return false;
3066 end
3067
3068 -- not within the selection and the root, test for conflict to all other implements
3069 return rootAttacherVehicle:hasInputConflict(inputs, true, self);
3070end

hasInputConflict

Description
Returns true if has input conflict
Definition
hasInputConflict(table inputs, boolean recursive, table excludedVehicle)
Arguments
tableinputsinputs
booleanrecursivecheck recursive
tableexcludedVehicleexcluded vehicle
Return Values
booleanhasConflicthas conflict
Code
3078function Vehicle:hasInputConflict(inputs, recursive, excludedVehicle)
3079 if next(self.conflictCheckedInputs) ~= nil and self ~= excludedVehicle then
3080 for input,_ in pairs(inputs) do
3081 if InputBinding.getHasInputConflict(input, self.conflictCheckedInputs) then
3082 return true;
3083 end
3084 end
3085 end
3086 if recursive then
3087 for _,v in pairs(self.attachedImplements) do
3088 if v.object ~= nil and v.object:hasInputConflict(inputs, recursive, excludedVehicle) then
3089 return true;
3090 end
3091 end
3092 end
3093 return false;
3094end

addConflictCheckedInput

Description
Add conflict checked input
Definition
addConflictCheckedInput(integer actionIndex)
Arguments
integeractionIndexindex of action
Code
3099function Vehicle:addConflictCheckedInput(actionIndex)
3100 self.conflictCheckedInputs[actionIndex] = true;
3101end

removeConflictCheckedInput

Description
Remove conflict checked input
Definition
removeConflictCheckedInput(integer actionIndex)
Arguments
integeractionIndexindex of action
Code
3106function Vehicle:removeConflictCheckedInput(actionIndex)
3107 self.conflictCheckedInputs[actionIndex] = nil;
3108end

getIsHired

Description
Get is hired
Definition
getIsHired()
Return Values
booleanisHiredis hired
Code
3117function Vehicle:getIsHired()
3118 if self.isHired then
3119 return true;
3120 elseif self.attacherVehicle ~= nil then
3121 return self.attacherVehicle:getIsHired();
3122 end;
3123 return false;
3124end;

getOwner

Description
Get owner of vehicle
Definition
getOwner()
Return Values
tableownerowner
Code
3129function Vehicle:getOwner()
3130 if self.owner ~= nil then
3131 return self.owner;
3132 end;
3133 if self.attacherVehicle ~= nil then
3134 return self.attacherVehicle:getOwner();
3135 end;
3136 return nil;
3137end;

onActivate

Description
Called on activate
Definition
onActivate()
Code
3141function Vehicle:onActivate()
3142 for _,v in pairs(self.specializations) do
3143 if v.onActivate ~= nil then
3144 v.onActivate(self);
3145 end;
3146 end;
3147end;

onDeactivate

Description
Called on deactivate
Definition
onDeactivate()
Code
3151function Vehicle:onDeactivate()
3152 for _,v in pairs(self.specializations) do
3153 if v.onDeactivate ~= nil then
3154 v.onDeactivate(self);
3155 end;
3156 end;
3157
3158 self:onDeactivateSounds();
3159 self:onDeactivateLights();
3160end;

onDeactivateSounds

Description
Called on deactivate sound
Definition
onDeactivateSounds()
Code
3164function Vehicle:onDeactivateSounds()
3165 if self.isClient then
3166 SoundUtil.stopSample(self.sampleHydraulic);
3167
3168 if g_currentMission.surfaceSounds ~= nil then
3169 for _,surfaceSound in pairs(g_currentMission.surfaceSounds) do
3170 SoundUtil.setSampleVolume(surfaceSound.sample, 0);
3171 SoundUtil.stopSample(surfaceSound.sample);
3172 end
3173 end
3174 end;
3175 for _,v in pairs(self.specializations) do
3176 if v.onDeactivateSounds ~= nil then
3177 v.onDeactivateSounds(self);
3178 end;
3179 end;
3180end;

getIsVehicleNode

Description
Returns true if node is from vehicle
Definition
getIsVehicleNode(integer nodeId)
Arguments
integernodeIdnode id
Return Values
booleanisFromVehicleis from vehicle
Code
3186function Vehicle:getIsVehicleNode(nodeId)
3187 return self.vehicleNodes[nodeId] ~= nil;
3188end;

getIsAttachedVehicleNode

Description
Returns true if node is from vehicle or attached vehicles
Definition
getIsAttachedVehicleNode(integer nodeId)
Arguments
integernodeIdnode id
Return Values
booleanisFromVehicleis from vehicle or attached vehicles
Code
3194function Vehicle:getIsAttachedVehicleNode(nodeId)
3195 if self.vehicleNodes[nodeId] ~= nil then
3196 return true;
3197 end;
3198 for _,v in pairs(self.attachedImplements) do
3199 if v.object ~= nil and v.object:getIsAttachedVehicleNode(nodeId) then
3200 return true;
3201 end;
3202 end;
3203 return false;
3204end;

getIsAttachedTo

Description
Checks if is attached to vehicle
Definition
getIsAttachedTo(table vehicle)
Arguments
tablevehiclevehicle to check
Return Values
booleanisAttachedTois attached to vehicle
Code
3210function Vehicle:getIsAttachedTo(vehicle)
3211 if vehicle == self then
3212 return true;
3213 end;
3214 if self.attacherVehicle ~= nil then
3215 return self.attacherVehicle:getIsAttachedTo(vehicle);
3216 end;
3217 return false;
3218end;

getIsDynamicallyMountedNode

Description
Returns true if node is dynamically mounted
Definition
getIsDynamicallyMountedNode(integer nodeId)
Arguments
integernodeIdid of node
Return Values
booleanisDynamicallyMountedis dynamically mounted
Code
3224function Vehicle:getIsDynamicallyMountedNode(nodeId)
3225 if self.dynamicMountedObjects ~= nil then
3226 local object = g_currentMission:getNodeObject(nodeId);
3227 if object == nil then
3228 object = g_currentMission.nodeToVehicle[nodeId];
3229 end;
3230 if object ~= nil then
3231 if self.dynamicMountedObjects[object] ~= nil or (self.pendingDynamicMountObjects ~= nil and self.pendingDynamicMountObjects[object] ~= nil) then
3232 return true;
3233 end;
3234 end;
3235 end;
3236 for _,v in pairs(self.attachedImplements) do
3237 if v.object ~= nil then
3238 if v.object:getIsDynamicallyMountedNode(nodeId) then
3239 return true;
3240 end;
3241 end;
3242 end;
3243end;

getRootAttacherVehicle

Description
Returns root attacher vehicle
Definition
getRootAttacherVehicle()
Return Values
tablerootAttacherVehicleroot attacher vehicle
Code
3248function Vehicle:getRootAttacherVehicle()
3249 local rootVehicle = self;
3250 while rootVehicle.attacherVehicle ~= nil do
3251 rootVehicle = rootVehicle.attacherVehicle;
3252 end
3253 return rootVehicle;
3254end

getMaximalAirConsumptionPerFullStop

Description
Returns maximal air consumption per full stop
Definition
getMaximalAirConsumptionPerFullStop()
Return Values
floatmaximalAirConsumptionPerFullStopmaximal air consumption per full stop
Code
3259function Vehicle:getMaximalAirConsumptionPerFullStop()
3260 local airConsumption = self.maximalAirConsumptionPerFullStop;
3261 for _,implement in pairs(self.attachedImplements) do
3262 if implement.object ~= nil then
3263 airConsumption = airConsumption + implement.object:getMaximalAirConsumptionPerFullStop();
3264 end
3265 end
3266 return airConsumption;
3267end

setComponentsVisibility

Description
Set visibility of components
Definition
setComponentsVisibility(boolean visibility)
Arguments
booleanvisibilityvisibility
Code
3272function Vehicle:setComponentsVisibility(visibility)
3273 if self.componentsVisibility ~= visibility then
3274 self.componentsVisibility = visibility;
3275 if visibility then
3276 self:addToPhysics();
3277 else
3278 self:removeFromPhysics();
3279 end
3280 end;
3281end;

setComponentJointFrame

Description
Set component joint frame
Definition
setComponentJointFrame(integer jointDesc, integer anchorActor)
Arguments
integerjointDescjoint desc index
integeranchorActoranchor actor
Code
3287function Vehicle:setComponentJointFrame(jointDesc, anchorActor)
3288 if anchorActor == 0 then
3289 local localPoses = jointDesc.jointLocalPoses[1]
3290 localPoses.trans[1], localPoses.trans[2], localPoses.trans[3] = localToLocal(jointDesc.jointNode, self.components[jointDesc.componentIndices[1]].node, 0, 0, 0)
3291 localPoses.rot[1], localPoses.rot[2], localPoses.rot[3] = localRotationToLocal(jointDesc.jointNode, self.components[jointDesc.componentIndices[1]].node, 0, 0, 0)
3292 else
3293 local localPoses = jointDesc.jointLocalPoses[2]
3294 localPoses.trans[1], localPoses.trans[2], localPoses.trans[3] = localToLocal(jointDesc.jointNodeActor1, self.components[jointDesc.componentIndices[2]].node, 0, 0, 0)
3295 localPoses.rot[1], localPoses.rot[2], localPoses.rot[3] = localRotationToLocal(jointDesc.jointNodeActor1, self.components[jointDesc.componentIndices[2]].node, 0, 0, 0)
3296 end
3297
3298 local jointNode = jointDesc.jointNode;
3299 if anchorActor == 1 then
3300 jointNode = jointDesc.jointNodeActor1;
3301 end
3302
3303 if jointDesc.jointIndex ~= 0 then
3304 setJointFrame(jointDesc.jointIndex, anchorActor, jointNode);
3305 end
3306end

setComponentJointRotLimit

Description
Set component joint rot limit
Definition
setComponentJointRotLimit(integer componentJoint, integer axis, float minLimit, float maxLimit)
Arguments
integercomponentJointindex of component joint
integeraxisaxis
floatminLimitmin limit
floatmaxLimitmax limit
Code
3315function Vehicle:setComponentJointRotLimit(componentJoint, axis, minLimit, maxLimit)
3316 if self.isServer then
3317 componentJoint.rotLimit[axis] = maxLimit;
3318 componentJoint.rotMinLimit[axis] = minLimit;
3319
3320 if componentJoint.jointIndex ~= 0 then
3321 if minLimit <= maxLimit then
3322 setJointRotationLimit(componentJoint.jointIndex, axis-1, true, minLimit, maxLimit);
3323 else
3324 setJointRotationLimit(componentJoint.jointIndex, axis-1, false, 0, 0);
3325 end
3326 end
3327 end
3328end

setComponentJointTransLimit

Description
Set component joint trans limit
Definition
setComponentJointTransLimit(integer componentJoint, integer axis, float minLimit, float maxLimit)
Arguments
integercomponentJointindex of component joint
integeraxisaxis
floatminLimitmin limit
floatmaxLimitmax limit
Code
3336function Vehicle:setComponentJointTransLimit(componentJoint, axis, minLimit, maxLimit)
3337 if self.isServer then
3338 componentJoint.transLimit[axis] = maxLimit;
3339 componentJoint.transMinLimit[axis] = minLimit;
3340
3341 if componentJoint.jointIndex ~= 0 then
3342 if minLimit <= maxLimit then
3343 setJointTranslationLimit(componentJoint.jointIndex, axis-1, true, minLimit, maxLimit);
3344 else
3345 setJointTranslationLimit(componentJoint.jointIndex, axis-1, false, 0, 0);
3346 end
3347 end
3348 end
3349end

loadComponentFromXML

Description
Load component from xml
Definition
loadComponentFromXML(table component, integer xmlFile, string key, table rootPosition, integer i)
Arguments
tablecomponentcomponent
integerxmlFileid of xml object
stringkeykey
tablerootPositionroot position (x, y, z)
integericomponent index
Return Values
booleansuccesssuccess
Code
3359function Vehicle:loadComponentFromXML(component, xmlFile, key, rootPosition, i)
3360 if not self.isServer then
3361 setRigidBodyType(component.node, "Kinematic");
3362 end;
3363 link(getRootNode(), component.node);
3364 if i == 1 then
3365 rootPosition[1], rootPosition[2], rootPosition[3] = getTranslation(component.node);
3366 if rootPosition[2] ~= 0 then
3367 print("Warning: Y-Translation of component 1 (node 0>) ("..self.i3dFilename..") should be 0, but it is: ".. tostring(rootPosition[2]));
3368 end;
3369 end;
3370 -- the position of the first component is the zero
3371 translate(component.node, -rootPosition[1], -rootPosition[2], -rootPosition[3]);
3372 local x,y,z = getTranslation(component.node);
3373 local rx,ry,rz = getRotation(component.node)
3374 local qx,qy,qz,qw = getQuaternion(component.node);
3375 component.originalTranslation = {x,y,z};
3376 component.originalRotation = {rx,ry,rz};
3377
3378 component.curTranslation = {x,y,z};
3379 component.lastTranslation = {x,y,z};
3380 component.targetTranslation = {x,y,z};
3381 component.curRotation = {qx,qy,qz,qw};
3382 component.lastRotation = {qx,qy,qz,qw};
3383 component.targetRotation = {qx,qy,qz,qw};
3384 component.sentTranslation = {x,y,z};
3385 component.sentRotation = {rx,ry,rz};
3386
3387 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#centerOfMass"));
3388 if x ~= nil and y ~= nil and z ~= nil then
3389 setCenterOfMass(component.node, x, y, z);
3390 component.centerOfMass = { x, y, z };
3391 end;
3392 local count = getXMLInt(xmlFile, key .. "#solverIterationCount");
3393 if count ~= nil then
3394 setSolverIterationCount(component.node, count);
3395 component.solverIterationCount = count;
3396 end;
3397 component.motorized = getXMLBool(xmlFile, key .. "#motorized"); -- Note: motorized is nil if not set in the xml, and can be set by the wheels
3398 self.vehicleNodes[component.node] = {component=component};
3399 local clipDistance = getClipDistance(component.node);
3400 if clipDistance >= 1000000 and getVisibility(component.node) then
3401 local defaultClipdistance = 300;
3402 print("Warning: No clipdistance set to node "..(i-1).."> ("..self.i3dFilename.."). Set default clipdistance ("..defaultClipdistance..")");
3403 setClipDistance(component.node, defaultClipdistance);
3404 end;
3405
3406 component.collideWithAttachables = Utils.getNoNil(getXMLBool(xmlFile, key.."#collideWithAttachables"), false)
3407
3408 if g_isDevelopmentVersion then
3409 if getLinearDamping(component.node) > 0.01 then
3410 print(string.format("DevWarning: Non-zero linear damping (%.4f) for node %d in (%s). Is this correct?", getLinearDamping(component.node), i-1, self.i3dFilename));
3411 elseif getAngularDamping(component.node) > 0.05 then
3412 print(string.format("DevWarning: Large angular damping (%.4f) for node %d in (%s). Is this correct?", getAngularDamping(component.node), i-1, self.i3dFilename));
3413 elseif getAngularDamping(component.node) < 0.0001 then
3414 print(string.format("DevWarning: Zero damping for node %d in (%s). Is this correct?", i-1, self.i3dFilename));
3415 end
3416 end
3417 return true;
3418end;

loadComponentJointFromXML

Description
Load component joints from xml
Definition
loadComponentJointFromXML(table jointDesc, integer xmlFile, string key, integer componentJointI, integer jointNode, integer index1, integer index2)
Arguments
tablejointDescjoint desc
integerxmlFileid of xml object
stringkeykey
integercomponentJointIcomponent joint index
integerjointNodeid of joint node
integerindex1index of component 1
integerindex2index of component 2
Return Values
booleansuccesssuccess
Code
3430function Vehicle:loadComponentJointFromXML(jointDesc, xmlFile, key, componentJointI, jointNode, index1, index2)
3431 jointDesc.componentIndices = {index1+1, index2+1};
3432 jointDesc.jointNode = jointNode;
3433 jointDesc.jointNodeActor1 = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#indexActor1")), jointNode);
3434 if self.isServer then
3435 if self.components[index1+1] == nil or self.components[index2+1] == nil then
3436 print("Error: invalid joint indices (".. index1.. ", "..index2..") for component joint "..componentJointI.." in '"..self.configFileName.."'");
3437 return false;
3438 end;
3439
3440 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimit"));
3441 local rotLimits = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) };
3442 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimit"));
3443 local transLimits = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3444 jointDesc.rotLimit = rotLimits;
3445 jointDesc.transLimit = transLimits;
3446
3447 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotMinLimit"));
3448 local rotMinLimits = { Utils.getNoNilRad(x, nil), Utils.getNoNilRad(y, nil), Utils.getNoNilRad(z, nil) };
3449
3450 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transMinLimit"));
3451 local transMinLimits = { x,y,z };
3452
3453 for i=1,3 do
3454 if rotMinLimits[i] == nil then
3455 if rotLimits[i] >= 0 then
3456 rotMinLimits[i] = -rotLimits[i];
3457 else
3458 rotMinLimits[i] = rotLimits[i]+1;
3459 end
3460 end
3461 if transMinLimits[i] == nil then
3462 if transLimits[i] >= 0 then
3463 transMinLimits[i] = -transLimits[i];
3464 else
3465 transMinLimits[i] = transLimits[i]+1;
3466 end
3467 end
3468 end
3469
3470 jointDesc.jointLocalPoses = {}
3471 local trans = {localToLocal(jointDesc.jointNode, self.components[index1+1].node, 0, 0, 0)}
3472 local rot = {localRotationToLocal(jointDesc.jointNode, self.components[index1+1].node, 0, 0, 0)}
3473 jointDesc.jointLocalPoses[1] = {trans=trans, rot=rot}
3474
3475 local trans = {localToLocal(jointDesc.jointNodeActor1, self.components[index2+1].node, 0, 0, 0)}
3476 local rot = {localRotationToLocal(jointDesc.jointNodeActor1, self.components[index2+1].node, 0, 0, 0)}
3477 jointDesc.jointLocalPoses[2] = {trans=trans, rot=rot}
3478
3479 jointDesc.rotMinLimit = rotMinLimits;
3480 jointDesc.transMinLimit = transMinLimits;
3481
3482 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimitSpring"));
3483 local rotLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3484 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimitDamping"));
3485 local rotLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) };
3486 jointDesc.rotLimitSpring = rotLimitSpring;
3487 jointDesc.rotLimitDamping = rotLimitDamping;
3488
3489 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimitForceLimit"));
3490 local rotLimitForceLimit = { Utils.getNoNil(x, -1), Utils.getNoNil(y, -1), Utils.getNoNil(z, -1) };
3491 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimitForceLimit"));
3492 local transLimitForceLimit = { Utils.getNoNil(x, -1), Utils.getNoNil(y, -1), Utils.getNoNil(z, -1) };
3493 jointDesc.rotLimitForceLimit = rotLimitForceLimit;
3494 jointDesc.transLimitForceLimit = transLimitForceLimit;
3495
3496 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimitSpring"));
3497 local transLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3498 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimitDamping"));
3499 local transLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) };
3500 jointDesc.transLimitSpring = transLimitSpring;
3501 jointDesc.transLimitDamping = transLimitDamping;
3502
3503 jointDesc.zRotationXOffset = 0;
3504 local zRotationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#zRotationNode"));
3505 if zRotationNode ~= nil then
3506 jointDesc.zRotationXOffset,_,_ = localToLocal(zRotationNode, jointNode, 0,0,0);
3507 end
3508
3509 jointDesc.isBreakable = Utils.getNoNil(getXMLBool(xmlFile, key.."#breakable"), false)
3510 if jointDesc.isBreakable then
3511 jointDesc.breakForce = Utils.getNoNil(getXMLFloat(xmlFile, key.."#breakForce"), 10);
3512 jointDesc.breakTorque = Utils.getNoNil(getXMLFloat(xmlFile, key.."#breakTorque"), 10);
3513 end;
3514 jointDesc.enableCollision = Utils.getNoNil(getXMLBool(xmlFile, key.."#enableCollision"), false);
3515
3516 -- Rotational drive
3517 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#maxRotDriveForce"));
3518 local maxRotDriveForce = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3519 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotDriveVelocity"));
3520 local rotDriveVelocity = { Utils.getNoNilRad(x, nil), Utils.getNoNilRad(y, nil), Utils.getNoNilRad(z, nil) }; -- convert from deg/s to rad/s
3521 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotDriveRotation"));
3522 local rotDriveRotation = { Utils.getNoNilRad(x, nil), Utils.getNoNilRad(y, nil), Utils.getNoNilRad(z, nil) }; -- convert from deg to rad
3523 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotDriveSpring"));
3524 local rotDriveSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3525 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotDriveDamping"));
3526 local rotDriveDamping = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3527
3528 jointDesc.rotDriveVelocity = rotDriveVelocity;
3529 jointDesc.rotDriveRotation = rotDriveRotation;
3530 jointDesc.rotDriveSpring = rotDriveSpring;
3531 jointDesc.rotDriveDamping = rotDriveDamping;
3532 jointDesc.maxRotDriveForce = maxRotDriveForce;
3533
3534 -- Translational drive
3535 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveVelocity"));
3536 local transDriveVelocity = { x,y,z };
3537
3538 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDrivePosition"));
3539 local transDrivePosition = { x,y,z };
3540
3541 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveSpring"));
3542 local transDriveSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3543
3544 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveDamping"));
3545 local transDriveDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) };
3546
3547 local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#maxTransDriveForce"));
3548 local maxTransDriveForce = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
3549
3550 jointDesc.transDriveVelocity = transDriveVelocity;
3551 jointDesc.transDrivePosition = transDrivePosition;
3552 jointDesc.transDriveSpring = transDriveSpring;
3553 jointDesc.transDriveDamping = transDriveDamping;
3554 jointDesc.maxTransDriveForce = maxTransDriveForce;
3555
3556 jointDesc.jointIndex = 0;
3557 end;
3558
3559 return true;
3560end;

createComponentJoint

Description
Create component joint between two components
Definition
createComponentJoint(table component1, table component2, table jointDesc)
Arguments
tablecomponent1component 1
tablecomponent2component 2
tablejointDescjoint desc
Return Values
booleansuccesssuccess
Code
3568function Vehicle:createComponentJoint(component1, component2, jointDesc)
3569 if component1 == nil or component2 == nil or jointDesc == nil then
3570 print("Error: could not create component joint in '"..self.configFileName.."'")
3571 return false
3572 end
3573
3574 local constr = JointConstructor:new();
3575 constr:setActors(component1.node, component2.node);
3576
3577 local localPoses1 = jointDesc.jointLocalPoses[1]
3578 local localPoses2 = jointDesc.jointLocalPoses[2]
3579 constr:setJointLocalPositions(localPoses1.trans[1], localPoses1.trans[2], localPoses1.trans[3], localPoses2.trans[1], localPoses2.trans[2], localPoses2.trans[3])
3580 constr:setJointLocalRotations(localPoses1.rot[1], localPoses1.rot[2], localPoses1.rot[3], localPoses2.rot[1], localPoses2.rot[2], localPoses2.rot[3])
3581 --constr:setJointTransforms(jointDesc.jointNode, jointDesc.jointNodeActor1);
3582
3583 constr:setRotationLimitSpring(jointDesc.rotLimitSpring[1], jointDesc.rotLimitDamping[1], jointDesc.rotLimitSpring[2], jointDesc.rotLimitDamping[2], jointDesc.rotLimitSpring[3], jointDesc.rotLimitDamping[3]);
3584 constr:setTranslationLimitSpring(jointDesc.transLimitSpring[1], jointDesc.transLimitDamping[1], jointDesc.transLimitSpring[2], jointDesc.transLimitDamping[2], jointDesc.transLimitSpring[3], jointDesc.transLimitDamping[3]);
3585 constr:setZRotationXOffset(jointDesc.zRotationXOffset);
3586 for i=1, 3 do
3587 if jointDesc.rotLimit[i] >= jointDesc.rotMinLimit[i] then
3588 constr:setRotationLimit(i-1, jointDesc.rotMinLimit[i], jointDesc.rotLimit[i]);
3589 end;
3590
3591 if jointDesc.transLimit[i] >= jointDesc.transMinLimit[i] then
3592 constr:setTranslationLimit(i-1, true, jointDesc.transMinLimit[i], jointDesc.transLimit[i]);
3593 else
3594 constr:setTranslationLimit(i-1, false, 0, 0);
3595 end;
3596 end;
3597
3598 constr:setRotationLimitForceLimit(jointDesc.rotLimitForceLimit[1], jointDesc.rotLimitForceLimit[2], jointDesc.rotLimitForceLimit[3]);
3599 constr:setTranslationLimitForceLimit(jointDesc.transLimitForceLimit[1], jointDesc.transLimitForceLimit[2], jointDesc.transLimitForceLimit[3]);
3600
3601 if jointDesc.isBreakable then
3602 constr:setBreakable(jointDesc.breakForce, jointDesc.breakTorque);
3603 end
3604 constr:setEnableCollision(jointDesc.enableCollision);
3605
3606 for i=1,3 do
3607 if jointDesc.maxRotDriveForce[i] > 0.0001 and (jointDesc.rotDriveVelocity[i] ~= nil or jointDesc.rotDriveRotation[i] ~= nil) then
3608 local pos = Utils.getNoNil(jointDesc.rotDriveRotation[i], 0);
3609 local vel = Utils.getNoNil(jointDesc.rotDriveVelocity[i], 0);
3610 constr:setAngularDrive(i-1, jointDesc.rotDriveRotation[i] ~= nil, jointDesc.rotDriveVelocity[i] ~= nil, jointDesc.rotDriveSpring[i], jointDesc.rotDriveDamping[i], jointDesc.maxRotDriveForce[i], pos, vel);
3611 end
3612 if jointDesc.maxTransDriveForce[i] > 0.0001 and (jointDesc.transDriveVelocity[i] ~= nil or jointDesc.transDrivePosition[i] ~= nil) then
3613 local pos = Utils.getNoNil(jointDesc.transDrivePosition[i], 0);
3614 local vel = Utils.getNoNil(jointDesc.transDriveVelocity[i], 0);
3615 constr:setLinearDrive(i-1, jointDesc.transDrivePosition[i] ~= nil, jointDesc.transDriveVelocity[i] ~= nil, jointDesc.transDriveSpring[i], jointDesc.transDriveDamping[i], jointDesc.maxTransDriveForce[i], pos, vel);
3616 end
3617 end
3618
3619 jointDesc.jointIndex = constr:finalize();
3620
3621 return true
3622end

loadDynamicallyPartsFromXML

Description
Load dynamically loaded parts from xml and link them to target
Definition
loadDynamicallyPartsFromXML(table dynamicallyLoadedPart, integer xmlFile, string key)
Arguments
tabledynamicallyLoadedPartdynamically loaded part
integerxmlFileid of xml object
stringkeykey
Return Values
booleansuccesssuccess
Code
3630function Vehicle:loadDynamicallyPartsFromXML(dynamicallyLoadedPart, xmlFile, key)
3631 local filename = getXMLString(xmlFile, key .. "#filename");
3632 if filename ~= nil then
3633 dynamicallyLoadedPart.filename = filename;
3634 local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false);
3635 if i3dNode ~= 0 then
3636 local node = Utils.indexToObject(i3dNode, Utils.getNoNil(getXMLString(xmlFile, key .. "#node"), "0|0"));
3637 local linkNode = Utils.indexToObject(self.components, Utils.getNoNil(getXMLString(xmlFile, key .. "#linkNode"), "0>"));
3638
3639 local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#position"));
3640 if x ~= nil and y ~= nil and z ~= nil then
3641 setTranslation(node, x,y,z);
3642 end;
3643 local rotX, rotY, rotZ = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#rotation"));
3644 if rotX ~= nil and rotY ~= nil and rotZ ~= nil then
3645 rotX = Utils.degToRad(rotX);
3646 rotY = Utils.degToRad(rotY);
3647 rotZ = Utils.degToRad(rotZ);
3648 setRotation(node, rotX, rotY, rotZ);
3649 end;
3650
3651 local shaderParameterName = getXMLString(xmlFile, key .. "#shaderParameterName");
3652 local x,y,z,w = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#shaderParameter"));
3653 if shaderParameterName ~= nil and x ~= nil and y ~= nil and z ~= nil and w ~= nil then
3654 setShaderParameter(node, shaderParameterName, x, y, z, w, false);
3655 end;
3656
3657 link(linkNode, node);
3658 delete(i3dNode);
3659 return true;
3660 end;
3661 end;
3662
3663 return false;
3664end;

loadDynamicallyWheelsFromXML

Description
Load dynamically loaded wheel from xml
Definition
loadDynamicallyWheelsFromXML(table dynamicallyLoadedWheel, integer xmlFile, string key)
Arguments
tabledynamicallyLoadedWheeldynamically loaded wheel
integerxmlFileid of xml object
stringkeykey
Return Values
booleansuccesssuccess
Code
3672function Vehicle:loadDynamicallyWheelsFromXML(dynamicallyLoadedWheel, xmlFile, key)
3673
3674 dynamicallyLoadedWheel.linkNode = Utils.indexToObject(self.components, Utils.getNoNil(getXMLString(xmlFile, key .. "#linkNode"), "0>"));
3675
3676 local wheelXmlFilename = getXMLString(xmlFile, key.."#filename")
3677 if wheelXmlFilename ~= nil and wheelXmlFilename ~= "" then
3678 local wheelConfigIndex = Utils.getNoNil(getXMLString(xmlFile, key.."#configIndex"), "basic")
3679 local isLeft = Utils.getNoNil(getXMLBool(xmlFile, key.."#isLeft"), true)
3680 local xRotOffset = Utils.getNoNilRad(getXMLFloat(xmlFile, key.."#xRotOffset"), 0);
3681 local colorStr = getXMLString(xmlFile, key.."#color")
3682 if colorStr ~= nil then
3683 dynamicallyLoadedWheel.color = Vehicle.getColorFromString(colorStr);
3684 end
3685 local axisColorStr = getXMLString(xmlFile, key.."#axisColor")
3686 if axisColorStr ~= nil then
3687 dynamicallyLoadedWheel.axisColor = Vehicle.getColorFromString(axisColorStr);
3688 end
3689 local additionalColorStr = getXMLString(xmlFile, key.."#additionalColor")
3690 if additionalColorStr ~= nil then
3691 dynamicallyLoadedWheel.additionalColor = Vehicle.getColorFromString(additionalColorStr);
3692 end
3693 self:loadWheelDataFromXML(dynamicallyLoadedWheel, wheelXmlFilename, wheelConfigIndex, isLeft, true, xRotOffset);
3694
3695 return true
3696 end
3697
3698 return false;
3699end;

removeFromPhysics

Description
Remove vehicle from physics
Definition
removeFromPhysics()
Code
3703function Vehicle:removeFromPhysics()
3704 for _, component in pairs(self.components) do
3705 removeFromPhysics(component.node)
3706 end
3707 -- invalidate wheel shapes and component joints (removing the components removes the wheels and joints too)
3708 if self.isServer then
3709 for _, wheel in pairs(self.wheels) do
3710 wheel.wheelShape = 0;
3711 end
3712 for _, jointDesc in pairs(self.componentJoints) do
3713 jointDesc.jointIndex = 0;
3714 end
3715 end
3716 self.isAddedToPhysics = false
3717
3718 return true
3719end

addToPhysics

Description
Add vehicle to physics
Definition
addToPhysics()
Return Values
booleansuccesssuccess
Code
3724function Vehicle:addToPhysics()
3725 if not self.isAddedToPhysics then
3726 local lastMotorizedNode = nil
3727 for _, component in pairs(self.components) do
3728 addToPhysics(component.node)
3729 if component.motorized then
3730 if lastMotorizedNode ~= nil then
3731 if self.isServer then
3732 addVehicleLink(lastMotorizedNode, component.node);
3733 end
3734 end
3735 lastMotorizedNode = component.node;
3736 end
3737 end
3738
3739 self.isAddedToPhysics = true
3740
3741 if self.isServer then
3742 for _, jointDesc in pairs(self.componentJoints) do
3743 self:createComponentJoint(self.components[jointDesc.componentIndices[1]], self.components[jointDesc.componentIndices[2]], jointDesc);
3744 end
3745 end
3746
3747 for _, collisionPair in pairs(self.collisionPairs) do
3748 setPairCollision(collisionPair.component1.node, collisionPair.component2.node, collisionPair.enabled);
3749 end
3750
3751 if self.wheels ~= nil then
3752 for _, wheel in pairs(self.wheels) do
3753 wheel.xDriveOffset = wheel.netInfo.xDrive
3754 wheel.updateWheel = false
3755 self:updateWheelBase(wheel);
3756 self:updateWheelTireFriction(wheel);
3757 end
3758 end
3759 end
3760
3761 return true
3762end

getLastSpeed

Description
Returns last speed in kph
Definition
getLastSpeed(boolean useAttacherVehicleSpeed)
Arguments
booleanuseAttacherVehicleSpeeduse speed of attacher vehicle
Return Values
floatlastSpeedlast speed
Code
3768function Vehicle:getLastSpeed(useAttacherVehicleSpeed)
3769 if useAttacherVehicleSpeed then
3770 if self.attacherVehicle ~= nil then
3771 return self.attacherVehicle:getLastSpeed(true);
3772 end;
3773 end;
3774
3775 return self.lastSpeed * 3600;
3776end;

getDirectionSnapAngle

Description
Get direction shape angle
Definition
getDirectionSnapAngle()
Return Values
floatdirectionshape angle
Code
3781function Vehicle:getDirectionSnapAngle()
3782 local maxAngle = 0;
3783 for _,v in pairs(self.attachedImplements) do
3784 if v.object ~= nil then
3785 maxAngle = math.max(maxAngle, v.object:getDirectionSnapAngle());
3786 end
3787 end
3788 return maxAngle;
3789end

getParentComponent

Description
Get parent component of node
Definition
getParentComponent(integer node)
Arguments
integernodeid of node
Return Values
integerparentComponentid of parent component node
Code
3795function Vehicle:getParentComponent(node)
3796 while node ~= 0 do
3797 if self:getIsVehicleNode(node) then
3798 return node;
3799 end;
3800
3801 node = getParent(node);
3802 end;
3803
3804 return 0;
3805end;

getIsOnField

Description
Returns true if vehicle is on a field
Definition
getIsOnField()
Return Values
booleanisOnFieldis on field
Code
3810function Vehicle:getIsOnField()
3811 local wx,wy,wz = getWorldTranslation(self.components[1].node);
3812 local densityBits = getDensityAtWorldPos(g_currentMission.terrainDetailId, wx, wy, wz);
3813 return densityBits ~= 0;
3814end;

getTotalMass

Description
Get total mass of vehicle
Definition
getTotalMass(boolean addAttachables, boolean addOnlyNonWheelAttachables)
Arguments
booleanaddAttachablesadd mass of attached vehicles
booleanaddOnlyNonWheelAttachablesadd only mass of non wheel attachables
Return Values
floattotalMasstotal mass
Code
3821function Vehicle:getTotalMass(addAttachables, addOnlyNonWheelAttachables)
3822 local mass = 0;
3823 if self.isServer then
3824 for _, comp in pairs(self.components) do
3825 mass = mass + getMass(comp.node);
3826 end;
3827 else
3828 mass = self.serverMass;
3829 for _,v in pairs(self.specializations) do
3830 if v.getAdditiveClientMass ~= nil then
3831 mass = mass + v.getAdditiveClientMass(self);
3832 end;
3833 end;
3834 end;
3835
3836 if addAttachables then
3837 if addOnlyNonWheelAttachables == nil then
3838 addOnlyNonWheelAttachables = true;
3839 end
3840 for _, implement in pairs(self.attachedImplements) do
3841 if implement.object ~= nil and (table.getn(implement.object.wheels) == 0 or not addOnlyNonWheelAttachables) then
3842 mass = mass + implement.object:getTotalMass(addAttachables, addOnlyNonWheelAttachables);
3843 end;
3844 end
3845 end
3846
3847 return mass;
3848end;

getTotalBrakeForce

Description
Get total break force
Definition
getTotalBrakeForce(boolean addAttachables)
Arguments
booleanaddAttachablesadd break force of attached vehicles
Return Values
floattotalBreakForcetotal break force
Code
3854function Vehicle:getTotalBrakeForce(addAttachables)
3855 local totalBrakeForce = 0;
3856
3857 local brakeForce;
3858 if self.motor ~= nil then
3859 brakeForce = self.motor:getBrakeForce();
3860 elseif self.brakeForce ~= nil then
3861 brakeForce = self.brakeForce;
3862 end
3863 if brakeForce ~= nil then
3864 for _, wheel in ipairs(self.wheels) do
3865 if wheel.hasGroundContact then
3866 totalBrakeForce = totalBrakeForce + brakeForce/wheel.radius;
3867 end
3868 end
3869 end
3870 if addAttachables then
3871 for _, implement in pairs(self.attachedImplements) do
3872 if implement.object ~= nil then
3873 totalBrakeForce = totalBrakeForce + implement.object:getTotalBrakeForce(true);
3874 end
3875 end
3876 end
3877
3878 return totalBrakeForce;
3879end

getIsAIReadyForWork

Description
Returns true if AI is ready for work
Definition
getIsAIReadyForWork()
Return Values
booleanisReadyForWorkai is ready for work
Code
3884function Vehicle.getIsAIReadyForWork(self)
3885 local ret = true;
3886 for _,spec in pairs(self.specializations) do
3887 if spec.getIsAIReadyForWork ~= nil then
3888 local retI = spec.getIsAIReadyForWork(self);
3889 ret = ret and retI;
3890 end
3891 end
3892 return ret;
3893end

aiTurnOn

Description
Turn ai on
Definition
aiTurnOn()
Code
3897function Vehicle:aiTurnOn()
3898 self:onAiTurnOn();
3899end

onAiTurnOn

Description
Called if ai turns on
Definition
onAiTurnOn()
Code
3904function Vehicle:onAiTurnOn()
3905 for _,spec in ipairs(self.specializations) do
3906 if spec.onAiTurnOn ~= nil then
3907 spec.onAiTurnOn(self);
3908 end
3909 end
3910end

aiTurnOff

Description
Turn ai off
Definition
aiTurnOff()
Code
3914function Vehicle:aiTurnOff()
3915 self:onAiTurnOff();
3916end

onAiTurnOff

Description
Called if ai turns off
Definition
onAiTurnOff()
Code
3920function Vehicle:onAiTurnOff()
3921 for _,spec in ipairs(self.specializations) do
3922 if spec.onAiTurnOff ~= nil then
3923 spec.onAiTurnOff(self);
3924 end
3925 end
3926end

aiLower

Description
Lower ai
Definition
aiLower()
Code
3930function Vehicle:aiLower()
3931 self:onAiLower();
3932end

onAiLower

Description
Called if ai lowers
Definition
onAiLower()
Code
3936function Vehicle:onAiLower()
3937 for _,spec in ipairs(self.specializations) do
3938 if spec.onAiLower ~= nil then
3939 spec.onAiLower(self);
3940 end
3941 end
3942end

aiRaise

Description
Raise ai
Definition
aiRaise()
Code
3946function Vehicle:aiRaise()
3947 self:onAiRaise();
3948end

onAiRaise

Description
Called if ai raises
Definition
onAiRaise()
Code
3952function Vehicle:onAiRaise()
3953 for _,spec in ipairs(self.specializations) do
3954 if spec.onAiRaise ~= nil then
3955 spec.onAiRaise(self);
3956 end
3957 end
3958end

aiRotateCenter

Description
Rotate ai center
Definition
aiRotateCenter()
Code
3962function Vehicle:aiRotateCenter()
3963 self:onAiRotateCenter();
3964end

onAiRotateCenter

Description
Called if ai rotates to center
Definition
onAiRotateCenter()
Code
3968function Vehicle:onAiRotateCenter()
3969 for _,spec in ipairs(self.specializations) do
3970 if spec.onAiRotateCenter ~= nil then
3971 spec.onAiRotateCenter(self);
3972 end
3973 end
3974end

aiRotateLeft

Description
Rotate ai left
Definition
aiRotateLeft()
Code
3978function Vehicle:aiRotateLeft()
3979 self:onAiRotateLeft();
3980end

onAiRotateLeft

Description
Called if ai rotates to left
Definition
onAiRotateLeft()
Code
3984function Vehicle:onAiRotateLeft()
3985 for _,spec in ipairs(self.specializations) do
3986 if spec.onAiRotateLeft ~= nil then
3987 spec.onAiRotateLeft(self);
3988 end
3989 end
3990end

aiRotateRight

Description
Rotate ai right
Definition
aiRotateRight()
Code
3994function Vehicle:aiRotateRight()
3995 self:onAiRotateRight();
3996end

onAiRotateRight

Description
Called if ai rotates to right
Definition
onAiRotateRight()
Code
4000function Vehicle:onAiRotateRight()
4001 for _,spec in ipairs(self.specializations) do
4002 if spec.onAiRotateRight ~= nil then
4003 spec.onAiRotateRight(self);
4004 end
4005 end
4006end

loadSchemaOverlay

Description
Load schema overlay from xml file
Definition
loadSchemaOverlay(integer xmlFile)
Arguments
integerxmlFileid of xml object
Code
4011function Vehicle:loadSchemaOverlay(xmlFile)
4012 local schemaOverlayFile = getXMLString(xmlFile, "vehicle.schemaOverlay#file");
4013 if schemaOverlayFile ~= nil then
4014 schemaOverlayFile = Utils.getFilename(schemaOverlayFile, self.baseDirectory);
4015 local width = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#width"), 1) * g_currentMission.vehicleSchemaOverlayScaleX;
4016 local height = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#height"), 1) * g_currentMission.vehicleSchemaOverlayScaleY;
4017 local invisibleBorderRight = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#invisibleBorderRight"), 0.05);
4018 local invisibleBorderLeft = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#invisibleBorderLeft"), 0.05);
4019 local x, y = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.schemaOverlay#attacherJointPosition"));
4020 local baseX, baseY = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.schemaOverlay#basePosition"));
4021 if baseX == nil then
4022 baseX = x;
4023 end;
4024 if baseY == nil then
4025 baseY = y;
4026 end;
4027 if x ~= nil and y ~= nil then
4028 Utils.checkDeprecatedXMLElements(xmlFile, self.configFileName, "vehicle.schemaOverlay.attacherJoint", "vehicle.attacherJoints.attacherJoint.schema")
4029
4030 local overlay = Overlay:new("", schemaOverlayFile, 0,0, width, height);
4031 local schemaOverlayFileSelected = getXMLString(xmlFile, "vehicle.schemaOverlay#fileSelected");
4032 local overlaySelected = overlay;
4033 if schemaOverlayFileSelected ~= nil then
4034 schemaOverlayFileSelected = Utils.getFilename(schemaOverlayFileSelected, self.baseDirectory);
4035 overlaySelected = Overlay:new("", schemaOverlayFileSelected, 0,0, width, height);
4036 end;
4037
4038 local schemaOverlayFileTurnedOn = getXMLString(xmlFile, "vehicle.schemaOverlay#fileTurnedOn");
4039 local overlayTurnedOn = overlay;
4040 if schemaOverlayFileTurnedOn ~= nil then
4041 schemaOverlayFileTurnedOn = Utils.getFilename(schemaOverlayFileTurnedOn, self.baseDirectory);
4042 overlayTurnedOn = Overlay:new("", schemaOverlayFileTurnedOn, 0,0, width, height);
4043 end;
4044
4045 local schemaOverlayFileSelectedTurnedOn = getXMLString(xmlFile, "vehicle.schemaOverlay#fileSelectedTurnedOn");
4046 local overlaySelectedTurnedOn = overlayTurnedOn;
4047 if schemaOverlayFileSelectedTurnedOn ~= nil then
4048 schemaOverlayFileSelectedTurnedOn = Utils.getFilename(schemaOverlayFileSelectedTurnedOn, self.baseDirectory);
4049 overlaySelectedTurnedOn = Overlay:new("", schemaOverlayFileSelectedTurnedOn, 0,0, width, height);
4050 end;
4051
4052 self.schemaOverlay = {overlay=overlay, overlaySelected=overlaySelected, overlayTurnedOn=overlayTurnedOn, overlaySelectedTurnedOn=overlaySelectedTurnedOn, attacherJoints={}, x=x, y=y, baseX=baseX, baseY=baseY, invisibleBorderRight=invisibleBorderRight,invisibleBorderLeft=invisibleBorderLeft};
4053 end;
4054 end;
4055end;

applyDesign

Description
Apply design config
Definition
applyDesign(integer xmlFile, integer configDesignId)
Arguments
integerxmlFileid of xml object
integerconfigDesignIdid of design to apply
Code
4061function Vehicle:applyDesign(xmlFile, configDesignId)
4062 local designKey = string.format("vehicle.designConfigurations.designConfiguration(%d)", configDesignId-1)
4063 if not hasXMLProperty(xmlFile, designKey) then
4064 print("Warning: Invalid design configuration " .. configDesignId);
4065 return
4066 end
4067 local i = 0
4068 while true do
4069 local materialKey = string.format(designKey..".material(%d)", i)
4070 if not hasXMLProperty(xmlFile, materialKey) then
4071 break
4072 end
4073 local baseMaterialNode = Utils.indexToObject(self.components, getXMLString(xmlFile, materialKey.."#node"));
4074 local refMaterialNode = Utils.indexToObject(self.components, getXMLString(xmlFile, materialKey.."#refNode"));
4075 if baseMaterialNode ~= nil and refMaterialNode ~= nil then
4076 local oldMaterial = getMaterial(baseMaterialNode, 0)
4077 local newMaterial = getMaterial(refMaterialNode, 0)
4078 for _, component in pairs(self.components) do
4079 self:replaceMaterialRec(component.node, oldMaterial, newMaterial)
4080 end;
4081 end
4082 i = i + 1
4083 end
4084 ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.designConfigurations.designConfiguration", configDesignId, self.components, self);
4085end

replaceMaterialRec

Description
Replace material of node
Definition
replaceMaterialRec(integer node, integer oldMaterial, integer newMaterial)
Arguments
integernodeid of node
integeroldMaterialid of old material
integernewMaterialid of new material
Code
4092function Vehicle:replaceMaterialRec(node, oldMaterial, newMaterial)
4093 if getHasClassId(node, ClassIds.SHAPE) then
4094 local nodeMaterial = getMaterial(node, 0);
4095 if nodeMaterial == oldMaterial then
4096 setMaterial(node, newMaterial, 0)
4097 end
4098 end;
4099
4100 local numChildren = getNumOfChildren(node);
4101 if numChildren > 0 then
4102 for i=0, numChildren-1 do
4103 self:replaceMaterialRec(getChildAt(node, i), oldMaterial, newMaterial)
4104 end;
4105 end;
4106end

setColor

Description
Sets color of vehicle
Definition
setColor(integer xmlFile, string configName, integer configColorId)
Arguments
integerxmlFileid of xml object
stringconfigNamename of config
integerconfigColorIdid of config color to use
Code
4113function Vehicle:setColor(xmlFile, configName, configColorId)
4114 local color = Vehicle.getColorByConfigId(self, configName, configColorId)
4115 if color ~= nil then
4116 local r,g,b,a = unpack(color);
4117 local i = 0;
4118 while true do
4119 local colorKey = string.format("vehicle.%sConfigurations.colorNode(%d)", configName, i)
4120 if not hasXMLProperty(xmlFile, colorKey) then
4121 break;
4122 end
4123
4124 local node = Utils.indexToObject(self.components, getXMLString(xmlFile, colorKey .. "#node"));
4125 if node ~= nil then
4126 if getHasClassId(node, ClassIds.SHAPE) then
4127 if Utils.getNoNil(getXMLBool(xmlFile, colorKey .. "#recursive"), false) then
4128 Utils.setShaderParameterRec(node, "colorScale", r, g, b, a);
4129 else
4130 setShaderParameter(node, "colorScale", r, g, b, a, false);
4131 end
4132 else
4133 print("Warning: Could not set vehicle color to '"..getName(node).."' because node is not a shape!")
4134 end
4135 end
4136 i = i + 1;
4137 end
4138 end
4139end

getPrice

Description
Returns price
Definition
getPrice(float price)
Arguments
floatpriceprice
Code
4144function Vehicle:getPrice()
4145 return self.price;
4146end

getDailyUpKeep

Description
Get daily up keep
Definition
getDailyUpKeep()
Return Values
floatdailyUpKeepdaily up keep
Code
4151function Vehicle:getDailyUpKeep()
4152 local storeItem = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()]
4153
4154 local multiplier = 1
4155 if storeItem.lifetime ~= nil and storeItem.lifetime ~= 0 then
4156 local ageMultiplier = 0.3 * math.min(self.age/storeItem.lifetime, 1)
4157 local operatingTime = self.operatingTime / (1000*60*60)
4158 local operatingTimeMultiplier = 0.7 * math.min(operatingTime / (storeItem.lifetime*EconomyManager.LIFETIME_OPERATINGTIME_RATIO), 1)
4159 multiplier = 1 + EconomyManager.MAX_DAILYUPKEEP_MULTIPLIER * (ageMultiplier+operatingTimeMultiplier)
4160 end
4161
4162 return StoreItemsUtil.getDailyUpkeep(storeItem, self.configurations) * multiplier
4163end

getSellPrice

Description
Get sell price
Definition
getSellPrice()
Return Values
floatsellPricesell price
Code
4168function Vehicle:getSellPrice()
4169 local priceMultiplier = 0.75
4170 local maxVehicleAge = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()].lifetime;
4171
4172 if maxVehicleAge ~= nil and maxVehicleAge ~= 0 then
4173 local ageMultiplier = 0.5 * math.min(self.age/maxVehicleAge, 1)
4174 local operatingTime = self.operatingTime / (1000*60*60)
4175 local operatingTimeMultiplier = 0.5 * math.min(operatingTime / (maxVehicleAge*EconomyManager.LIFETIME_OPERATINGTIME_RATIO), 1)
4176 priceMultiplier = priceMultiplier * math.exp(-3.5 * (ageMultiplier+operatingTimeMultiplier))
4177 end
4178
4179 return math.floor(self:getPrice() * math.max(priceMultiplier, 0.05));
4180end

setOperatingTime

Description
Sets operating time
Definition
setOperatingTime(float operatingTime, boolean isLoading)
Arguments
floatoperatingTimenew operating time
booleanisLoadingis loading
Code
4186function Vehicle:setOperatingTime(operatingTime, isLoading)
4187 if not isLoading and self.propertyState == Vehicle.PROPERTY_STATE_LEASED and g_currentMission ~= nil and g_currentMission.economyManager ~= nil and math.floor(operatingTime/(1000*60*60)) > math.floor(self.operatingTime/(1000*60*60)) then
4188 g_currentMission.economyManager:vehicleOperatingHourChanged(self)
4189 end
4190 self.operatingTime = math.max(Utils.getNoNil(operatingTime, 0), 0);
4191end

dayChanged

Description
Called if day changed
Definition
dayChanged()
Code
4195function Vehicle:dayChanged()
4196 self.age = self.age + 1
4197end

addBoughtConfiguration

Description
Add bought configuration
Definition
addBoughtConfiguration(string name, integer id)
Arguments
stringnameof bought configuration type
integeridid of bought configuration
Code
4219function Vehicle:addBoughtConfiguration(name, id)
4220 if self.boughtConfigurations[name] == nil then
4221 self.boughtConfigurations[name] = {}
4222 end
4223 self.boughtConfigurations[name][id] = true
4224end

hasBoughtConfiguration

Description
Returns true if configuration has been bought
Definition
hasBoughtConfiguration(string name, integer id)
Arguments
stringnameof bought configuration type
integeridid of bought configuration
Return Values
booleanconfigurationHasBeenBoughtconfiguration has been bought
Code
4231function Vehicle:hasBoughtConfiguration(name, id)
4232 if self.boughtConfigurations[name] ~= nil and self.boughtConfigurations[name][id] then
4233 return true
4234 end
4235 return false
4236end

setConfiguration

Description
Set configuration value
Definition
setConfiguration(string name, integer id)
Arguments
stringnamename of configuration type
integeridid of configuration value
Code
4242function Vehicle:setConfiguration(name, id)
4243 self.configurations[name] = id
4244end

getReloadXML

Description
Get reload xml
Definition
getReloadXML(table vehicle)
Arguments
tablevehiclevehicle
Return Values
stringxmlxml
Code
4250function Vehicle.getReloadXML(vehicle)
4251 local xml = '<?xml version="1.0" encoding="utf-8" standalone="no" ?>\n<careerVehicles>\n';
4252
4253 xml = xml..' <vehicle id="1" filename="'.. Utils.encodeToHTML(Utils.convertToNetworkFilename(vehicle.configFileName))..'"';
4254 local attributes, nodes = vehicle:getSaveAttributesAndNodes(" ");
4255 if attributes ~= nil and attributes ~= "" then
4256 xml = xml.." "..attributes;
4257 end;
4258 if nodes ~= nil and nodes ~= "" then
4259 xml = xml..">\n"..nodes.."\n </vehicle>\n";
4260 else
4261 xml = xml.."/>\n";
4262 end;
4263 xml = xml.."</careerVehicles>";
4264
4265 return xml;
4266end

getColorByConfigId

Description
Returns color of config id
Definition
getColorByConfigId(string configName, integer configId)
Arguments
stringconfigNamename if config
integerconfigIdid of config to get color
Return Values
tablecolorcolor (r, g, b)
Code
4273function Vehicle.getColorByConfigId(self, configName, configId)
4274 local configId = self.configurations[configName];
4275 if configId ~= nil then
4276 local item = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()];
4277 local config = item.configurations[configName][configId];
4278 if config ~= nil then
4279 return config.color;
4280 end;
4281 end;
4282
4283 return nil;
4284end;

getConfigurationValue

Description
Get value of configuration
Definition
getConfigurationValue(integer xmlFile, string key, string subKey, string param, function xmlFunc, any_type defaultValue, string fallbackConfigKey, string fallbackOldgKey)
Arguments
integerxmlFileid of xml object
stringkeykey
stringsubKeysub key
stringparamparameter
functionxmlFuncxml function
any_typedefaultValuedefault value
stringfallbackConfigKeyfallback config key
stringfallbackOldgKeyfallback old key
Return Values
any_typevaluevalue of config
Code
4297function Vehicle.getConfigurationValue(xmlFile, key, subKey, param, xmlFunc, defaultValue, fallbackConfigKey, fallbackOldKey)
4298 local value = nil;
4299 if key ~= nil then
4300 value = xmlFunc(xmlFile, key..subKey..param);
4301 end;
4302
4303 if value == nil and fallbackConfigKey ~= nil then
4304 value = xmlFunc(xmlFile, fallbackConfigKey..subKey..param); -- Check for default configuration (xml index 0)
4305 end;
4306 if value == nil and fallbackOldKey ~= nil then
4307 value = xmlFunc(xmlFile, fallbackOldKey..subKey..param); -- Fallback to old xml setup
4308 end;
4309 return Utils.getNoNil(value, defaultValue); -- using default value
4310end;

getXMLConfigurationKey

Description
Get xml configuration key
Definition
getXMLConfigurationKey(integer xmlFile, integer index, string key, string defaultKey, string configurationKey)
Arguments
integerxmlFileid of xml object
integerindexindex
stringkeykey
stringdefaultKeydefault key
stringconfigurationKeyconfiguration key
Return Values
stringconfigKeykey of configuration
integerconfigIndexindex of configuration
Code
4321function Vehicle.getXMLConfigurationKey(xmlFile, index, key, defaultKey, configurationKey)
4322 local configIndex = Utils.getNoNil(index, 1);
4323 local configKey = string.format(key.."(%d)", configIndex-1);
4324 if index ~= nil and not hasXMLProperty(xmlFile, configKey) then
4325 print("Warning: Invalid "..configurationKey.." index '"..tostring(index).."'. Using default "..configurationKey.." settings instead!");
4326 end;
4327
4328 if not hasXMLProperty(xmlFile, configKey) then
4329 configKey = key.."(0)";
4330 end;
4331 if not hasXMLProperty(xmlFile, configKey) then
4332 configKey = defaultKey;
4333 end;
4334
4335 return configKey, configIndex;
4336end;

getConfigColorSingleItemLoad

Description
Get config color single item load
Definition
getConfigColorSingleItemLoad(integer xmlFile, string baseXMLName, string baseDir, string customEnvironment, boolean isMod, table configItem)
Arguments
integerxmlFileid of xml object
stringbaseXMLNamebase xml name
stringbaseDirbase directory
stringcustomEnvironmentcustom environment
booleanisModis mod
tableconfigItemconfig item
Code
4346function Vehicle.getConfigColorSingleItemLoad(xmlFile, baseXMLName, baseDir, customEnvironment, isMod, configItem)
4347 configItem.color = Vehicle.getColorFromString(Utils.getNoNil(getXMLString(xmlFile, baseXMLName.."#color"), "1 1 1 1"));
4348
4349 if configItem.icon == nil then
4350 local icon = getXMLString(xmlFile, baseKey.."#icon")
4351 if icon ~= nil and icon ~= "" then
4352 configItem.icon = Utils.getFilename(icon, baseDir)
4353 end
4354 end
4355end;

getConfigColorPostLoad

Description
Get config color post load
Definition
getConfigColorPostLoad(integer xmlFile, string baseKey, string baseDir, string customEnvironment, boolean isMod, table configurationItems)
Arguments
integerxmlFileid of xml object
stringbaseKeybase key
stringbaseDirbase directory
stringcustomEnvironmentcustom environment
booleanisModis mod
tableconfigurationItemsconfig items
Code
4365function Vehicle.getConfigColorPostLoad(xmlFile, baseKey, baseDir, customEnvironment, isMod, configurationItems)
4366 if Utils.getNoNil(getXMLBool(xmlFile, baseKey.."#useDefaultColors"), false) then
4367 local price = Utils.getNoNil(getXMLInt(xmlFile, baseKey.."#price"), 1000);
4368
4369 local icon = getXMLString(xmlFile, baseKey.."#icon")
4370 if icon ~= nil and icon ~= "" then
4371 icon = Utils.getFilename(icon, baseDir)
4372 end
4373 for _, color in pairs(g_vehicleColors) do
4374 local configItem = StoreItemsUtil.addConfigurationItem(configurationItems, "", "", price, 0, icon);
4375 configItem.color = color;
4376 end
4377 end;
4378
4379 local defaultColorIndex = getXMLInt(xmlFile, baseKey.."#defaultColorIndex")
4380
4381 if defaultColorIndex ~= nil then
4382 for _, item in pairs(configurationItems) do
4383 if item.color == g_vehicleColors[defaultColorIndex] then
4384 item.isDefault = true
4385 item.price = 0
4386 item.icon = nil
4387 end
4388 end
4389 else
4390 if #configurationItems > 0 then
4391 configurationItems[1].isDefault = true
4392 configurationItems[1].price = 0
4393 configurationItems[1].icon = nil
4394 end
4395 end
4396end;

getIsIndoorCameraActive

Description
Returns true if indoor camera is active
Definition
getIsIndoorCameraActive()
Return Values
booleanisActiveindoor camera is active
Code
4401function Vehicle:getIsIndoorCameraActive()
4402 if self.attacherVehicle ~= nil then
4403 return self.attacherVehicle:getIsIndoorCameraActive();
4404 else
4405 if self.cameras ~= nil and self.cameras[self.camIndex] ~= nil then
4406 return self.cameras[self.camIndex].isInside;
4407 end
4408 end
4409 return false;
4410end

getStoreAddtionalConfigData

Description
Get store additional config data
Definition
getStoreAddtionalConfigData(integer xmlFile, string baseXMLName, string baseDir, string customEnvironment, boolean isMod, table configItem)
Arguments
integerxmlFileid of xml object
stringbaseXMLNamebase xml name
stringbaseDirbase directory
stringcustomEnvironmentcustom environment
booleanisModis mod
tableconfigItemconfig item
Code
4426function Vehicle.getStoreAddtionalConfigData(xmlFile, baseXMLName, baseDir, customEnvironment, isMod, configItem)
4427 configItem.vehicleType = getXMLString(xmlFile, baseXMLName.."#vehicleType")
4428end

getSpecValueAge

Description
Returns age spec value
Definition
getSpecValueAge(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
floatageage
Code
4436function Vehicle.getSpecValueAge(storeItem, realItem)
4437 if realItem ~= nil and realItem.age ~= nil then
4438 return string.format(g_i18n:getText("shop_age"), realItem.age)
4439 end
4440 return nil
4441end

getSpecValueDailyUpKeep

Description
Returns daily up keep spec value
Definition
getSpecValueDailyUpKeep(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
floatdailyUpKeepdaily up keep
Code
4448function Vehicle.getSpecValueDailyUpKeep(storeItem, realItem)
4449 local dailyUpkeep = storeItem.dailyUpkeep
4450 if realItem ~= nil and realItem.getDailyUpKeep ~= nil then
4451 dailyUpkeep = realItem:getDailyUpKeep()
4452 end
4453 return string.format(g_i18n:getText("shop_maintenanceValue"), g_i18n:formatMoney(dailyUpkeep, 2))
4454end

getSpecValueOperatingTime

Description
Returns operating time spec value
Definition
getSpecValueOperatingTime(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
floatoperatingTimeoperating time
Code
4461function Vehicle.getSpecValueOperatingTime(storeItem, realItem)
4462 if realItem ~= nil and realItem.operatingTime ~= nil then
4463 local minutes = realItem.operatingTime / (1000 * 60);
4464 local hours = math.floor(minutes / 60);
4465 minutes = math.floor((minutes - hours * 60) / 6);
4466 return string.format(g_i18n:getText("shop_operatingTime"), hours, minutes)
4467 end
4468 return nil
4469end

loadSpecValueWorkingWidth

Description
Loads working width spec value
Definition
loadSpecValueWorkingWidth(integer xmlFile, string customEnvironment)
Arguments
integerxmlFileid of xml object
stringcustomEnvironmentcustom environment
Return Values
floatworkingWidthworking width
Code
4476function Vehicle.loadSpecValueWorkingWidth(xmlFile, customEnvironment)
4477 return getXMLString(xmlFile, "vehicle.storeData.specs.workingWidth")
4478end

getSpecValueWorkingWidth

Description
Returns working width spec value
Definition
getSpecValueWorkingWidth(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
floatworkingWidthworking width
Code
4485function Vehicle.getSpecValueWorkingWidth(storeItem, realItem)
4486 if storeItem.specs.workingWidth ~= nil then
4487 return string.format(g_i18n:getText("shop_workingWidthValue"), g_i18n:formatNumber(storeItem.specs.workingWidth, 1, true))
4488 end;
4489 return nil
4490end

loadSpecValueSpeedLimit

Description
Loads speed limit spec value
Definition
loadSpecValueSpeedLimit(integer xmlFile, string customEnvironment)
Arguments
integerxmlFileid of xml object
stringcustomEnvironmentcustom environment
Return Values
floatspeedLimitspeed limit
Code
4498function Vehicle.loadSpecValueSpeedLimit(xmlFile, customEnvironment)
4499 return getXMLString(xmlFile, "vehicle.speedLimit#value")
4500end

getSpecValueSpeedLimit

Description
Returns speed limit spec value
Definition
getSpecValueSpeedLimit(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
floatspeedLimitspeed limit
Code
4507function Vehicle.getSpecValueSpeedLimit(storeItem, realItem)
4508 if storeItem.specs.speedLimit ~= nil then
4509 return string.format(g_i18n:getText("shop_maxSpeed"), string.format("%1d", g_i18n:getSpeed(storeItem.specs.speedLimit)), g_i18n:getSpeedMeasuringUnit())
4510 end
4511 return nil
4512end

loadSpecValueCombinations

Description
Loads combinations spec value
Definition
loadSpecValueCombinations(integer xmlFile, string customEnvironment)
Arguments
integerxmlFileid of xml object
stringcustomEnvironmentcustom environment
Return Values
stringcombinationscombinations
Code
4519function Vehicle.loadSpecValueCombinations(xmlFile, customEnvironment)
4520 return Utils.getXMLI18NValue(xmlFile, "vehicle.storeData.specs", getXMLString, "combination", nil, customEnvironment, false)
4521end

getSpecValueCombinations

Description
Returns combination spec value
Definition
getSpecValueCombinations(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
stringcombinationscombinations
Code
4528function Vehicle.getSpecValueCombinations(storeItem, realItem)
4529 return storeItem.specs.combination
4530end

getColorFromString

Description
Get color from string
Definition
getColorFromString(string colorString)
Arguments
stringcolorStringcolor string
Return Values
tablecolorcolor (r, g, b)
Code
4536function Vehicle.getColorFromString(colorString)
4537 if colorString ~= nil then
4538 local colorVector = Utils.getVectorNFromString(colorString, 4)
4539 if colorVector == nil then
4540 print("Error: Invalid color string '" .. colorString .. "'")
4541 end
4542 return colorVector;
4543 end
4544 return nil;
4545end

getSpecValueSlots

Description
Returns slots spec value
Definition
getSpecValueSlots(table storeItem, table realItem, boolean isGarage)
Arguments
tablestoreItemstore item
tablerealItemreal item
booleanisGarageis garage mode
Return Values
stringslotsslots
Code
4553function Vehicle.getSpecValueSlots(storeItem, realItem, isGarage)
4554 local numOwned = g_currentMission:getNumOwnedItems(storeItem);
4555 local valueText = ""
4556 if isGarage then
4557 local sellSlotUsage = g_currentMission:getStoreItemSlotUsage(storeItem, numOwned == 1);
4558 if sellSlotUsage ~= 0 then
4559 valueText = "+"..sellSlotUsage;
4560 end;
4561 else
4562 local buySlotUsage = g_currentMission:getStoreItemSlotUsage(storeItem, numOwned == 0);
4563 if buySlotUsage ~= 0 then
4564 valueText = "-"..buySlotUsage;
4565 end;
4566 end
4567
4568 if valueText ~= "" then
4569 return valueText
4570 else
4571 return nil
4572 end
4573end