443 | function WorkParticles:groundParticleI3DLoaded(i3dNode, failedReason, args) |
444 | local xmlFile = args.xmlFile |
445 | local key = args.key |
446 | local particle = args.particle |
447 | local filename = args.filename |
448 | |
449 | local j = 0 |
450 | while true do |
451 | local nodeBaseName = string.format(key .. ".node(%d)", j) |
452 | if not xmlFile:hasProperty(nodeBaseName) then |
453 | break |
454 | end |
455 | |
456 | local mapping = {} |
457 | if self:loadGroundParticleMapping(xmlFile, nodeBaseName, mapping, j, i3dNode) then |
458 | table.insert(particle.mappings, mapping) |
459 | end |
460 | j = j + 1 |
461 | end |
462 | |
463 | if i3dNode ~= 0 then |
464 | -- link after loading to make sure all indices are correct |
465 | for _, mapping in ipairs(particle.mappings) do |
466 | link(mapping.node, mapping.particleNode) |
467 | ParticleUtil.loadParticleSystemFromNode(mapping.particleNode, mapping.particleSystem, false, true) |
468 | end |
469 | |
470 | particle.filename = filename |
471 | delete(i3dNode) |
472 | end |
473 | end |
23 | function WorkParticles.initSpecialization() |
24 | local schema = Vehicle.xmlSchema |
25 | schema:setXMLSpecializationType("WorkParticles") |
26 | |
27 | schema:register(XMLValueType.STRING, "vehicle.workParticles.particleAnimation(?)#file", "External effect i3d file") |
28 | schema:register(XMLValueType.FLOAT, "vehicle.workParticles.particleAnimation(?)#speedThreshold", "Speed threshold", 0) |
29 | WorkParticles.registerGroundAnimationMappingXMLPaths(schema, "vehicle.workParticles.particleAnimation(?).node(?)") |
30 | |
31 | schema:register(XMLValueType.STRING, "vehicle.workParticles.particle(?)#file", "External effect i3d file") |
32 | WorkParticles.registerGroundParticleMappingXMLPaths(schema, "vehicle.workParticles.particle(?).node(?)") |
33 | |
34 | EffectManager.registerEffectXMLPaths(schema, "vehicle.workParticles.effect(?)") |
35 | schema:register(XMLValueType.FLOAT, "vehicle.workParticles.effect(?)#speedThreshold", "Speed threshold", 0.5) |
36 | schema:register(XMLValueType.INT, "vehicle.workParticles.effect(?)#activeDirection", "Active Direction (effect will be turned off wen in opposite direction)", 1) |
37 | schema:register(XMLValueType.INT, "vehicle.workParticles.effect(?)#workAreaIndex", "Work area index") |
38 | schema:register(XMLValueType.INT, "vehicle.workParticles.effect(?)#groundReferenceNodeIndex", "Index of ground reference node") |
39 | schema:register(XMLValueType.BOOL, "vehicle.workParticles.effect(?)#needsSetIsTurnedOn", "Needs set is turned on", false) |
40 | |
41 | schema:register(XMLValueType.NODE_INDEX, GroundReference.GROUND_REFERENCE_XML_KEY .. "#depthNode", "Depth node") |
42 | |
43 | schema:setXMLSpecializationType() |
44 | end |
353 | function WorkParticles:loadGroundAnimationMapping(xmlFile, key, mapping, index) |
354 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key.."#index", key.."#node") --FS17 to FS19 |
355 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key.."#animMeshIndex", key.."#animMeshNode") --FS17 to FS19 |
356 | |
357 | local node = xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings) |
358 | if node == nil then |
359 | Logging.xmlWarning(self.xmlFile, "Invalid node '%s' for '%s'", getXMLString(xmlFile.handle, key.."#node"), key) |
360 | return false |
361 | end |
362 | |
363 | local groundRefIndex = xmlFile:getValue(key .. "#refNodeIndex") |
364 | if groundRefIndex == nil or self:getGroundReferenceNodeFromIndex(groundRefIndex) == nil then |
365 | Logging.xmlWarning(self.xmlFile, "Invalid refNodeIndex '%s' for '%s'", xmlFile:getValue(key.."#refNodeIndex"), key) |
366 | return false |
367 | end |
368 | |
369 | local animNode |
370 | local animMeshNode = xmlFile:getValue(key.."#animMeshNode") |
371 | if animMeshNode ~= nil then |
372 | animNode = I3DUtil.indexToObject(node, animMeshNode, self.i3dMappings) |
373 | if animNode == nil then |
374 | Logging.xmlWarning(self.xmlFile, "Invalid animMesh node '%s' '%s'", xmlFile:getValue(key.."#animMeshNode"), key) |
375 | return false |
376 | end |
377 | else |
378 | local materialType = xmlFile:getValue(key.."#materialType") |
379 | local materialId = xmlFile:getValue(key.."#materialId", 1) |
380 | if materialType == nil then |
381 | Logging.xmlWarning(self.xmlFile, "Missing materialType in '%s'", key) |
382 | return false |
383 | end |
384 | |
385 | animNode = node |
386 | |
387 | local material = g_materialManager:getBaseMaterialByName(materialType) |
388 | if material ~= nil then |
389 | setMaterial(node, material, 0) |
390 | else |
391 | Logging.xmlWarning(self.xmlFile, "Invalid materialType '%s' or materialId '%s' in '%s'", materialType, materialId, key) |
392 | end |
393 | |
394 | setVisibility(animNode, false) |
395 | end |
396 | |
397 | mapping.node = node |
398 | mapping.animNode = animNode |
399 | mapping.groundRefNode = self:getGroundReferenceNodeFromIndex(groundRefIndex) |
400 | mapping.lastDepth = 0 |
401 | mapping.speed = 0 |
402 | mapping.maxWorkDepth = xmlFile:getValue(key.."#maxDepth", -0.1) |
403 | |
404 | return true |
405 | end |
304 | function WorkParticles:loadGroundAnimations(xmlFile, key, animation, index) |
305 | animation.speedThreshold = xmlFile:getValue(key .. "#speedThreshold", 0) |
306 | |
307 | animation.mappings = {} |
308 | local j = 0 |
309 | while true do |
310 | local nodeBaseName = string.format(key .. ".node(%d)", j) |
311 | if not self.xmlFile:hasProperty(nodeBaseName) then |
312 | break |
313 | end |
314 | |
315 | local mapping = {} |
316 | if self:loadGroundAnimationMapping(xmlFile, nodeBaseName, mapping, j) then |
317 | table.insert(animation.mappings, mapping) |
318 | end |
319 | j = j + 1 |
320 | end |
321 | |
322 | local filenameStr = self.xmlFile:getValue(key .. "#file") |
323 | if filenameStr ~= nil then |
324 | filenameStr = Utils.getFilename(filenameStr, self.baseDirectory) |
325 | local arguments = { |
326 | filename = filenameStr, |
327 | animation = animation |
328 | } |
329 | animation.sharedLoadRequestId = self:loadSubSharedI3DFile(filenameStr, false, false, self.onGroundAnimationI3DLoaded, self, arguments) |
330 | end |
331 | |
332 | return true |
333 | end |
541 | function WorkParticles:loadGroundEffects(xmlFile, key, effect, index) |
542 | effect.speedThreshold = xmlFile:getValue(key .. "#speedThreshold", 0.5) |
543 | effect.activeDirection = xmlFile:getValue(key .. "#activeDirection", 1) |
544 | effect.workAreaIndex = xmlFile:getValue(key .. "#workAreaIndex") |
545 | effect.groundReferenceNodeIndex = xmlFile:getValue(key .. "#groundReferenceNodeIndex") |
546 | effect.needsSetIsTurnedOn = xmlFile:getValue(key .. "#needsSetIsTurnedOn", false) |
547 | effect.effect = g_effectManager:loadEffect(xmlFile, key, self.components, self, self.i3dMappings) |
548 | |
549 | return true |
550 | end |
477 | function WorkParticles:loadGroundParticleMapping(xmlFile, key, mapping, index, i3dNode) |
478 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key.."#index", key.."#node") --FS17 to FS19 |
479 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key.."#particleIndex", key.."#particleNode") --FS17 to FS19 |
480 | |
481 | mapping.particleSystem = {} |
482 | |
483 | local node = xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings) |
484 | if node == nil then |
485 | Logging.xmlWarning(self.xmlFile, "Invalid node '%s' for '%s'", xmlFile:getValue(key.."#node"), key) |
486 | return false |
487 | end |
488 | |
489 | local groundRefIndex = xmlFile:getValue(key .. "#refNodeIndex") |
490 | if groundRefIndex == nil or self:getGroundReferenceNodeFromIndex(groundRefIndex) == nil then |
491 | Logging.xmlWarning(self.xmlFile, "Invalid refNodeIndex '%s' for '%s'", xmlFile:getValue(key.."#refNodeIndex"), key) |
492 | return false |
493 | end |
494 | |
495 | local particleNode |
496 | local particleNodeIndex = xmlFile:getValue(key.."#particleNode") |
497 | if particleNodeIndex ~= nil then |
498 | particleNode = I3DUtil.indexToObject(i3dNode, particleNodeIndex, self.i3dMappings) |
499 | if particleNode == nil then |
500 | Logging.xmlWarning(self.xmlFile, "Invalid particle node '%s' '%s'", xmlFile:getValue(key.."#particleNode"), key) |
501 | return false |
502 | end |
503 | else |
504 | particleNode = node |
505 | |
506 | local particleType = xmlFile:getValue(key.."#particleType") |
507 | if particleType == nil then |
508 | Logging.xmlWarning(self.xmlFile, "Missing particleType in '%s'", key) |
509 | return false |
510 | end |
511 | |
512 | local fillTypeStr = xmlFile:getValue(key.."#fillType") |
513 | local fillType = Utils.getNoNil(g_fillTypeManager:getFillTypeIndexByName(fillTypeStr), FillType.UNKNOWN) |
514 | |
515 | local particleSystem = g_particleSystemManager:getParticleSystem(particleType) |
516 | if particleSystem ~= nil then |
517 | if fillType ~= FillType.UNKNOWN then |
518 | local material = g_materialManager:getParticleMaterial(fillType, particleType, 1) |
519 | if material ~= nil then |
520 | ParticleUtil.setMaterial(particleSystem, material) |
521 | end |
522 | end |
523 | |
524 | mapping.particleSystem = ParticleUtil.copyParticleSystem(xmlFile, key, particleSystem, node) |
525 | else |
526 | return false |
527 | end |
528 | end |
529 | |
530 | mapping.node = node |
531 | mapping.particleNode = particleNode |
532 | mapping.groundRefNode = self:getGroundReferenceNodeFromIndex(groundRefIndex) |
533 | mapping.speedThreshold = xmlFile:getValue(key.."#speedThreshold", 0.5) |
534 | mapping.movingDirection = xmlFile:getValue(key.."#movingDirection") |
535 | |
536 | return true |
537 | end |
409 | function WorkParticles:loadGroundParticles(xmlFile, key, particle, index) |
410 | particle.mappings = {} |
411 | |
412 | local filename = xmlFile:getValue(key .. "#file") |
413 | if filename ~= nil then |
414 | filename = Utils.getFilename(filename, self.baseDirectory) |
415 | local arguments = { |
416 | xmlFile = xmlFile, |
417 | key = key, |
418 | particle = particle, |
419 | filename = filename |
420 | } |
421 | particle.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(filename, false, false, self.groundParticleI3DLoaded, self, arguments) |
422 | else |
423 | local j = 0 |
424 | while true do |
425 | local nodeBaseName = string.format(key .. ".node(%d)", j) |
426 | if not xmlFile:hasProperty(nodeBaseName) then |
427 | break |
428 | end |
429 | |
430 | local mapping = {} |
431 | if self:loadGroundParticleMapping(xmlFile, nodeBaseName, mapping, j) then |
432 | table.insert(particle.mappings, mapping) |
433 | end |
434 | j = j + 1 |
435 | end |
436 | end |
437 | |
438 | return true |
439 | end |
172 | function WorkParticles:onDelete() |
173 | local spec = self.spec_workParticles |
174 | |
175 | if spec.particleAnimations ~= nil then |
176 | for _, animation in ipairs(spec.particleAnimations) do |
177 | if animation.sharedLoadRequestId ~= nil then |
178 | g_i3DManager:releaseSharedI3DFile(animation.sharedLoadRequestId) |
179 | animation.sharedLoadRequestId = nil |
180 | end |
181 | end |
182 | end |
183 | |
184 | if spec.particles ~= nil then |
185 | for _, ps in pairs(spec.particles) do |
186 | for _, mapping in ipairs(ps.mappings) do |
187 | ParticleUtil.deleteParticleSystem(mapping.particleSystem) |
188 | end |
189 | |
190 | if ps.sharedLoadRequestId ~= nil then |
191 | g_i3DManager:releaseSharedI3DFile(ps.sharedLoadRequestId) |
192 | ps.sharedLoadRequestId = nil |
193 | end |
194 | end |
195 | end |
196 | |
197 | if spec.effects ~= nil then |
198 | for _, effect in pairs(spec.effects) do |
199 | g_effectManager:deleteEffects(effect.effect) |
200 | end |
201 | end |
202 | end |
107 | function WorkParticles:onLoad(savegame) |
108 | local spec = self.spec_workParticles |
109 | |
110 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.groundParticleAnimations.groundParticleAnimation", "vehicle.workParticles.particleAnimation") --FS17 to FS19 |
111 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.groundParticleAnimations.groundParticle", "vehicle.workParticles.particle") --FS17 to FS19 |
112 | |
113 | if self.isClient then |
114 | spec.particleAnimations = {} |
115 | local i = 0 |
116 | while true do |
117 | local key = string.format("vehicle.workParticles.particleAnimation(%d)", i) |
118 | if not self.xmlFile:hasProperty(key) then |
119 | break |
120 | end |
121 | |
122 | local animation = {} |
123 | if self:loadGroundAnimations(self.xmlFile, key, animation, i) then |
124 | table.insert(spec.particleAnimations, animation) |
125 | end |
126 | |
127 | i = i + 1 |
128 | end |
129 | |
130 | spec.particles = {} |
131 | i = 0 |
132 | while true do |
133 | local key = string.format("vehicle.workParticles.particle(%d)", i) |
134 | if not self.xmlFile:hasProperty(key) then |
135 | break |
136 | end |
137 | |
138 | local particle = {} |
139 | if self:loadGroundParticles(self.xmlFile, key, particle, i) then |
140 | table.insert(spec.particles, particle) |
141 | end |
142 | |
143 | i = i + 1 |
144 | end |
145 | |
146 | spec.effects = {} |
147 | i = 0 |
148 | while true do |
149 | local key = string.format("vehicle.workParticles.effect(%d)", i) |
150 | if not self.xmlFile:hasProperty(key) then |
151 | break |
152 | end |
153 | |
154 | local effect = {} |
155 | if self:loadGroundEffects(self.xmlFile, key, effect, i) then |
156 | table.insert(spec.effects, effect) |
157 | end |
158 | |
159 | i = i + 1 |
160 | end |
161 | end |
162 | |
163 | if not self.isClient then |
164 | SpecializationUtil.removeEventListener(self, "onDelete", WorkParticles) |
165 | SpecializationUtil.removeEventListener(self, "onUpdateTick", WorkParticles) |
166 | SpecializationUtil.removeEventListener(self, "onDeactivate", WorkParticles) |
167 | end |
168 | end |
207 | function WorkParticles:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
208 | local spec = self.spec_workParticles |
209 | local isOnField = self:getIsOnField() |
210 | |
211 | for _, animation in ipairs(spec.particleAnimations) do |
212 | for _, mapping in ipairs(animation.mappings) do |
213 | local refNode = mapping.groundRefNode |
214 | if refNode ~= nil and refNode.depthNode ~= nil then |
215 | local depth = MathUtil.clamp(refNode.depth / mapping.maxWorkDepth, 0, 1) |
216 | if not isOnField then |
217 | depth = 0 |
218 | end |
219 | |
220 | depth = MathUtil.clamp(math.min(depth, mapping.lastDepth + refNode.movingDirection * (refNode.movedDistance/0.5)), 0 ,1) |
221 | mapping.lastDepth = depth |
222 | mapping.speed = mapping.speed - (refNode.movedDistance * refNode.movingDirection) |
223 | setVisibility(mapping.animNode, depth > 0) |
224 | setShaderParameter(mapping.animNode, "VertxoffsetVertexdeformMotionUVscale", -6, depth, mapping.speed, 1.5, false) |
225 | end |
226 | end |
227 | end |
228 | |
229 | local lastSpeed = self:getLastSpeed(true) |
230 | local enabled = self:getDoGroundManipulation() and isOnField |
231 | |
232 | for _,ps in pairs(spec.particles) do |
233 | for _, mapping in ipairs(ps.mappings) do |
234 | local nodeEnabled = enabled |
235 | if nodeEnabled then |
236 | nodeEnabled = mapping.groundRefNode.isActive and lastSpeed > mapping.speedThreshold |
237 | end |
238 | if mapping.movingDirection ~= nil then |
239 | nodeEnabled = nodeEnabled and mapping.movingDirection == self.movingDirection |
240 | end |
241 | ParticleUtil.setEmittingState(mapping.particleSystem, nodeEnabled) |
242 | end |
243 | end |
244 | |
245 | for _,effect in pairs(spec.effects) do |
246 | local state = enabled and lastSpeed > effect.speedThreshold |
247 | |
248 | if effect.needsSetIsTurnedOn then |
249 | if self.getIsTurnedOn ~= nil then |
250 | local turnedOn = self:getIsTurnedOn() |
251 | if self.getAttacherVehicle ~= nil then |
252 | local attacherVehicle = self:getAttacherVehicle() |
253 | if attacherVehicle ~= nil then |
254 | if attacherVehicle.getIsTurnedOn ~= nil then |
255 | turnedOn = turnedOn or attacherVehicle:getIsTurnedOn() |
256 | end |
257 | end |
258 | end |
259 | |
260 | state = state and turnedOn |
261 | end |
262 | end |
263 | |
264 | local workArea = self:getWorkAreaByIndex(effect.workAreaIndex) |
265 | if workArea ~= nil then |
266 | if workArea.requiresGroundContact then |
267 | state = state and workArea.groundReferenceNode ~= nil and workArea.groundReferenceNode.isActive |
268 | end |
269 | end |
270 | |
271 | if effect.groundReferenceNodeIndex ~= nil and effect.groundReferenceNode == nil then |
272 | effect.groundReferenceNode = self:getGroundReferenceNodeFromIndex(effect.groundReferenceNodeIndex) |
273 | if effect.groundReferenceNode == nil then |
274 | Logging.warning("Unknown ground reference node '%s' for WorkParticle effect!", effect.groundReferenceNodeIndex) |
275 | effect.groundReferenceNodeIndex = nil |
276 | end |
277 | end |
278 | |
279 | if effect.groundReferenceNode ~= nil then |
280 | state = state and effect.groundReferenceNode.isActive |
281 | end |
282 | |
283 | -- ignore direction 0 |
284 | state = state and (self.movingDirection ~= -effect.activeDirection or self:getLastSpeed() < 0.5) |
285 | |
286 | if state then |
287 | local fillType = self:getFillTypeFromWorkAreaIndex(effect.workAreaIndex) |
288 | g_effectManager:setFillType(effect.effect, fillType) |
289 | g_effectManager:startEffects(effect.effect) |
290 | else |
291 | g_effectManager:stopEffects(effect.effect) |
292 | end |
293 | end |
294 | end |
77 | function WorkParticles.registerFunctions(vehicleType) |
78 | SpecializationUtil.registerFunction(vehicleType, "getDoGroundManipulation", WorkParticles.getDoGroundManipulation) |
79 | SpecializationUtil.registerFunction(vehicleType, "loadGroundAnimations", WorkParticles.loadGroundAnimations) |
80 | SpecializationUtil.registerFunction(vehicleType, "onGroundAnimationI3DLoaded", WorkParticles.onGroundAnimationI3DLoaded) |
81 | SpecializationUtil.registerFunction(vehicleType, "loadGroundAnimationMapping", WorkParticles.loadGroundAnimationMapping) |
82 | SpecializationUtil.registerFunction(vehicleType, "loadGroundParticles", WorkParticles.loadGroundParticles) |
83 | SpecializationUtil.registerFunction(vehicleType, "groundParticleI3DLoaded", WorkParticles.groundParticleI3DLoaded) |
84 | SpecializationUtil.registerFunction(vehicleType, "loadGroundParticleMapping", WorkParticles.loadGroundParticleMapping) |
85 | SpecializationUtil.registerFunction(vehicleType, "loadGroundEffects", WorkParticles.loadGroundEffects) |
86 | SpecializationUtil.registerFunction(vehicleType, "getFillTypeFromWorkAreaIndex", WorkParticles.getFillTypeFromWorkAreaIndex) |
87 | end |
48 | function WorkParticles.registerGroundAnimationMappingXMLPaths(schema, key) |
49 | schema:register(XMLValueType.NODE_INDEX, key .. "#node", "Link node in vehicle") |
50 | schema:register(XMLValueType.INT, key .. "#refNodeIndex", "Ground reference node index") |
51 | schema:register(XMLValueType.STRING, key .. "#animMeshNode", "Animation mesh node in external file") |
52 | |
53 | schema:register(XMLValueType.STRING, key .. "#materialType", "Material type name (If external file is not given)") |
54 | schema:register(XMLValueType.INT, key .. "#materialId", "Material index (If external file is not given)", 1) |
55 | |
56 | schema:register(XMLValueType.FLOAT, key .. "#maxDepth", "Max. depth", -0.1) |
57 | end |
61 | function WorkParticles.registerGroundParticleMappingXMLPaths(schema, key) |
62 | schema:register(XMLValueType.NODE_INDEX, key .. "#node", "Link node in vehicle") |
63 | schema:register(XMLValueType.INT, key .. "#refNodeIndex", "Ground reference node index") |
64 | schema:register(XMLValueType.STRING, key .. "#particleNode", "Particle node in external file") |
65 | |
66 | schema:register(XMLValueType.STRING, key .. "#particleType", "Particle type name (If external file is not given)") |
67 | schema:register(XMLValueType.STRING, key .. "#fillType", "Fill type for particles (If external file is not given)", "UNKNOWN") |
68 | |
69 | schema:register(XMLValueType.FLOAT, key .. "#speedThreshold", "Speed threshold", 0.5) |
70 | schema:register(XMLValueType.INT, key .. "#movingDirection", "Moving direction") |
71 | |
72 | ParticleUtil.registerParticleCopyXMLPaths(schema, key) |
73 | end |
584 | function WorkParticles:updateGroundReferenceNode(superFunc, groundReferenceNode, x, y, z, terrainHeight, densityHeight) |
585 | |
586 | superFunc(self, groundReferenceNode, x, y, z, terrainHeight, densityHeight) |
587 | if self.isClient and groundReferenceNode.depthNode ~= nil then |
588 | local newX, newY, newZ = getWorldTranslation(groundReferenceNode.depthNode) |
589 | if groundReferenceNode.lastPosition == nil then |
590 | groundReferenceNode.lastPosition = {newX, newY, newZ} |
591 | end |
592 | local dx, dy, dz = worldDirectionToLocal(groundReferenceNode.depthNode, newX-groundReferenceNode.lastPosition[1], newY-groundReferenceNode.lastPosition[2], newZ-groundReferenceNode.lastPosition[3]) |
593 | groundReferenceNode.movingDirection = 0 |
594 | if dz > 0.0001 then |
595 | groundReferenceNode.movingDirection = 1 |
596 | elseif dz < -0.0001 then |
597 | groundReferenceNode.movingDirection = -1 |
598 | end |
599 | groundReferenceNode.movedDistance = MathUtil.vector3Length(dx, dy, dz) |
600 | groundReferenceNode.lastPosition[1], groundReferenceNode.lastPosition[2], groundReferenceNode.lastPosition[3] = newX, newY, newZ |
601 | local terrainHeightDepthNode = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, newX, newY, newZ) |
602 | groundReferenceNode.depth = newY - terrainHeightDepthNode |
603 | end |
604 | end |