Script v1.4.4.0
Engine v7.0.0.2
- General
- Entity
- Node
- Scenegraph
- Lighting
- Camera
- Shape
- Particle System
- Physics
- Spline
- Animation
- Overlays
- Sound
- Input
- XML
- Network
- Callbacks
- Text Rendering
- Terrain Detail
- Tire Track
- Editor
- Rendering
- String
- Math
- I3D
- Fillplanes
Foundation Reference
Vehicle
DescriptionBase class for all vehiclesFunctions
- registerInteractionFlag
- new
- load
- loadFinished
- updateWheelBase
- updateWheelTireFriction
- loadDynamicWheelDataFromXML
- loadWheelParticleSystem
- loadWheelDataFromXML
- loadWheelsSteeringDataFromXML
- delete
- deleteVisualWheel
- readStream
- writeStream
- readUpdateStream
- writeUpdateStream
- testScope
- getUpdatePriority
- onGhostRemove
- onGhostAdd
- getSaveAttributesAndNodes
- getXMLStatsAttributes
- setRelativePosition
- setWorldPosition
- setWorldPositionQuaternion
- addNodeVehicleMapping
- removeNodeVehicleMapping
- mouseEvent
- keyEvent
- update
- updateTick
- drawUIInfo
- draw
- getDriveGroundParticleSystemsScale
- getSpeedLimit
- getAttachedTrailersFillLevelAndCapacity
- getFillLevelInformation
- handleToggleTipStateEvent
- handleAttachEvent
- handleAttachAttachableEvent
- handleDetachAttachableEvent
- handleLowerImplementEvent
- getIsActiveForInput
- getIsActiveForSound
- getIsOperating
- getIsActive
- hasInputConflictWithSelection
- hasInputConflict
- addConflictCheckedInput
- removeConflictCheckedInput
- getIsHired
- getOwner
- onActivate
- onDeactivate
- onDeactivateSounds
- getIsVehicleNode
- getIsAttachedVehicleNode
- getIsAttachedTo
- getIsDynamicallyMountedNode
- getRootAttacherVehicle
- getMaximalAirConsumptionPerFullStop
- setComponentsVisibility
- setComponentJointFrame
- setComponentJointRotLimit
- setComponentJointTransLimit
- loadComponentFromXML
- loadComponentJointFromXML
- createComponentJoint
- loadDynamicallyPartsFromXML
- loadDynamicallyWheelsFromXML
- removeFromPhysics
- addToPhysics
- getLastSpeed
- getDirectionSnapAngle
- getParentComponent
- getIsOnField
- getTotalMass
- getTotalBrakeForce
- getIsAIReadyForWork
- aiTurnOn
- onAiTurnOn
- aiTurnOff
- onAiTurnOff
- aiLower
- onAiLower
- aiRaise
- onAiRaise
- aiRotateCenter
- onAiRotateCenter
- aiRotateLeft
- onAiRotateLeft
- aiRotateRight
- onAiRotateRight
- loadSchemaOverlay
- applyDesign
- replaceMaterialRec
- setColor
- getPrice
- getDailyUpKeep
- getSellPrice
- setOperatingTime
- dayChanged
- addBoughtConfiguration
- hasBoughtConfiguration
- setConfiguration
- getReloadXML
- getColorByConfigId
- getConfigurationValue
- getXMLConfigurationKey
- getConfigColorSingleItemLoad
- getConfigColorPostLoad
- getIsIndoorCameraActive
- getStoreAddtionalConfigData
- getSpecValueAge
- getSpecValueDailyUpKeep
- getSpecValueOperatingTime
- loadSpecValueWorkingWidth
- getSpecValueWorkingWidth
- loadSpecValueSpeedLimit
- getSpecValueSpeedLimit
- loadSpecValueCombinations
- getSpecValueCombinations
- getColorFromString
- getSpecValueSlots
registerInteractionFlag
DescriptionRegister interaction flagDefinition
registerInteractionFlag(string name)Arguments
string | name | name of flag |
49 | function 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 |
55 | end |
new
DescriptionCreating vehicle objectDefinition
new(boolean isServer, boolean isClient, table customMt)Arguments
boolean | isServer | is server |
boolean | isClient | is client |
table | customMt | custom metatable |
table | instance | Instance of object |
63 | function 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; |
79 | end; |
load
DescriptionLoad vehicleDefinition
load(table vehicleData, function asyncCallbackFunction, table asyncCallbackObject, table asyncCallbackArguments)Arguments
table | vehicleData | data from vehicle |
function | asyncCallbackFunction | asyncron callback function |
table | asyncCallbackObject | asyncron callback object |
table | asyncCallbackArguments | asyncron callback arguments |
integer | vehicleLoadState | vehicle load state |
88 | function 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 |
137 | end |
loadFinished
DescriptionLoad finishedDefinition
loadFinished(integer i3dNode, table arguments)Arguments
integer | i3dNode | i3d node id |
table | arguments | arguments |
integer | vehicleLoadState | vehicle load state |
144 | function 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; |
782 | end; |
updateWheelBase
DescriptionUpdates wheel baseDefinition
updateWheelBase(table wheel)Arguments
table | wheel | wheel to update |
787 | function 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; |
811 | end |
updateWheelTireFriction
DescriptionUpdates wheel tire frictionDefinition
updateWheelTireFriction(table wheel)Arguments
table | wheel | wheel to update tire friction |
816 | function 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; |
820 | end |
loadDynamicWheelDataFromXML
DescriptionLoading dynamic wheel data from xmlDefinition
loadDynamicWheelDataFromXML(integer xmlFile, string key, string wheelnamei, table wheel)Arguments
integer | xmlFile | id of xml object |
string | key | key to load |
string | wheelnamei | wheel name with index |
table | wheel | wheel table to write in |
828 | function 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; |
1165 | end |
loadWheelParticleSystem
DescriptionLoad wheel particle systemDefinition
loadWheelParticleSystem(table wheel, integer xmlFile, string wheelKey, float xOffset)Arguments
table | wheel | wheel |
integer | xmlFile | id of xml object |
string | wheelKey | wheek key |
float | xOffset | x offset of particleSystem |
1173 | function 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); |
1200 | end |
loadWheelDataFromXML
DescriptionLoading wheel data from xmlDefinition
loadWheelDataFromXML(table wheel, string xmlFilename, integer wheelConfigIndex, boolean isLeft, boolean isRealWheel, float xRotOffset)Arguments
table | wheel | wheel |
string | xmlFilename | xml file name |
integer | wheelConfigIndex | wheel config index |
boolean | isLeft | is left |
boolean | isRealWheel | is real wheel |
float | xRotOffset | x rotation offset |
1210 | function 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; |
1357 | end; |
loadWheelsSteeringDataFromXML
DescriptionLoading wheel steering data from xmlDefinition
loadWheelsSteeringDataFromXML(integer xmlFile, integer ackermannSteeringIndex)Arguments
integer | xmlFile | id of xml object |
integer | ackermannSteeringIndex | ackermann steering index |
1363 | function 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; |
1480 | end |
delete
DescriptionDeleting vehicleDefinition
delete()Code
1484 | function 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; |
1569 | end; |
deleteVisualWheel
DescriptionDeleting visual wheelDefinition
deleteVisualWheel(table wheel)Arguments
table | wheel | wheel |
1574 | function 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 |
1593 | end |
readStream
DescriptionCalled on client side on joinDefinition
readStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
1599 | function 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; |
1692 | end; |
writeStream
DescriptionCalled on server side on joinDefinition
writeStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
1698 | function 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; |
1773 | end; |
readUpdateStream
DescriptionCalled on client side on updateDefinition
readUpdateStream(integer streamId, integer timestamp, table connection)Arguments
integer | streamId | stream ID |
integer | timestamp | timestamp |
table | connection | connection |
1780 | function 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; |
1871 | end; |
writeUpdateStream
DescriptionCalled on server side on updateDefinition
writeUpdateStream(integer streamId, table connection, integer dirtyMask)Arguments
integer | streamId | stream ID |
table | connection | connection |
integer | dirtyMask | dirty mask |
1878 | function 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; |
1924 | end; |
testScope
DescriptionTest if vehicle is in scopeDefinition
testScope(float x, float y, float z, float coeff)Arguments
float | x | x world position |
float | y | y world position |
float | z | z world position |
float | coeff | coeff |
boolean | isInScope | is in scope |
1933 | function 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; |
1945 | end; |
getUpdatePriority
DescriptionTest if vehicle is in scopeDefinition
getUpdatePriority(float skipCount, float x, float y, float z, float coeff, table connection)Arguments
float | skipCount | skip count |
float | x | x world position |
float | y | y world position |
float | z | z world position |
float | coeff | coeff |
table | connection | connection |
float | priority | priority |
1956 | function 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; |
1964 | end; |
onGhostRemove
DescriptionCalled on ghost removeDefinition
onGhostRemove()Code
1968 | function Vehicle:onGhostRemove() |
1969 | for _,v in pairs(self.specializations) do |
1970 | if v.onGhostRemove ~= nil then |
1971 | v.onGhostRemove(self); |
1972 | end; |
1973 | end; |
1974 | end; |
onGhostAdd
DescriptionCalled on ghost addDefinition
onGhostAdd()Code
1978 | function Vehicle:onGhostAdd() |
1979 | for _,v in pairs(self.specializations) do |
1980 | if v.onGhostAdd ~= nil then |
1981 | v.onGhostAdd(self); |
1982 | end; |
1983 | end; |
1984 | end; |
getSaveAttributesAndNodes
DescriptionGet Save attributes and nodesDefinition
getSaveAttributesAndNodes(string nodeIdent, boolean usedModNames)Arguments
string | nodeIdent | node ident |
boolean | usedModNames | used mod names |
string | attributes | attributes |
string | nodes | nodes |
1992 | function 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; |
2033 | end; |
getXMLStatsAttributes
DescriptionGet xml states attributesDefinition
getXMLStatsAttributes()Return Values
string | attributes | attributes |
2038 | function 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; |
2069 | end; |
setRelativePosition
DescriptionSet relative position of vehicleDefinition
setRelativePosition(float positionX, float offsetY, float positionZ, float yRot)Arguments
float | positionX | x position |
float | offsetY | y offset |
float | positionZ | z position |
float | yRot | y rotation |
2077 | function 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; |
2101 | end; |
setWorldPosition
DescriptionSet world position and rotation of componentDefinition
setWorldPosition(float x, float y, float z, float xRot, float yRot, float zRot, integer i, boolean changeInterp)Arguments
float | x | x position |
float | y | y position |
float | z | z position |
float | xRot | x rotation |
float | yRot | y rotation |
float | zRot | z rotation |
integer | i | index if component |
boolean | changeInterp | change interpolation |
2113 | function 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 |
2128 | end; |
setWorldPositionQuaternion
DescriptionSet world position and quaternion rotation of componentDefinition
setWorldPositionQuaternion(float x, float y, float z, float qx, float qy, float qz, float qw, integer i, boolean changeInterp)Arguments
float | x | x position |
float | y | y position |
float | z | z position |
float | qx | x rotation |
float | qy | y rotation |
float | qz | z rotation |
float | qw | w rotation |
integer | i | index if component |
boolean | changeInterp | change interpolation |
2141 | function 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 |
2155 | end; |
addNodeVehicleMapping
DescriptionAdd component nodes to listDefinition
addNodeVehicleMapping(table list)Arguments
table | list | list |
2160 | function 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; |
2170 | end; |
removeNodeVehicleMapping
DescriptionRemove component nodes from listDefinition
removeNodeVehicleMapping(table list)Arguments
table | list | list |
2175 | function 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; |
2185 | end; |
mouseEvent
DescriptionMouse eventDefinition
mouseEvent(float posX, float posY, boolean isDown, boolean isUp, integer button)Arguments
float | posX | mouse position x (0-1) |
float | posY | mouse position y (0-1) |
boolean | isDown | button is down |
boolean | isUp | button is up |
integer | button | acting button |
2194 | function 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; |
2205 | end; |
keyEvent
DescriptionKey eventDefinition
keyEvent(integer unicode, integer sym, integer modifier, boolean isDown)Arguments
integer | unicode | unicode |
integer | sym | sym |
integer | modifier | modifier |
boolean | isDown | button is down |
2213 | function 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; |
2224 | end; |
update
DescriptionUpdateDefinition
update(float dt)Arguments
float | dt | time since last call in ms |
2229 | function 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; |
2613 | end; |
updateTick
DescriptionupdateTickDefinition
updateTick(float dt)Arguments
float | dt | time since last call in ms |
2618 | function 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; |
2718 | end; |
drawUIInfo
DescriptionDraw UI infoDefinition
drawUIInfo()Code
2722 | function 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; |
2731 | end; |
draw
DescriptionDrawDefinition
draw()Code
2735 | function 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 |
2760 | end; |
getDriveGroundParticleSystemsScale
DescriptionGet drive ground particle system scaleDefinition
getDriveGroundParticleSystemsScale(table particleSystem)Arguments
table | particleSystem | particleSystem |
float | scale | scale |
2766 | function 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; |
2785 | end; |
getSpeedLimit
DescriptionGet speed limitDefinition
getSpeedLimit(boolean onlyIfWorking)Arguments
boolean | onlyIfWorking | only if working |
float | limit | limit |
boolean | doCheckSpeedLimit | do check speed limit |
2792 | function 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; |
2809 | end; |
getAttachedTrailersFillLevelAndCapacity
DescriptionGet fill level and capacity of attached trailersDefinition
getAttachedTrailersFillLevelAndCapacity()Return Values
float | fillLevel | fill level |
float | capacity | capacity |
2819 | function 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; |
2844 | end; |
getFillLevelInformation
DescriptionGet fill level informationDefinition
getFillLevelInformation(table fillLevelInformations)Arguments
table | fillLevelInformations | fill level informations |
2849 | function 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 | |
2876 | end |
handleToggleTipStateEvent
DescriptionHandle toggle tip state eventDefinition
handleToggleTipStateEvent()Code
2880 | function 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 |
2896 | end |
handleAttachEvent
DescriptionHandle attach eventDefinition
handleAttachEvent()Code
2900 | function Vehicle:handleAttachEvent() |
2901 | if self:handleAttachAttachableEvent() then |
2902 | -- |
2903 | elseif self:handleDetachAttachableEvent() then |
2904 | -- |
2905 | end |
2906 | end; |
handleAttachAttachableEvent
DescriptionHandle attach attachable eventDefinition
handleAttachAttachableEvent()Return Values
boolean | success | success |
2911 | function 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; |
2927 | end; |
handleDetachAttachableEvent
DescriptionHandle detach attachable eventDefinition
handleDetachAttachableEvent()Return Values
boolean | success | success |
2932 | function 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; |
2956 | end; |
handleLowerImplementEvent
DescriptionHandle lower implement eventDefinition
handleLowerImplementEvent()Code
2960 | function 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; |
2968 | end; |
getIsActiveForInput
DescriptionReturns true if active for inputDefinition
getIsActiveForInput(boolean onlyTrueIfSelected, boolean activeIfIngameMessageShown)Arguments
boolean | onlyTrueIfSelected | return only true if vehicle is selected |
boolean | activeIfIngameMessageShown | is active if ingame message shown |
boolean | isActiveForInput | is active for input |
2975 | function 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; |
3002 | end; |
getIsActiveForSound
DescriptionReturns true if active for soundDefinition
getIsActiveForSound()Return Values
boolean | isActiveForSound | is active for sound |
3007 | function 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; |
3017 | end; |
getIsOperating
DescriptionReturns true if is operatingDefinition
getIsOperating()Return Values
boolean | isOperating | is operating |
3022 | function Vehicle:getIsOperating() |
3023 | return false; |
3024 | end |
getIsActive
DescriptionReturns true if is activeDefinition
getIsActive()Return Values
boolean | isActive | is active |
3029 | function 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; |
3040 | end; |
hasInputConflictWithSelection
DescriptionReturns true if input conflict with selectionDefinition
hasInputConflictWithSelection(table inputs)Arguments
table | inputs | inputs |
boolean | hasConflict | has conflict |
3046 | function 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); |
3070 | end |
hasInputConflict
DescriptionReturns true if has input conflictDefinition
hasInputConflict(table inputs, boolean recursive, table excludedVehicle)Arguments
table | inputs | inputs |
boolean | recursive | check recursive |
table | excludedVehicle | excluded vehicle |
boolean | hasConflict | has conflict |
3078 | function 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; |
3094 | end |
addConflictCheckedInput
DescriptionAdd conflict checked inputDefinition
addConflictCheckedInput(integer actionIndex)Arguments
integer | actionIndex | index of action |
3099 | function Vehicle:addConflictCheckedInput(actionIndex) |
3100 | self.conflictCheckedInputs[actionIndex] = true; |
3101 | end |
removeConflictCheckedInput
DescriptionRemove conflict checked inputDefinition
removeConflictCheckedInput(integer actionIndex)Arguments
integer | actionIndex | index of action |
3106 | function Vehicle:removeConflictCheckedInput(actionIndex) |
3107 | self.conflictCheckedInputs[actionIndex] = nil; |
3108 | end |
getIsHired
DescriptionGet is hiredDefinition
getIsHired()Return Values
boolean | isHired | is hired |
3117 | function 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; |
3124 | end; |
getOwner
DescriptionGet owner of vehicleDefinition
getOwner()Return Values
table | owner | owner |
3129 | function 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; |
3137 | end; |
onActivate
DescriptionCalled on activateDefinition
onActivate()Code
3141 | function Vehicle:onActivate() |
3142 | for _,v in pairs(self.specializations) do |
3143 | if v.onActivate ~= nil then |
3144 | v.onActivate(self); |
3145 | end; |
3146 | end; |
3147 | end; |
onDeactivate
DescriptionCalled on deactivateDefinition
onDeactivate()Code
3151 | function 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(); |
3160 | end; |
onDeactivateSounds
DescriptionCalled on deactivate soundDefinition
onDeactivateSounds()Code
3164 | function 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; |
3180 | end; |
getIsVehicleNode
DescriptionReturns true if node is from vehicleDefinition
getIsVehicleNode(integer nodeId)Arguments
integer | nodeId | node id |
boolean | isFromVehicle | is from vehicle |
3186 | function Vehicle:getIsVehicleNode(nodeId) |
3187 | return self.vehicleNodes[nodeId] ~= nil; |
3188 | end; |
getIsAttachedVehicleNode
DescriptionReturns true if node is from vehicle or attached vehiclesDefinition
getIsAttachedVehicleNode(integer nodeId)Arguments
integer | nodeId | node id |
boolean | isFromVehicle | is from vehicle or attached vehicles |
3194 | function 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; |
3204 | end; |
getIsAttachedTo
DescriptionChecks if is attached to vehicleDefinition
getIsAttachedTo(table vehicle)Arguments
table | vehicle | vehicle to check |
boolean | isAttachedTo | is attached to vehicle |
3210 | function 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; |
3218 | end; |
getIsDynamicallyMountedNode
DescriptionReturns true if node is dynamically mountedDefinition
getIsDynamicallyMountedNode(integer nodeId)Arguments
integer | nodeId | id of node |
boolean | isDynamicallyMounted | is dynamically mounted |
3224 | function 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; |
3243 | end; |
getRootAttacherVehicle
DescriptionReturns root attacher vehicleDefinition
getRootAttacherVehicle()Return Values
table | rootAttacherVehicle | root attacher vehicle |
3248 | function Vehicle:getRootAttacherVehicle() |
3249 | local rootVehicle = self; |
3250 | while rootVehicle.attacherVehicle ~= nil do |
3251 | rootVehicle = rootVehicle.attacherVehicle; |
3252 | end |
3253 | return rootVehicle; |
3254 | end |
getMaximalAirConsumptionPerFullStop
DescriptionReturns maximal air consumption per full stopDefinition
getMaximalAirConsumptionPerFullStop()Return Values
float | maximalAirConsumptionPerFullStop | maximal air consumption per full stop |
3259 | function 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; |
3267 | end |
setComponentsVisibility
DescriptionSet visibility of componentsDefinition
setComponentsVisibility(boolean visibility)Arguments
boolean | visibility | visibility |
3272 | function 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; |
3281 | end; |
setComponentJointFrame
DescriptionSet component joint frameDefinition
setComponentJointFrame(integer jointDesc, integer anchorActor)Arguments
integer | jointDesc | joint desc index |
integer | anchorActor | anchor actor |
3287 | function 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 |
3306 | end |
setComponentJointRotLimit
DescriptionSet component joint rot limitDefinition
setComponentJointRotLimit(integer componentJoint, integer axis, float minLimit, float maxLimit)Arguments
integer | componentJoint | index of component joint |
integer | axis | axis |
float | minLimit | min limit |
float | maxLimit | max limit |
3315 | function 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 |
3328 | end |
setComponentJointTransLimit
DescriptionSet component joint trans limitDefinition
setComponentJointTransLimit(integer componentJoint, integer axis, float minLimit, float maxLimit)Arguments
integer | componentJoint | index of component joint |
integer | axis | axis |
float | minLimit | min limit |
float | maxLimit | max limit |
3336 | function 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 |
3349 | end |
loadComponentFromXML
DescriptionLoad component from xmlDefinition
loadComponentFromXML(table component, integer xmlFile, string key, table rootPosition, integer i)Arguments
table | component | component |
integer | xmlFile | id of xml object |
string | key | key |
table | rootPosition | root position (x, y, z) |
integer | i | component index |
boolean | success | success |
3359 | function 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; |
3418 | end; |
loadComponentJointFromXML
DescriptionLoad component joints from xmlDefinition
loadComponentJointFromXML(table jointDesc, integer xmlFile, string key, integer componentJointI, integer jointNode, integer index1, integer index2)Arguments
table | jointDesc | joint desc |
integer | xmlFile | id of xml object |
string | key | key |
integer | componentJointI | component joint index |
integer | jointNode | id of joint node |
integer | index1 | index of component 1 |
integer | index2 | index of component 2 |
boolean | success | success |
3430 | function 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; |
3560 | end; |
createComponentJoint
DescriptionCreate component joint between two componentsDefinition
createComponentJoint(table component1, table component2, table jointDesc)Arguments
table | component1 | component 1 |
table | component2 | component 2 |
table | jointDesc | joint desc |
boolean | success | success |
3568 | function 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 |
3622 | end |
loadDynamicallyPartsFromXML
DescriptionLoad dynamically loaded parts from xml and link them to targetDefinition
loadDynamicallyPartsFromXML(table dynamicallyLoadedPart, integer xmlFile, string key)Arguments
table | dynamicallyLoadedPart | dynamically loaded part |
integer | xmlFile | id of xml object |
string | key | key |
boolean | success | success |
3630 | function 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; |
3664 | end; |
loadDynamicallyWheelsFromXML
DescriptionLoad dynamically loaded wheel from xmlDefinition
loadDynamicallyWheelsFromXML(table dynamicallyLoadedWheel, integer xmlFile, string key)Arguments
table | dynamicallyLoadedWheel | dynamically loaded wheel |
integer | xmlFile | id of xml object |
string | key | key |
boolean | success | success |
3672 | function 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; |
3699 | end; |
removeFromPhysics
DescriptionRemove vehicle from physicsDefinition
removeFromPhysics()Code
3703 | function 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 |
3719 | end |
addToPhysics
DescriptionAdd vehicle to physicsDefinition
addToPhysics()Return Values
boolean | success | success |
3724 | function 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 |
3762 | end |
getLastSpeed
DescriptionReturns last speed in kphDefinition
getLastSpeed(boolean useAttacherVehicleSpeed)Arguments
boolean | useAttacherVehicleSpeed | use speed of attacher vehicle |
float | lastSpeed | last speed |
3768 | function 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; |
3776 | end; |
getDirectionSnapAngle
DescriptionGet direction shape angleDefinition
getDirectionSnapAngle()Return Values
float | direction | shape angle |
3781 | function 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; |
3789 | end |
getParentComponent
DescriptionGet parent component of nodeDefinition
getParentComponent(integer node)Arguments
integer | node | id of node |
integer | parentComponent | id of parent component node |
3795 | function 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; |
3805 | end; |
getIsOnField
DescriptionReturns true if vehicle is on a fieldDefinition
getIsOnField()Return Values
boolean | isOnField | is on field |
3810 | function 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; |
3814 | end; |
getTotalMass
DescriptionGet total mass of vehicleDefinition
getTotalMass(boolean addAttachables, boolean addOnlyNonWheelAttachables)Arguments
boolean | addAttachables | add mass of attached vehicles |
boolean | addOnlyNonWheelAttachables | add only mass of non wheel attachables |
float | totalMass | total mass |
3821 | function 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; |
3848 | end; |
getTotalBrakeForce
DescriptionGet total break forceDefinition
getTotalBrakeForce(boolean addAttachables)Arguments
boolean | addAttachables | add break force of attached vehicles |
float | totalBreakForce | total break force |
3854 | function 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; |
3879 | end |
getIsAIReadyForWork
DescriptionReturns true if AI is ready for workDefinition
getIsAIReadyForWork()Return Values
boolean | isReadyForWork | ai is ready for work |
3884 | function 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; |
3893 | end |
aiTurnOn
DescriptionTurn ai onDefinition
aiTurnOn()Code
3897 | function Vehicle:aiTurnOn() |
3898 | self:onAiTurnOn(); |
3899 | end |
onAiTurnOn
DescriptionCalled if ai turns onDefinition
onAiTurnOn()Code
3904 | function Vehicle:onAiTurnOn() |
3905 | for _,spec in ipairs(self.specializations) do |
3906 | if spec.onAiTurnOn ~= nil then |
3907 | spec.onAiTurnOn(self); |
3908 | end |
3909 | end |
3910 | end |
aiTurnOff
DescriptionTurn ai offDefinition
aiTurnOff()Code
3914 | function Vehicle:aiTurnOff() |
3915 | self:onAiTurnOff(); |
3916 | end |
onAiTurnOff
DescriptionCalled if ai turns offDefinition
onAiTurnOff()Code
3920 | function Vehicle:onAiTurnOff() |
3921 | for _,spec in ipairs(self.specializations) do |
3922 | if spec.onAiTurnOff ~= nil then |
3923 | spec.onAiTurnOff(self); |
3924 | end |
3925 | end |
3926 | end |
aiLower
DescriptionLower aiDefinition
aiLower()Code
3930 | function Vehicle:aiLower() |
3931 | self:onAiLower(); |
3932 | end |
onAiLower
DescriptionCalled if ai lowersDefinition
onAiLower()Code
3936 | function Vehicle:onAiLower() |
3937 | for _,spec in ipairs(self.specializations) do |
3938 | if spec.onAiLower ~= nil then |
3939 | spec.onAiLower(self); |
3940 | end |
3941 | end |
3942 | end |
aiRaise
DescriptionRaise aiDefinition
aiRaise()Code
3946 | function Vehicle:aiRaise() |
3947 | self:onAiRaise(); |
3948 | end |
onAiRaise
DescriptionCalled if ai raisesDefinition
onAiRaise()Code
3952 | function Vehicle:onAiRaise() |
3953 | for _,spec in ipairs(self.specializations) do |
3954 | if spec.onAiRaise ~= nil then |
3955 | spec.onAiRaise(self); |
3956 | end |
3957 | end |
3958 | end |
aiRotateCenter
DescriptionRotate ai centerDefinition
aiRotateCenter()Code
3962 | function Vehicle:aiRotateCenter() |
3963 | self:onAiRotateCenter(); |
3964 | end |
onAiRotateCenter
DescriptionCalled if ai rotates to centerDefinition
onAiRotateCenter()Code
3968 | function Vehicle:onAiRotateCenter() |
3969 | for _,spec in ipairs(self.specializations) do |
3970 | if spec.onAiRotateCenter ~= nil then |
3971 | spec.onAiRotateCenter(self); |
3972 | end |
3973 | end |
3974 | end |
aiRotateLeft
DescriptionRotate ai leftDefinition
aiRotateLeft()Code
3978 | function Vehicle:aiRotateLeft() |
3979 | self:onAiRotateLeft(); |
3980 | end |
onAiRotateLeft
DescriptionCalled if ai rotates to leftDefinition
onAiRotateLeft()Code
3984 | function Vehicle:onAiRotateLeft() |
3985 | for _,spec in ipairs(self.specializations) do |
3986 | if spec.onAiRotateLeft ~= nil then |
3987 | spec.onAiRotateLeft(self); |
3988 | end |
3989 | end |
3990 | end |
aiRotateRight
DescriptionRotate ai rightDefinition
aiRotateRight()Code
3994 | function Vehicle:aiRotateRight() |
3995 | self:onAiRotateRight(); |
3996 | end |
onAiRotateRight
DescriptionCalled if ai rotates to rightDefinition
onAiRotateRight()Code
4000 | function Vehicle:onAiRotateRight() |
4001 | for _,spec in ipairs(self.specializations) do |
4002 | if spec.onAiRotateRight ~= nil then |
4003 | spec.onAiRotateRight(self); |
4004 | end |
4005 | end |
4006 | end |
loadSchemaOverlay
DescriptionLoad schema overlay from xml fileDefinition
loadSchemaOverlay(integer xmlFile)Arguments
integer | xmlFile | id of xml object |
4011 | function 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; |
4055 | end; |
applyDesign
DescriptionApply design configDefinition
applyDesign(integer xmlFile, integer configDesignId)Arguments
integer | xmlFile | id of xml object |
integer | configDesignId | id of design to apply |
4061 | function 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); |
4085 | end |
replaceMaterialRec
DescriptionReplace material of nodeDefinition
replaceMaterialRec(integer node, integer oldMaterial, integer newMaterial)Arguments
integer | node | id of node |
integer | oldMaterial | id of old material |
integer | newMaterial | id of new material |
4092 | function 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; |
4106 | end |
setColor
DescriptionSets color of vehicleDefinition
setColor(integer xmlFile, string configName, integer configColorId)Arguments
integer | xmlFile | id of xml object |
string | configName | name of config |
integer | configColorId | id of config color to use |
4113 | function 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 |
4139 | end |
getPrice
DescriptionReturns priceDefinition
getPrice(float price)Arguments
float | price | price |
4144 | function Vehicle:getPrice() |
4145 | return self.price; |
4146 | end |
getDailyUpKeep
DescriptionGet daily up keepDefinition
getDailyUpKeep()Return Values
float | dailyUpKeep | daily up keep |
4151 | function 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 |
4163 | end |
getSellPrice
DescriptionGet sell priceDefinition
getSellPrice()Return Values
float | sellPrice | sell price |
4168 | function 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)); |
4180 | end |
setOperatingTime
DescriptionSets operating timeDefinition
setOperatingTime(float operatingTime, boolean isLoading)Arguments
float | operatingTime | new operating time |
boolean | isLoading | is loading |
4186 | function 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); |
4191 | end |
dayChanged
DescriptionCalled if day changedDefinition
dayChanged()Code
4195 | function Vehicle:dayChanged() |
4196 | self.age = self.age + 1 |
4197 | end |
addBoughtConfiguration
DescriptionAdd bought configurationDefinition
addBoughtConfiguration(string name, integer id)Arguments
string | name | of bought configuration type |
integer | id | id of bought configuration |
4219 | function Vehicle:addBoughtConfiguration(name, id) |
4220 | if self.boughtConfigurations[name] == nil then |
4221 | self.boughtConfigurations[name] = {} |
4222 | end |
4223 | self.boughtConfigurations[name][id] = true |
4224 | end |
hasBoughtConfiguration
DescriptionReturns true if configuration has been boughtDefinition
hasBoughtConfiguration(string name, integer id)Arguments
string | name | of bought configuration type |
integer | id | id of bought configuration |
boolean | configurationHasBeenBought | configuration has been bought |
4231 | function Vehicle:hasBoughtConfiguration(name, id) |
4232 | if self.boughtConfigurations[name] ~= nil and self.boughtConfigurations[name][id] then |
4233 | return true |
4234 | end |
4235 | return false |
4236 | end |
setConfiguration
DescriptionSet configuration valueDefinition
setConfiguration(string name, integer id)Arguments
string | name | name of configuration type |
integer | id | id of configuration value |
4242 | function Vehicle:setConfiguration(name, id) |
4243 | self.configurations[name] = id |
4244 | end |
getReloadXML
DescriptionGet reload xmlDefinition
getReloadXML(table vehicle)Arguments
table | vehicle | vehicle |
string | xml | xml |
4250 | function 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; |
4266 | end |
getColorByConfigId
DescriptionReturns color of config idDefinition
getColorByConfigId(string configName, integer configId)Arguments
string | configName | name if config |
integer | configId | id of config to get color |
table | color | color (r, g, b) |
4273 | function 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; |
4284 | end; |
getConfigurationValue
DescriptionGet value of configurationDefinition
getConfigurationValue(integer xmlFile, string key, string subKey, string param, function xmlFunc, any_type defaultValue, string fallbackConfigKey, string fallbackOldgKey)Arguments
integer | xmlFile | id of xml object |
string | key | key |
string | subKey | sub key |
string | param | parameter |
function | xmlFunc | xml function |
any_type | defaultValue | default value |
string | fallbackConfigKey | fallback config key |
string | fallbackOldgKey | fallback old key |
any_type | value | value of config |
4297 | function 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 |
4310 | end; |
getXMLConfigurationKey
DescriptionGet xml configuration keyDefinition
getXMLConfigurationKey(integer xmlFile, integer index, string key, string defaultKey, string configurationKey)Arguments
integer | xmlFile | id of xml object |
integer | index | index |
string | key | key |
string | defaultKey | default key |
string | configurationKey | configuration key |
string | configKey | key of configuration |
integer | configIndex | index of configuration |
4321 | function 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; |
4336 | end; |
getConfigColorSingleItemLoad
DescriptionGet config color single item loadDefinition
getConfigColorSingleItemLoad(integer xmlFile, string baseXMLName, string baseDir, string customEnvironment, boolean isMod, table configItem)Arguments
integer | xmlFile | id of xml object |
string | baseXMLName | base xml name |
string | baseDir | base directory |
string | customEnvironment | custom environment |
boolean | isMod | is mod |
table | configItem | config item |
4346 | function 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 |
4355 | end; |
getConfigColorPostLoad
DescriptionGet config color post loadDefinition
getConfigColorPostLoad(integer xmlFile, string baseKey, string baseDir, string customEnvironment, boolean isMod, table configurationItems)Arguments
integer | xmlFile | id of xml object |
string | baseKey | base key |
string | baseDir | base directory |
string | customEnvironment | custom environment |
boolean | isMod | is mod |
table | configurationItems | config items |
4365 | function 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 |
4396 | end; |
getIsIndoorCameraActive
DescriptionReturns true if indoor camera is activeDefinition
getIsIndoorCameraActive()Return Values
boolean | isActive | indoor camera is active |
4401 | function 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; |
4410 | end |
getStoreAddtionalConfigData
DescriptionGet store additional config dataDefinition
getStoreAddtionalConfigData(integer xmlFile, string baseXMLName, string baseDir, string customEnvironment, boolean isMod, table configItem)Arguments
integer | xmlFile | id of xml object |
string | baseXMLName | base xml name |
string | baseDir | base directory |
string | customEnvironment | custom environment |
boolean | isMod | is mod |
table | configItem | config item |
4426 | function Vehicle.getStoreAddtionalConfigData(xmlFile, baseXMLName, baseDir, customEnvironment, isMod, configItem) |
4427 | configItem.vehicleType = getXMLString(xmlFile, baseXMLName.."#vehicleType") |
4428 | end |
getSpecValueAge
DescriptionReturns age spec valueDefinition
getSpecValueAge(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
float | age | age |
4436 | function 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 |
4441 | end |
getSpecValueDailyUpKeep
DescriptionReturns daily up keep spec valueDefinition
getSpecValueDailyUpKeep(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
float | dailyUpKeep | daily up keep |
4448 | function 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)) |
4454 | end |
getSpecValueOperatingTime
DescriptionReturns operating time spec valueDefinition
getSpecValueOperatingTime(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
float | operatingTime | operating time |
4461 | function 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 |
4469 | end |
loadSpecValueWorkingWidth
DescriptionLoads working width spec valueDefinition
loadSpecValueWorkingWidth(integer xmlFile, string customEnvironment)Arguments
integer | xmlFile | id of xml object |
string | customEnvironment | custom environment |
float | workingWidth | working width |
4476 | function Vehicle.loadSpecValueWorkingWidth(xmlFile, customEnvironment) |
4477 | return getXMLString(xmlFile, "vehicle.storeData.specs.workingWidth") |
4478 | end |
getSpecValueWorkingWidth
DescriptionReturns working width spec valueDefinition
getSpecValueWorkingWidth(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
float | workingWidth | working width |
4485 | function 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 |
4490 | end |
loadSpecValueSpeedLimit
DescriptionLoads speed limit spec valueDefinition
loadSpecValueSpeedLimit(integer xmlFile, string customEnvironment)Arguments
integer | xmlFile | id of xml object |
string | customEnvironment | custom environment |
float | speedLimit | speed limit |
4498 | function Vehicle.loadSpecValueSpeedLimit(xmlFile, customEnvironment) |
4499 | return getXMLString(xmlFile, "vehicle.speedLimit#value") |
4500 | end |
getSpecValueSpeedLimit
DescriptionReturns speed limit spec valueDefinition
getSpecValueSpeedLimit(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
float | speedLimit | speed limit |
4507 | function 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 |
4512 | end |
loadSpecValueCombinations
DescriptionLoads combinations spec valueDefinition
loadSpecValueCombinations(integer xmlFile, string customEnvironment)Arguments
integer | xmlFile | id of xml object |
string | customEnvironment | custom environment |
string | combinations | combinations |
4519 | function Vehicle.loadSpecValueCombinations(xmlFile, customEnvironment) |
4520 | return Utils.getXMLI18NValue(xmlFile, "vehicle.storeData.specs", getXMLString, "combination", nil, customEnvironment, false) |
4521 | end |
getSpecValueCombinations
DescriptionReturns combination spec valueDefinition
getSpecValueCombinations(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
string | combinations | combinations |
4528 | function Vehicle.getSpecValueCombinations(storeItem, realItem) |
4529 | return storeItem.specs.combination |
4530 | end |
getColorFromString
DescriptionGet color from stringDefinition
getColorFromString(string colorString)Arguments
string | colorString | color string |
table | color | color (r, g, b) |
4536 | function 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; |
4545 | end |
getSpecValueSlots
DescriptionReturns slots spec valueDefinition
getSpecValueSlots(table storeItem, table realItem, boolean isGarage)Arguments
table | storeItem | store item |
table | realItem | real item |
boolean | isGarage | is garage mode |
string | slots | slots |
4553 | function 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 |
4573 | end |