LUADOC - Farming Simulator 22

Script v1_7_1_0

Engine v1_7_1_0

Foundation Reference

GroundAdjustedNodes

Description
Specialization for adjusting nodes to the ground/terrain height (e.g. liquid manure spreaders with hoses)
Functions

getIsGroundAdjustedNodeActive

Description
Definition
getIsGroundAdjustedNodeActive()
Code
336function GroundAdjustedNodes:getIsGroundAdjustedNodeActive(groundAdjustedNode)
337 return self.getAttacherVehicle == nil or self:getAttacherVehicle() ~= nil
338end

groundAdjustRaycastCallback

Description
Raycast callback
Definition
groundAdjustRaycastCallback(integer transformId, float x, float y, float z, float distance)
Arguments
integertransformIdid raycasted object
floatxx raycast position
floatyy raycast position
floatzz raycast position
floatdistancedistance to raycast position
Code
347function GroundAdjustedNodes:groundAdjustRaycastCallback(transformId, x, y, z, distance)
348 if getHasTrigger(transformId) then
349 return true
350 end
351
352 self.lastRaycastDistance = distance
353 self.lastRaycastGroundPos[1], self.lastRaycastGroundPos[2], self.lastRaycastGroundPos[3] = x, y, z
354
355 --#debug DebugUtil.drawDebugGizmoAtWorldPos(x, y, z, 0, 0, 1, 0, 1, 0, "", false)
356
357 return false
358end

initSpecialization

Description
Called on specialization initializing
Definition
initSpecialization()
Code
23function GroundAdjustedNodes.initSpecialization()
24 local schema = Vehicle.xmlSchema
25 schema:setXMLSpecializationType("GroundAdjustedNodes")
26
27 local basePath = GroundAdjustedNodes.GROUND_ADJUSTED_NODE_XML_KEY
28
29 schema:register(XMLValueType.NODE_INDEX, basePath .. "#node", "Ground adjusted node")
30 schema:register(XMLValueType.NODE_INDEX, basePath .. ".raycastNode(?)#node", "Ground adjusted raycast node")
31 schema:register(XMLValueType.FLOAT, basePath .. ".raycastNode(?)#distance", "Ground adjusted raycast distance", 4)
32 schema:register(XMLValueType.INT, basePath .. ".raycastNode(?)#updateFrame", "Defines the frame delay between two raycasts", "Number of raycasts")
33
34 schema:register(XMLValueType.FLOAT, basePath .. "#minY", "Min. Y translation", "translation in i3d - 1")
35 schema:register(XMLValueType.FLOAT, basePath .. "#maxY", "Max. Y translation", "minY + 1")
36 schema:register(XMLValueType.FLOAT, basePath .. "#yOffset", "Y translation offset", 0)
37 schema:register(XMLValueType.FLOAT, basePath .. "#moveSpeed", "Move speed", 1)
38 schema:register(XMLValueType.BOOL, basePath .. "#resetIfNotActive", "Reset node to start translation if not active", true)
39 schema:register(XMLValueType.FLOAT, basePath .. "#activationTime", "In this time after the activation of the node the #moveSpeedStateChange will be used", 0)
40 schema:register(XMLValueType.FLOAT, basePath .. "#moveSpeedStateChange", "Move speed while node is inactive or active an in range of #activationTime", "#moveSpeed")
41 schema:register(XMLValueType.FLOAT, basePath .. "#updateThreshold", "Position of node will be updated if change is greater than this value", 0.002)
42 schema:register(XMLValueType.BOOL, basePath .. "#averageInActivePosY", "While nodes are turned off the average Y position will be used as target for all nodes", false)
43
44 schema:setXMLSpecializationType()
45end

loadGroundAdjustedNodeFromXML

Description
Definition
loadGroundAdjustedNodeFromXML()
Code
138function GroundAdjustedNodes:loadGroundAdjustedNodeFromXML(xmlFile, key, adjustedNode)
139
140 XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, key.."#index", key.."#node") --FS17 to FS19
141
142 local node = xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings)
143 if node == nil then
144 Logging.xmlWarning(self.xmlFile, "Missing 'node' for groundAdjustedNode '%s'!", key)
145 return false
146 end
147
148 local x, y, z = getTranslation(node)
149 adjustedNode.node = node
150 adjustedNode.x = x
151 adjustedNode.y = y
152 adjustedNode.z = z
153 adjustedNode.raycastNodes = {}
154
155 local j = 0
156 while true do
157 local raycastKey = string.format("%s.raycastNode(%d)", key, j)
158 if not self.xmlFile:hasProperty(raycastKey) then
159 break
160 end
161
162 local raycastNode = {}
163 if self:loadGroundAdjustedRaycastNodeFromXML(xmlFile, raycastKey, adjustedNode, raycastNode) then
164 table.insert(adjustedNode.raycastNodes, raycastNode)
165 end
166
167 j = j + 1
168 end
169
170 if #adjustedNode.raycastNodes > 0 then
171 adjustedNode.minY = self.xmlFile:getValue(key.."#minY", y - 1)
172 adjustedNode.maxY = self.xmlFile:getValue(key.."#maxY", adjustedNode.minY + 1)
173 adjustedNode.yOffset = self.xmlFile:getValue(key.."#yOffset", 0)
174 adjustedNode.moveSpeed = (self.xmlFile:getValue(key.."#moveSpeed", 1)) / 1000
175 adjustedNode.moveSpeedStateChange = (self.xmlFile:getValue(key.."#moveSpeedStateChange", 1)) / 1000
176 adjustedNode.activationTime = self.xmlFile:getValue(key.."#activationTime", 0) * 1000
177 adjustedNode.activationTimer = 0
178 adjustedNode.resetIfNotActive = self.xmlFile:getValue(key.."#resetIfNotActive", true)
179 adjustedNode.updateThreshold = self.xmlFile:getValue(key.."#updateThreshold", 0.002)
180
181 adjustedNode.inActiveY = y
182 adjustedNode.averageInActivePosY = self.xmlFile:getValue(key.."#averageInActivePosY", false)
183
184 adjustedNode.targetY = y
185 adjustedNode.curY = y
186 adjustedNode.lastY = y
187 adjustedNode.isActive = false
188 else
189 Logging.xmlWarning(self.xmlFile, "No raycastNodes defined for groundAdjustedNode '%s'!", key)
190 return false
191 end
192
193 return true
194end

loadGroundAdjustedRaycastNodeFromXML

Description
Definition
loadGroundAdjustedRaycastNodeFromXML()
Code
198function GroundAdjustedNodes:loadGroundAdjustedRaycastNodeFromXML(xmlFile, key, groundAdjustedNode, raycastNode)
199 XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, key.."#index", key.."#node") --FS17 to FS19
200
201 local node = self.xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings)
202 if node == nil then
203 Logging.xmlWarning(self.xmlFile, "Missing 'node' for groundAdjustedNodes raycast '%s'!", key)
204 return false
205 end
206
207 if getParent(groundAdjustedNode.node) ~= getParent(node) then
208 Logging.xmlWarning(self.xmlFile, "Raycast node is not on the same hierarchy level as the groundAdjustedNode (%s)!", key)
209 return false
210 end
211
212 local _,y1,_ = getTranslation(node)
213 raycastNode.node = node
214 raycastNode.yDiff = y1 - groundAdjustedNode.y
215 raycastNode.distance = self.xmlFile:getValue(key.."#distance", 4)
216
217 raycastNode.history = {}
218 for i=1, 2 do
219 raycastNode.history[i] = {0, 0, 0, 0}
220 end
221 raycastNode.lastRaycastPos = {0, 0, 0, 0}
222
223 raycastNode.updateFrame = self.xmlFile:getValue(key.."#updateFrame", -1)
224 raycastNode.frameCount = 1
225
226 return true
227end

onLoad

Description
Definition
onLoad()
Code
66function GroundAdjustedNodes:onLoad(savegame)
67 local spec = self.spec_groundAdjustedNodes
68
69 self.raycastMask = CollisionFlag.TERRAIN + CollisionFlag.STATIC_OBJECT
70
71 spec.groundAdjustedNodes = {}
72 local i = 0
73 while true do
74 local key = string.format("vehicle.groundAdjustedNodes.groundAdjustedNode(%d)", i)
75 if not self.xmlFile:hasProperty(key) then
76 break
77 end
78
79 local node = {}
80 if self:loadGroundAdjustedNodeFromXML(self.xmlFile, key, node) then
81 table.insert(spec.groundAdjustedNodes, node)
82 end
83
84 i = i + 1
85 end
86
87 for j=1, #spec.groundAdjustedNodes do
88 local adjustedNode = spec.groundAdjustedNodes[j]
89 for l=1, #adjustedNode.raycastNodes do
90 local raycastNode = adjustedNode.raycastNodes[l]
91 if raycastNode.updateFrame < 0 then
92 raycastNode.updateFrame = #spec.groundAdjustedNodes
93 end
94 raycastNode.frameCount = j % raycastNode.updateFrame + 1
95 end
96 end
97
98 self.lastRaycastDistance = 0
99 self.lastRaycastGroundPos = {0, 0, 0}
100
101 if #spec.groundAdjustedNodes == 0 then
102 SpecializationUtil.removeEventListener(self, "onUpdate", GroundAdjustedNodes)
103 end
104end

onUpdate

Description
Definition
onUpdate()
Code
108function GroundAdjustedNodes:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
109 local spec = self.spec_groundAdjustedNodes
110
111 for _, adjustedNode in pairs(spec.groundAdjustedNodes) do
112 self:updateGroundAdjustedNode(adjustedNode, dt)
113
114 if adjustedNode.targetY ~= adjustedNode.curY then
115 local stateChangeActive = not adjustedNode.isActive or adjustedNode.activationTimer > 0
116 local moveSpeed = stateChangeActive and adjustedNode.moveSpeedStateChange or adjustedNode.moveSpeed
117
118 if adjustedNode.targetY > adjustedNode.curY then
119 adjustedNode.curY = math.min(adjustedNode.curY + moveSpeed*dt, adjustedNode.targetY)
120 else
121 adjustedNode.curY = math.max(adjustedNode.curY - moveSpeed*dt, adjustedNode.targetY)
122 end
123
124 if math.abs(adjustedNode.lastY - adjustedNode.curY) > adjustedNode.updateThreshold then
125 setTranslation(adjustedNode.node, adjustedNode.x, adjustedNode.curY, adjustedNode.z)
126 adjustedNode.lastY = adjustedNode.curY
127
128 if self.setMovingToolDirty ~= nil then
129 self:setMovingToolDirty(adjustedNode.node)
130 end
131 end
132 end
133 end
134end

prerequisitesPresent

Description
Definition
prerequisitesPresent()
Code
17function GroundAdjustedNodes.prerequisitesPresent(specializations)
18 return true
19end

registerEventListeners

Description
Definition
registerEventListeners()
Code
59function GroundAdjustedNodes.registerEventListeners(vehicleType)
60 SpecializationUtil.registerEventListener(vehicleType, "onLoad", GroundAdjustedNodes)
61 SpecializationUtil.registerEventListener(vehicleType, "onUpdate", GroundAdjustedNodes)
62end

registerFunctions

Description
Definition
registerFunctions()
Code
49function GroundAdjustedNodes.registerFunctions(vehicleType)
50 SpecializationUtil.registerFunction(vehicleType, "loadGroundAdjustedNodeFromXML", GroundAdjustedNodes.loadGroundAdjustedNodeFromXML)
51 SpecializationUtil.registerFunction(vehicleType, "loadGroundAdjustedRaycastNodeFromXML", GroundAdjustedNodes.loadGroundAdjustedRaycastNodeFromXML)
52 SpecializationUtil.registerFunction(vehicleType, "getIsGroundAdjustedNodeActive", GroundAdjustedNodes.getIsGroundAdjustedNodeActive)
53 SpecializationUtil.registerFunction(vehicleType, "updateGroundAdjustedNode", GroundAdjustedNodes.updateGroundAdjustedNode)
54 SpecializationUtil.registerFunction(vehicleType, "groundAdjustRaycastCallback", GroundAdjustedNodes.groundAdjustRaycastCallback)
55end

updateGroundAdjustedNode

Description
Definition
updateGroundAdjustedNode()
Code
231function GroundAdjustedNodes:updateGroundAdjustedNode(adjustedNode, dt)
232 local wasActive = adjustedNode.isActive
233 adjustedNode.isActive = self:getIsGroundAdjustedNodeActive(adjustedNode)
234 if adjustedNode.isActive then
235 adjustedNode.activationTimer = math.max(adjustedNode.activationTimer - dt, 0)
236
237 for i=1, #adjustedNode.raycastNodes do
238 local raycastNode = adjustedNode.raycastNodes[i]
239
240 local distance
241 local rx, ry, rz
242 if raycastNode.frameCount == raycastNode.updateFrame then
243 local x,y,z = localToWorld(raycastNode.node, 0, adjustedNode.yOffset, 0)
244 local dx,dy,dz = localDirectionToWorld(raycastNode.node, 0, -1, 0)
245
246 self.lastRaycastDistance = 0
247 raycastAll(x, y, z, dx, dy, dz, "groundAdjustRaycastCallback", raycastNode.distance, self, self.raycastMask)
248 distance = self.lastRaycastDistance
249
250 --#debug drawDebugLine(x, y, z, 0, 1, 0, x+dx*raycastNode.distance, y+dy*raycastNode.distance, z+dz*raycastNode.distance, 1, 0, 0, true)
251
252 if raycastNode.updateFrame > 1 and distance ~= 0 then
253 local oldData = raycastNode.history[2]
254 oldData[1], oldData[2], oldData[3], oldData[4] = self.lastRaycastGroundPos[1], self.lastRaycastGroundPos[2], self.lastRaycastGroundPos[3], self.lastRaycastDistance
255 raycastNode.history[2] = raycastNode.history[1]
256 raycastNode.history[1] = oldData
257
258 rx, ry, rz = oldData[1], oldData[2], oldData[3]
259 end
260 else
261 local history1 = raycastNode.history[1]
262 local history2 = raycastNode.history[2]
263 local x1, y1, z1 = history1[1], history1[2], history1[3]
264 local x2, y2, z2 = history2[1], history2[2], history2[3]
265
266 if raycastNode.lastRaycastPos[1] ~= nil then
267 rx = raycastNode.lastRaycastPos[1] + (x1 - x2) / raycastNode.updateFrame
268 ry = raycastNode.lastRaycastPos[2] + (y1 - y2) / raycastNode.updateFrame
269 rz = raycastNode.lastRaycastPos[3] + (z1 - z2) / raycastNode.updateFrame
270
271 local x, y, z = localToWorld(raycastNode.node, 0, adjustedNode.yOffset, 0)
272 distance = MathUtil.vector3Length(x-rx, y-ry, z-rz)
273 else
274 distance = 0
275 end
276 end
277
278 if raycastNode.updateFrame > 1 then
279 raycastNode.lastRaycastPos[1] = rx
280 raycastNode.lastRaycastPos[2] = ry
281 raycastNode.lastRaycastPos[3] = rz
282 raycastNode.lastRaycastPos[4] = distance
283
284 raycastNode.frameCount = raycastNode.frameCount + 1
285 if raycastNode.frameCount > raycastNode.updateFrame then
286 raycastNode.frameCount = 1
287 end
288 end
289
290 local newY
291 if distance ~= 0 then
292 newY = adjustedNode.y + adjustedNode.yOffset - distance + raycastNode.yDiff
293 else
294 -- if we did not hit the ground we use the last target value, since we don't know where we exceeded the limit
295 newY = adjustedNode.targetY
296 end
297
298 newY = MathUtil.clamp(newY, adjustedNode.minY, adjustedNode.maxY)
299 adjustedNode.targetY = newY
300 local _
301 _, adjustedNode.curY, _ = getTranslation(adjustedNode.node)
302 end
303 else
304 if adjustedNode.averageInActivePosY and wasActive then
305 local groundAdjustedNodes = self.spec_groundAdjustedNodes.groundAdjustedNodes
306 local inActiveY, numNodes = 0, 0
307 for _, _adjustedNode in pairs(groundAdjustedNodes) do
308 if _adjustedNode.averageInActivePosY then
309 inActiveY = inActiveY + _adjustedNode.curY
310 numNodes = numNodes + 1
311 end
312 end
313
314 if numNodes > 0 then
315 adjustedNode.inActiveY = inActiveY / numNodes
316
317 -- reapply to all since some could have already changed
318 for _, _adjustedNode in pairs(groundAdjustedNodes) do
319 if _adjustedNode.averageInActivePosY then
320 _adjustedNode.inActiveY = inActiveY / numNodes
321 end
322 end
323 end
324 end
325
326 if adjustedNode.resetIfNotActive then
327 adjustedNode.targetY = adjustedNode.inActiveY
328 end
329
330 adjustedNode.activationTimer = adjustedNode.activationTime
331 end
332end