40 | function Shovel:load(savegame) |
41 | self.shovelTipReferenceNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.shovel#tipReferenceNode")); |
42 | self.shovelTipRaycastDistance = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#tipRaycastDistance"), 10); |
43 | self.shovelOpenNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.shovel#openNode")); |
44 | self.shovelOpenRotation = math.rad(Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#openRotation"), 0)); |
45 | self.shovelOpenDirection = Utils.sign(Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.shovel#openDirection"), 1)); |
46 | self.shovelEmptyStartCosAngle = math.cos(math.rad(Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#emptyStartAngle"), 110))); |
47 | self.shovelEmptyFullCosAngle = math.cos(math.rad(Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#emptyFullAngle"), 135))); |
48 | self.shovelEmptySpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#emptySpeed"), self:getCapacity()/2)*0.001; |
49 | self.allowFillFromShovelTrigger = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#allowFillFromShovelTrigger"), true); |
50 | |
51 | self.shovelShowFillTypeIcon = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#showFillTypeIcon"), true); |
52 | |
53 | -- Note: self.shovelFillLitersPerSecond has the wrong naming, it should be called self.shovelFillLitersPerMS) |
54 | self.shovelFillLitersPerSecond = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#fillLitersPerSecond"), 10000) * 0.001; |
55 | self.ignoreVehicleDirectionOnLoad = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#ignoreVehicleDirectionOnLoad"), false); |
56 | |
57 | |
58 | |
59 | self.pickUp = {}; |
60 | self.pickUp.node = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.shovel#pickUpNode")); |
61 | self.pickUp.width = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#pickUpWidth"), 1.0); |
62 | self.pickUp.length = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#pickUpLength"), 0.5); |
63 | self.pickUp.yOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#pickUpYOffset"), 0.0); |
64 | self.pickUp.pickUpRequiresMovement = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#pickUpRequiresMovement"), true); |
65 | self.pickUp.pickUpNeedsToBeTurnedOn = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#pickUpNeedsToBeTurnedOn"), false); |
66 | self.pickUp.pickUpNeedsActiveVehicle = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovel#pickUpNeedsActiveVehicle"), true); |
67 | |
68 | self.pickUp.remainingDelta = 0; |
69 | |
70 | local hasShovelNodes = false; |
71 | local shovelNodes = {}; |
72 | local i = 0; |
73 | while true do |
74 | local key = string.format("vehicle.shovel.node(%d)", i); |
75 | if not hasXMLProperty(self.xmlFile, key) then |
76 | break; |
77 | end; |
78 | hasShovelNodes = true; |
79 | local node = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key.."#index")); |
80 | if node ~= nil then |
81 | shovelNodes[node] = true; |
82 | end; |
83 | i = i + 1; |
84 | end; |
85 | if hasShovelNodes then |
86 | self.shovelNodes = shovelNodes; |
87 | end; |
88 | |
89 | |
90 | if self.isClient then |
91 | for _, key in pairs({"empty", "fill"}) do |
92 | self[key.."EmitterShape"] = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle."..key.."ParticleSystems.emitterShape#node")); |
93 | self[key.."ReferenceParticleSystems"] = {}; |
94 | |
95 | if self[key.."EmitterShape"] ~= nil then |
96 | for _,fillUnit in pairs(self.fillUnits) do |
97 | for fillType,state in pairs(fillUnit.fillTypes) do |
98 | local particleSystems = MaterialUtil.getParticleSystem(fillType, "unloading") |
99 | if state and particleSystems ~= nil then |
100 | self[key.."ReferenceParticleSystems"][fillType] = ParticleUtil.copyParticleSystem(self.xmlFile, "vehicle."..key.."ParticleSystems.emitterShape", particleSystems, self[key.."EmitterShape"]) |
101 | end |
102 | end |
103 | end |
104 | end; |
105 | end; |
106 | |
107 | self.effectsTimeout = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#effectsTimeout"), 0.1)*1000; |
108 | self.fillEffectsTimeout = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.shovel#fillEffectsTimeout"), 0.1)*1000; |
109 | |
110 | self.shovelEmptyEffects = EffectManager:loadEffect(self.xmlFile, "vehicle.shovelEmptyEffect", self.components, self); |
111 | self.shovelEmptyEffectsRotateNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.shovelEmptyEffect#rotationNode")); |
112 | self.shovelEmptyEffectsRotateTimeOut = 0; |
113 | |
114 | self.shovelFillEffects = EffectManager:loadEffect(self.xmlFile, "vehicle.shovelFillEffect", self.components, self); |
115 | end; |
116 | |
117 | self.shovel = {}; |
118 | self.shovel.fillUnitIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.shovel#fillUnitIndex"), 1); |
119 | self.shovel.unloadInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.shovel#unloadInfoIndex"), 1); |
120 | self.shovel.loadInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.shovel#loadInfoIndex"), 1); |
121 | self.shovel.dischargeInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.shovel#dischargeInfoIndex"), 1); |
122 | |
123 | local shovelTipTriggerId = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.shovelTipTrigger#index")); |
124 | self.shovel.allowPallets = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.shovelTipTrigger#allowPallets"), false); |
125 | |
126 | if shovelTipTriggerId ~= nil then |
127 | local shovelTipTrigger = TipTrigger:new(self.isServer, self.isClient); |
128 | if shovelTipTrigger:load(shovelTipTriggerId) then |
129 | shovelTipTrigger:addTipTriggerTarget(self); |
130 | self.shovelTipTrigger = shovelTipTrigger; |
131 | self.shovelTipTrigger:register(true); |
132 | else |
133 | shovelTipTrigger:delete(); |
134 | end |
135 | end |
136 | |
137 | self.accumulatedFillDelta = 0; |
138 | self.accumulatedEmptyDelta = 0; |
139 | |
140 | self.fillEffectActive = false; |
141 | self.sentFillEffectActive = false; |
142 | self.lastFillEffectActiveTime = 0; |
143 | |
144 | self.emptyEffectActive = false; |
145 | self.sentEmptyEffectActive = false; |
146 | self.lastEmptyEffectActiveTime = 0; |
147 | |
148 | self.curShovelTipRaycastDistance = 0; |
149 | self.sentCurShovelTipRaycastDistance = 0; |
150 | |
151 | |
152 | if self.shovelDirtyFlag == nil then |
153 | self.shovelDirtyFlag = self:getNextDirtyFlag(); |
154 | end |
155 | end; |
288 | function Shovel:updateTick(dt) |
289 | if self.isServer then |
290 | -- |
291 | self.emptyEffectActive = false; |
292 | self.fillEffectActive = false; |
293 | |
294 | if (self:getIsActive() or not self.pickUp.pickUpNeedsActiveVehicle) then |
295 | -- calc movement of pickUpNode |
296 | local pickUpIsAllowed = true; |
297 | local pickUpSpeed = 0; |
298 | local pickUp = self.pickUp; |
299 | if pickUp.node ~= nil then |
300 | if Vehicle.debugRendering then |
301 | DebugUtil.drawDebugNode(pickUp.node); |
302 | end |
303 | |
304 | if self.pickUpRequiresMovement then |
305 | if pickUp.lastPos == nil then |
306 | pickUp.lastPos = {getWorldTranslation(pickUp.node)}; |
307 | end |
308 | local _,dy,dz = worldDirectionToLocal(getParent(pickUp.node), 0,1,0); |
309 | local alpha = math.atan2(dz, dy); |
310 | setRotation(pickUp.node, alpha, 0, 0); |
311 | |
312 | local x,y,z = worldToLocal(pickUp.node, pickUp.lastPos[1], pickUp.lastPos[2], pickUp.lastPos[3]); |
313 | pickUpIsAllowed = z < -0.01; |
314 | pickUpIsAllowed = pickUpIsAllowed and (alpha < math.pi/2) and (alpha > -math.pi/2); |
315 | |
316 | if pickUpIsAllowed then |
317 | pickUpSpeed = math.min(1, Utils.vector3Length(x,y,z) / (dt*0.001)); |
318 | end |
319 | |
320 | pickUp.lastPos[1], pickUp.lastPos[2], pickUp.lastPos[3] = getWorldTranslation(pickUp.node); |
321 | else |
322 | pickUpSpeed = 1; |
323 | end |
324 | |
325 | if self.pickUp.pickUpNeedsToBeTurnedOn and self.getIsTurnedOn ~= nil and not self:getIsTurnedOn() then |
326 | pickUpSpeed = 0; |
327 | end |
328 | |
329 | end |
330 | |
331 | -- check openNode |
332 | if pickUpIsAllowed then |
333 | if self.shovelOpenNode ~= nil then |
334 | local x,_,_ = getRotation(self.shovelOpenNode); |
335 | if self.shovelOpenDirection > 0 then |
336 | if x < self.shovelOpenRotation then |
337 | pickUpIsAllowed = false; |
338 | end; |
339 | else |
340 | if x > self.shovelOpenRotation then |
341 | pickUpIsAllowed = false; |
342 | end |
343 | end |
344 | end |
345 | end |
346 | |
347 | -- |
348 | local emptySpeed = self:getShovelEmptyingSpeed(); |
349 | local fillType = self:getUnitFillType(self.shovel.fillUnitIndex); |
350 | local fillLevel = self:getUnitFillLevel(self.shovel.fillUnitIndex); |
351 | if emptySpeed > 0 then |
352 | if fillLevel > 0 or self.accumulatedEmptyDelta > 0 then |
353 | if self.shovelTipReferenceNode ~= nil then |
354 | -- do raycast and empty.. |
355 | |
356 | self.trailerFound = nil; |
357 | self.objectFound = nil; |
358 | self.curShovelTipRaycastDistance = 0; |
359 | local x,y,z = getWorldTranslation(self.shovelTipReferenceNode); |
360 | raycastAll(x, y, z, 0, -1, 0, "findTrailerRaycastCallback", self.shovelTipRaycastDistance, self); |
361 | |
362 | if math.abs(self.sentCurShovelTipRaycastDistance-self.curShovelTipRaycastDistance) > 0.05 then |
363 | self:raiseDirtyFlags(self.shovelDirtyFlag); |
364 | end; |
365 | |
366 | local delta = math.min(fillLevel, emptySpeed*dt) + self.accumulatedEmptyDelta; |
367 | self.accumulatedEmptyDelta = 0; |
368 | if self.trailerFound ~= nil or self.objectFound ~= nil then |
369 | if self.trailerFound ~= nil and self.trailerFoundSupported then |
370 | self.trailerFound:resetFillLevelIfNeeded(fillType); |
371 | local oldFillLevel = self.trailerFound:getFillLevel(fillType); |
372 | |
373 | local dischargeInfos; |
374 | if self.fillVolumeDischargeInfos ~= nil and self.fillVolumeDischargeInfos[self.shovel.fillUnitIndex] ~= nil then |
375 | dischargeInfos = self.fillVolumeDischargeInfos[self.shovel.fillUnitIndex]; |
376 | for _,node in pairs(self.fillVolumeDischargeInfos[self.shovel.fillUnitIndex].nodes) do |
377 | local _,dy,_ = localDirectionToWorld(getParent(node.node), 0,0,1); |
378 | local alpha = math.acos(dy); |
379 | setRotation(node.node, -alpha+math.pi/2,0,0); |
380 | end |
381 | end |
382 | self.trailerFound:setFillLevel(oldFillLevel + delta, fillType, false, dischargeInfos); |
383 | delta = self.trailerFound:getFillLevel(fillType) - oldFillLevel; |
384 | elseif self.objectFound ~= nil and self.objectFoundSupported then |
385 | -- check if feedingThrough has enough space |
386 | local delta1 = self.objectFound:addShovelFillLevel(self, delta, fillType); |
387 | if delta1 ~= nil then |
388 | delta = delta1; |
389 | end; |
390 | else |
391 | if self.objectFound ~= nil and self.objectFound.getShovelNotAllowedText ~= nil then |
392 | local text = self.objectFound:getShovelNotAllowedText(self); |
393 | |
394 | if text ~= nil and text ~= "" then |
395 | self.tipErrorMessageTime = 3000; |
396 | self.tipErrorMessage = text; |
397 | end |
398 | end |
399 | -- do not empty shovel above unsupported trailer or shovel target |
400 | delta = 0; |
401 | end |
402 | else |
403 | -- tipAny |
404 | local minValidFillValue = TipUtil.getMinValidLiterValue(fillType); |
405 | if delta < minValidFillValue then |
406 | self.accumulatedEmptyDelta = self.accumulatedEmptyDelta + delta; |
407 | delta = 0; |
408 | if fillLevel < minValidFillValue then |
409 | self:setUnitFillLevel(self.shovel.fillUnitIndex, 0, FillUtil.FILLTYPE_UNKNOWN, true); |
410 | end |
411 | else |
412 | local sx,sy,sz; |
413 | local ex,ey,ez; |
414 | |
415 | local infos = self.fillVolumeDischargeInfos[self.shovel.fillUnitIndex]; |
416 | if infos ~= nil then |
417 | sx,sy,sz = localToWorld(infos.nodes[1].node, infos.nodes[1].width,0,0); |
418 | ex,ey,ez = localToWorld(infos.nodes[1].node, -infos.nodes[1].width,0,0); |
419 | else |
420 | local pickUp = self.pickUp; |
421 | sx,sy,sz = localToWorld(pickUp.node, pickUp.width, 0, 0); |
422 | ex,ey,ez = localToWorld(pickUp.node, -pickUp.width, 0, 0); |
423 | end |
424 | |
425 | local dropped, lineOffset = TipUtil.tipToGroundAroundLine(self, delta, fillType, sx,sy,sz, ex,ey,ez, 0, nil, self.shovel.lineOffsetDrop, true, nil); |
426 | self.shovel.lineOffsetDrop = lineOffset; |
427 | |
428 | if dropped > 0 then |
429 | if self.changedFillLevelCallback ~= nil then |
430 | if self.changedFillLevelCallbackTarget ~= nil then |
431 | self.changedFillLevelCallback(self.changedFillLevelCallbackTarget, self, dropped, fillType) |
432 | else |
433 | self.changedFillLevelCallback(self, dropped, fillType) |
434 | end |
435 | end |
436 | end |
437 | |
438 | delta = dropped; |
439 | end |
440 | end; |
441 | if delta ~= 0 then |
442 | self:setUnitFillLevel(self.shovel.fillUnitIndex, fillLevel - delta, fillType, false, self.fillVolumeUnloadInfos[self.shovel.unloadInfoIndex]); |
443 | self.emptyEffectActive = true; |
444 | end; |
445 | end; |
446 | end; |
447 | end |
448 | |
449 | -- always try to refill shovel |
450 | local fillLevel = self:getUnitFillLevel(self.shovel.fillUnitIndex); |
451 | local capacity = self:getUnitCapacity(self.shovel.fillUnitIndex); |
452 | |
453 | if self.pickUp.node ~= nil and pickUpIsAllowed and fillLevel < capacity then |
454 | local pickUp = self.pickUp; |
455 | |
456 | local length = pickUp.length; |
457 | local maxRadius = 1.75; |
458 | local zPos = math.min(pickUp.length/2, maxRadius); |
459 | |
460 | local foundFillType = false; |
461 | |
462 | while true do |
463 | local heapFillType = FillUtil.FILLTYPE_UNKNOWN; |
464 | |
465 | for i=1, 2 do |
466 | local width = i * pickUp.width / 2; |
467 | local x0,y0,z0 = localToWorld(pickUp.node, math.min(-width+zPos, 0), 0, zPos); |
468 | local x1,y1,z1 = localToWorld(pickUp.node, math.max(width-zPos, 0), 0, zPos); |
469 | |
470 | heapFillType = TipUtil.getFillTypeAtLine(x0,y0,z0, x1,y1,z1, math.min(length, maxRadius)); |
471 | if heapFillType ~= FillUtil.FILLTYPE_UNKNOWN then |
472 | break; |
473 | end; |
474 | end; |
475 | |
476 | if heapFillType ~= FillUtil.FILLTYPE_UNKNOWN and self:allowFillType(heapFillType) and self:allowUnitFillType(self.shovel.fillUnitIndex, heapFillType) then |
477 | local emptyFactor = 1.0 - (emptySpeed / self.shovelEmptySpeed); |
478 | local fillFactor = pickUpSpeed * emptyFactor; -- * 0.001 * dt; |
479 | |
480 | local pickUpDelta = math.min(emptyFactor*(capacity - fillLevel), self.shovelFillLitersPerSecond*dt + self.accumulatedFillDelta); |
481 | |
482 | pickUpDelta = pickUpDelta * math.min(1, fillFactor); |
483 | |
484 | if length > maxRadius * 2 then |
485 | pickUpDelta = pickUpDelta / math.floor(self.pickUp.length / (maxRadius * 2)); |
486 | end; |
487 | |
488 | |
489 | local sx,sy,sz = localToWorld(pickUp.node, -pickUp.width, pickUp.yOffset, zPos); |
490 | local ex,ey,ez = localToWorld(pickUp.node, pickUp.width, pickUp.yOffset, zPos); |
491 | |
492 | local sh = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, sx,sy,sz); |
493 | local eh = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ex,ey,ez); |
494 | if sh < 0.1 then |
495 | sy = sh; |
496 | end |
497 | if eh < 0.1 then |
498 | ey = eh; |
499 | end |
500 | |
501 | local innerRadius = math.min(length, maxRadius); --0.05; |
502 | local radius = nil; --1.0; |
503 | |
504 | pickUp.remainingDelta = pickUp.remainingDelta + pickUpDelta; |
505 | if pickUp.remainingDelta > 20 then |
506 | local fillLevel = self:getUnitFillLevel(self.shovel.fillUnitIndex); |
507 | local capacity = self:getUnitCapacity(self.shovel.fillUnitIndex); |
508 | pickUpDelta = math.min(pickUp.remainingDelta, capacity-fillLevel); |
509 | pickUp.remainingDelta = pickUp.remainingDelta - pickUpDelta; |
510 | else |
511 | pickUpDelta = 0; |
512 | end; |
513 | |
514 | -- (vehicle, delta, fillType, sx,sy,sz, ex,ey,ez, innerRadius, radius, lineOffset, limitToLineHeight, occlusionAreas) |
515 | local fillDelta, lineOffset = TipUtil.tipToGroundAroundLine(self, -pickUpDelta, heapFillType, sx,sy,sz, ex,ey,ez, innerRadius, radius, self.shovel.lineOffsetPickUp, true, nil); |
516 | self.shovel.lineOffsetPickUp = lineOffset; |
517 | |
518 | self.accumulatedFillDelta = math.min(pickUpDelta + fillDelta, TipUtil.getMinValidLiterValue(heapFillType)); |
519 | |
520 | if fillDelta < 0 then |
521 | self:setUnitFillLevel(self.shovel.fillUnitIndex, fillLevel - fillDelta, heapFillType, false, self.fillVolumeLoadInfos[self.shovel.unloadInfoIndex]); |
522 | |
523 | self.fillEffectActive = true; |
524 | |
525 | if self.changedFillLevelCallback ~= nil then |
526 | if self.changedFillLevelCallbackTarget ~= nil then |
527 | self.changedFillLevelCallback(self.changedFillLevelCallbackTarget, self, fillDelta, heapFillType); |
528 | else |
529 | self.changedFillLevelCallback(self, fillDelta, heapFillType); |
530 | end |
531 | end |
532 | end |
533 | |
534 | if foundFillType then |
535 | break; |
536 | end; |
537 | foundFillType = true; |
538 | else |
539 | self.accumulatedFillDelta = 0; |
540 | end |
541 | |
542 | if length > maxRadius * 2 then |
543 | length = length - maxRadius * 2; |
544 | if length > maxRadius * 2 then |
545 | zPos = zPos + maxRadius * 2; |
546 | else |
547 | zPos = zPos + (pickUp.length-zPos+maxRadius)/2; |
548 | length = pickUp.length-zPos; |
549 | end; |
550 | else |
551 | break; |
552 | end; |
553 | end; |
554 | end |
555 | end; |
556 | |
557 | if self.fillEffectActive ~= self.sentFillEffectActive or self.emptyEffectActive ~= self.sentEmptyEffectActive then |
558 | self:raiseDirtyFlags(self.shovelDirtyFlag); |
559 | self:updateShovelParticleSystems(self.emptyEffectActive, self.fillEffectActive); |
560 | end; |
561 | |
562 | end; |
563 | |
564 | if self.isClient then |
565 | |
566 | if not self.fillEffectActive and self.lastFillEffectActiveTime > 0 and self.lastFillEffectActiveTime + self.effectsTimeout < g_currentMission.time then |
567 | self.lastFillEffectActiveTime = 0; |
568 | if self.lastFillPS ~= nil then |
569 | ParticleUtil.setEmittingState(self.lastFillPS, false); |
570 | end |
571 | if self.shovelFillEffects ~= nil then |
572 | EffectManager:stopEffects(self.shovelFillEffects); |
573 | end |
574 | end |
575 | |
576 | if not self.emptyEffectActive and self.lastEmptyEffectActiveTime > 0 and self.lastEmptyEffectActiveTime + self.fillEffectsTimeout < g_currentMission.time then |
577 | self.lastEmptyEffectActiveTime = 0; |
578 | if self.lastEmptyPS ~= nil then |
579 | ParticleUtil.setEmittingState(self.lastEmptyPS, false); |
580 | end |
581 | if self.shovelEmptyEffects ~= nil then |
582 | EffectManager:stopEffects(self.shovelEmptyEffects); |
583 | end |
584 | end |
585 | |
586 | if self.emptyEffectActive then |
587 | if self.shovelEmptyEffects ~= nil then |
588 | for _, effect in pairs(self.shovelEmptyEffects) do |
589 | effect:setUnloadingDistance(self.curShovelTipRaycastDistance); |
590 | end; |
591 | end |
592 | |
593 | if self.lastEmptyPS ~= nil then |
594 | self.curShovelTipRaycastDistance = self.curShovelTipRaycastDistance - 0.3; -- size of particles |
595 | |
596 | local dx,dy,dz = localDirectionToWorld(self.emptyEmitterShape, 0,0,1); |
597 | local factor = 1-(math.abs(dy)/1); |
598 | local maxFactor = 28*math.sqrt(self.curShovelTipRaycastDistance)*0.3162/100; |
599 | factor = 1 + factor * maxFactor; |
600 | |
601 | local lifespan = math.sqrt(self.curShovelTipRaycastDistance)*379.47; -- on gravity y factor of -1.5 |
602 | |
603 | ParticleUtil.setParticleLifespan(self.lastEmptyPS, lifespan/factor); |
604 | end |
605 | end; |
606 | end; |
607 | end; |