Advertisement
GreenMs02

RbxStamper

Nov 17th, 2019
523
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 81.85 KB | None | 0 0
  1. local t = {}
  2.  
  3. -- Do a line/plane intersection.  The line starts at the camera.  The plane is at y == 0, normal(0, 1, 0)
  4. --
  5. -- vectorPos - End point of the line.
  6. --
  7. -- Return:
  8. -- cellPos - The terrain cell intersection point if there is one, vectorPos if there isn't.
  9. -- hit - Whether there was a plane intersection.  Value is true if there was, false if not.
  10. function PlaneIntersection(vectorPos)
  11.     local hit = false
  12.     local currCamera = game:GetService("Workspace").CurrentCamera
  13.     local startPos = Vector3.new(currCamera.CoordinateFrame.p.X, currCamera.CoordinateFrame.p.Y, currCamera.CoordinateFrame.p.Z)
  14.     local endPos = Vector3.new(vectorPos.X, vectorPos.Y, vectorPos.Z)
  15.     local normal = Vector3.new(0, 1, 0)
  16.     local p3 = Vector3.new(0, 0, 0)
  17.     local startEndDot = normal:Dot(endPos - startPos)
  18.     local cellPos = vectorPos
  19.     if startEndDot ~= 0  then
  20.         local t = normal:Dot(p3 - startPos) / startEndDot
  21.         if(t >=0 and t <=1) then
  22.             local intersection = ((endPos - startPos) * t) + startPos
  23.             cellPos = game:GetService("Workspace").Terrain:WorldToCell(intersection)
  24.             hit = true
  25.         end
  26.     end
  27.  
  28.     return cellPos, hit
  29. end
  30.  
  31.  
  32. -- Purpose:
  33. -- Checks for terrain touched by the mouse hit.
  34. -- Will do a plane intersection if no terrain is touched.
  35. --
  36. -- mouse - Mouse to check the .hit for.
  37. --
  38. -- Return:
  39. -- cellPos - Cell position hit.  Nil if none.
  40. function GetTerrainForMouse(mouse)
  41.     -- There was no target, so all it could be is a plane intersection.
  42.     -- Check for a plane intersection.  If there isn't one then nothing will get hit.
  43.     local cell = game:GetService("Workspace").Terrain:WorldToCellPreferSolid(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
  44.     local planeLoc = nil
  45.     local hit = nil
  46.     -- If nothing was hit, do the plane intersection.
  47.     if 0 == game:GetService("Workspace").Terrain:GetCell(cell.X, cell.Y, cell.Z).Value then
  48.         cell = nil
  49.         planeLoc, hit = PlaneIntersection(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
  50.         if hit then
  51.             cell = planeLoc
  52.         end
  53.     end
  54.     return cell
  55. end
  56.  
  57. -- setup helper functions
  58. local insertBoundingBoxOverlapVector = Vector3.new(.3, .3, .3) -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units
  59.  
  60. -- rotates a model by yAngle radians about the global y-axis
  61. local function rotatePartAndChildren(part, rotCF, offsetFromOrigin)
  62.     -- rotate this thing, if it's a part
  63.     if part:IsA("BasePart") then
  64.         part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin
  65.     end
  66.  
  67.     -- recursively do the same to all children
  68.     local partChildren = part:GetChildren()
  69.     for c = 1, #partChildren do rotatePartAndChildren(partChildren[c], rotCF, offsetFromOrigin) end
  70. end
  71.  
  72. local function modelRotate(model, yAngle)
  73.     local rotCF = CFrame.Angles(0, yAngle, 0)
  74.     local offsetFromOrigin = model:GetModelCFrame().p
  75.  
  76.     rotatePartAndChildren(model, rotCF, offsetFromOrigin)
  77. end
  78.  
  79.  
  80. local function collectParts(object, baseParts, scripts, decals)
  81.     if object:IsA("BasePart") then
  82.         baseParts[#baseParts+1] = object
  83.     elseif object:IsA("Script") then
  84.         scripts[#scripts+1] = object
  85.     elseif object:IsA("Decal") then
  86.         decals[#decals+1] = object
  87.     end
  88.  
  89.     for index,child in pairs(object:GetChildren()) do
  90.         collectParts(child, baseParts, scripts, decals)
  91.     end
  92. end
  93.  
  94. local function clusterPartsInRegion(startVector, endVector)
  95.     local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
  96.  
  97.     local startCell = cluster:WorldToCell(startVector)
  98.     local endCell = cluster:WorldToCell(endVector)
  99.  
  100.     local startX = startCell.X
  101.     local startY = startCell.Y
  102.     local startZ = startCell.Z
  103.  
  104.     local endX = endCell.X
  105.     local endY = endCell.Y
  106.     local endZ = endCell.Z
  107.  
  108.     if startX < cluster.MaxExtents.Min.X then startX = cluster.MaxExtents.Min.X end
  109.     if startY < cluster.MaxExtents.Min.Y then startY = cluster.MaxExtents.Min.Y end
  110.     if startZ < cluster.MaxExtents.Min.Z then startZ = cluster.MaxExtents.Min.Z end
  111.  
  112.     if endX > cluster.MaxExtents.Max.X then endX = cluster.MaxExtents.Max.X end
  113.     if endY > cluster.MaxExtents.Max.Y then endY = cluster.MaxExtents.Max.Y end
  114.     if endZ > cluster.MaxExtents.Max.Z then endZ = cluster.MaxExtents.Max.Z end
  115.  
  116.     for x = startX, endX do
  117.         for y = startY, endY do
  118.             for z = startZ, endZ do
  119.                 if (cluster:GetCell(x, y, z).Value) > 0 then return true end
  120.             end
  121.         end
  122.     end
  123.  
  124.     return false
  125. end
  126.  
  127. local function findSeatsInModel(parent, seatTable)
  128.     if not parent then return end
  129.  
  130.     if parent.className == "Seat" or parent.className == "VehicleSeat" then
  131.         table.insert(seatTable, parent)
  132.     end
  133.     local myChildren = parent:GetChildren()
  134.     for j = 1, #myChildren do
  135.         findSeatsInModel(myChildren[j], seatTable)
  136.     end
  137. end
  138.  
  139. local function setSeatEnabledStatus(model, isEnabled)
  140.     local seatList = {}
  141.     findSeatsInModel(model, seatList)
  142.  
  143.     if isEnabled then
  144.         -- remove any welds called "SeatWeld" in seats
  145.         for i = 1, #seatList do
  146.             local nextSeat = seatList[i]:FindFirstChild("SeatWeld")
  147.             while nextSeat do nextSeat:Remove() nextSeat = seatList[i]:FindFirstChild("SeatWeld") end
  148.         end
  149.     else
  150.         -- put a weld called "SeatWeld" in every seat
  151.         --   this tricks it into thinking there's already someone sitting there, and it won't make you sit XD
  152.         for i = 1, #seatList do
  153.             local fakeWeld = Instance.new("Weld")
  154.             fakeWeld.Name = "SeatWeld"
  155.             fakeWeld.Parent = seatList[i]
  156.         end
  157.     end
  158. end
  159.  
  160. local function autoAlignToFace(parts)
  161.     local aatf = parts:FindFirstChild("AutoAlignToFace")
  162.     if aatf then return aatf.Value else return false end
  163. end
  164.  
  165. local function getClosestAlignedWorldDirection(aVector3InWorld)
  166.     local xDir = Vector3.new(1,0,0)
  167.     local yDir = Vector3.new(0,1,0)
  168.     local zDir = Vector3.new(0,0,1)
  169.     local xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z
  170.     local yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z
  171.     local zDot = aVector3InWorld.x * zDir.x + aVector3InWorld.y * zDir.y + aVector3InWorld.z * zDir.z
  172.  
  173.     if math.abs(xDot) > math.abs(yDot) and math.abs(xDot) > math.abs(zDot) then
  174.         if xDot > 0 then
  175.             return 0
  176.         else
  177.             return 3
  178.         end
  179.     elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs(zDot) then
  180.         if yDot > 0 then
  181.             return 1
  182.         else
  183.             return 4
  184.         end
  185.     else
  186.         if zDot > 0 then
  187.             return 2
  188.         else
  189.             return 5
  190.         end
  191.     end
  192. end
  193.  
  194. local function positionPartsAtCFrame3(aCFrame, currentParts)
  195.     local insertCFrame = nil
  196.     if not currentParts then return currentParts end
  197.     if currentParts and (currentParts:IsA("Model") or currentParts:IsA("Tool")) then
  198.         insertCFrame = currentParts:GetModelCFrame()
  199.         currentParts:TranslateBy(aCFrame.p - insertCFrame.p)
  200.     else
  201.         currentParts.CFrame = aCFrame
  202.     end
  203.     return currentParts
  204. end
  205.  
  206. local function calcRayHitTime(rayStart, raySlope, intersectionPlane)
  207.     if math.abs(raySlope) < .01 then return 0 end -- 0 slope -->  we just say intersection time is 0, and sidestep this dimension
  208.     return (intersectionPlane - rayStart) / raySlope
  209. end
  210.  
  211. local function modelTargetSurface(partOrModel, rayStart, rayEnd)
  212.     if not partOrModel then
  213.         return 0
  214.     end
  215.  
  216.     local modelCFrame = nil
  217.     local modelSize = nil
  218.     if partOrModel:IsA("Model") then
  219.         modelCFrame = partOrModel:GetModelCFrame()
  220.         modelSize = partOrModel:GetModelSize()
  221.     else
  222.         modelCFrame = partOrModel.CFrame
  223.         modelSize = partOrModel.Size
  224.     end
  225.  
  226.     local mouseRayStart = modelCFrame:pointToObjectSpace(rayStart)
  227.     local mouseRayEnd = modelCFrame:pointToObjectSpace(rayEnd)
  228.     local mouseSlope = mouseRayEnd - mouseRayStart
  229.  
  230.     local xPositive = 1
  231.     local yPositive = 1
  232.     local zPositive = 1
  233.     if mouseSlope.X > 0 then xPositive = -1 end
  234.     if mouseSlope.Y > 0 then yPositive = -1 end
  235.     if mouseSlope.Z > 0 then zPositive = -1 end
  236.  
  237.     -- find which surface the transformed mouse ray hits (using modelSize):
  238.     local xHitTime = calcRayHitTime(mouseRayStart.X, mouseSlope.X, modelSize.X/2 * xPositive)
  239.     local yHitTime = calcRayHitTime(mouseRayStart.Y, mouseSlope.Y, modelSize.Y/2 * yPositive)
  240.     local zHitTime = calcRayHitTime(mouseRayStart.Z, mouseSlope.Z, modelSize.Z/2 * zPositive)
  241.  
  242.     local hitFace = 0
  243.  
  244.     --if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 then
  245.     if xHitTime > yHitTime then
  246.         if xHitTime > zHitTime then
  247.             -- xFace is hit
  248.             hitFace = 1*xPositive
  249.         else
  250.             -- zFace is hit
  251.             hitFace = 3*zPositive
  252.         end
  253.     else
  254.         if yHitTime > zHitTime then
  255.             -- yFace is hit
  256.             hitFace = 2*yPositive
  257.         else
  258.             -- zFace is hit
  259.             hitFace = 3*zPositive
  260.         end
  261.     end
  262.  
  263.     return hitFace
  264. end
  265.  
  266. local function getBoundingBox2(partOrModel)
  267.  
  268.     -- for models, the bounding box is defined as the minimum and maximum individual part bounding boxes
  269.     -- relative to the first part's coordinate frame.
  270.     local minVec = Vector3.new(math.huge, math.huge, math.huge)
  271.     local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
  272.  
  273.     if partOrModel:IsA("Terrain") then
  274.         minVec = Vector3.new(-2, -2, -2)
  275.         maxVec = Vector3.new(2, 2, 2)
  276.     elseif partOrModel:IsA("BasePart") then
  277.         minVec = -0.5 * partOrModel.Size
  278.         maxVec = -minVec
  279.     else
  280.         maxVec = partOrModel:GetModelSize()*0.5
  281.         minVec = -maxVec
  282.     end
  283.  
  284.     -- Adjust bounding box to reflect what the model or part author wants in  terms of justification
  285.     local justifyValue = partOrModel:FindFirstChild("Justification")
  286.     if justifyValue ~= nil then
  287.         -- find the multiple of 4 that contains the model
  288.         local justify = justifyValue.Value
  289.         local two = Vector3.new(2, 2, 2)
  290.         local actualBox = maxVec - minVec - Vector3.new(0.01, 0.01, 0.01)
  291.         local containingGridBox = Vector3.new(4 * math.ceil(actualBox.x/4), 4 * math.ceil(actualBox.y/4), 4 * math.ceil(actualBox.z/4))
  292.         local adjustment = containingGridBox - actualBox
  293.         minVec = minVec - 0.5 * adjustment * justify
  294.         maxVec = maxVec + 0.5 * adjustment * (two - justify)
  295.     end
  296.  
  297.     return minVec, maxVec
  298. end
  299.  
  300. local function getBoundingBoxInWorldCoordinates(partOrModel)
  301.     local minVec = Vector3.new(math.huge, math.huge, math.huge)
  302.     local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
  303.  
  304.     if partOrModel:IsA("BasePart") and not partOrModel:IsA("Terrain") then
  305.         local vec1 = partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
  306.         local vec2 = partOrModel.CFrame:pointToWorldSpace(0.5 * partOrModel.Size)
  307.         minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
  308.         maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
  309.     elseif partOrModel:IsA("Terrain") then
  310.         -- we shouldn't have to deal with this case
  311.         --minVec = Vector3.new(-2, -2, -2)
  312.         --maxVec = Vector3.new(2, 2, 2)
  313.     else
  314.         local vec1 = partOrModel:GetModelCFrame():pointToWorldSpace(-0.5 * partOrModel:GetModelSize())
  315.         local vec2 = partOrModel:GetModelCFrame():pointToWorldSpace(0.5 * partOrModel:GetModelSize())
  316.         minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
  317.         maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
  318.     end
  319.  
  320.     return minVec, maxVec
  321. end
  322.  
  323. local function getTargetPartBoundingBox(targetPart)
  324.     if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
  325.         return getBoundingBox2(targetPart.Parent)
  326.     else
  327.         return getBoundingBox2(targetPart)
  328.     end
  329. end
  330.  
  331. local function getMouseTargetCFrame(targetPart)
  332.     if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
  333.         if targetPart.Parent:IsA("Tool") then return targetPart.Parent.Handle.CFrame
  334.         else return targetPart.Parent:GetModelCFrame() end
  335.     else
  336.         return targetPart.CFrame
  337.     end
  338. end
  339.  
  340. local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this part
  341.     if not part then return false end
  342.     if not part.Parent then return false end
  343.     if part:FindFirstChild("Humanoid") then return false end
  344.     if part:FindFirstChild("RobloxStamper") or part:FindFirstChild("RobloxModel") then return true end
  345.     if part:IsA("Part") and not part.CanCollide then return false end
  346.     if part == game:GetService("Lighting") then return false end
  347.     return isBlocker(part.Parent)
  348. end
  349.  
  350. -- helper function to determine if a character can be pushed upwards by a certain amount
  351. -- character is 5 studs tall, we'll check a 1.5 x 1.5 x 4.5 box around char, with center .5 studs below torsocenter
  352. local function spaceAboveCharacter(charTorso, newTorsoY, stampData)
  353.     local partsAboveChar = game:GetService("Workspace"):FindPartsInRegion3(
  354.         Region3.new(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75),
  355.         Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)),
  356.         charTorso.Parent,
  357.         100)
  358.  
  359.     for j = 1, #partsAboveChar do
  360.         if partsAboveChar[j].CanCollide and not partsAboveChar[j]:IsDescendantOf(stampData.CurrentParts) then return false end
  361.     end
  362.  
  363.     if clusterPartsInRegion(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75),
  364.         Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)) then
  365.             return false
  366.     end
  367.  
  368.     return true
  369. end
  370.  
  371.  
  372. local function findConfigAtMouseTarget(Mouse, stampData)
  373.     -- *Critical Assumption* :
  374.     --              This function assumes the target CF axes are orthogonal with the target bounding box faces
  375.     --              And, it assumes the insert CF axes are orthongonal with the insert bounding box faces
  376.     --              Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor
  377.     --              will it work for parts in a model that are not orthogonally aligned with the model's CF.
  378.  
  379.     if not Mouse then return nil end -- This can happen sometimes, return if so
  380.     if not stampData then error("findConfigAtMouseTarget: stampData is nil") return nil end
  381.     if not stampData["CurrentParts"] then return nil end
  382.  
  383.     local grid = 4.0
  384.     local admissibleConfig = false
  385.     local targetConfig = CFrame.new(0,0,0)
  386.  
  387.     local minBB, maxBB = getBoundingBox2(stampData.CurrentParts)
  388.     local diagBB = maxBB - minBB
  389.  
  390.     local insertCFrame
  391.     if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
  392.         insertCFrame = stampData.CurrentParts:GetModelCFrame()
  393.     else
  394.         insertCFrame = stampData.CurrentParts.CFrame
  395.     end
  396.  
  397.     if Mouse then
  398.         if stampData.CurrentParts:IsA("Tool") then
  399.             Mouse.TargetFilter = stampData.CurrentParts.Handle
  400.         else
  401.             Mouse.TargetFilter = stampData.CurrentParts
  402.         end
  403.     end
  404.  
  405.     local hitPlane = false
  406.     local targetPart = nil
  407.     local success = pcall(function() targetPart = Mouse.Target end)
  408.  
  409.     if not success then-- or targetPart == nil then
  410.         return admissibleConfig, targetConfig
  411.     end
  412.  
  413.     local mouseHitInWorld = Vector3.new(0, 0, 0)
  414.     if Mouse then
  415.         mouseHitInWorld = Vector3.new(Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z)
  416.     end
  417.  
  418.     local cellPos = nil
  419.  
  420.     -- Nothing was hit, so check for the default plane.
  421.     if nil == targetPart then
  422.         cellPos = GetTerrainForMouse(Mouse)
  423.         if nil == cellPos then
  424.             hitPlane = false
  425.             return admissibleConfig, targetConfig
  426.         else
  427.             targetPart = game:GetService("Workspace").Terrain
  428.             hitPlane = true
  429.             -- Take into account error that will occur.
  430.             cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z)
  431.             mouseHitInWorld = game:GetService("Workspace").Terrain:CellCenterToWorld(cellPos.x, cellPos.y, cellPos.z)
  432.         end
  433.     end
  434.  
  435.     -- test mouse hit location
  436.     local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart)
  437.     local diagBBTarget = maxBBTarget - minBBTarget
  438.     local targetCFrame = getMouseTargetCFrame(targetPart)
  439.  
  440.     if targetPart:IsA("Terrain") then
  441.         local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
  442.         local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld)
  443.         if hitPlane then
  444.             cellID = cellPos
  445.         end
  446.  
  447.         targetCFrame = CFrame.new(game:GetService("Workspace").Terrain:CellCenterToWorld(cellID.x, cellID.y, cellID.z))
  448.     end
  449.  
  450.     local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld)
  451.     local targetVectorInWorld = Vector3.new(0,0,0)
  452.     if Mouse then
  453.         -- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME!  (.TargetSurface is in terms of the part CFrame, so this would break, right?  [HotThoth])
  454.         --   (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse)
  455.         --targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))
  456.         targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))   -- better, but model cframe would be best
  457.         --[[if targetPart.Parent:IsA("Model") then
  458.             local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p)  -- best, if you get it right
  459.             local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
  460.             if hitFace > 0 then
  461.                 targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace])
  462.             elseif hitFace < 0 then
  463.                 targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace])
  464.             end
  465.         end]]
  466.     end
  467.  
  468.     local targetRefPointInTarget
  469.     local clampToSurface
  470.     local insertRefPointInInsert
  471.  
  472.     if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 then
  473.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
  474.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
  475.         clampToSurface = Vector3.new(0,1,1)
  476.     elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 3 then
  477.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
  478.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
  479.         clampToSurface = Vector3.new(0,1,1)
  480.     elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 1 then
  481.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
  482.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
  483.         clampToSurface = Vector3.new(1,0,1)
  484.     elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 4 then
  485.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
  486.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
  487.         clampToSurface = Vector3.new(1,0,1)
  488.     elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 2 then
  489.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
  490.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
  491.         clampToSurface = Vector3.new(1,1,0)
  492.     else
  493.         targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
  494.         insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
  495.         clampToSurface = Vector3.new(1,1,0)
  496.     end
  497.  
  498.     targetRefPointInTarget = targetRefPointInTarget * (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget)
  499.     insertRefPointInInsert = insertRefPointInInsert * (0.5 * diagBB) + 0.5 * (maxBB + minBB)
  500.  
  501.     -- To Do: For cases that are not aligned to the world grid, account for the minimal rotation
  502.     -- needed to bring the Insert part(s) into alignment with the Target Part
  503.     -- Apply the rotation here
  504.  
  505.     local delta = mouseHitInTarget - targetRefPointInTarget
  506.     local deltaClamped = Vector3.new(grid * math.modf(delta.x/grid), grid * math.modf(delta.y/grid), grid * math.modf(delta.z/grid))
  507.     deltaClamped = deltaClamped * clampToSurface
  508.     local targetTouchInTarget = deltaClamped + targetRefPointInTarget
  509.  
  510.     local TargetTouchRelToWorld = targetCFrame:pointToWorldSpace(targetTouchInTarget)
  511.     local InsertTouchInWorld = insertCFrame:vectorToWorldSpace(insertRefPointInInsert)
  512.     local posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld
  513.  
  514.     local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = insertCFrame:components()
  515.     targetConfig = CFrame.new(posInsertOriginInWorld.x, posInsertOriginInWorld.y, posInsertOriginInWorld.z, R00, R01, R02, R10, R11, R12, R20, R21, R22)
  516.     admissibleConfig = true
  517.  
  518.     return admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld)
  519. end
  520.  
  521. local function truncateToCircleEighth(bigValue, littleValue)
  522.     local big = math.abs(bigValue)
  523.     local little = math.abs(littleValue)
  524.     local hypotenuse = math.sqrt(big*big + little*little)
  525.     local frac = little / hypotenuse
  526.  
  527.     local bigSign = 1
  528.     local littleSign = 1
  529.     if bigValue < 0 then bigSign = -1 end
  530.     if littleValue < 0 then littleSign = -1 end
  531.  
  532.     if frac > .382683432 then
  533.         -- between 22.5 and 45 degrees, so truncate to 45-degree tilt
  534.         return .707106781 * hypotenuse * bigSign, .707106781 * hypotenuse * littleSign
  535.     else
  536.         -- between 0 and 22.5 degrees, so truncate to 0-degree tilt
  537.         return hypotenuse * bigSign, 0
  538.     end
  539. end
  540.  
  541.  
  542. local function saveTheWelds(object, manualWeldTable, manualWeldParentTable)
  543.     if object:IsA("ManualWeld") or object:IsA("Rotate") then
  544.         table.insert(manualWeldTable, object)
  545.         table.insert(manualWeldParentTable, object.Parent)
  546.     else
  547.         local children = object:GetChildren()
  548.         for i = 1, #children do
  549.             saveTheWelds(children[i], manualWeldTable, manualWeldParentTable)
  550.         end
  551.     end
  552. end
  553.  
  554. local function restoreTheWelds(manualWeldTable, manualWeldParentTable)
  555.     for i = 1, #manualWeldTable do
  556.         manualWeldTable[i].Parent = manualWeldParentTable[i]
  557.     end
  558. end
  559.  
  560. t.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model and stamper metadata
  561.     if not EditRegion then return true, false end
  562.  
  563.     local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel)
  564.  
  565.     if minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X/2 or
  566.         minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y/2 or
  567.         minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z/2 then
  568.             return false, false
  569.     end
  570.  
  571.     if maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X/2 or
  572.         maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y/2 or
  573.         maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z/2 then
  574.             return false, false
  575.     end
  576.  
  577.     return true, false
  578. end
  579.  
  580. t.GetStampModel = function(assetId, terrainShape, useAssetVersionId)
  581.     if assetId == 0 then
  582.         return nil, "No Asset"
  583.     end
  584.     if assetId < 0 then
  585.         return nil, "Negative Asset"
  586.     end
  587.  
  588.     local function UnlockInstances(object)
  589.         if object:IsA("BasePart") then
  590.             object.Locked = false
  591.         end
  592.         for index,child in pairs(object:GetChildren()) do
  593.             UnlockInstances(child)
  594.         end
  595.     end
  596.  
  597.     local function getClosestColorToTerrainMaterial(terrainValue)
  598.         if terrainValue == 1 then
  599.             return BrickColor.new("Bright green")
  600.         elseif terrainValue == 2 then
  601.             return BrickColor.new("Bright yellow")
  602.         elseif terrainValue == 3 then
  603.             return BrickColor.new("Bright red")
  604.         elseif terrainValue == 4 then
  605.             return BrickColor.new("Sand red")
  606.         elseif terrainValue == 5 then
  607.             return BrickColor.new("Black")
  608.         elseif terrainValue == 6 then
  609.             return BrickColor.new("Dark stone grey")
  610.         elseif terrainValue == 7 then
  611.             return BrickColor.new("Sand blue")
  612.         elseif terrainValue == 8 then
  613.             return BrickColor.new("Deep orange")
  614.         elseif terrainValue == 9 then
  615.             return BrickColor.new("Dark orange")
  616.         elseif terrainValue == 10 then
  617.             return BrickColor.new("Reddish brown")
  618.         elseif terrainValue == 11 then
  619.             return BrickColor.new("Light orange")
  620.         elseif terrainValue == 12 then
  621.             return BrickColor.new("Light stone grey")
  622.         elseif terrainValue == 13 then
  623.             return BrickColor.new("Sand green")
  624.         elseif terrainValue == 14 then
  625.             return BrickColor.new("Medium stone grey")
  626.         elseif terrainValue == 15 then
  627.             return BrickColor.new("Really red")
  628.         elseif terrainValue == 16 then
  629.             return BrickColor.new("Really blue")
  630.         elseif terrainValue == 17 then
  631.             return BrickColor.new("Bright blue")
  632.         else
  633.             return BrickColor.new("Bright green")
  634.         end
  635.     end
  636.  
  637.     local function setupFakeTerrainPart(cellMat, cellType, cellOrient)
  638.         local newTerrainPiece = nil
  639.         if (cellType == 1 or cellType == 4) then newTerrainPiece = Instance.new("WedgePart")
  640.         elseif (cellType == 2) then newTerrainPiece = Instance.new("CornerWedgePart")
  641.         else newTerrainPiece = Instance.new("Part") end
  642.         newTerrainPiece.Name = "MegaClusterCube"
  643.         newTerrainPiece.Size = Vector3.new(4, 4, 4)
  644.         newTerrainPiece.BottomSurface = "Smooth"
  645.         newTerrainPiece.TopSurface = "Smooth"
  646.  
  647.         -- can add decals or textures here if feeling particularly adventurous...  for now, can make a table of look-up colors
  648.         newTerrainPiece.BrickColor = getClosestColorToTerrainMaterial(cellMat)
  649.  
  650.         local sideways = 0
  651.         local flipped = math.pi
  652.         if cellType == 4 then sideways = -math.pi/2 end
  653.         if cellType == 2 or cellType == 3 then flipped = 0 end
  654.         newTerrainPiece.CFrame = CFrame.Angles(0, math.pi/2*cellOrient + flipped, sideways)
  655.  
  656.         if cellType == 3 then
  657.             local inverseCornerWedgeMesh = Instance.new("SpecialMesh")
  658.             inverseCornerWedgeMesh.MeshType = "FileMesh"
  659.             inverseCornerWedgeMesh.MeshId = "https://www.roblox.com/asset/?id=66832495"
  660.             inverseCornerWedgeMesh.Scale = Vector3.new(2, 2, 2)
  661.             inverseCornerWedgeMesh.Parent = newTerrainPiece
  662.         end
  663.  
  664.         local materialTag = Instance.new("Vector3Value")
  665.         materialTag.Value = Vector3.new(cellMat, cellType, cellOrient)
  666.         materialTag.Name = "ClusterMaterial"
  667.         materialTag.Parent = newTerrainPiece
  668.  
  669.         return newTerrainPiece
  670.     end
  671.  
  672.     -- This call will cause a "wait" until the data comes back
  673.     -- below we wait a max of 8 seconds before deciding to bail out on loading
  674.     local root
  675.     local loader
  676.     loading = true
  677.     if useAssetVersionId then
  678.         loader = coroutine.create(function()
  679.             root = game:GetService("InsertService"):LoadAssetVersion(assetId)
  680.             loading = false
  681.         end)
  682.         coroutine.resume(loader)
  683.     else
  684.         loader = coroutine.create(function()
  685.             root = game:GetService("InsertService"):LoadAsset(assetId)
  686.             loading = false
  687.         end)
  688.         coroutine.resume(loader)
  689.     end
  690.  
  691.     local lastGameTime = 0
  692.     local totalTime = 0
  693.     local maxWait = 8
  694.     while loading and totalTime < maxWait do
  695.         lastGameTime = tick()
  696.         wait(1)
  697.         totalTime = totalTime + tick() - lastGameTime
  698.     end
  699.     loading = false
  700.  
  701.     if totalTime >= maxWait then
  702.         return nil, "Load Time Fail"
  703.     end
  704.  
  705.  
  706.     if root == nil then
  707.         return nil, "Load Asset Fail"
  708.     end
  709.  
  710.     if not root:IsA("Model") then
  711.         return nil, "Load Type Fail"
  712.     end
  713.  
  714.     local instances = root:GetChildren()
  715.     if #instances == 0 then
  716.         return nil, "Empty Model Fail"
  717.     end
  718.  
  719.     --Unlock all parts that are inserted, to make sure they are editable
  720.     UnlockInstances(root)
  721.  
  722.     --Continue the insert process
  723.     root = root:GetChildren()[1]
  724.  
  725.     --Examine the contents and decide what it looks like
  726.     for pos, instance in pairs(instances) do
  727.         if instance:IsA("Team") then
  728.             instance.Parent = game:GetService("Teams")
  729.         elseif instance:IsA("Sky") then
  730.             local lightingService = game:GetService("Lighting")
  731.             for index,child in pairs(lightingService:GetChildren()) do
  732.                 if child:IsA("Sky") then
  733.                     child:Remove();
  734.                 end
  735.             end
  736.             instance.Parent = lightingService
  737.             return
  738.         end
  739.     end
  740.  
  741.     -- ...and tag all inserted models for subsequent origin identification
  742.     -- if no RobloxModel tag already exists, then add it.
  743.     if root:FindFirstChild("RobloxModel") == nil then
  744.         local stringTag = Instance.new("BoolValue", root)
  745.         stringTag.Name = "RobloxModel"
  746.  
  747.         if root:FindFirstChild("RobloxStamper") == nil then
  748.             local stringTag2 = Instance.new("BoolValue", root)
  749.             stringTag2.Name = "RobloxStamper"
  750.         end
  751.     end
  752.  
  753.     if terrainShape then
  754.         if root.Name == "MegaClusterCube" then
  755.             if (terrainShape == 6) then -- insert an autowedging tag
  756.                 local autowedgeTag = Instance.new("BoolValue")
  757.                 autowedgeTag.Name = "AutoWedge"
  758.                 autowedgeTag.Parent = root
  759.             else
  760.                 local clusterTag = root:FindFirstChild("ClusterMaterial")
  761.                 if clusterTag then
  762.                     if clusterTag:IsA("Vector3Value") then
  763.                         root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z)
  764.                     else
  765.                         root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0)
  766.                     end
  767.                 else
  768.                     root = setupFakeTerrainPart(1, terrainShape, 0)
  769.                 end
  770.             end
  771.         end
  772.     end
  773.  
  774.     return root
  775. end
  776.  
  777.  
  778.  
  779. t.SetupStamperDragger = function(modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc)
  780.     if not modelToStamp then
  781.         error("SetupStamperDragger: modelToStamp (first arg) is nil!  Should be a stamper model")
  782.         return nil
  783.     end
  784.     if not modelToStamp:IsA("Model") and not modelToStamp:IsA("BasePart") then
  785.         error("SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!")
  786.         return nil
  787.     end
  788.     if not Mouse then
  789.         error("SetupStamperDragger: Mouse (second arg) is nil!  Should be a mouse object")
  790.         return nil
  791.     end
  792.     if not Mouse:IsA("Mouse") then
  793.         error("SetupStamperDragger: Mouse (second arg) is not of type Mouse!")
  794.         return nil
  795.     end
  796.  
  797.     local stampInModel = nil
  798.     local allowedStampRegion = nil
  799.     local stampFailedFunc = nil
  800.     if StampInModel then
  801.         if not StampInModel:IsA("Model") then
  802.             error("SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'")
  803.             return nil
  804.         end
  805.         if not AllowedStampRegion then
  806.             error("SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined")
  807.             return nil
  808.         end
  809.         stampFailedFunc = StampFailedFunc
  810.         stampInModel = StampInModel
  811.         allowedStampRegion = AllowedStampRegion
  812.     end
  813.  
  814.     -- Init all state variables
  815.     local gInitial90DegreeRotations = 0
  816.     local stampData = nil
  817.     local mouseTarget = nil
  818.  
  819.     local errorBox = Instance.new("SelectionBox")
  820.     errorBox.Color = BrickColor.new("Bright red")
  821.     errorBox.Transparency = 0
  822.     errorBox.Archivable = false
  823.  
  824.     -- for megacluster MEGA STAMPING
  825.     local adornPart = Instance.new("Part")
  826.     adornPart.Parent = nil
  827.     adornPart.Size = Vector3.new(4, 4, 4)
  828.     adornPart.CFrame = CFrame.new()
  829.     adornPart.Archivable = false
  830.  
  831.     local adorn = Instance.new("SelectionBox")
  832.     adorn.Color = BrickColor.new("Toothpaste")
  833.     adorn.Adornee = adornPart
  834.     adorn.Visible = true
  835.     adorn.Transparency = 0
  836.     adorn.Name = "HighScalabilityStamperLine"
  837.     adorn.Archivable = false
  838.  
  839.     local HighScalabilityLine = {}
  840.     HighScalabilityLine.Start = nil
  841.     HighScalabilityLine.End = nil
  842.     HighScalabilityLine.Adorn = adorn
  843.     HighScalabilityLine.AdornPart = adornPart
  844.     HighScalabilityLine.InternalLine = nil
  845.     HighScalabilityLine.NewHint = true
  846.  
  847.     HighScalabilityLine.MorePoints = {nil, nil}
  848.     HighScalabilityLine.MoreLines = {nil, nil}
  849.     HighScalabilityLine.Dimensions = 1
  850.  
  851.     local control = {}
  852.     local movingLock = false
  853.     local stampUpLock = false
  854.     local unstampableSurface = false
  855.     local mouseCons = {}
  856.     local keyCon = nil
  857.  
  858.     local stamped = Instance.new("BoolValue")
  859.     stamped.Archivable = false
  860.     stamped.Value = false
  861.  
  862.     local lastTarget = {}
  863.     lastTarget.TerrainOrientation = 0
  864.     lastTarget.CFrame = 0
  865.  
  866.     local cellInfo = {}
  867.     cellInfo.Material = 1
  868.     cellInfo.clusterType = 0
  869.     cellInfo.clusterOrientation = 0
  870.  
  871.     local function isMegaClusterPart()
  872.         if not stampData then return false end
  873.         if not stampData.CurrentParts then return false end
  874.  
  875.         return ( stampData.CurrentParts:FindFirstChild("ClusterMaterial",true) or (stampData.CurrentParts.Name == "MegaClusterCube") )
  876.     end
  877.  
  878.     local function DoHighScalabilityRegionSelect()
  879.         local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube")
  880.         if not megaCube then
  881.             if not stampData.CurrentParts.Name == "MegaClusterCube" then
  882.                 return
  883.             else
  884.                 megaCube = stampData.CurrentParts
  885.             end
  886.         end
  887.  
  888.         HighScalabilityLine.End = megaCube.CFrame.p
  889.         local line = nil
  890.         local line2 = Vector3.new(0, 0, 0)
  891.         local line3 = Vector3.new(0, 0, 0)
  892.  
  893.         if HighScalabilityLine.Dimensions == 1 then
  894.             -- extract the line from these positions and limit to a 2D plane made from 2 of the world axes
  895.             --   then use dominating axis to limit line to be at 45-degree intervals
  896.             --   will use this internal representation of the line for the actual stamping
  897.             line = (HighScalabilityLine.End - HighScalabilityLine.Start)
  898.  
  899.             if math.abs(line.X) < math.abs(line.Y) then
  900.                 if math.abs(line.X) < math.abs(line.Z) then
  901.                     -- limit to Y/Z plane, domination unknown
  902.                     local newY, newZ
  903.                     if (math.abs(line.Y) > math.abs(line.Z)) then
  904.                         newY, newZ = truncateToCircleEighth(line.Y, line.Z)
  905.                     else
  906.                         newZ, newY = truncateToCircleEighth(line.Z, line.Y)
  907.                     end
  908.                     line = Vector3.new(0, newY, newZ)
  909.                 else
  910.                     -- limit to X/Y plane, with Y dominating
  911.                     local newY, newX = truncateToCircleEighth(line.Y, line.X)
  912.                     line = Vector3.new(newX, newY, 0)
  913.                 end
  914.             else
  915.                 if math.abs(line.Y) < math.abs(line.Z) then
  916.                     -- limit to X/Z plane, domination unknown
  917.                     local newX, newZ
  918.                     if math.abs(line.X) > math.abs(line.Z) then
  919.                         newX, newZ = truncateToCircleEighth(line.X, line.Z)
  920.                     else
  921.                         newZ, newX = truncateToCircleEighth(line.Z, line.X)
  922.                     end
  923.                     line = Vector3.new(newX, 0, newZ)
  924.                 else
  925.                     -- limit to X/Y plane, with X dominating
  926.                     local newX, newY = truncateToCircleEighth(line.X, line.Y)
  927.                     line = Vector3.new(newX, newY, 0)
  928.                 end
  929.             end
  930.             HighScalabilityLine.InternalLine = line
  931.  
  932.         elseif HighScalabilityLine.Dimensions == 2 then
  933.             line = HighScalabilityLine.MoreLines[1]
  934.             line2 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[1]
  935.  
  936.             -- take out any component of line2 along line1, so you get perpendicular to line1 component
  937.             line2 = line2 - line.unit*line.unit:Dot(line2)
  938.  
  939.             local tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
  940.  
  941.             -- then zero out whichever is the smaller component
  942.             local yAxis = tempCFrame:vectorToWorldSpace(Vector3.new(0, 1, 0))
  943.             local xAxis = tempCFrame:vectorToWorldSpace(Vector3.new(1, 0, 0))
  944.  
  945.             local xComp = xAxis:Dot(line2)
  946.             local yComp = yAxis:Dot(line2)
  947.  
  948.             if math.abs(yComp) > math.abs(xComp) then
  949.                 line2 = line2 - xAxis * xComp
  950.             else
  951.                 line2 = line2 - yAxis * yComp
  952.             end
  953.  
  954.             HighScalabilityLine.InternalLine = line2
  955.  
  956.         elseif HighScalabilityLine.Dimensions == 3 then
  957.             line  = HighScalabilityLine.MoreLines[1]
  958.             line2 = HighScalabilityLine.MoreLines[2]
  959.             line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2]
  960.  
  961.             -- zero out all components of previous lines
  962.             line3 = line3 - line.unit * line.unit:Dot(line3)
  963.             line3 = line3 - line2.unit * line2.unit:Dot(line3)
  964.  
  965.             HighScalabilityLine.InternalLine = line3
  966.         end
  967.  
  968.         -- resize the "line" graphic to be the correct size and orientation
  969.         local tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
  970.  
  971.         if HighScalabilityLine.Dimensions == 1 then  -- faster calculation for line
  972.             HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, line.magnitude + 4)
  973.             HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(Vector3.new(2, 2, 2) - HighScalabilityLine.AdornPart.Size/2)
  974.         else
  975.             local boxSize = tempCFrame:vectorToObjectSpace(line + line2 + line3)
  976.             HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, 4) + Vector3.new(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z))
  977.             HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(boxSize/2)
  978.         end
  979.  
  980.         -- make player able to see this ish
  981.  
  982.         local gui = nil
  983.         if game:GetService("Players")["LocalPlayer"] then
  984.             gui = game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui")
  985.             if gui and gui:IsA("PlayerGui") then
  986.                 if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
  987.                     HighScalabilityLine.Adorn.Parent = gui
  988.                 elseif HighScalabilityLine.Dimensions > 1 then
  989.                     HighScalabilityLine.Adorn.Parent = gui
  990.                 end
  991.             end
  992.         end
  993.  
  994.         if gui == nil then -- we are in studio
  995.             gui = game:GetService("CoreGui")
  996.             if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
  997.                 HighScalabilityLine.Adorn.Parent = gui
  998.             elseif HighScalabilityLine.Dimensions > 1 then
  999.                 HighScalabilityLine.Adorn.Parent = gui
  1000.             end
  1001.         end
  1002.     end
  1003.  
  1004.  
  1005.     local function DoStamperMouseMove(Mouse)
  1006.         if not Mouse then
  1007.             error("Error: RbxStamper.DoStamperMouseMove: Mouse is nil")
  1008.             return
  1009.         end
  1010.         if not Mouse:IsA("Mouse") then
  1011.             error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className,"should be of type Mouse")
  1012.             return
  1013.         end
  1014.  
  1015.         -- There wasn't a target (no part or terrain), so check for plane intersection.
  1016.         if not Mouse.Target then
  1017.             local cellPos = GetTerrainForMouse(Mouse)
  1018.             if nil == cellPos then
  1019.                 return
  1020.             end
  1021.         end
  1022.  
  1023.         if not stampData then
  1024.             return
  1025.         end
  1026.  
  1027.         -- don't move with dragger - will move in one step on mouse down
  1028.         -- draw ghost at acceptable positions
  1029.         local configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Mouse, stampData)
  1030.         if not configFound then
  1031.             error("RbxStamper.DoStamperMouseMove No configFound, returning")
  1032.             return
  1033.         end
  1034.  
  1035.         local numRotations = 0 -- update this according to how many rotations you need to get it to target surface
  1036.         if autoAlignToFace(stampData.CurrentParts) and targetSurface ~= 1 and targetSurface ~= 4 then -- pre-rotate the flag or portrait so it's aligned correctly
  1037.             if      targetSurface == 3 then numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
  1038.             elseif  targetSurface == 0 then numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
  1039.             elseif  targetSurface == 5 then numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
  1040.             elseif  targetSurface == 2 then numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
  1041.             end
  1042.         end
  1043.  
  1044.         local ry = math.pi/2
  1045.         gInitial90DegreeRotations = gInitial90DegreeRotations + numRotations
  1046.         if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
  1047.             --stampData.CurrentParts:Rotate(0, ry*numRotations, 0)
  1048.             modelRotate(stampData.CurrentParts, ry*numRotations)
  1049.         else
  1050.             stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry*numRotations, 0) * stampData.CurrentParts.CFrame
  1051.         end
  1052.  
  1053.         -- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE
  1054.         local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
  1055.  
  1056.         -- need to offset by distance to be dragged
  1057.         local currModelCFrame = nil
  1058.         if stampData.CurrentParts:IsA("Model") then
  1059.             currModelCFrame = stampData.CurrentParts:GetModelCFrame()
  1060.         else
  1061.             currModelCFrame = stampData.CurrentParts.CFrame
  1062.         end
  1063.  
  1064.         minBB = minBB + targetCFrame.p - currModelCFrame.p
  1065.         maxBB = maxBB + targetCFrame.p - currModelCFrame.p
  1066.  
  1067.         -- don't drag into terrain
  1068.         if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
  1069.             if lastTarget.CFrame then
  1070.                 if (stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)) then
  1071.                     local theClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1072.                     if theClusterMaterial:IsA("Vector3Value") then
  1073.                         local stampClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1074.                         if stampClusterMaterial then
  1075.                             stampClusterMaterial = theClusterMaterial
  1076.                         end
  1077.                     end
  1078.                 end
  1079.             end
  1080.             return
  1081.         end
  1082.  
  1083.         -- if we are stamping a terrain part, make sure it goes on the grid! Otherwise preview block could be placed off grid, but stamped on grid
  1084.         if isMegaClusterPart() then
  1085.             local cellToStamp = game:GetService("Workspace").Terrain:WorldToCell(targetCFrame.p)
  1086.             local newCFramePosition = game:GetService("Workspace").Terrain:CellCenterToWorld(cellToStamp.X, cellToStamp.Y, cellToStamp.Z)
  1087.             local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = targetCFrame:components()
  1088.             targetCFrame = CFrame.new(newCFramePosition.X,newCFramePosition.Y,newCFramePosition.Z,R00, R01, R02, R10, R11, R12, R20, R21, R22)
  1089.         end
  1090.  
  1091.         positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
  1092.         lastTarget.CFrame = targetCFrame  -- successful positioning, so update 'dat cframe
  1093.         if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
  1094.             local clusterMat = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1095.             if clusterMat:IsA("Vector3Value") then
  1096.                 lastTarget.TerrainOrientation = clusterMat.Value.Z
  1097.             end
  1098.         end
  1099.  
  1100.  
  1101.         -- auto break joints code
  1102.         if Mouse and Mouse.Target and Mouse.Target.Parent then
  1103.             local modelInfo = Mouse.Target:FindFirstChild("RobloxModel")
  1104.             if not modelInfo then modelInfo = Mouse.Target.Parent:FindFirstChild("RobloxModel") end
  1105.  
  1106.             local myModelInfo = stampData.CurrentParts:FindFirstChild("UnstampableFaces")
  1107.  
  1108.             --if (modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then  -- need better targetSurface calcs
  1109.             if (true) then
  1110.                 local breakingFaces = ""
  1111.                 local myBreakingFaces = ""
  1112.                 if modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces") then breakingFaces = modelInfo.Parent.UnstampableFaces.Value end
  1113.                 if myModelInfo then myBreakingFaces = myModelInfo.Value end
  1114.                 local hitFace = 0
  1115.  
  1116.                 if modelInfo then hitFace = modelTargetSurface(modelInfo.Parent, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p, Mouse.Hit.p) end
  1117.  
  1118.                 -- are we stamping TO an unstampable surface?
  1119.                 for bf in string.gmatch(breakingFaces, "[^,]+") do
  1120.                     if hitFace == tonumber(bf) then
  1121.                         -- return before we hit the JointsService code below!
  1122.                         unstampableSurface = true
  1123.                         game:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
  1124.                         return
  1125.                     end
  1126.                 end
  1127.  
  1128.                 -- now we have to cast the ray back in the other direction to find the surface we're stamping FROM
  1129.                 hitFace = modelTargetSurface(stampData.CurrentParts, Mouse.Hit.p, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p)
  1130.  
  1131.                 -- are we stamping WITH an unstampable surface?
  1132.                 for bf in string.gmatch(myBreakingFaces, "[^,]+") do
  1133.                     if hitFace == tonumber(bf) then
  1134.                         unstampableSurface = true
  1135.                         game:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
  1136.                         return
  1137.                     end
  1138.                 end
  1139.  
  1140.                 -- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
  1141.                 -- targetSurface: 1 is top, 4 is bottom,
  1142.             end
  1143.         end
  1144.  
  1145.         -- to show joints during the mouse move
  1146.         unstampableSurface = false
  1147.         game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
  1148.  
  1149.         -- most common mouse inactive error occurs here, so check mouse active one more time in a pcall
  1150.         if not pcall(function()
  1151.                 if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
  1152.                     return
  1153.                 else
  1154.                     return
  1155.                 end
  1156.             end)
  1157.         then
  1158.             error("Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check")
  1159.             game:GetService("JointsService"):ClearJoinAfterMoveJoints()
  1160.             Mouse = nil
  1161.             return
  1162.         end
  1163.  
  1164.         if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
  1165.             game:GetService("JointsService"):SetJoinAfterMoveTarget(Mouse.Target)
  1166.         else
  1167.             game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
  1168.         end
  1169.         game:GetService("JointsService"):ShowPermissibleJoints()
  1170.  
  1171.         -- here we allow for a line of high-scalability parts
  1172.         if isMegaClusterPart() and HighScalabilityLine and HighScalabilityLine.Start then
  1173.                 DoHighScalabilityRegionSelect()
  1174.         end
  1175.     end
  1176.  
  1177.     local function setupKeyListener(key, Mouse)
  1178.         if control and control["Paused"] then return end -- don't do this if we have no stamp
  1179.  
  1180.         key = string.lower(key)
  1181.         if key == 'r' and not autoAlignToFace(stampData.CurrentParts) then -- rotate the model
  1182.             gInitial90DegreeRotations = gInitial90DegreeRotations + 1
  1183.  
  1184.             -- Update orientation value if this is a fake terrain part
  1185.             local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1186.             if clusterValues and clusterValues:IsA("Vector3Value") then
  1187.                 clusterValues.Value = Vector3.new(clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z + 1) % 4)
  1188.             end
  1189.  
  1190.             -- Rotate the parts or all the parts in the model
  1191.             local ry = math.pi/2
  1192.             if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
  1193.                 --stampData.CurrentParts:Rotate(0, ry, 0)
  1194.                 modelRotate(stampData.CurrentParts, ry)
  1195.             else
  1196.                 stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
  1197.             end
  1198.  
  1199.             -- After rotating, update the position
  1200.             configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
  1201.             if configFound then
  1202.                 positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
  1203.  
  1204.                 -- update everything else in MouseMove
  1205.                 DoStamperMouseMove(Mouse)
  1206.             end
  1207.         elseif key == 'c' then -- try to expand our high scalability dragger dimension
  1208.             if HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 and HighScalabilityLine.Dimensions < 3 then
  1209.                 HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End
  1210.                 HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine
  1211.                 HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1
  1212.                 HighScalabilityLine.NewHint = true
  1213.             end
  1214.         end
  1215.     end
  1216.  
  1217.     keyCon = Mouse.KeyDown:connect(function(key) -- init key connection (keeping code close to func)
  1218.         setupKeyListener(key, Mouse)
  1219.     end)
  1220.  
  1221.     local function resetHighScalabilityLine()
  1222.         if HighScalabilityLine then
  1223.             HighScalabilityLine.Start = nil
  1224.             HighScalabilityLine.End = nil
  1225.             HighScalabilityLine.InternalLine = nil
  1226.             HighScalabilityLine.NewHint = true
  1227.         end
  1228.     end
  1229.  
  1230.     local function flashRedBox()
  1231.         local gui = game:GetService("CoreGui")
  1232.         if game:GetService("Players") then
  1233.             if game:GetService("Players")["LocalPlayer"] then
  1234.                 if game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui") then
  1235.                     gui = game:GetService("Players").LocalPlayer.PlayerGui
  1236.                 end
  1237.             end
  1238.         end
  1239.         if not stampData["ErrorBox"] then return end
  1240.  
  1241.         stampData.ErrorBox.Parent = gui
  1242.         if stampData.CurrentParts:IsA("Tool") then
  1243.             stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
  1244.         else
  1245.             stampData.ErrorBox.Adornee = stampData.CurrentParts
  1246.         end
  1247.  
  1248.         delay(0,function()
  1249.             for i = 1, 3 do
  1250.                 if stampData["ErrorBox"] then stampData.ErrorBox.Visible = true end
  1251.                 wait(0.13)
  1252.                 if stampData["ErrorBox"] then stampData.ErrorBox.Visible = false end
  1253.                 wait(0.13)
  1254.             end
  1255.             if stampData["ErrorBox"] then
  1256.                 stampData.ErrorBox.Adornee = nil
  1257.                 stampData.ErrorBox.Parent = nil
  1258.             end
  1259.         end)
  1260.     end
  1261.  
  1262.     local function DoStamperMouseDown(Mouse)
  1263.         if not Mouse then
  1264.             error("Error: RbxStamper.DoStamperMouseDown: Mouse is nil")
  1265.             return
  1266.         end
  1267.         if not Mouse:IsA("Mouse") then
  1268.             error("Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className,"should be of type Mouse")
  1269.             return
  1270.         end
  1271.         if not stampData then
  1272.             return
  1273.         end
  1274.  
  1275.         if isMegaClusterPart() then
  1276.             if Mouse and HighScalabilityLine then
  1277.                 local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube", true)
  1278.                 local terrain = game:GetService("Workspace").Terrain
  1279.                 if megaCube then
  1280.                     HighScalabilityLine.Dimensions = 1
  1281.                     local tempCell = terrain:WorldToCell(megaCube.CFrame.p)
  1282.                     HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
  1283.                     return
  1284.                 else
  1285.                     HighScalabilityLine.Dimensions = 1
  1286.                     local tempCell = terrain:WorldToCell(stampData.CurrentParts.CFrame.p)
  1287.                     HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
  1288.                     return
  1289.                 end
  1290.             end
  1291.         end
  1292.     end
  1293.  
  1294.     local function loadSurfaceTypes(part, surfaces)
  1295.         part.TopSurface = surfaces[1]
  1296.         part.BottomSurface = surfaces[2]
  1297.         part.LeftSurface = surfaces[3]
  1298.         part.RightSurface = surfaces[4]
  1299.         part.FrontSurface = surfaces[5]
  1300.         part.BackSurface = surfaces[6]
  1301.     end
  1302.  
  1303.     local function saveSurfaceTypes(part, myTable)
  1304.         local tempTable = {}
  1305.         tempTable[1] = part.TopSurface
  1306.         tempTable[2] = part.BottomSurface
  1307.         tempTable[3] = part.LeftSurface
  1308.         tempTable[4] = part.RightSurface
  1309.         tempTable[5] = part.FrontSurface
  1310.         tempTable[6] = part.BackSurface
  1311.  
  1312.         myTable[part] = tempTable
  1313.     end
  1314.  
  1315.     local function makeSurfaceUnjoinable(part, surface)
  1316.         -- TODO: FILL OUT!
  1317.     end
  1318.  
  1319.     local function prepareModel(model)
  1320.         if not model then return nil end
  1321.  
  1322.         local gDesiredTrans = 0.7
  1323.         local gStaticTrans = 1
  1324.  
  1325.         local clone = model:Clone()
  1326.         local scripts = {}
  1327.         local parts = {}
  1328.         local decals = {}
  1329.  
  1330.         stampData = {}
  1331.         stampData.DisabledScripts = {}
  1332.         stampData.TransparencyTable = {}
  1333.         stampData.MaterialTable = {}
  1334.         stampData.CanCollideTable = {}
  1335.         stampData.AnchoredTable = {}
  1336.         stampData.ArchivableTable = {}
  1337.         stampData.DecalTransparencyTable = {}
  1338.         stampData.SurfaceTypeTable = {}
  1339.  
  1340.         collectParts(clone, parts, scripts, decals)
  1341.  
  1342.         if #parts <= 0 then return nil, "no parts found in modelToStamp" end
  1343.  
  1344.         for index,script in pairs(scripts) do
  1345.             if not(script.Disabled) then
  1346.                 script.Disabled = true
  1347.                 stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script
  1348.             end
  1349.         end
  1350.         for index, part in pairs(parts) do
  1351.             stampData.TransparencyTable[part] = part.Transparency
  1352.             part.Transparency = gStaticTrans + (1 - gStaticTrans) * part.Transparency
  1353.             stampData.MaterialTable[part] = part.Material
  1354.             part.Material = Enum.Material.Plastic
  1355.             stampData.CanCollideTable[part] = part.CanCollide
  1356.             part.CanCollide = false
  1357.             stampData.AnchoredTable[part] = part.Anchored
  1358.             part.Anchored = true
  1359.             stampData.ArchivableTable[part] = part.Archivable
  1360.             part.Archivable = false
  1361.  
  1362.             saveSurfaceTypes(part, stampData.SurfaceTypeTable)
  1363.  
  1364.             local fadeInDelayTime = 0.5
  1365.             local transFadeInTime = 0.5
  1366.             delay(0,function()
  1367.                 wait(fadeInDelayTime) -- give it some time to be completely transparent
  1368.  
  1369.                 local begTime = tick()
  1370.                 local currTime = begTime
  1371.                 while (currTime - begTime) < transFadeInTime and part and part:IsA("BasePart") and part.Transparency > gDesiredTrans do
  1372.                     local newTrans = 1 - (((currTime - begTime)/transFadeInTime) * (gStaticTrans - gDesiredTrans))
  1373.                     if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
  1374.                         part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part]
  1375.                     end
  1376.                     wait(0.03)
  1377.                     currTime = tick()
  1378.                 end
  1379.                 if part and part:IsA("BasePart") then
  1380.                     if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
  1381.                         part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part]
  1382.                     end
  1383.                 end
  1384.             end)
  1385.         end
  1386.  
  1387.         for index, decal in pairs(decals) do
  1388.             stampData.DecalTransparencyTable[decal] = decal.Transparency
  1389.             decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency
  1390.         end
  1391.  
  1392.         -- disable all seats
  1393.         setSeatEnabledStatus(clone, true)
  1394.         setSeatEnabledStatus(clone, false)
  1395.  
  1396.         stampData.CurrentParts = clone
  1397.  
  1398.         -- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame"
  1399.         if autoAlignToFace(clone) then
  1400.             stampData.CurrentParts:ResetOrientationToIdentity()
  1401.             gInitial90DegreeRotations = 0
  1402.         else -- pre-rotate if necessary
  1403.             local ry = gInitial90DegreeRotations * math.pi/2
  1404.             if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
  1405.                 --stampData.CurrentParts:Rotate(0, ry, 0)
  1406.                 modelRotate(stampData.CurrentParts, ry)
  1407.             else
  1408.                 stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
  1409.             end
  1410.         end
  1411.  
  1412.         -- since we're cloning the old model instead of the new one, we will need to update the orientation based on the original value AND how many more
  1413.         --  rotations we expect since then [either that or we need to store the just-stamped clusterMaterial.Value.Z somewhere].  This should fix the terrain rotation
  1414.         --  issue (fingers crossed) [HotThoth]
  1415.  
  1416.         local clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1417.         if clusterMaterial and clusterMaterial:IsA("Vector3Value") then
  1418.             clusterMaterial.Value = Vector3.new(clusterMaterial.Value.X, clusterMaterial.Value.Y, (clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4)
  1419.         end
  1420.  
  1421.         -- After rotating, update the position
  1422.         local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
  1423.         if configFound then
  1424.             stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
  1425.         end
  1426.  
  1427.         -- to show joints during the mouse move
  1428.         game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
  1429.  
  1430.         return clone, parts
  1431.     end
  1432.  
  1433.     local function checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
  1434.         local cellCenterToWorld = game:GetService("Workspace").Terrain.CellCenterToWorld
  1435.         local cellCenter = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
  1436.         local cellBlockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector, cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector), stampData.CurrentParts,  100)
  1437.  
  1438.         local skipThisCell = false
  1439.  
  1440.         for b = 1, #cellBlockingParts do
  1441.             if isBlocker(cellBlockingParts[b]) then skipThisCell = true break end
  1442.         end
  1443.  
  1444.         if not skipThisCell then
  1445.             -- pop players up above any set cells
  1446.             local alreadyPushedUp = {}
  1447.             -- if no blocking model below, then see if stamping on top of a character
  1448.             for b = 1, #cellBlockingParts do
  1449.                 if  cellBlockingParts[b].Parent and
  1450.                     not alreadyPushedUp[cellBlockingParts[b].Parent] and
  1451.                     cellBlockingParts[b].Parent:FindFirstChild("Humanoid") and
  1452.                     cellBlockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
  1453.                 -----------------------------------------------------------------------------------
  1454.                         local blockingPersonTorso = cellBlockingParts[b].Parent:FindFirstChild("Torso")
  1455.                         alreadyPushedUp[cellBlockingParts[b].Parent] = true
  1456.  
  1457.                         if blockingPersonTorso then
  1458.                             -- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
  1459.                             local newY = cellCenter.Y + 5
  1460.                             if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
  1461.                                 blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
  1462.                             else
  1463.                                 -- if no space, we just skip this one
  1464.                                 skipThisCell = true
  1465.                                 break
  1466.                             end
  1467.                         end
  1468.                 -----------------------------------------------------------------------------------
  1469.                 end
  1470.             end
  1471.         end
  1472.  
  1473.         if not skipThisCell then  -- if we STILL aren't skipping...  then we're good to go!
  1474.             local canSetCell = true
  1475.  
  1476.             if checkHighScalabilityStamp then -- check to see if cell is in region, if not we'll skip set
  1477.                 if allowedStampRegion then
  1478.                     local cellPos = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
  1479.                     if cellPos.X + 2 > allowedStampRegion.CFrame.p.X + allowedStampRegion.Size.X/2 then
  1480.                         canSetCell = false
  1481.                     elseif cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X/2 then
  1482.                         canSetCell = false
  1483.                     elseif cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y/2 then
  1484.                         canSetCell = false
  1485.                     elseif cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y/2 then
  1486.                         canSetCell = false
  1487.                     elseif cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z/2 then
  1488.                         canSetCell = false
  1489.                     elseif cellPos.Z - 2 < allowedStampRegion.CFrame.p.Z - allowedStampRegion.Size.Z/2 then
  1490.                         canSetCell = false
  1491.                     end
  1492.                 end
  1493.             end
  1494.  
  1495.             return canSetCell
  1496.         end
  1497.         return false
  1498.     end
  1499.  
  1500.  
  1501.     local function ResolveMegaClusterStamp(checkHighScalabilityStamp)
  1502.         local cellSet = false
  1503.  
  1504.         local cluser = game:GetService("Workspace").Terrain
  1505.  
  1506.         local line = HighScalabilityLine.InternalLine
  1507.         local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
  1508.         local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
  1509.  
  1510.         local clusterMaterial = 1 -- default is grass
  1511.         local clusterType = 0 -- default is brick
  1512.         local clusterOrientation = 0 -- default is 0 rotation
  1513.  
  1514.         local autoWedgeClusterParts = false
  1515.         if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
  1516.  
  1517.         if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
  1518.             clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1519.             if clusterMaterial:IsA("Vector3Value") then
  1520.                 clusterType = clusterMaterial.Value.Y
  1521.                 clusterOrientation = clusterMaterial.Value.Z
  1522.                 clusterMaterial = clusterMaterial.Value.X
  1523.             elseif clusterMaterial:IsA("IntValue") then
  1524.                 clusterMaterial = clusterMaterial.Value
  1525.             end
  1526.         end
  1527.  
  1528.         if HighScalabilityLine.Adorn.Parent and HighScalabilityLine.Start and ((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0)) then
  1529.             local startCell = game:GetService("Workspace").Terrain:WorldToCell(HighScalabilityLine.Start)
  1530.             local xInc = {0,0,0}
  1531.             local yInc = {0,0,0}
  1532.             local zInc = {0,0,0}
  1533.  
  1534.             local cluster = game:GetService("Workspace").Terrain
  1535.  
  1536.             local incrementVect = {nil, nil, nil}
  1537.             local stepVect = {Vector3.new(0, 0, 0), Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)}
  1538.  
  1539.             local worldAxes = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
  1540.  
  1541.             local lines = {}
  1542.             if HighScalabilityLine.Dimensions > 1 then table.insert(lines, HighScalabilityLine.MoreLines[1]) end
  1543.             if line and line.magnitude > 0 then table.insert(lines, line) end
  1544.             if HighScalabilityLine.Dimensions > 2 then table.insert(lines, HighScalabilityLine.MoreLines[2]) end
  1545.  
  1546.             for i = 1, #lines do
  1547.                 lines[i] = Vector3.new(math.floor(lines[i].X+.5), math.floor(lines[i].Y+.5), math.floor(lines[i].Z+.5)) -- round to integers
  1548.  
  1549.                 if lines[i].X > 0 then xInc[i] = 1 elseif lines[i].X < 0 then xInc[i] = -1 end
  1550.                 if lines[i].Y > 0 then yInc[i] = 1 elseif lines[i].Y < 0 then yInc[i] = -1 end
  1551.                 if lines[i].Z > 0 then zInc[i] = 1 elseif lines[i].Z < 0 then zInc[i] = -1 end
  1552.  
  1553.                 incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i])
  1554.                 if incrementVect[i].magnitude < .9 then incrementVect[i] = nil end
  1555.             end
  1556.  
  1557.  
  1558.             if not lines[2] then lines[2] = Vector3.new(0, 0, 0) end
  1559.             if not lines[3] then lines[3] = Vector3.new(0, 0, 0) end
  1560.  
  1561.             local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
  1562.             local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
  1563.  
  1564.             while (stepVect[3].magnitude*4 <= lines[3].magnitude) do
  1565.                 local outerStepVectIndex = 1
  1566.                 while outerStepVectIndex < 4 do
  1567.                     stepVect[2] = Vector3.new(0, 0, 0)
  1568.                     while (stepVect[2].magnitude*4 <= lines[2].magnitude) do
  1569.                         local innerStepVectIndex = 1
  1570.                         while innerStepVectIndex < 4 do
  1571.                             stepVect[1] = Vector3.new(0, 0, 0)
  1572.                             while (stepVect[1].magnitude*4 <= lines[1].magnitude) do
  1573.                                 local stepVectSum = stepVect[1] + stepVect[2] + stepVect[3]
  1574.                                 local cellPos = Vector3int16.new(startCell.X + stepVectSum.X, startCell.Y + stepVectSum.Y, startCell.Z + stepVectSum.Z)
  1575.                                 if cellPos.X >= cMin.X and cellPos.Y >= cMin.Y and cellPos.Z >= cMin.Z and cellPos.X < cMax.X and cellPos.Y < cMax.Y and cellPos.Z < cMax.Z then
  1576.                                     -- check if overlaps player or part
  1577.                                     local okToStampTerrainBlock = checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
  1578.  
  1579.                                     if okToStampTerrainBlock then
  1580.                                         if waterForceTag then
  1581.                                             cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
  1582.                                         else
  1583.                                             cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation)
  1584.                                         end
  1585.                                         cellSet = true
  1586.  
  1587.                                         -- auto-wedge it?
  1588.                                         if (autoWedgeClusterParts) then
  1589.                                             game:GetService("Workspace").Terrain:AutowedgeCells(Region3int16.new(Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
  1590.                                                 Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)))
  1591.                                         end
  1592.                                     end
  1593.                                 end
  1594.                                 stepVect[1] = stepVect[1] + incrementVect[1]
  1595.                             end
  1596.                             if incrementVect[2] then
  1597.                                 while innerStepVectIndex < 4 and worldAxes[innerStepVectIndex]:Dot(incrementVect[2]) == 0 do
  1598.                                     innerStepVectIndex = innerStepVectIndex + 1
  1599.                                 end
  1600.                                 if innerStepVectIndex < 4 then
  1601.                                     stepVect[2] = stepVect[2] + worldAxes[innerStepVectIndex] * worldAxes[innerStepVectIndex]:Dot(incrementVect[2])
  1602.                                 end
  1603.                                 innerStepVectIndex = innerStepVectIndex + 1
  1604.                             else
  1605.                                 stepVect[2] = Vector3.new(1, 0, 0)
  1606.                                 innerStepVectIndex = 4 -- skip all remaining loops
  1607.                             end
  1608.                             if (stepVect[2].magnitude*4 > lines[2].magnitude) then innerStepVectIndex = 4 end
  1609.                         end
  1610.                     end
  1611.                     if incrementVect[3] then
  1612.                         while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) == 0 do
  1613.                             outerStepVectIndex = outerStepVectIndex + 1
  1614.                         end
  1615.                         if outerStepVectIndex < 4 then
  1616.                             stepVect[3] = stepVect[3] + worldAxes[outerStepVectIndex] * worldAxes[outerStepVectIndex]:Dot(incrementVect[3])
  1617.                         end
  1618.                         outerStepVectIndex = outerStepVectIndex + 1
  1619.                     else -- skip all remaining loops
  1620.                         stepVect[3] = Vector3.new(1, 0, 0) outerStepVectIndex = 4
  1621.                     end
  1622.                     if (stepVect[3].magnitude*4 > lines[3].magnitude) then outerStepVectIndex = 4 end
  1623.                 end
  1624.             end
  1625.         end
  1626.  
  1627.         -- and also get rid of any HighScalabilityLine stuff if it's there
  1628.         HighScalabilityLine.Start = nil
  1629.         HighScalabilityLine.Adorn.Parent = nil
  1630.  
  1631.         -- Mark for undo.
  1632.         if cellSet then
  1633.             stampData.CurrentParts.Parent = nil
  1634.             pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StamperMulti") end)
  1635.         end
  1636.  
  1637.         return cellSet
  1638.     end
  1639.  
  1640.     local function DoStamperMouseUp(Mouse)
  1641.         if not Mouse then
  1642.             error("Error: RbxStamper.DoStamperMouseUp: Mouse is nil")
  1643.             return false
  1644.         end
  1645.         if not Mouse:IsA("Mouse") then
  1646.             error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className,"should be of type Mouse")
  1647.             return false
  1648.         end
  1649.  
  1650.         if not stampData.Dragger then
  1651.             error("Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil")
  1652.             return false
  1653.         end
  1654.  
  1655.         if not HighScalabilityLine then
  1656.             return false
  1657.         end
  1658.  
  1659.         local checkHighScalabilityStamp = nil
  1660.         if stampInModel then
  1661.             local canStamp = nil
  1662.             local isHSLPart = isMegaClusterPart()
  1663.  
  1664.             if isHSLPart and
  1665.                 HighScalabilityLine and
  1666.                 HighScalabilityLine.Start and
  1667.                 HighScalabilityLine.InternalLine and
  1668.                 HighScalabilityLine.InternalLine.magnitude > 0 then -- we have an HSL line, test later
  1669.                     canStamp = true
  1670.                     checkHighScalabilityStamp = true
  1671.             else
  1672.                 canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
  1673.             end
  1674.  
  1675.             if not canStamp then
  1676.                 if stampFailedFunc then
  1677.                     stampFailedFunc()
  1678.                 end
  1679.                 return false
  1680.             end
  1681.         end
  1682.  
  1683.         -- if unstampable face, then don't let us stamp there!
  1684.         if unstampableSurface then
  1685.             flashRedBox()
  1686.             return false
  1687.         end
  1688.  
  1689.         -- recheck if we can stamp, as we just moved part
  1690.         local canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
  1691.         if not canStamp then
  1692.             if stampFailedFunc then
  1693.                 stampFailedFunc()
  1694.             end
  1695.             return false
  1696.         end
  1697.  
  1698.         -- Prevent part from being stamped on top of a player
  1699.  
  1700.         local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
  1701.  
  1702.         -- HotThoth's note:  Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the
  1703.         --                     value of configFound from the previous onStamperMouseMove call which moved the CurrentParts
  1704.         --                     Shouldn't this be true when lastTargetCFrame has been set and false otherwise?
  1705.         configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
  1706.  
  1707.         if configFound and not HighScalabilityLine.Adorn.Parent then
  1708.             if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
  1709.                 flashRedBox()
  1710.                 return false
  1711.             end
  1712.  
  1713.             local blockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(minBB + insertBoundingBoxOverlapVector,
  1714.                                                                     maxBB - insertBoundingBoxOverlapVector),
  1715.                                                                     stampData.CurrentParts,
  1716.                                                                     100)
  1717.  
  1718.  
  1719.             for b = 1, #blockingParts do
  1720.                 if isBlocker(blockingParts[b]) then
  1721.                     flashRedBox()
  1722.                     return false
  1723.                 end
  1724.             end
  1725.  
  1726.             local alreadyPushedUp = {}
  1727.                 -- if no blocking model below, then see if stamping on top of a character
  1728.                 for b = 1, #blockingParts do
  1729.                     if  blockingParts[b].Parent and
  1730.                         not alreadyPushedUp[blockingParts[b].Parent] and
  1731.                         blockingParts[b].Parent:FindFirstChild("Humanoid") and
  1732.                         blockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
  1733.                     ---------------------------------------------------------------------------
  1734.                             local blockingPersonTorso = blockingParts[b].Parent:FindFirstChild("Torso")
  1735.                             alreadyPushedUp[blockingParts[b].Parent] = true
  1736.  
  1737.                             if blockingPersonTorso then
  1738.                                 -- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
  1739.                                 local newY = maxBB.Y + 3
  1740.                                 if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
  1741.                                     blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
  1742.                                 else
  1743.                                     -- if no space, we just error
  1744.                                     flashRedBox()
  1745.                                     return false
  1746.                                 end
  1747.                             end
  1748.                     ---------------------------------------------------------------------------
  1749.                     end
  1750.                 end
  1751.  
  1752.         elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) then -- if no config then only stamp if it's a real HSL!
  1753.             resetHighScalabilityLine()
  1754.             return false
  1755.         end
  1756.  
  1757.         -- something will be stamped!  so set the "StampedSomething" toggle to true
  1758.         if game:GetService("Players")["LocalPlayer"] then
  1759.             if game:GetService("Players").LocalPlayer["Character"] then
  1760.                 local localChar = game:GetService("Players").LocalPlayer.Character
  1761.                 local stampTracker = localChar:FindFirstChild("StampTracker")
  1762.                 if stampTracker and not stampTracker.Value then
  1763.                     stampTracker.Value = true
  1764.                 end
  1765.             end
  1766.         end
  1767.  
  1768.         -- if we drew a line of mega parts, stamp them out
  1769.         if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart() then
  1770.             if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp then
  1771.                 -- kill the ghost part
  1772.                 stampData.CurrentParts.Parent = nil
  1773.                 return true
  1774.             end
  1775.         end
  1776.  
  1777.         -- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
  1778.         HighScalabilityLine.Start = nil
  1779.         HighScalabilityLine.Adorn.Parent = nil
  1780.  
  1781.         local cluster = game:GetService("Workspace").Terrain
  1782.  
  1783.         -- if target point is in cluster, just use cluster:SetCell
  1784.         if isMegaClusterPart() then
  1785.             -- if targetCFrame is inside cluster, just set that cell to 1 and return
  1786.             --local cellPos = cluster:WorldToCell(targetCFrame.p)
  1787.  
  1788.             local cellPos
  1789.             if stampData.CurrentParts:IsA("Model") then cellPos = cluster:WorldToCell(stampData.CurrentParts:GetModelCFrame().p)
  1790.             else cellPos = cluster:WorldToCell(stampData.CurrentParts.CFrame.p) end
  1791.  
  1792.             local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
  1793.             local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
  1794.  
  1795.             if checkTerrainBlockCollisions(cellPos, false) then
  1796.  
  1797.                 local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
  1798.                 local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
  1799.                 local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
  1800.  
  1801.                 if cellPos.X >= cMin.X and cellPos.Y >= cMin.Y and cellPos.Z >= cMin.Z and cellPos.X < cMax.X and cellPos.Y < cMax.Y and cellPos.Z < cMax.Z then
  1802.  
  1803.                     if waterForceTag then
  1804.                         cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
  1805.                     elseif not clusterValues then
  1806.                         cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, cellInfo.Material, cellInfo.clusterType, gInitial90DegreeRotations % 4)
  1807.                     elseif clusterValues:IsA("Vector3Value") then
  1808.                         cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value.X, clusterValues.Value.Y, clusterValues.Value.Z)
  1809.                     else
  1810.                         cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value, 0, 0)
  1811.                     end
  1812.  
  1813.                     local autoWedgeClusterParts = false
  1814.                     if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
  1815.  
  1816.                     -- auto-wedge it
  1817.                     if (autoWedgeClusterParts) then
  1818.                         game:GetService("Workspace").Terrain:AutowedgeCells(
  1819.                             Region3int16.new(
  1820.                                 Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
  1821.                                 Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)
  1822.                             )
  1823.                         )
  1824.                     end
  1825.  
  1826.                     -- kill the ghost part
  1827.                     stampData.CurrentParts.Parent = nil
  1828.  
  1829.                     -- Mark for undo.  It has to happen here or the selection display will come back also.
  1830.                     pcall(function() game:GetService("ChangeHistoryService"):SetWaypoint("StamperSingle") end)
  1831.                     return true
  1832.                 end
  1833.             else
  1834.                 -- you tried to stamp a HSL-single part where one does not belong!
  1835.                 flashRedBox()
  1836.                 return false
  1837.             end
  1838.         end
  1839.  
  1840.         local function getPlayer()
  1841.             if game:GetService("Players")["LocalPlayer"] then
  1842.                 return game:GetService("Players").LocalPlayer
  1843.             end
  1844.             return nil
  1845.         end
  1846.  
  1847.  
  1848.         -- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints
  1849.         if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
  1850.             if stampData.CurrentParts:IsA("Model") then
  1851.                 -- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds...  just don't ask X<
  1852.                 local manualWeldTable = {}
  1853.                 local manualWeldParentTable = {}
  1854.                 saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable)
  1855.                 stampData.CurrentParts:BreakJoints()
  1856.                 stampData.CurrentParts:MakeJoints()
  1857.                 restoreTheWelds(manualWeldTable, manualWeldParentTable)
  1858.             end
  1859.  
  1860.             -- if it's a model, we also want to fill in the playerID and playerName tags, if it has those (e.g. for the friend-only door)
  1861.             local playerIdTag = stampData.CurrentParts:FindFirstChild("PlayerIdTag")
  1862.             local playerNameTag = stampData.CurrentParts:FindFirstChild("PlayerNameTag")
  1863.             if playerIdTag ~= nil then
  1864.                 local tempPlayerValue = getPlayer()
  1865.                 if tempPlayerValue ~= nil then playerIdTag.Value = tempPlayerValue.UserId end
  1866.             end
  1867.             if playerNameTag ~= nil then
  1868.                 if game:GetService("Players")["LocalPlayer"] then
  1869.                     local tempPlayerValue = game:GetService("Players").LocalPlayer
  1870.                     if tempPlayerValue ~= nil then playerNameTag.Value = tempPlayerValue.Name end
  1871.                 end
  1872.             end
  1873.             -- ...and tag all inserted models for subsequent origin identification
  1874.             -- if no RobloxModel tag already exists, then add it.
  1875.             if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
  1876.                 local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
  1877.                 stringTag.Name = "RobloxModel"
  1878.  
  1879.                 if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
  1880.                     local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
  1881.                     stringTag2.Name = "RobloxStamper"
  1882.                 end
  1883.             end
  1884.  
  1885.         else
  1886.             stampData.CurrentParts:BreakJoints()
  1887.             if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
  1888.                 local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
  1889.                 stringTag2.Name = "RobloxStamper"
  1890.             end
  1891.         end
  1892.  
  1893.         -- make sure all the joints are activated before restoring anchor states
  1894.         game:GetService("JointsService"):CreateJoinAfterMoveJoints()
  1895.  
  1896.         -- Restore the original properties for all parts being stamped
  1897.         for part, transparency in pairs(stampData.TransparencyTable) do
  1898.             part.Transparency = transparency
  1899.         end
  1900.         for part, archivable in pairs(stampData.ArchivableTable) do
  1901.             part.Archivable = archivable
  1902.         end
  1903.         for part, material in pairs(stampData.MaterialTable) do
  1904.             part.Material = material
  1905.         end
  1906.         for part, collide in pairs(stampData.CanCollideTable) do
  1907.             part.CanCollide = collide
  1908.         end
  1909.         for part, anchored in pairs(stampData.AnchoredTable) do
  1910.             part.Anchored = anchored
  1911.         end
  1912.         for decal, transparency in pairs(stampData.DecalTransparencyTable) do
  1913.             decal.Transparency = transparency
  1914.         end
  1915.  
  1916.         for part, surfaces in pairs(stampData.SurfaceTypeTable) do
  1917.             loadSurfaceTypes(part, surfaces)
  1918.         end
  1919.  
  1920.         if isMegaClusterPart() then
  1921.             stampData.CurrentParts.Transparency = 0
  1922.         end
  1923.  
  1924.         -- re-enable all seats
  1925.         setSeatEnabledStatus(stampData.CurrentParts, true)
  1926.  
  1927.         stampData.TransparencyTable = nil
  1928.         stampData.ArchivableTable = nil
  1929.         stampData.MaterialTable = nil
  1930.         stampData.CanCollideTable = nil
  1931.         stampData.AnchoredTable = nil
  1932.         stampData.SurfaceTypeTable = nil
  1933.  
  1934.         -- ...and tag all inserted models for subsequent origin identification
  1935.         -- if no RobloxModel tag already exists, then add it.
  1936.         if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
  1937.             local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
  1938.             stringTag.Name = "RobloxModel"
  1939.         end
  1940.  
  1941.         --Re-enable the scripts
  1942.         for index,script in pairs(stampData.DisabledScripts) do
  1943.             script.Disabled = false
  1944.         end
  1945.  
  1946.         --Now that they are all marked enabled, reinsert them into the world so they start running
  1947.         for index,script in pairs(stampData.DisabledScripts) do
  1948.             local oldParent = script.Parent
  1949.             script.Parent = nil
  1950.             script:Clone().Parent = oldParent
  1951.         end
  1952.  
  1953.         -- clear out more data
  1954.         stampData.DisabledScripts = nil
  1955.         stampData.Dragger = nil
  1956.         stampData.CurrentParts = nil
  1957.  
  1958.         pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StampedObject") end)
  1959.         return true
  1960.     end
  1961.  
  1962.     local function pauseStamper()
  1963.         for i = 1, #mouseCons do -- stop the mouse from doing anything
  1964.             mouseCons[i]:disconnect()
  1965.             mouseCons[i] = nil
  1966.         end
  1967.         mouseCons = {}
  1968.  
  1969.         if stampData and stampData.CurrentParts then -- remove our ghost part
  1970.             stampData.CurrentParts.Parent = nil
  1971.             stampData.CurrentParts:Remove()
  1972.         end
  1973.  
  1974.         resetHighScalabilityLine()
  1975.  
  1976.         game:GetService("JointsService"):ClearJoinAfterMoveJoints()
  1977.     end
  1978.  
  1979.  
  1980.     local function prepareUnjoinableSurfaces(modelCFrame, parts, whichSurface)
  1981.         local AXIS_VECTORS = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}  -- maybe last one is negative?  TODO: check this!
  1982.         local isPositive = 1
  1983.         if whichSurface < 0 then isPositive = isPositive * -1 whichSurface = whichSurface*-1 end
  1984.         local surfaceNormal = isPositive * modelCFrame:vectorToWorldSpace(AXIS_VECTORS[whichSurface])
  1985.  
  1986.         for i = 1, #parts do
  1987.             local currPart = parts[i]
  1988.  
  1989.             -- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
  1990.             local surfaceNormalInLocalCoords = currPart.CFrame:vectorToObjectSpace(surfaceNormal)
  1991.             if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Y) then
  1992.                 if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Z) then
  1993.                     if surfaceNormalInLocalCoords.X > 0 then currPart.RightSurface = "Unjoinable" else currPart.LeftSurface = "Unjoinable" end
  1994.                 else
  1995.                     if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
  1996.                 end
  1997.             else
  1998.                 if math.abs(surfaceNormalInLocalCoords.Y) > math.abs(surfaceNormalInLocalCoords.Z) then
  1999.                     if surfaceNormalInLocalCoords.Y > 0 then currPart.TopSurface = "Unjoinable" else currPart.BottomSurface = "Unjoinable" end
  2000.                 else
  2001.                     if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
  2002.                 end
  2003.             end
  2004.         end
  2005.     end
  2006.  
  2007.     local function resumeStamper()
  2008.         local clone, parts = prepareModel(modelToStamp)
  2009.  
  2010.         if not clone or not parts then
  2011.             return
  2012.         end
  2013.  
  2014.         -- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
  2015.         local unjoinableTag = clone:FindFirstChild("UnjoinableFaces", true)
  2016.         if unjoinableTag then
  2017.             for unjoinableSurface in string.gmatch(unjoinableTag.Value, "[^,]*") do
  2018.                 if tonumber(unjoinableSurface) then
  2019.                     if clone:IsA("Model") then
  2020.                         prepareUnjoinableSurfaces(clone:GetModelCFrame(), parts, tonumber(unjoinableSurface))
  2021.                     else
  2022.                         prepareUnjoinableSurfaces(clone.CFrame, parts, tonumber(unjoinableSurface))
  2023.                     end
  2024.                 end
  2025.             end
  2026.         end
  2027.  
  2028.         stampData.ErrorBox = errorBox
  2029.         if stampInModel then
  2030.             clone.Parent = stampInModel
  2031.         else
  2032.             clone.Parent = game:GetService("Workspace")
  2033.         end
  2034.  
  2035.         if clone:FindFirstChild("ClusterMaterial", true) then -- extract all info from vector
  2036.             local clusterMaterial = clone:FindFirstChild("ClusterMaterial", true)
  2037.             if (clusterMaterial:IsA("Vector3Value")) then
  2038.                 cellInfo.Material = clusterMaterial.Value.X
  2039.                 cellInfo.clusterType = clusterMaterial.Value.Y
  2040.                 cellInfo.clusterOrientation = clusterMaterial.Value.Z
  2041.             elseif clusterMaterial:IsA("IntValue") then
  2042.                 cellInfo.Material = clusterMaterial.Value
  2043.             end
  2044.         end
  2045.  
  2046.         pcall(function() mouseTarget = Mouse.Target end)
  2047.  
  2048.         if mouseTarget and mouseTarget.Parent:FindFirstChild("RobloxModel") == nil then
  2049.             game:GetService("JointsService"):SetJoinAfterMoveTarget(mouseTarget)
  2050.         else
  2051.             game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
  2052.         end
  2053.         game:GetService("JointsService"):ShowPermissibleJoints()
  2054.  
  2055.         for index, object in pairs(stampData.DisabledScripts) do
  2056.             if object.Name == "GhostRemovalScript" then
  2057.                 object.Parent = stampData.CurrentParts
  2058.             end
  2059.         end
  2060.  
  2061.         stampData.Dragger = Instance.new("Dragger")
  2062.  
  2063.         --Begin a movement by faking a MouseDown signal
  2064.         stampData.Dragger:MouseDown(parts[1], Vector3.new(0,0,0), parts)
  2065.         stampData.Dragger:MouseUp()
  2066.  
  2067.         DoStamperMouseMove(Mouse)
  2068.  
  2069.         table.insert(mouseCons,Mouse.Move:connect(function()
  2070.             if movingLock or stampUpLock then return end
  2071.             movingLock = true
  2072.                 DoStamperMouseMove(Mouse)
  2073.             movingLock = false
  2074.         end))
  2075.  
  2076.         table.insert(mouseCons,Mouse.Button1Down:connect(function()
  2077.             DoStamperMouseDown(Mouse)
  2078.         end))
  2079.  
  2080.         table.insert(mouseCons,Mouse.Button1Up:connect(function()
  2081.             stampUpLock = true
  2082.                 while movingLock do wait() end
  2083.                 stamped.Value = DoStamperMouseUp(Mouse)
  2084.                 resetHighScalabilityLine()
  2085.             stampUpLock = false
  2086.         end))
  2087.  
  2088.         stamped.Value = false
  2089.     end
  2090.  
  2091.     local function resetStamperState(newModelToStamp)
  2092.  
  2093.         -- if we have a new model, swap it out
  2094.         if newModelToStamp then
  2095.             if not newModelToStamp:IsA("Model") and not newModelToStamp:IsA("BasePart") then
  2096.                 error("resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!")
  2097.             end
  2098.             modelToStamp = newModelToStamp
  2099.         end
  2100.  
  2101.         -- first clear our state
  2102.         pauseStamper()
  2103.         -- now lets load in the new model
  2104.         resumeStamper()
  2105.  
  2106.     end
  2107.  
  2108.     -- load the model initially
  2109.     resetStamperState()
  2110.  
  2111.  
  2112.     -- setup the control table we pass back to the user
  2113.     control.Stamped = stamped -- BoolValue that fires when user stamps
  2114.     control.Paused = false
  2115.  
  2116.     control.LoadNewModel = function(newStampModel) -- allows us to specify a new stamper model to be used with this stamper
  2117.         if newStampModel and not newStampModel:IsA("Model") and not newStampModel:IsA("BasePart") then
  2118.             error("Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!")
  2119.             return nil
  2120.         end
  2121.         resetStamperState(newStampModel)
  2122.     end
  2123.  
  2124.     control.ReloadModel = function() -- will automatically set stamper to get a new model of current model and start stamping with new model
  2125.         resetStamperState()
  2126.     end
  2127.  
  2128.     control.Pause = function() -- temporarily stops stamping, use resume to start up again
  2129.         if not control.Paused then
  2130.             pauseStamper()
  2131.             control.Paused = true
  2132.         else
  2133.             print("RbxStamper Warning: Tried to call Control.Pause() when already paused")
  2134.         end
  2135.     end
  2136.  
  2137.     control.Resume = function() -- resumes stamping, if currently paused
  2138.         if control.Paused then
  2139.             resumeStamper()
  2140.             control.Paused = false
  2141.         else
  2142.             print("RbxStamper Warning: Tried to call Control.Resume() without Pausing First")
  2143.         end
  2144.     end
  2145.  
  2146.     control.ResetRotation = function() -- resets the model rotation so new models are at default orientation
  2147.         -- gInitial90DegreeRotations = 0
  2148.         -- Note:  This function will not always work quite the way we want it to; we will have to build this out further so it works with
  2149.         --        High-Scalability and with the new model orientation setting methods (model:ResetOrientationToIdentity())  [HotThoth]
  2150.     end
  2151.  
  2152.     control.Destroy = function() -- Stops current Stamp operation and destroys control construct
  2153.         for i = 1, #mouseCons do
  2154.             mouseCons[i]:disconnect()
  2155.             mouseCons[i] = nil
  2156.         end
  2157.  
  2158.         if keyCon then
  2159.             keyCon:disconnect()
  2160.         end
  2161.  
  2162.         game:GetService("JointsService"):ClearJoinAfterMoveJoints()
  2163.  
  2164.         if adorn then adorn:Destroy() end
  2165.         if adornPart then adornPart:Destroy() end
  2166.         if errorBox then errorBox:Destroy() end
  2167.         if stampData then
  2168.             if stampData["Dragger"] then
  2169.                 stampData.Dragger:Destroy()
  2170.             end
  2171.             if stampData.CurrentParts then
  2172.                 stampData.CurrentParts:Destroy()
  2173.             end
  2174.         end
  2175.         if control and control["Stamped"] then
  2176.             control.Stamped:Destroy()
  2177.         end
  2178.         control = nil
  2179.     end
  2180.  
  2181.     return control
  2182. end
  2183.  
  2184. t.Help =
  2185.     function(funcNameOrFunc)
  2186.         --input argument can be a string or a function.  Should return a description (of arguments and expected side effects)
  2187.         if funcNameOrFunc == "GetStampModel" or funcNameOrFunc == t.GetStampModel then
  2188.             return "Function GetStampModel.  Arguments: assetId, useAssetVersionId.  assetId is the asset to load in, define useAssetVersionId as true if assetId is a version id instead of a relative assetId.  Side effect: returns a model of the assetId, or a string with error message if something fails"
  2189.         end
  2190.         if funcNameOrFunc == "SetupStamperDragger" or funcNameOrFunc == t.SetupStamperDragger then
  2191.             return "Function SetupStamperDragger. Side Effect: Creates 4x4 stamping mechanism for building out parts quickly. Arguments: ModelToStamp, Mouse, LegalStampCheckFunction. ModelToStamp should be a Model or Part, preferrably loaded from RbxStamper.GetStampModel and should have extents that are multiples of 4.  Mouse should be a mouse object (obtained from things such as Tool.OnEquipped), used to drag parts around 'stamp' them out. LegalStampCheckFunction is optional, used as a callback with a table argument (table is full of instances about to be stamped). Function should return either true or false, false stopping the stamp action."
  2192.         end
  2193.     end
  2194.  
  2195. return t
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement