258 | function WorkParticles:loadGroundAnimationMapping(xmlFile, key, mapping, index) |
259 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, key.."#index", key.."#node") --FS17 to FS19 |
260 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, key.."#animMeshIndex", key.."#animMeshNode") --FS17 to FS19 |
261 | |
262 | local node = I3DUtil.indexToObject(self.components, getXMLString(xmlFile, key.."#node"), self.i3dMappings) |
263 | if node == nil then |
264 | g_logManager:xmlWarning(self.configFileName, "Invalid node '%s' for '%s'", getXMLString(xmlFile, key.."#node"), key) |
265 | return false |
266 | end |
267 | |
268 | local groundRefIndex = getXMLInt(xmlFile, key .. "#refNodeIndex") |
269 | if groundRefIndex == nil or self:getGroundReferenceNodeFromIndex(groundRefIndex) == nil then |
270 | g_logManager:xmlWarning(self.configFileName, "Invalid refNodeIndex '%s' for '%s'", getXMLString(xmlFile, key.."#refNodeIndex"), key) |
271 | return false |
272 | end |
273 | |
274 | local animNode |
275 | local animMeshNode = getXMLString(xmlFile, key.."#animMeshNode") |
276 | if animMeshNode ~= nil then |
277 | animNode = I3DUtil.indexToObject(node, animMeshNode, self.i3dMappings) |
278 | if animNode == nil then |
279 | g_logManager:xmlWarning(self.configFileName, "Invalid animMesh node '%s' '%s'", getXMLString(xmlFile, key.."#animMeshNode"), key) |
280 | return false |
281 | end |
282 | else |
283 | local materialType = getXMLString(xmlFile, key.."#materialType") |
284 | local materialId = Utils.getNoNil(getXMLInt(xmlFile, key.."#materialId"), 1) |
285 | if materialType == nil then |
286 | g_logManager:xmlWarning(self.configFileName, "Missing materialType in '%s'", key) |
287 | return false |
288 | end |
289 | |
290 | animNode = node |
291 | |
292 | local material = g_materialManager:getMaterial(FillType.UNKNOWN, materialType, materialId) |
293 | if material ~= nil then |
294 | setMaterial(node, material, 0) |
295 | else |
296 | g_logManager:xmlWarning(self.configFileName, "Invalid materialType '%s' or materialId '%s' in '%s'", materialType, materialId, key) |
297 | end |
298 | |
299 | setVisibility(animNode, false) |
300 | end |
301 | |
302 | mapping.node = node |
303 | mapping.animNode = animNode |
304 | mapping.groundRefNode = self:getGroundReferenceNodeFromIndex(groundRefIndex) |
305 | mapping.lastDepth = 0 |
306 | mapping.speed = 0 |
307 | mapping.maxWorkDepth = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxDepth"), -0.1) |
308 | |
309 | return true |
310 | end |
218 | function WorkParticles:loadGroundAnimations(xmlFile, key, animation, index) |
219 | local filenameStr = getXMLString(self.xmlFile, key .. "#file") |
220 | local i3dNode = nil |
221 | if filenameStr ~= nil then |
222 | i3dNode = g_i3DManager:loadSharedI3DFile(filenameStr, self.baseDirectory) |
223 | end |
224 | |
225 | animation.speedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#speedThreshold"), 0) |
226 | |
227 | animation.mappings = {} |
228 | local j = 0 |
229 | while true do |
230 | local nodeBaseName = string.format(key .. ".node(%d)", j) |
231 | if not hasXMLProperty(self.xmlFile, nodeBaseName) then |
232 | break |
233 | end |
234 | |
235 | local mapping = {} |
236 | if self:loadGroundAnimationMapping(xmlFile, nodeBaseName, mapping, j, i3dNode) then |
237 | table.insert(animation.mappings, mapping) |
238 | end |
239 | j = j + 1 |
240 | end |
241 | |
242 | if i3dNode ~= nil and i3dNode ~= 0 then |
243 | -- link after loading to make sure all indices are correct |
244 | for _, mapping in ipairs(animation.mappings) do |
245 | link(mapping.node, mapping.animNode) |
246 | setVisibility(mapping.animNode, false) |
247 | end |
248 | |
249 | animation.filename = filenameStr |
250 | delete(i3dNode) |
251 | end |
252 | |
253 | return true |
254 | end |
412 | function WorkParticles:loadGroundEffects(xmlFile, key, effect, index) |
413 | effect.speedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#speedThreshold"), 0.5) |
414 | effect.workAreaIndex = getXMLInt(xmlFile, key .. "#workAreaIndex") |
415 | effect.needsSetIsTurnedOn = Utils.getNoNil(getXMLBool(xmlFile, key .. "#needsSetIsTurnedOn"), false) |
416 | effect.effect = g_effectManager:loadEffect(xmlFile, key, self.components, self, self.i3dMappings) |
417 | |
418 | return true |
419 | end |
355 | function WorkParticles:loadGroundParticleMapping(xmlFile, key, mapping, index, i3dNode) |
356 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, key.."#index", key.."#node") --FS17 to FS19 |
357 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, key.."#particleIndex", key.."#particleNode") --FS17 to FS19 |
358 | |
359 | mapping.particleSystem = {} |
360 | |
361 | local node = I3DUtil.indexToObject(self.components, getXMLString(xmlFile, key.."#node"), self.i3dMappings) |
362 | if node == nil then |
363 | g_logManager:xmlWarning(self.configFileName, "Invalid node '%s' for '%s'", getXMLString(xmlFile, key.."#node"), key) |
364 | return false |
365 | end |
366 | |
367 | local groundRefIndex = getXMLInt(xmlFile, key .. "#refNodeIndex") |
368 | if groundRefIndex == nil or self:getGroundReferenceNodeFromIndex(groundRefIndex) == nil then |
369 | g_logManager:xmlWarning(self.configFileName, "Invalid refNodeIndex '%s' for '%s'", getXMLString(xmlFile, key.."#refNodeIndex"), key) |
370 | return false |
371 | end |
372 | |
373 | local particleNode |
374 | local particleNodeIndex = getXMLString(xmlFile, key.."#particleNode") |
375 | if particleNodeIndex ~= nil then |
376 | particleNode = I3DUtil.indexToObject(i3dNode, particleNodeIndex, self.i3dMappings) |
377 | if particleNode == nil then |
378 | g_logManager:xmlWarning(self.configFileName, "Invalid particle node '%s' '%s'", getXMLString(xmlFile, key.."#particleNode"), key) |
379 | return false |
380 | end |
381 | else |
382 | particleNode = node |
383 | |
384 | local particleType = getXMLString(xmlFile, key.."#particleType") |
385 | if particleType == nil then |
386 | g_logManager:xmlWarning(self.configFileName, "Missing particleType in '%s'", key) |
387 | return false |
388 | end |
389 | |
390 | local fillTypeStr = getXMLString(xmlFile, key.."#fillType") |
391 | local fillType = Utils.getNoNil(g_fillTypeManager:getFillTypeIndexByName(fillTypeStr), FillType.UNKNOWN) |
392 | |
393 | local particleSystem = g_particleSystemManager:getParticleSystem(fillType, particleType) |
394 | if particleSystem ~= nil then |
395 | mapping.particleSystem = ParticleUtil.copyParticleSystem(xmlFile, key, particleSystem, node) |
396 | else |
397 | return false |
398 | end |
399 | end |
400 | |
401 | mapping.node = node |
402 | mapping.particleNode = particleNode |
403 | mapping.groundRefNode = self:getGroundReferenceNodeFromIndex(groundRefIndex) |
404 | mapping.speedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, key.."#speedThreshold"), 0.5) |
405 | mapping.movingDirection = getXMLInt(xmlFile, key.."#movingDirection") |
406 | |
407 | return true |
408 | end |
314 | function WorkParticles:loadGroundParticles(xmlFile, key, particle, index) |
315 | particle.mappings = {} |
316 | |
317 | local filename = getXMLString(xmlFile, key .. "#file") |
318 | local i3dNode = nil |
319 | if filename ~= nil then |
320 | filename = Utils.getFilename(filename, self.baseDirectory) |
321 | i3dNode = loadI3DFile(filename, true, true, false) |
322 | end |
323 | |
324 | local j = 0 |
325 | while true do |
326 | local nodeBaseName = string.format(key .. ".node(%d)", j) |
327 | if not hasXMLProperty(xmlFile, nodeBaseName) then |
328 | break |
329 | end |
330 | |
331 | local mapping = {} |
332 | if self:loadGroundParticleMapping(xmlFile, nodeBaseName, mapping, j, i3dNode) then |
333 | table.insert(particle.mappings, mapping) |
334 | end |
335 | j = j + 1 |
336 | end |
337 | |
338 | |
339 | if i3dNode ~= nil and i3dNode ~= 0 then |
340 | -- link after loading to make sure all indices are correct |
341 | for _, mapping in ipairs(particle.mappings) do |
342 | link(mapping.node, mapping.particleNode) |
343 | ParticleUtil.loadParticleSystemFromNode(mapping.particleNode, mapping.particleSystem, false, true) |
344 | end |
345 | |
346 | particle.filename = filename |
347 | delete(i3dNode) |
348 | end |
349 | |
350 | return true |
351 | end |
438 | function WorkParticles:loadGroundReferenceNode(superFunc, xmlFile, key, groundReferenceNode) |
439 | local returnValue = superFunc(self, xmlFile, key, groundReferenceNode) |
440 | |
441 | if returnValue then |
442 | groundReferenceNode.depthNode = I3DUtil.indexToObject(self.components, getXMLString(xmlFile, key .. "#depthNode"), self.i3dMappings) |
443 | groundReferenceNode.movedDistance = 0 |
444 | groundReferenceNode.depth = 0 |
445 | groundReferenceNode.movingDirection = 0 |
446 | end |
447 | |
448 | return returnValue |
449 | end |
477 | function WorkParticles:onDeactivate() |
478 | local spec = self.spec_workParticles |
479 | |
480 | if self.isClient then |
481 | for _,ps in pairs(spec.particles) do |
482 | for _, mapping in ipairs(ps.mappings) do |
483 | ParticleUtil.setEmittingState(mapping.particleSystem, false) |
484 | end |
485 | end |
486 | for _, animation in ipairs(spec.particleAnimations) do |
487 | for _, mapping in ipairs(animation.mappings) do |
488 | setVisibility(mapping.animNode, false) |
489 | end |
490 | end |
491 | end |
492 | end |
108 | function WorkParticles:onDelete() |
109 | local spec = self.spec_workParticles |
110 | |
111 | if self.isClient then |
112 | for _, animation in ipairs(spec.particleAnimations) do |
113 | if animation.filename ~= nil then |
114 | g_i3DManager:releaseSharedI3DFile(animation.filename, self.baseDirectory, true) |
115 | end |
116 | end |
117 | |
118 | for _, ps in pairs(spec.particles) do |
119 | for _, mapping in ipairs(ps.mappings) do |
120 | ParticleUtil.deleteParticleSystem(mapping.particleSystem) |
121 | end |
122 | end |
123 | |
124 | for _, effect in pairs(spec.effects) do |
125 | g_effectManager:deleteEffects(effect.effect) |
126 | end |
127 | end |
128 | end |
49 | function WorkParticles:onLoad(savegame) |
50 | local spec = self.spec_workParticles |
51 | |
52 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.groundParticleAnimations.groundParticleAnimation", "vehicle.workParticles.particleAnimation") --FS17 to FS19 |
53 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.groundParticleAnimations.groundParticle", "vehicle.workParticles.particle") --FS17 to FS19 |
54 | |
55 | if self.isClient then |
56 | spec.particleAnimations = {} |
57 | local i = 0 |
58 | while true do |
59 | local key = string.format("vehicle.workParticles.particleAnimation(%d)", i) |
60 | if not hasXMLProperty(self.xmlFile, key) then |
61 | break |
62 | end |
63 | |
64 | local animation = {} |
65 | if self:loadGroundAnimations(self.xmlFile, key, animation, i) then |
66 | table.insert(spec.particleAnimations, animation) |
67 | end |
68 | |
69 | i = i + 1 |
70 | end |
71 | |
72 | spec.particles = {} |
73 | i = 0 |
74 | while true do |
75 | local key = string.format("vehicle.workParticles.particle(%d)", i) |
76 | if not hasXMLProperty(self.xmlFile, key) then |
77 | break |
78 | end |
79 | |
80 | local particle = {} |
81 | if self:loadGroundParticles(self.xmlFile, key, particle, i) then |
82 | table.insert(spec.particles, particle) |
83 | end |
84 | |
85 | i = i + 1 |
86 | end |
87 | |
88 | spec.effects = {} |
89 | i = 0 |
90 | while true do |
91 | local key = string.format("vehicle.workParticles.effect(%d)", i) |
92 | if not hasXMLProperty(self.xmlFile, key) then |
93 | break |
94 | end |
95 | |
96 | local effect = {} |
97 | if self:loadGroundEffects(self.xmlFile, key, effect, i) then |
98 | table.insert(spec.effects, effect) |
99 | end |
100 | |
101 | i = i + 1 |
102 | end |
103 | end |
104 | end |
133 | function WorkParticles:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
134 | local spec = self.spec_workParticles |
135 | |
136 | if self.isClient then |
137 | local isOnField = self:getIsOnField() |
138 | |
139 | for _, animation in ipairs(spec.particleAnimations) do |
140 | for _, mapping in ipairs(animation.mappings) do |
141 | local refNode = mapping.groundRefNode |
142 | if refNode ~= nil and refNode.depthNode ~= nil then |
143 | local depth = MathUtil.clamp(refNode.depth / mapping.maxWorkDepth, 0, 1) |
144 | if not isOnField then |
145 | depth = 0 |
146 | end |
147 | |
148 | depth = MathUtil.clamp(math.min(depth, mapping.lastDepth + refNode.movingDirection * (refNode.movedDistance/0.5)), 0 ,1) |
149 | mapping.lastDepth = depth |
150 | mapping.speed = mapping.speed - (refNode.movedDistance * refNode.movingDirection) |
151 | setVisibility(mapping.animNode, depth > 0) |
152 | setShaderParameter(mapping.animNode, "VertxoffsetVertexdeformMotionUVscale", -6, depth, mapping.speed, 1.5, false) |
153 | end |
154 | end |
155 | end |
156 | |
157 | local lastSpeed = self:getLastSpeed(true) |
158 | local enabled = self:getDoGroundManipulation() and isOnField |
159 | |
160 | for _,ps in pairs(spec.particles) do |
161 | for _, mapping in ipairs(ps.mappings) do |
162 | local nodeEnabled = enabled |
163 | if nodeEnabled then |
164 | nodeEnabled = mapping.groundRefNode.isActive and lastSpeed > mapping.speedThreshold |
165 | end |
166 | if mapping.movingDirection ~= nil then |
167 | nodeEnabled = nodeEnabled and mapping.movingDirection == self.movingDirection |
168 | end |
169 | ParticleUtil.setEmittingState(mapping.particleSystem, nodeEnabled) |
170 | end |
171 | end |
172 | |
173 | for _,effect in pairs(spec.effects) do |
174 | local state = enabled and lastSpeed > effect.speedThreshold |
175 | |
176 | if effect.needsSetIsTurnedOn then |
177 | if self.getIsTurnedOn ~= nil then |
178 | local turnedOn = self:getIsTurnedOn() |
179 | if self.getAttacherVehicle ~= nil then |
180 | local attacherVehicle = self:getAttacherVehicle() |
181 | if attacherVehicle ~= nil then |
182 | if attacherVehicle.getIsTurnedOn ~= nil then |
183 | turnedOn = turnedOn or attacherVehicle:getIsTurnedOn() |
184 | end |
185 | end |
186 | end |
187 | |
188 | state = state and turnedOn |
189 | end |
190 | end |
191 | |
192 | local workArea = self:getWorkAreaByIndex(effect.workAreaIndex) |
193 | if workArea ~= nil then |
194 | if workArea.requiresGroundContact then |
195 | state = state and workArea.groundReferenceNode ~= nil and workArea.groundReferenceNode.isActive |
196 | end |
197 | end |
198 | |
199 | if state then |
200 | local fillType = self:getFillTypeFromWorkAreaIndex(effect.workAreaIndex) |
201 | g_effectManager:setFillType(effect.effect, fillType) |
202 | g_effectManager:startEffects(effect.effect) |
203 | else |
204 | g_effectManager:stopEffects(effect.effect) |
205 | end |
206 | end |
207 | end |
208 | end |
21 | function WorkParticles.registerFunctions(vehicleType) |
22 | SpecializationUtil.registerFunction(vehicleType, "getDoGroundManipulation", WorkParticles.getDoGroundManipulation) |
23 | SpecializationUtil.registerFunction(vehicleType, "loadGroundAnimations", WorkParticles.loadGroundAnimations) |
24 | SpecializationUtil.registerFunction(vehicleType, "loadGroundAnimationMapping", WorkParticles.loadGroundAnimationMapping) |
25 | SpecializationUtil.registerFunction(vehicleType, "loadGroundParticles", WorkParticles.loadGroundParticles) |
26 | SpecializationUtil.registerFunction(vehicleType, "loadGroundParticleMapping", WorkParticles.loadGroundParticleMapping) |
27 | SpecializationUtil.registerFunction(vehicleType, "loadGroundEffects", WorkParticles.loadGroundEffects) |
28 | SpecializationUtil.registerFunction(vehicleType, "getFillTypeFromWorkAreaIndex", WorkParticles.getFillTypeFromWorkAreaIndex) |
29 | end |
453 | function WorkParticles:updateGroundReferenceNode(superFunc, groundReferenceNode, x, y, z, terrainHeight, densityHeight) |
454 | |
455 | superFunc(self, groundReferenceNode, x, y, z, terrainHeight, densityHeight) |
456 | if self.isClient and groundReferenceNode.depthNode ~= nil then |
457 | local newX, newY, newZ = getWorldTranslation(groundReferenceNode.depthNode) |
458 | if groundReferenceNode.lastPosition == nil then |
459 | groundReferenceNode.lastPosition = {newX, newY, newZ} |
460 | end |
461 | local dx, dy, dz = worldDirectionToLocal(groundReferenceNode.depthNode, newX-groundReferenceNode.lastPosition[1], newY-groundReferenceNode.lastPosition[2], newZ-groundReferenceNode.lastPosition[3]) |
462 | groundReferenceNode.movingDirection = 0 |
463 | if dz > 0.0001 then |
464 | groundReferenceNode.movingDirection = 1 |
465 | elseif dz < -0.0001 then |
466 | groundReferenceNode.movingDirection = -1 |
467 | end |
468 | groundReferenceNode.movedDistance = MathUtil.vector3Length(dx, dy, dz) |
469 | groundReferenceNode.lastPosition = {newX, newY, newZ} |
470 | local terrainHeightDepthNode = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, newX, newY, newZ) |
471 | groundReferenceNode.depth = newY - terrainHeightDepthNode |
472 | end |
473 | end |