26 | function SpeedRotatingParts.initSpecialization() |
27 | local schema = Vehicle.xmlSchema |
28 | schema:setXMLSpecializationType("SpeedRotatingParts") |
29 | |
30 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#node", "Speed rotating part node") |
31 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#shaderNode", "Speed rotating part shader node") |
32 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#useRotation", "Use shader rotation", true) |
33 | schema:register(XMLValueType.STRING, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#vtxPositionArrayFilename", "Path to vertex position filename (If this is set the shader variation 'vtxRotate_colorMask' is forced)") |
34 | schema:register(XMLValueType.VECTOR_2, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#scrollScale", "Shader scroll speed") |
35 | schema:register(XMLValueType.INT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#shaderComponent", "Shader parameter component to control", "Default based on available shader attributes") |
36 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#scrollLength", "Shader scroll length") |
37 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#driveNode", "Drive node to apply x drive", "speedRotatingPart#node") |
38 | schema:register(XMLValueType.INT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#refComponentIndex", "Reference component index") |
39 | schema:register(XMLValueType.INT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#wheelIndex", "Reference wheel index") |
40 | |
41 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#dirRefNode", "Direction reference node") |
42 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#dirFrameNode", "Direction reference frame") |
43 | |
44 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#alignDirection", "Align direction", false) |
45 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#applySteeringAngle", "Apply steering angle", false) |
46 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#useWheelReprTranslation", "Apply wheel repr translation", true) |
47 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#updateXDrive", "Update X drive", true) |
48 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#versatileYRot", "Versatile Y rot", false) |
49 | |
50 | schema:register(XMLValueType.ANGLE, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#minYRot", "Min. Y rotation") |
51 | schema:register(XMLValueType.ANGLE, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#maxYRot", "Max. Y rotation") |
52 | |
53 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#wheelScale", "Wheel scale") |
54 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#radius", "Radius", 1) |
55 | |
56 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#onlyActiveWhenLowered", "Only active if lowered", false) |
57 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#stopIfNotActive", "Stop if not active", false) |
58 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#fadeOutTime", "Fade out time", 3) |
59 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#activationSpeed", "Min. speed for activation", 1) |
60 | schema:register(XMLValueType.NODE_INDEX, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#speedReferenceNode", "Speed reference node") |
61 | |
62 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#hasTireTracks", "Has Tire Tracks", false) |
63 | schema:register(XMLValueType.INT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#tireTrackAtlasIndex", "Index on tire track atlas", 0) |
64 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#tireTrackWidth", "Width of tire tracks", 0.5) |
65 | schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#tireTrackInverted", "Tire track texture inverted", false) |
66 | |
67 | schema:register(XMLValueType.FLOAT, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#maxUpdateDistance", "Max. distance from current camera to vehicle to update part", SpeedRotatingParts.DEFAULT_MAX_UPDATE_DISTANCE) |
68 | |
69 | schema:setXMLSpecializationType() |
70 | end |
256 | function SpeedRotatingParts:loadSpeedRotatingPartFromXML(speedRotatingPart, xmlFile, key) |
257 | speedRotatingPart.repr = xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings) |
258 | speedRotatingPart.shaderNode = xmlFile:getValue(key.."#shaderNode", nil, self.components, self.i3dMappings) |
259 | |
260 | speedRotatingPart.shaderParameterName = "offsetUV" |
261 | speedRotatingPart.shaderParameterPrevName = nil |
262 | speedRotatingPart.shaderParameterComponent = 3 |
263 | speedRotatingPart.shaderParameterSpeedScale = 1 |
264 | speedRotatingPart.shaderParameterValues = {0, 0, 0, 0} |
265 | if speedRotatingPart.shaderNode ~= nil then |
266 | speedRotatingPart.useShaderRotation = xmlFile:getValue(key.."#useRotation", true) |
267 | speedRotatingPart.scrollScale = xmlFile:getValue(key.."#scrollScale", "1 0", true) |
268 | speedRotatingPart.scrollLength = xmlFile:getValue(key.."#scrollLength") |
269 | |
270 | local vtxPositionArrayFilename = xmlFile:getValue(key.."#vtxPositionArrayFilename") |
271 | if vtxPositionArrayFilename ~= nil then |
272 | vtxPositionArrayFilename = Utils.getFilename(vtxPositionArrayFilename, self.baseDirectory) |
273 | |
274 | local materialId = getMaterial(speedRotatingPart.shaderNode, 0) |
275 | |
276 | local curVariation = getMaterialCustomShaderVariation(materialId) |
277 | if curVariation ~= "vtxRotate_colorMask" then |
278 | materialId = setMaterialCustomShaderVariation(materialId, "vtxRotate_colorMask", false) |
279 | materialId = setMaterialCustomMapFromFile(materialId, "mTrackArray", vtxPositionArrayFilename, true, false, true) |
280 | else |
281 | materialId = setMaterialCustomMapFromFile(materialId, "mTrackArray", vtxPositionArrayFilename, true, false, false) |
282 | end |
283 | |
284 | setMaterial(speedRotatingPart.shaderNode, materialId, 0) |
285 | end |
286 | |
287 | if getHasShaderParameter(speedRotatingPart.shaderNode, "rotationAngle") then |
288 | speedRotatingPart.shaderParameterName = "rotationAngle" |
289 | speedRotatingPart.shaderParameterPrevName = "prevRotationAngle" |
290 | speedRotatingPart.shaderParameterComponent = 1 |
291 | speedRotatingPart.shaderParameterSpeedScale = -1 |
292 | end |
293 | |
294 | if getHasShaderParameter(speedRotatingPart.shaderNode, "scrollPosition") then |
295 | speedRotatingPart.shaderParameterName = "scrollPosition" |
296 | speedRotatingPart.shaderParameterPrevName = "prevScrollPosition" |
297 | speedRotatingPart.shaderParameterComponent = 1 |
298 | speedRotatingPart.shaderParameterSpeedScale = 1 |
299 | end |
300 | |
301 | speedRotatingPart.shaderParameterComponent = xmlFile:getValue(key.."#shaderComponent", speedRotatingPart.shaderParameterComponent) |
302 | end |
303 | |
304 | if speedRotatingPart.repr == nil and speedRotatingPart.shaderNode == nil then |
305 | Logging.xmlWarning(self.xmlFile, "Invalid speedRotationPart node '%s' in '%s'", tostring(getXMLString(xmlFile.handle, key.."#node") or getXMLString(xmlFile.handle, key.."#shaderNode")), key) |
306 | return false |
307 | end |
308 | speedRotatingPart.driveNode = xmlFile:getValue(key .. "#driveNode", speedRotatingPart.repr, self.components, self.i3dMappings) |
309 | |
310 | local componentIndex = xmlFile:getValue(key.."#refComponentIndex") |
311 | if componentIndex ~= nil and self.components[componentIndex] ~= nil then |
312 | speedRotatingPart.componentNode = self.components[componentIndex].node |
313 | else |
314 | local node = Utils.getNoNil(speedRotatingPart.driveNode, speedRotatingPart.shaderNode) |
315 | speedRotatingPart.componentNode = self:getParentComponent(node) |
316 | end |
317 | |
318 | speedRotatingPart.xDrive = 0 |
319 | local wheelIndex = xmlFile:getValue(key.."#wheelIndex") |
320 | if wheelIndex ~= nil then |
321 | if self.getWheels == nil then |
322 | Logging.xmlWarning(self.xmlFile, "wheelIndex for speedRotatingPart '%s' given, but no wheels loaded/defined", key) |
323 | else |
324 | local wheels = self:getWheels() |
325 | local wheel = wheels[wheelIndex] |
326 | if wheel == nil then |
327 | Logging.xmlWarning(self.xmlFile, "Invalid wheel index '%s' for speedRotatingPart '%s'", tostring(wheelIndex), key) |
328 | return false |
329 | end |
330 | if not wheel.isSynchronized then |
331 | Logging.xmlWarning(self.xmlFile, "Referenced wheel with index '%s' for speedRotatingPart '%s' is not synchronized in multiplayer", tostring(wheelIndex), key) |
332 | end |
333 | speedRotatingPart.wheel = wheel |
334 | speedRotatingPart.lastWheelXRot = nil |
335 | |
336 | speedRotatingPart.hasTireTracks = xmlFile:getValue(key .. "#hasTireTracks", false) |
337 | speedRotatingPart.tireTrackAtlasIndex = xmlFile:getValue(key .. "#tireTrackAtlasIndex", 0) |
338 | speedRotatingPart.tireTrackWidth = xmlFile:getValue(key .. "#tireTrackWidth", 0.5) |
339 | speedRotatingPart.tireTrackInverted = xmlFile:getValue(key .. "#tireTrackInverted", false) |
340 | if speedRotatingPart.hasTireTracks then |
341 | local activeFunc = function() |
342 | return self:getIsSpeedRotatingPartActive(speedRotatingPart) |
343 | end |
344 | |
345 | speedRotatingPart.tireTrackNodeIndex = self:addTireTrackNode(wheel, true, speedRotatingPart.componentNode, speedRotatingPart.driveNode, speedRotatingPart.tireTrackAtlasIndex, speedRotatingPart.tireTrackWidth, wheel.radius, 0, speedRotatingPart.tireTrackInverted, activeFunc) |
346 | end |
347 | end |
348 | end |
349 | |
350 | speedRotatingPart.dirRefNode = xmlFile:getValue(key.."#dirRefNode", nil, self.components, self.i3dMappings) |
351 | speedRotatingPart.dirFrameNode = xmlFile:getValue(key.."#dirFrameNode", nil, self.components, self.i3dMappings) |
352 | speedRotatingPart.alignDirection = xmlFile:getValue(key .. "#alignDirection", false) |
353 | speedRotatingPart.applySteeringAngle = xmlFile:getValue(key .. "#applySteeringAngle", false) |
354 | speedRotatingPart.useWheelReprTranslation = xmlFile:getValue(key .. "#useWheelReprTranslation", true) |
355 | speedRotatingPart.updateXDrive = xmlFile:getValue(key .. "#updateXDrive", true) |
356 | |
357 | speedRotatingPart.versatileYRot = xmlFile:getValue(key .. "#versatileYRot", false) |
358 | if speedRotatingPart.versatileYRot and speedRotatingPart.repr == nil then |
359 | Logging.xmlWarning(self.xmlFile, "Versatile speedRotationPart '%s' does not support shaderNodes", key) |
360 | return false |
361 | end |
362 | |
363 | speedRotatingPart.minYRot = xmlFile:getValue(key .. "#minYRot") |
364 | speedRotatingPart.maxYRot = xmlFile:getValue(key .. "#maxYRot") |
365 | speedRotatingPart.steeringAngle = 0 |
366 | speedRotatingPart.steeringAngleSent = 0 |
367 | |
368 | speedRotatingPart.speedReferenceNode = xmlFile:getValue(key.."#speedReferenceNode", nil, self.components, self.i3dMappings) |
369 | if speedRotatingPart.speedReferenceNode ~= nil and speedRotatingPart.speedReferenceNode == speedRotatingPart.driveNode then |
370 | Logging.xmlWarning(self.xmlFile, "Ignoring speedRotationPart '%s' because speedReferenceNode is identical with driveNode. Need to be different!", key) |
371 | return false |
372 | end |
373 | |
374 | speedRotatingPart.wheelScale = xmlFile:getValue(key .. "#wheelScale") |
375 | if speedRotatingPart.wheelScale == nil then |
376 | local baseRadius = 1.0 |
377 | local radius = 1.0 |
378 | if speedRotatingPart.wheel ~= nil and speedRotatingPart.speedReferenceNode == nil then |
379 | baseRadius = speedRotatingPart.wheel.radius |
380 | radius = speedRotatingPart.wheel.radius |
381 | end |
382 | speedRotatingPart.wheelScale = baseRadius / xmlFile:getValue(key.."#radius", radius) |
383 | end |
384 | |
385 | speedRotatingPart.wheelScaleBackup = speedRotatingPart.wheelScale |
386 | |
387 | speedRotatingPart.onlyActiveWhenLowered = xmlFile:getValue(key .. "#onlyActiveWhenLowered", false) |
388 | speedRotatingPart.stopIfNotActive = xmlFile:getValue(key .. "#stopIfNotActive", false) |
389 | speedRotatingPart.fadeOutTime = xmlFile:getValue(key .. "#fadeOutTime", 3) * 1000 |
390 | speedRotatingPart.activationSpeed = xmlFile:getValue(key .. "#activationSpeed", 1) |
391 | |
392 | speedRotatingPart.lastSpeed = 0 |
393 | speedRotatingPart.lastDir = 1 |
394 | |
395 | speedRotatingPart.maxUpdateDistance = xmlFile:getValue(key .. "#maxUpdateDistance", SpeedRotatingParts.DEFAULT_MAX_UPDATE_DISTANCE) |
396 | |
397 | -- always update to sync the correct angle to the clients |
398 | if self.isServer and speedRotatingPart.versatileYRot then |
399 | speedRotatingPart.maxUpdateDistance = math.huge |
400 | end |
401 | |
402 | return true |
403 | end |
431 | function SpeedRotatingParts:updateSpeedRotatingPart(speedRotatingPart, dt, isPartActive) |
432 | local spec = self.spec_speedRotatingParts |
433 | local speed = speedRotatingPart.lastSpeed |
434 | local dir = speedRotatingPart.lastDir |
435 | |
436 | -- use angle from the repr node since the repr node could be rotated by another spec |
437 | if speedRotatingPart.repr ~= nil then |
438 | if self.isServer or not speedRotatingPart.versatileYRot then |
439 | local _ |
440 | _, speedRotatingPart.steeringAngle, _ = getRotation(speedRotatingPart.repr) |
441 | end |
442 | end |
443 | |
444 | if isPartActive then |
445 | if speedRotatingPart.speedReferenceNode ~= nil then |
446 | local newX, newY, newZ = getWorldTranslation(speedRotatingPart.speedReferenceNode) |
447 | if speedRotatingPart.lastPosition == nil then |
448 | speedRotatingPart.lastPosition = {newX, newY, newZ} |
449 | end |
450 | |
451 | local dx, dy, dz = worldDirectionToLocal(speedRotatingPart.speedReferenceNode, newX-speedRotatingPart.lastPosition[1], newY-speedRotatingPart.lastPosition[2], newZ-speedRotatingPart.lastPosition[3]) |
452 | speed = MathUtil.vector3Length(dx, dy, dz) |
453 | |
454 | if dz > 0.001 then |
455 | dir = 1 |
456 | elseif dz < -0.001 then |
457 | dir = -1 |
458 | else |
459 | dir = 0 |
460 | end |
461 | |
462 | speedRotatingPart.lastPosition[1], speedRotatingPart.lastPosition[2], speedRotatingPart.lastPosition[3] = newX, newY, newZ |
463 | elseif speedRotatingPart.wheel ~= nil then |
464 | if speedRotatingPart.lastWheelXRot == nil then |
465 | speedRotatingPart.lastWheelXRot = speedRotatingPart.wheel.netInfo.xDrive |
466 | end |
467 | |
468 | local rotDiff = speedRotatingPart.wheel.netInfo.xDrive - speedRotatingPart.lastWheelXRot |
469 | if rotDiff > math.pi then |
470 | rotDiff = rotDiff - (2*math.pi) |
471 | elseif rotDiff < -math.pi then |
472 | rotDiff = rotDiff + (2*math.pi) |
473 | end |
474 | speed = math.abs(rotDiff) |
475 | dir = MathUtil.sign(rotDiff) |
476 | speedRotatingPart.lastWheelXRot = speedRotatingPart.wheel.netInfo.xDrive |
477 | |
478 | if not speedRotatingPart.versatileYRot then |
479 | local _ |
480 | _, speedRotatingPart.steeringAngle, _ = getRotation(speedRotatingPart.wheel.repr) |
481 | end |
482 | else |
483 | speed = self.lastSpeedReal * dt |
484 | dir = self.movingDirection |
485 | end |
486 | speedRotatingPart.brakeForce = speed * dt/speedRotatingPart.fadeOutTime |
487 | else |
488 | speed = math.max(speed - speedRotatingPart.brakeForce, 0) |
489 | speedRotatingPart.lastWheelXRot = nil |
490 | end |
491 | |
492 | speedRotatingPart.lastSpeed = speed |
493 | speedRotatingPart.lastDir = dir |
494 | if speedRotatingPart.updateXDrive then |
495 | speedRotatingPart.xDrive = (speedRotatingPart.xDrive + speed * dir * self:getSpeedRotatingPartDirection(speedRotatingPart) * speedRotatingPart.wheelScale) % (2*math.pi) |
496 | end |
497 | |
498 | if speedRotatingPart.versatileYRot then |
499 | if speed > 0.0017 then -- 0.1deg threshold cause float accuracy |
500 | if self.isServer and self:getLastSpeed(true) > speedRotatingPart.activationSpeed then |
501 | local posX, posY, posZ = localToLocal(speedRotatingPart.repr, speedRotatingPart.componentNode, 0,0,0) |
502 | speedRotatingPart.steeringAngle = Utils.getVersatileRotation(speedRotatingPart.repr, speedRotatingPart.componentNode, dt, posX, posY, posZ, speedRotatingPart.steeringAngle, speedRotatingPart.minYRot, speedRotatingPart.maxYRot) |
503 | |
504 | local steeringAngleSent = math.floor((speedRotatingPart.steeringAngle % (math.pi*2)) / (math.pi*2) * 511) |
505 | if steeringAngleSent ~= speedRotatingPart.steeringAngleSent then |
506 | speedRotatingPart.steeringAngleSent = steeringAngleSent |
507 | self:raiseDirtyFlags(spec.dirtyFlag) |
508 | end |
509 | end |
510 | end |
511 | else |
512 | if speedRotatingPart.componentNode ~= nil and speedRotatingPart.dirRefNode ~= nil and not speedRotatingPart.alignDirection then |
513 | speedRotatingPart.steeringAngle = Utils.getYRotationBetweenNodes(speedRotatingPart.componentNode, speedRotatingPart.dirRefNode) |
514 | local _,yTrans,_ = localToLocal(speedRotatingPart.driveNode, speedRotatingPart.wheel.driveNode, 0, 0, 0) |
515 | setTranslation(speedRotatingPart.driveNode, 0, yTrans, 0) |
516 | end |
517 | |
518 | if speedRotatingPart.dirRefNode ~= nil and speedRotatingPart.alignDirection then |
519 | local upX, upY, upZ = localDirectionToWorld(speedRotatingPart.dirFrameNode, 0, 1, 0) |
520 | local dirX, dirY, dirZ = localDirectionToWorld(speedRotatingPart.dirRefNode, 0, 0, 1) |
521 | I3DUtil.setWorldDirection(speedRotatingPart.repr, dirX, dirY, dirZ, upX, upY, upZ, 2) |
522 | if speedRotatingPart.wheel ~= nil and speedRotatingPart.useWheelReprTranslation then |
523 | local _,yTrans,_ = localToLocal(speedRotatingPart.wheel.driveNode, getParent(speedRotatingPart.repr), 0,0,0) |
524 | setTranslation(speedRotatingPart.repr, 0, yTrans, 0) |
525 | end |
526 | end |
527 | end |
528 | |
529 | if speedRotatingPart.driveNode ~= nil then |
530 | if speedRotatingPart.repr == speedRotatingPart.driveNode then |
531 | local steeringAngle = speedRotatingPart.steeringAngle |
532 | if not speedRotatingPart.applySteeringAngle then |
533 | steeringAngle = 0 |
534 | end |
535 | |
536 | setRotation(speedRotatingPart.repr, speedRotatingPart.xDrive, steeringAngle, 0) |
537 | else |
538 | if not speedRotatingPart.alignDirection and (speedRotatingPart.versatileYRot or speedRotatingPart.applySteeringAngle) then |
539 | setRotation(speedRotatingPart.repr, 0, speedRotatingPart.steeringAngle, 0) |
540 | end |
541 | setRotation(speedRotatingPart.driveNode, speedRotatingPart.xDrive, 0, 0) |
542 | end |
543 | end |
544 | |
545 | if speedRotatingPart.shaderNode ~= nil then |
546 | if speedRotatingPart.useShaderRotation then |
547 | local values = speedRotatingPart.shaderParameterValues |
548 | if speedRotatingPart.scrollLength ~= nil then |
549 | values[speedRotatingPart.shaderParameterComponent] = (speedRotatingPart.xDrive * speedRotatingPart.shaderParameterSpeedScale) % speedRotatingPart.scrollLength |
550 | else |
551 | values[speedRotatingPart.shaderParameterComponent] = (speedRotatingPart.xDrive * speedRotatingPart.shaderParameterSpeedScale) |
552 | end |
553 | |
554 | if speedRotatingPart.shaderParameterPrevName ~= nil then |
555 | g_animationManager:setPrevShaderParameter(speedRotatingPart.shaderNode, speedRotatingPart.shaderParameterName, values[1], values[2], values[3], values[4], false, speedRotatingPart.shaderParameterPrevName) |
556 | else |
557 | setShaderParameter(speedRotatingPart.shaderNode, speedRotatingPart.shaderParameterName, values[1], values[2], values[3], values[4], false) |
558 | end |
559 | else |
560 | local pos = (speedRotatingPart.xDrive % math.pi) / (2*math.pi) -- normalize rotation |
561 | setShaderParameter(speedRotatingPart.shaderNode, "offsetUV", pos*speedRotatingPart.scrollScale[1], pos*speedRotatingPart.scrollScale[2], 0, 0, false) |
562 | end |
563 | end |
564 | end |