import "macros" as { $ } $load $FILE t = {} -- waitForChild = (instance, name) -> -- while not instance\FindFirstChild(name) -- instance.ChildAdded\wait! -- end -- end -- Do a line/plane intersection. The line starts at the camera. The plane is at y == 0, normal(0, 1, 0) -- -- vectorPos - End point of the line. -- -- Return: -- cellPos - The terrain cell intersection point if there is one, vectorPos if there isn't. -- hit - Whether there was a plane intersection. Value is true if there was, false if not. PlaneIntersection = (vectorPos) -> hit = false currCamera = game.Workspace.CurrentCamera local startPos with currCamera.CoordinateFrame.p startPos = Vector3.new .X, .Y, .Z endPos = Vector3.new vectorPos.X, vectorPos.Y, vectorPos.Z normal = Vector3.new(0, 1, 0) p3 = Vector3.new 0, 0, 0 startEndDot = normal\Dot(endPos - startPos) cellPos = vectorPos if startEndDot ~= 0 t = normal\Dot(p3 - startPos) / startEndDot if t >= 0 and t <= 1 intersection = ((endPos - startPos) * t) + startPos cellPos = game.Workspace.Terrain\WorldToCell(intersection) hit = true cellPos, hit -- Purpose: -- Checks for terrain touched by the mouse hit. -- Will do a plane intersection if no terrain is touched. -- -- mouse - Mouse to check the .hit for. -- -- Return: -- cellPos - Cell position hit. Nil if none. GetTerrainForMouse = (mouse) -> -- There was no target, so all it could be is a plane intersection. -- Check for a plane intersection. If there isn't one then nothing will get hit. cell = game.Workspace.Terrain\WorldToCellPreferSolid Vector3.new mouse.hit.x, mouse.hit.y, mouse.hit.z local planeLoc -- If nothing was hit, do the plane intersection. if 0 == game.Workspace.Terrain\GetCell(cell.X, cell.Y, cell.Z).Value cell = nil planeLoc, hit = PlaneIntersection Vector3.new mouse.hit.x, mouse.hit.y, mouse.hit.z if hit cell = planeLoc cell -- setup helper functions insertBoundingBoxOverlapVector = Vector3.new 0.3, 0.3, 0.3 -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units -- rotates a model by yAngle radians about the global y-axis rotatePartAndChildren = (part, rotCF, offsetFromOrigin) -> -- rotate this thing, if it's a part if part\IsA "BasePart" part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin -- recursively do the same to all children partChildren = part\GetChildren! for c = 1, #partChildren rotatePartAndChildren partChildren[c], rotCF, offsetFromOrigin modelRotate = (model, yAngle) -> rotCF = CFrame.Angles 0, yAngle, 0 offsetFromOrigin = model\GetModelCFrame!.p rotatePartAndChildren model, rotCF, offsetFromOrigin collectParts = (object, baseParts, scripts, decals) -> if object\IsA "BasePart" baseParts[] = object elseif object\IsA "Script" scripts[] = object elseif object\IsA "Decal" decals[] = object for _, child in pairs object\GetChildren! collectParts child, baseParts, scripts, decals clusterPartsInRegion = (startVector, endVector) -> cluster = game.Workspace\FindFirstChild "Terrain" startCell = cluster\WorldToCell startVector endCell = cluster\WorldToCell endVector startX = startCell.X startY = startCell.Y startZ = startCell.Z endX = endCell.X endY = endCell.Y endZ = endCell.Z if startX < cluster.MaxExtents.Min.X startX = cluster.MaxExtents.Min.X if startY < cluster.MaxExtents.Min.Y startY = cluster.MaxExtents.Min.Y if startZ < cluster.MaxExtents.Min.Z startZ = cluster.MaxExtents.Min.Z if endX > cluster.MaxExtents.Max.X endX = cluster.MaxExtents.Max.X if endY > cluster.MaxExtents.Max.Y endY = cluster.MaxExtents.Max.Y if endZ > cluster.MaxExtents.Max.Z endZ = cluster.MaxExtents.Max.Z for x = startX, endX for y = startY, endY for z = startZ, endZ if cluster\GetCell(x, y, z).Value > 0 return true false findSeatsInModel = (parent, seatTable) -> return if not parent if parent.className == "Seat" or parent.className == "VehicleSeat" table.insert seatTable, parent myChildren = parent\GetChildren! for j = 1, #myChildren findSeatsInModel myChildren[j], seatTable setSeatEnabledStatus = (model, isEnabled) -> seatList = {} findSeatsInModel model, seatList if isEnabled -- remove any welds called "SeatWeld" in seats for i = 1, #seatList nextSeat = seatList[i]\FindFirstChild "SeatWeld" while nextSeat nextSeat\Remove! nextSeat = seatList[i]\FindFirstChild "SeatWeld" else -- put a weld called "SeatWeld" in every seat -- this tricks it into thinking there's already someone sitting there, and it won't make you sit XD for i = 1, #seatList fakeWeld = Instance.new "Weld" fakeWeld.Name = "SeatWeld" fakeWeld.Parent = seatList[i] autoAlignToFace = (parts) -> aatf = parts\FindFirstChild "AutoAlignToFace" if aatf aatf.Value else false getClosestAlignedWorldDirection = (aVector3InWorld) -> xDir = Vector3.new 1, 0, 0 yDir = Vector3.new 0, 1, 0 zDir = Vector3.new 0, 0, 1 xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z zDot = aVector3InWorld.x * zDir.x + aVector3InWorld.y * zDir.y + aVector3InWorld.z * zDir.z if math.abs(xDot) > math.abs(yDot) and math.abs(xDot) > math.abs zDot if xDot > 0 0 else 3 elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs zDot if yDot > 0 1 else 4 else if zDot > 0 2 else 5 positionPartsAtCFrame3 = (aCFrame, currentParts) -> local insertCFrame if not currentParts return currentParts if currentParts and (currentParts\IsA"Model" or currentParts\IsA "Tool") insertCFrame = currentParts\GetModelCFrame! currentParts\TranslateBy aCFrame.p - insertCFrame.p else currentParts.CFrame = aCFrame currentParts calcRayHitTime = (rayStart, raySlope, intersectionPlane) -> if math.abs(raySlope) < 0.01 return 0 -- 0 slope --> we just say intersection time is 0, and sidestep this dimension (intersectionPlane - rayStart) / raySlope modelTargetSurface = (partOrModel, rayStart, rayEnd) -> if not partOrModel return 0 local modelCFrame, modelSize if partOrModel\IsA "Model" modelCFrame = partOrModel\GetModelCFrame! modelSize = partOrModel\GetModelSize! else modelCFrame = partOrModel.CFrame modelSize = partOrModel.Size mouseRayStart = modelCFrame\pointToObjectSpace rayStart mouseRayEnd = modelCFrame\pointToObjectSpace rayEnd mouseSlope = mouseRayEnd - mouseRayStart xPositive = 1 yPositive = 1 zPositive = 1 if mouseSlope.X > 0 xPositive = -1 if mouseSlope.Y > 0 yPositive = -1 if mouseSlope.Z > 0 zPositive = -1 -- find which surface the transformed mouse ray hits (using modelSize): xHitTime = calcRayHitTime mouseRayStart.X, mouseSlope.X, modelSize.X / 2 * xPositive yHitTime = calcRayHitTime mouseRayStart.Y, mouseSlope.Y, modelSize.Y / 2 * yPositive zHitTime = calcRayHitTime mouseRayStart.Z, mouseSlope.Z, modelSize.Z / 2 * zPositive hitFace = 0 --if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 if xHitTime > yHitTime if xHitTime > zHitTime -- xFace is hit hitFace = 1 * xPositive else -- zFace is hit hitFace = 3 * zPositive else if yHitTime > zHitTime -- yFace is hit hitFace = 2 * yPositive else -- zFace is hit hitFace = 3 * zPositive hitFace getBoundingBox2 = (partOrModel) -> -- for models, the bounding box is defined as the minimum and maximum individual part bounding boxes -- relative to the first part's coordinate frame. minVec = Vector3.new math.huge, math.huge, math.huge maxVec = Vector3.new -math.huge, -math.huge, -math.huge if partOrModel\IsA "Terrain" minVec = Vector3.new -2, -2, -2 maxVec = Vector3.new 2, 2, 2 elseif partOrModel\IsA "BasePart" minVec = -0.5 * partOrModel.Size maxVec = -minVec else maxVec = partOrModel\GetModelSize! * 0.5 minVec = -maxVec -- Adjust bounding box to reflect what the model or part author wants in terms of justification justifyValue = partOrModel\FindFirstChild "Justification" if justifyValue? -- find the multiple of 4 that contains the model justify = justifyValue.Value two = Vector3.new(2, 2, 2) actualBox = maxVec - minVec - Vector3.new 0.01, 0.01, 0.01 containingGridBox = Vector3.new( 4 * math.ceil(actualBox.x / 4), 4 * math.ceil(actualBox.y / 4), 4 * math.ceil actualBox.z / 4 ) adjustment = containingGridBox - actualBox minVec -= 0.5 * adjustment * justify maxVec += 0.5 * adjustment * (two - justify) minVec, maxVec getBoundingBoxInWorldCoordinates = (partOrModel) -> minVec = Vector3.new math.huge, math.huge, math.huge maxVec = Vector3.new -math.huge, -math.huge, -math.huge if partOrModel\IsA"BasePart" and not partOrModel\IsA "Terrain" vec1 = partOrModel.CFrame\pointToWorldSpace -0.5 * partOrModel.Size vec2 = partOrModel.CFrame\pointToWorldSpace 0.5 * partOrModel.Size minVec = Vector3.new math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min vec1.Z, vec2.Z maxVec = Vector3.new math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max vec1.Z, vec2.Z elseif not partOrModel\IsA "Terrain" -- we shouldn't have to deal with this case --minVec = Vector3.new(-2, -2, -2) --maxVec = Vector3.new(2, 2, 2) -- else vec1 = partOrModel\GetModelCFrame!\pointToWorldSpace -0.5 * partOrModel\GetModelSize! vec2 = partOrModel\GetModelCFrame!\pointToWorldSpace 0.5 * partOrModel\GetModelSize! minVec = Vector3.new math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min vec1.Z, vec2.Z maxVec = Vector3.new math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max vec1.Z, vec2.Z minVec, maxVec getTargetPartBoundingBox = (targetPart) -> getBoundingBox2 if targetPart.Parent\FindFirstChild"RobloxModel"? targetPart.Parent else targetPart getMouseTargetCFrame = (targetPart) -> with targetPart return if .Parent\FindFirstChild"RobloxModel"? if .Parent\IsA "Tool" .Parent.Handle.CFrame else .Parent\GetModelCFrame! else .CFrame isBlocker = (part) -> -- returns whether or not we want to cancel the stamp because we're blocked by this part if not part return false if not part.Parent return false if part\FindFirstChild "Humanoid" return false if part\FindFirstChild "RobloxStamper" or part\FindFirstChild "RobloxModel" return true if part\IsA"Part" and not part.CanCollide return false if part == game.Lighting return false isBlocker part.Parent -- helper function to determine if a character can be pushed upwards by a certain amount -- 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 spaceAboveCharacter = (charTorso, newTorsoY, stampData) -> partsAboveChar = game.Workspace\FindPartsInRegion3( Region3.new( Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(0.75, 2.75, 0.75), Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(0.75, 1.75, 0.75) ), charTorso.Parent, 100 ) for j = 1, #partsAboveChar if partsAboveChar[j].CanCollide and not partsAboveChar[j]\IsDescendantOf stampData.CurrentParts return false if clusterPartsInRegion( Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(0.75, 2.75, 0.75), Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(0.75, 1.75, 0.75) ) return false true findConfigAtMouseTarget = (Mouse, stampData) -> -- *Critical Assumption* : -- This function assumes the target CF axes are orthogonal with the target bounding box faces -- And, it assumes the insert CF axes are orthongonal with the insert bounding box faces -- Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor -- will it work for parts in a model that are not orthogonally aligned with the model's CF. return if not Mouse -- This can happen sometimes, return if so return if not stampData error "findConfigAtMouseTarget: stampData is nil" return if not stampData["CurrentParts"] grid = 4.0 admissibleConfig = false targetConfig = CFrame.new 0, 0, 0 minBB, maxBB = getBoundingBox2 stampData.CurrentParts diagBB = maxBB - minBB local insertCFrame if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool" insertCFrame = stampData.CurrentParts\GetModelCFrame! else insertCFrame = stampData.CurrentParts.CFrame if Mouse if stampData.CurrentParts\IsA "Tool" Mouse.TargetFilter = stampData.CurrentParts.Handle else Mouse.TargetFilter = stampData.CurrentParts hitPlane = false local targetPart success = try targetPart = Mouse.Target if not success -- or targetPart == nil return admissibleConfig, targetConfig mouseHitInWorld = Vector3.new 0, 0, 0 if Mouse mouseHitInWorld = Vector3.new Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z local cellPos -- Nothing was hit, so check for the default plane. if nil == targetPart cellPos = GetTerrainForMouse Mouse if nil == cellPos hitPlane = false return admissibleConfig, targetConfig else targetPart = game.Workspace.Terrain hitPlane = true -- Take into account error that will occur. cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z) mouseHitInWorld = game.Workspace.Terrain\CellCenterToWorld(cellPos.x, cellPos.y, cellPos.z) -- test mouse hit location local minBBTarget, maxBBTarget = getTargetPartBoundingBox targetPart diagBBTarget = maxBBTarget - minBBTarget targetCFrame = getMouseTargetCFrame targetPart if targetPart\IsA "Terrain" if not cluster global cluster = game.Workspace\FindFirstChild "Terrain" cellID = cluster\WorldToCellPreferSolid mouseHitInWorld if hitPlane cellID = cellPos targetCFrame = CFrame.new game.Workspace.Terrain\CellCenterToWorld cellID.x, cellID.y, cellID.z mouseHitInTarget = targetCFrame\pointToObjectSpace mouseHitInWorld targetVectorInWorld = Vector3.new 0, 0, 0 if Mouse -- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME! (.TargetSurface is in terms of the part CFrame, so this would break, right? [HotThoth]) -- (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse) --targetVectorInWorld = targetCFrame\vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface)) targetVectorInWorld = targetPart.CFrame\vectorToWorldSpace Vector3.FromNormalId Mouse.TargetSurface -- better, but model cframe would be best --[[if targetPart.Parent\IsA("Model") hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)} if hitFace > 0 targetVectorInWorld = targetCFrame\vectorToWorldSpace(WORLD_AXES[hitFace]) elseif hitFace < 0 targetVectorInWorld = targetCFrame\vectorToWorldSpace(-WORLD_AXES[-hitFace]) end]] local targetRefPointInTarget, insertRefPointInInsert local clampToSurface if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new 1, -1, 1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new -1, -1, 1 clampToSurface = Vector3.new 0, 1, 1 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 3 targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new -1, -1, -1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new 1, -1, -1 clampToSurface = Vector3.new 0, 1, 1 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 1 targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new -1, 1, 1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new -1, -1, 1 clampToSurface = Vector3.new 1, 0, 1 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 4 targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new -1, -1, 1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new -1, 1, 1 clampToSurface = Vector3.new 1, 0, 1 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 2 targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new -1, -1, 1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new -1, -1, -1 clampToSurface = Vector3.new 1, 1, 0 else targetRefPointInTarget = targetCFrame\vectorToObjectSpace Vector3.new 1, -1, -1 insertRefPointInInsert = insertCFrame\vectorToObjectSpace Vector3.new 1, -1, 1 clampToSurface = Vector3.new 1, 1, 0 targetRefPointInTarget *= (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget) insertRefPointInInsert *= (0.5 * diagBB) + 0.5 * (maxBB + minBB) -- To Do: For cases that are not aligned to the world grid, account for the minimal rotation -- needed to bring the Insert part(s) into alignment with the Target Part -- Apply the rotation here delta = mouseHitInTarget - targetRefPointInTarget deltaClamped = Vector3.new( grid * math.modf(delta.x / grid), grid * math.modf(delta.y / grid), grid * math.modf delta.z / grid ) deltaClamped *= clampToSurface targetTouchInTarget = deltaClamped + targetRefPointInTarget TargetTouchRelToWorld = targetCFrame\pointToWorldSpace targetTouchInTarget InsertTouchInWorld = insertCFrame\vectorToWorldSpace insertRefPointInInsert posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld local _, _, _, R00, R01, R02, R10, R11, R12, R20, R21, R22 = insertCFrame\components! targetConfig = CFrame.new( posInsertOriginInWorld.x, posInsertOriginInWorld.y, posInsertOriginInWorld.z, R00, R01, R02, R10, R11, R12, R20, R21, R22 ) admissibleConfig = true admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld) truncateToCircleEighth = (bigValue, littleValue) -> big = math.abs bigValue little = math.abs littleValue hypotenuse = math.sqrt big * big + little * little frac = little / hypotenuse bigSign = 1 littleSign = 1 if bigValue < 0 bigSign = -1 if littleValue < 0 littleSign = -1 if frac > 0.382683432 -- between 22.5 and 45 degrees, so truncate to 45-degree tilt return 0.707106781 * hypotenuse * bigSign, 0.707106781 * hypotenuse * littleSign else -- between 0 and 22.5 degrees, so truncate to 0-degree tilt return hypotenuse * bigSign, 0 saveTheWelds = (object, manualWeldTable, manualWeldParentTable) -> if object\IsA"ManualWeld" or object\IsA "Rotate" table.insert manualWeldTable, object table.insert manualWeldParentTable, object.Parent else children = object\GetChildren! for i = 1, #children saveTheWelds children[i], manualWeldTable, manualWeldParentTable restoreTheWelds = (manualWeldTable, manualWeldParentTable) -> for i = 1, #manualWeldTable manualWeldTable[i].Parent = manualWeldParentTable[i] t.CanEditRegion = (partOrModel, EditRegion) -> -- todo: use model and stamper metadata if not EditRegion return true, false local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel) if minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X / 2 or minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y / 2 or minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z / 2 return false, false if maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X / 2 or maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y / 2 or maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z / 2 return false, false true, false t.GetStampModel = (assetId, terrainShape, useAssetVersionId) -> if assetId == 0 return nil, "No Asset" if assetId < 0 return nil, "Negative Asset" UnlockInstances = (object) -> if object\IsA "BasePart" object.Locked = false for _, child in pairs object\GetChildren! UnlockInstances child getClosestColorToTerrainMaterial = (terrainValue) -> BrickColor.new switch terrainValue when 1 then "Bright green" when 2 then "Bright yellow" when 3 then "Bright red" when 4 then "Sand red" when 5 then "Black" when 6 then "Dark stone grey" when 7 then "Sand blue" when 8 then "Deep orange" when 9 then "Dark orange" when 10 then "Reddish brown" when 11 then "Light orange" when 12 then "Light stone grey" when 13 then "Sand green" when 14 then "Medium stone grey" when 15 then "Really red" when 16 then "Really blue" when 17 then "Bright blue" else "Bright green" setupFakeTerrainPart = (cellMat, cellType, cellOrient) -> local newTerrainPiece if cellType == 1 or cellType == 4 newTerrainPiece = Instance.new "WedgePart" newTerrainPiece.formFactor = "Custom" elseif cellType == 2 newTerrainPiece = Instance.new "CornerWedgePart" else newTerrainPiece = Instance.new "Part" newTerrainPiece.formFactor = "Custom" newTerrainPiece.Name = "MegaClusterCube" newTerrainPiece.Size = Vector3.new 4, 4, 4 newTerrainPiece.BottomSurface = "Smooth" newTerrainPiece.TopSurface = "Smooth" -- can add decals or textures here if feeling particularly adventurous... for now, can make a table of look-up colors newTerrainPiece.BrickColor = getClosestColorToTerrainMaterial cellMat sideways = 0 flipped = math.pi if cellType == 4 sideways = -math.pi / 2 if cellType == 2 or cellType == 3 flipped = 0 newTerrainPiece.CFrame = CFrame.Angles 0, math.pi / 2 * cellOrient + flipped, sideways if cellType == 3 inverseCornerWedgeMesh = Instance.new "SpecialMesh" inverseCornerWedgeMesh.MeshType = "FileMesh" inverseCornerWedgeMesh.MeshId = "http://www.roblox.com/asset?id=66832495" inverseCornerWedgeMesh.Scale = Vector3.new 2, 2, 2 inverseCornerWedgeMesh.Parent = newTerrainPiece materialTag = Instance.new "Vector3Value" materialTag.Value = Vector3.new cellMat, cellType, cellOrient materialTag.Name = "ClusterMaterial" materialTag.Parent = newTerrainPiece return newTerrainPiece -- This call will cause a "wait" until the data comes back -- below we wait a max of 8 seconds before deciding to bail out on loading local root local loader loading = true if useAssetVersionId loader = coroutine.create -> root = game\GetService"InsertService"\LoadAssetVersion assetId loading = false coroutine.resume loader else loader = coroutine.create -> root = game\GetService"InsertService"\LoadAsset assetId loading = false coroutine.resume loader lastGameTime = 0 totalTime = 0 maxWait = 8 while loading and totalTime < maxWait lastGameTime = tick! wait 1 totalTime += tick! - lastGameTime loading = false if totalTime >= maxWait return nil, "Load Time Fail" if root == nil return nil, "Load Asset Fail" if not root\IsA "Model" return nil, "Load Type Fail" instances = root\GetChildren! if #instances == 0 return nil, "Empty Model Fail" --Unlock all parts that are inserted, to make sure they are editable UnlockInstances(root) --Continue the insert process root = root\GetChildren![1] --Examine the contents and decide what it looks like for _, instance in pairs instances if instance\IsA "Team" instance.Parent = game\GetService "Teams" elseif instance\IsA "Sky" lightingService = game\GetService "Lighting" for _, child in pairs lightingService\GetChildren! if child\IsA "Sky" child\Remove! instance.Parent = lightingService return -- ...and tag all inserted models for subsequent origin identification -- if no RobloxModel tag already exists, then add it. if not root\FindFirstChild"RobloxModel"? stringTag = Instance.new "BoolValue" stringTag.Name = "RobloxModel" stringTag.Parent = root if not root\FindFirstChild"RobloxStamper"? stringTag2 = Instance.new "BoolValue" stringTag2.Name = "RobloxStamper" stringTag2.Parent = root if terrainShape if root.Name == "MegaClusterCube" if terrainShape == 6 -- insert an autowedging tag autowedgeTag = Instance.new "BoolValue" autowedgeTag.Name = "AutoWedge" autowedgeTag.Parent = root else clusterTag = root\FindFirstChild "ClusterMaterial" if clusterTag if clusterTag\IsA "Vector3Value" root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z) else root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0) else root = setupFakeTerrainPart(1, terrainShape, 0) return root t.SetupStamperDragger = (modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc) -> if not modelToStamp error "SetupStamperDragger: modelToStamp (first arg) is nil! Should be a stamper model" return nil if not modelToStamp\IsA"Model" and not modelToStamp\IsA "BasePart" error "SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!" return nil if not Mouse error "SetupStamperDragger: Mouse (second arg) is nil! Should be a mouse object" return nil if not Mouse\IsA "Mouse" error "SetupStamperDragger: Mouse (second arg) is not of type Mouse!" return nil local stampInModel local allowedStampRegion local stampFailedFunc if StampInModel if not StampInModel\IsA "Model" error "SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'" return nil if not AllowedStampRegion error "SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined" return nil stampFailedFunc = StampFailedFunc stampInModel = StampInModel allowedStampRegion = AllowedStampRegion -- Init all state variables gInitial90DegreeRotations = 0 local stampData local mouseTarget errorBox = Instance.new "SelectionBox" errorBox.Color = BrickColor.new "Bright red" errorBox.Transparency = 0 errorBox.Archivable = false -- for megacluster MEGA STAMPING adornPart = Instance.new "Part" adornPart.Parent = nil adornPart.formFactor = "Custom" adornPart.Size = Vector3.new(4, 4, 4) adornPart.CFrame = CFrame.new! adornPart.Archivable = false adorn = Instance.new "SelectionBox" adorn.Color = BrickColor.new "Toothpaste" adorn.Adornee = adornPart adorn.Visible = true adorn.Transparency = 0 adorn.Name = "HighScalabilityStamperLine" adorn.Archivable = false HighScalabilityLine = {} HighScalabilityLine.Start = nil HighScalabilityLine.End = nil HighScalabilityLine.Adorn = adorn HighScalabilityLine.AdornPart = adornPart HighScalabilityLine.InternalLine = nil HighScalabilityLine.NewHint = true HighScalabilityLine.MorePoints = { nil, nil } HighScalabilityLine.MoreLines = { nil, nil } HighScalabilityLine.Dimensions = 1 control = {} movingLock = false stampUpLock = false unstampableSurface = false mouseCons = {} local keyCon stamped = Instance.new "BoolValue" stamped.Archivable = false stamped.Value = false lastTarget = {} lastTarget.TerrainOrientation = 0 lastTarget.CFrame = 0 cellInfo = {} cellInfo.Material = 1 cellInfo.clusterType = 0 cellInfo.clusterOrientation = 0 isMegaClusterPart = -> if not stampData return false if not stampData.CurrentParts return false stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) or (stampData.CurrentParts.Name == "MegaClusterCube") DoHighScalabilityRegionSelect = -> megaCube = stampData.CurrentParts\FindFirstChild "MegaClusterCube" if not megaCube if not stampData.CurrentParts.Name == "MegaClusterCube" return else megaCube = stampData.CurrentParts HighScalabilityLine.End = megaCube.CFrame.p local line line2 = Vector3.new 0, 0, 0 line3 = Vector3.new 0, 0, 0 if HighScalabilityLine.Dimensions == 1 -- extract the line from these positions and limit to a 2D plane made from 2 of the world axes -- then use dominating axis to limit line to be at 45-degree intervals -- will use this internal representation of the line for the actual stamping line = (HighScalabilityLine.End - HighScalabilityLine.Start) if math.abs(line.X) < math.abs(line.Y) if math.abs(line.X) < math.abs(line.Z) -- limit to Y/Z plane, domination unknown local newY, newZ if math.abs(line.Y) > math.abs(line.Z) newY, newZ = truncateToCircleEighth(line.Y, line.Z) else newZ, newY = truncateToCircleEighth(line.Z, line.Y) line = Vector3.new(0, newY, newZ) else -- limit to X/Y plane, with Y dominating local newY, newX = truncateToCircleEighth(line.Y, line.X) line = Vector3.new(newX, newY, 0) else if math.abs(line.Y) < math.abs(line.Z) -- limit to X/Z plane, domination unknown local newX, newZ if math.abs(line.X) > math.abs(line.Z) newX, newZ = truncateToCircleEighth(line.X, line.Z) else newZ, newX = truncateToCircleEighth(line.Z, line.X) line = Vector3.new(newX, 0, newZ) else -- limit to X/Y plane, with X dominating local newX, newY = truncateToCircleEighth(line.X, line.Y) line = Vector3.new(newX, newY, 0) HighScalabilityLine.InternalLine = line elseif HighScalabilityLine.Dimensions == 2 line = HighScalabilityLine.MoreLines[1] line2 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[1] -- take out any component of line2 along line1, so you get perpendicular to line1 component line2 -= line.unit * line.unit\Dot(line2) tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line) -- then zero out whichever is the smaller component yAxis = tempCFrame\vectorToWorldSpace(Vector3.new(0, 1, 0)) xAxis = tempCFrame\vectorToWorldSpace(Vector3.new(1, 0, 0)) xComp = xAxis\Dot(line2) yComp = yAxis\Dot(line2) if math.abs(yComp) > math.abs(xComp) line2 -= xAxis * xComp else line2 -= yAxis * yComp HighScalabilityLine.InternalLine = line2 elseif HighScalabilityLine.Dimensions == 3 line = HighScalabilityLine.MoreLines[1] line2 = HighScalabilityLine.MoreLines[2] line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2] -- zero out all components of previous lines line3 -= line.unit * line.unit\Dot(line3) line3 -= line2.unit * line2.unit\Dot(line3) HighScalabilityLine.InternalLine = line3 -- resize the "line" graphic to be the correct size and orientation tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line) if HighScalabilityLine.Dimensions == 1 -- faster calculation for line HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, line.magnitude + 4) HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame\vectorToWorldSpace Vector3.new(2, 2, 2) - HighScalabilityLine.AdornPart.Size / 2 else boxSize = tempCFrame\vectorToObjectSpace(line + line2 + line3) HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, 4) + Vector3.new(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z)) HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame\vectorToWorldSpace boxSize / 2 -- make player able to see this ish local gui if game.Players["LocalPlayer"] gui = game.Players.LocalPlayer\FindFirstChild "PlayerGui" if gui and gui\IsA "PlayerGui" if (HighScalabilityLine.Dimensions == 1 and line.magnitude > 3) or HighScalabilityLine.Dimensions > 1 -- don't show if mouse hasn't moved enough HighScalabilityLine.Adorn.Parent = gui if not gui? -- we are in studio gui = game\GetService "CoreGui" if (HighScalabilityLine.Dimensions == 1 and line.magnitude > 3) or HighScalabilityLine.Dimensions > 1 -- don't show if mouse hasn't moved enough HighScalabilityLine.Adorn.Parent = gui DoStamperMouseMove = (Mouse) -> if not Mouse error "Error: RbxStamper.DoStamperMouseMove: Mouse is nil" return if not Mouse\IsA "Mouse" error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className, "should be of type Mouse") return -- There wasn't a target (no part or terrain), so check for plane intersection. if not Mouse.Target cellPos = GetTerrainForMouse(Mouse) if nil == cellPos return if not stampData return -- don't move with dragger - will move in one step on mouse down -- draw ghost at acceptable positions configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Mouse, stampData) if not configFound error "RbxStamper.DoStamperMouseMove No configFound, returning" return numRotations = 0 -- update this according to how many rotations you need to get it to target surface if autoAlignToFace(stampData.CurrentParts) and targetSurface ~= 1 and targetSurface ~= 4 -- pre-rotate the flag or portrait so it's aligned correctly if targetSurface == 3 numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts) elseif targetSurface == 0 numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts) elseif targetSurface == 5 numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts) elseif targetSurface == 2 numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts) ry = math.pi / 2 gInitial90DegreeRotations += numRotations if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool" --stampData.CurrentParts\Rotate(0, ry*numRotations, 0) modelRotate(stampData.CurrentParts, ry * numRotations) else stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry * numRotations, 0) * stampData.CurrentParts.CFrame -- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts) -- need to offset by distance to be dragged local currModelCFrame if stampData.CurrentParts\IsA "Model" currModelCFrame = stampData.CurrentParts\GetModelCFrame! else currModelCFrame = stampData.CurrentParts.CFrame minBB += targetCFrame.p - currModelCFrame.p maxBB += targetCFrame.p - currModelCFrame.p -- don't drag into terrain if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) if lastTarget.CFrame if stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) theClusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) if theClusterMaterial\IsA "Vector3Value" stampClusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) if stampClusterMaterial stampClusterMaterial = clusterMat return -- 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 if isMegaClusterPart! cellToStamp = game.Workspace.Terrain\WorldToCell targetCFrame.p newCFramePosition = game.Workspace.Terrain\CellCenterToWorld cellToStamp.X, cellToStamp.Y, cellToStamp.Z local _, _, _, R00, R01, R02, R10, R11, R12, R20, R21, R22 = targetCFrame\components! targetCFrame = CFrame.new( newCFramePosition.X, newCFramePosition.Y, newCFramePosition.Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 ) positionPartsAtCFrame3 targetCFrame, stampData.CurrentParts lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe if stampData.CurrentParts\FindFirstChild "ClusterMaterial", true clusterMat = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true if clusterMat\IsA "Vector3Value" lastTarget.TerrainOrientation = clusterMat.Value.Z -- auto break joints code if Mouse and Mouse.Target and Mouse.Target.Parent modelInfo = Mouse.Target\FindFirstChild "RobloxModel" if not modelInfo modelInfo = Mouse.Target.Parent\FindFirstChild "RobloxModel" myModelInfo = stampData.CurrentParts\FindFirstChild "UnstampableFaces" --if (modelInfo and modelInfo.Parent\FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then -- need better targetSurface calcs if true breakingFaces = "" myBreakingFaces = "" if modelInfo and modelInfo.Parent\FindFirstChild "UnstampableFaces" breakingFaces = modelInfo.Parent.UnstampableFaces.Value if myModelInfo myBreakingFaces = myModelInfo.Value hitFace = 0 if modelInfo hitFace = modelTargetSurface( modelInfo.Parent, game.Workspace.CurrentCamera.CoordinateFrame.p, Mouse.Hit.p ) -- are we stamping TO an unstampable surface? for bf in string.gmatch breakingFaces, "[^,]+" if hitFace == tonumber bf -- return before we hit the JointsService code below! unstampableSurface = true game.JointsService\ClearJoinAfterMoveJoints! -- clear the JointsService cache return -- now we have to cast the ray back in the other direction to find the surface we're stamping FROM hitFace = modelTargetSurface( stampData.CurrentParts, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p ) -- are we stamping WITH an unstampable surface? for bf in string.gmatch myBreakingFaces, "[^,]+" if hitFace == tonumber bf unstampableSurface = true game.JointsService\ClearJoinAfterMoveJoints! -- clear the JointsService cache return -- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame -- targetSurface: 1 is top, 4 is bottom, -- to show joints during the mouse move unstampableSurface = false game.JointsService\SetJoinAfterMoveInstance stampData.CurrentParts -- most common mouse inactive error occurs here, so check mouse active one more time in a pcall if (not try if Mouse and Mouse.Target and not Mouse.Target.Parent\FindFirstChild"RobloxModel"? return else return -- ? ) error "Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check" game.JointsService\ClearJoinAfterMoveJoints! Mouse = nil return if Mouse and Mouse.Target and not Mouse.Target.Parent\FindFirstChild"RobloxModel"? game.JointsService\SetJoinAfterMoveTarget Mouse.Target else game.JointsService\SetJoinAfterMoveTarget nil game.JointsService\ShowPermissibleJoints! -- here we allow for a line of high-scalability parts if isMegaClusterPart! and HighScalabilityLine and HighScalabilityLine.Start DoHighScalabilityRegionSelect! setupKeyListener = (key, Mouse) -> return if control and control["Paused"] -- don't do this if we have no stamp key = string.lower(key) if key == "r" and not autoAlignToFace(stampData.CurrentParts) -- rotate the model gInitial90DegreeRotations += 1 -- Update orientation value if this is a fake terrain part clusterValues = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true if clusterValues and clusterValues\IsA "Vector3Value" clusterValues.Value = Vector3.new( clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z + 1) % 4 ) -- Rotate the parts or all the parts in the model ry = math.pi / 2 if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool" --stampData.CurrentParts\Rotate(0, ry, 0) modelRotate stampData.CurrentParts, ry else stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame -- After rotating, update the position configFound, targetCFrame = findConfigAtMouseTarget Mouse, stampData if configFound positionPartsAtCFrame3 targetCFrame, stampData.CurrentParts -- update everything else in MouseMove DoStamperMouseMove Mouse elseif key == "c" -- try to expand our high scalability dragger dimension if HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 and HighScalabilityLine.Dimensions < 3 HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1 HighScalabilityLine.NewHint = true keyCon = Mouse.KeyDown\connect (key) -> -- init key connection (keeping code close to func) setupKeyListener key, Mouse resetHighScalabilityLine = -> if HighScalabilityLine HighScalabilityLine.Start = nil HighScalabilityLine.End = nil HighScalabilityLine.InternalLine = nil HighScalabilityLine.NewHint = true flashRedBox = -> gui = game.CoreGui if game\FindFirstChild "Players" if game.Players["LocalPlayer"] if game.Players.LocalPlayer\FindFirstChild "PlayerGui" gui = game.Players.LocalPlayer.PlayerGui return if not stampData["ErrorBox"] stampData.ErrorBox.Parent = gui if stampData.CurrentParts\IsA "Tool" stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle else stampData.ErrorBox.Adornee = stampData.CurrentParts delay 0, -> for _ = 1, 3 if stampData["ErrorBox"] stampData.ErrorBox.Visible = true wait 0.13 if stampData["ErrorBox"] stampData.ErrorBox.Visible = false wait 0.13 if stampData["ErrorBox"] stampData.ErrorBox.Adornee = nil stampData.ErrorBox.Parent = Tool DoStamperMouseDown = (Mouse) -> if not Mouse error "Error: RbxStamper.DoStamperMouseDown: Mouse is nil" return if not Mouse\IsA "Mouse" error "Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className, "should be of type Mouse" return if not stampData return if isMegaClusterPart! if Mouse and HighScalabilityLine megaCube = stampData.CurrentParts\FindFirstChild("MegaClusterCube", true) terrain = game.Workspace.Terrain if megaCube HighScalabilityLine.Dimensions = 1 tempCell = terrain\WorldToCell(megaCube.CFrame.p) HighScalabilityLine.Start = terrain\CellCenterToWorld tempCell.X, tempCell.Y, tempCell.Z return else HighScalabilityLine.Dimensions = 1 tempCell = terrain\WorldToCell(stampData.CurrentParts.CFrame.p) HighScalabilityLine.Start = terrain\CellCenterToWorld tempCell.X, tempCell.Y, tempCell.Z return loadSurfaceTypes = (part, surfaces) -> with part .TopSurface = surfaces[1] .BottomSurface = surfaces[2] .LeftSurface = surfaces[3] .RightSurface = surfaces[4] .FrontSurface = surfaces[5] .BackSurface = surfaces[6] saveSurfaceTypes = (part, myTable) -> tempTable = {} tempTable[1] = part.TopSurface tempTable[2] = part.BottomSurface tempTable[3] = part.LeftSurface tempTable[4] = part.RightSurface tempTable[5] = part.FrontSurface tempTable[6] = part.BackSurface myTable[part] = tempTable -- makeSurfaceUnjoinable = (part, surface) -> -- -- TODO: FILL OUT! -- end prepareModel = (model) -> if not model return nil gDesiredTrans = 0.7 gStaticTrans = 1 clone = model\Clone! scripts = {} parts = {} decals = {} stampData = {} stampData.DisabledScripts = {} stampData.TransparencyTable = {} stampData.MaterialTable = {} stampData.CanCollideTable = {} stampData.AnchoredTable = {} stampData.ArchivableTable = {} stampData.DecalTransparencyTable = {} stampData.SurfaceTypeTable = {} collectParts(clone, parts, scripts, decals) if #parts <= 0 return nil, "no parts found in modelToStamp" for _, script in pairs scripts if not script.Disabled script.Disabled = true stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script for _, part in pairs parts stampData.TransparencyTable[part] = part.Transparency part.Transparency = gStaticTrans + (1 - gStaticTrans) * part.Transparency stampData.MaterialTable[part] = part.Material part.Material = Enum.Material.Plastic stampData.CanCollideTable[part] = part.CanCollide part.CanCollide = false stampData.AnchoredTable[part] = part.Anchored part.Anchored = true stampData.ArchivableTable[part] = part.Archivable part.Archivable = false saveSurfaceTypes(part, stampData.SurfaceTypeTable) fadeInDelayTime = 0.5 transFadeInTime = 0.5 delay 0, -> wait fadeInDelayTime -- give it some time to be completely transparent begTime = tick! currTime = begTime while (currTime - begTime) < transFadeInTime and part and part\IsA"BasePart" and part.Transparency > gDesiredTrans newTrans = 1 - (((currTime - begTime) / transFadeInTime) * (gStaticTrans - gDesiredTrans)) if stampData["TransparencyTable"] and stampData.TransparencyTable[part] part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part] wait 0.03 currTime = tick! if part and part\IsA "BasePart" if stampData["TransparencyTable"] and stampData.TransparencyTable[part] part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part] for _, decal in pairs decals stampData.DecalTransparencyTable[decal] = decal.Transparency decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency -- disable all seats setSeatEnabledStatus clone, true setSeatEnabledStatus clone, false stampData.CurrentParts = clone -- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame" if autoAlignToFace clone stampData.CurrentParts\ResetOrientationToIdentity! gInitial90DegreeRotations = 0 else -- pre-rotate if necessary ry = gInitial90DegreeRotations * math.pi / 2 if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool" --stampData.CurrentParts\Rotate(0, ry, 0) modelRotate stampData.CurrentParts, ry else stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame -- 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 -- 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 -- issue (fingers crossed) [HotThoth] clusterMaterial = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true if clusterMaterial and clusterMaterial\IsA"Vector3Value" clusterMaterial.Value = Vector3.new( clusterMaterial.Value.X, clusterMaterial.Value.Y, (clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4 ) -- After rotating, update the position local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData) if configFound stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts) -- to show joints during the mouse move game.JointsService\SetJoinAfterMoveInstance(stampData.CurrentParts) return clone, parts checkTerrainBlockCollisions = (cellPos, checkHighScalabilityStamp) -> cellCenterToWorld = game.Workspace.Terrain.CellCenterToWorld cellCenter = cellCenterToWorld game.Workspace.Terrain, cellPos.X, cellPos.Y, cellPos.Z cellBlockingParts = game.Workspace\FindPartsInRegion3( Region3.new( cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector, cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector ), stampData.CurrentParts, 100 ) skipThisCell = false for b = 1, #cellBlockingParts if isBlocker cellBlockingParts[b] skipThisCell = true break if not skipThisCell -- pop players up above any set cells alreadyPushedUp = {} -- if no blocking model below, then see if stamping on top of a character for b = 1, #cellBlockingParts if cellBlockingParts[b].Parent and not alreadyPushedUp[cellBlockingParts[b].Parent] and cellBlockingParts[b].Parent\FindFirstChild "Humanoid" and cellBlockingParts[b].Parent\FindFirstChild"Humanoid"\IsA "Humanoid" ----------------------------------------------------------------------------------- blockingPersonTorso = cellBlockingParts[b].Parent\FindFirstChild "Torso" alreadyPushedUp[cellBlockingParts[b].Parent] = true if blockingPersonTorso -- 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) newY = cellCenter.Y + 5 if spaceAboveCharacter blockingPersonTorso, newY, stampData blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new 0, newY - blockingPersonTorso.CFrame.p.Y, 0 else -- if no space, we just skip this one skipThisCell = true break ----------------------------------------------------------------------------------- if not skipThisCell -- if we STILL aren't skipping... then we're good to go! canSetCell = true if checkHighScalabilityStamp -- check to see if cell is in region, if not we'll skip set if allowedStampRegion cellPos = cellCenterToWorld(game.Workspace.Terrain, cellPos.X, cellPos.Y, cellPos.Z) if (cellPos.X + 2 > allowedStampRegion.CFrame.p.X + allowedStampRegion.Size.X / 2) or (cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X / 2) or (cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y / 2) or (cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y / 2) or (cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z / 2) or (cellPos.Z - 2 < allowedStampRegion.CFrame.p.Z - allowedStampRegion.Size.Z / 2) canSetCell = false return canSetCell false ResolveMegaClusterStamp = (checkHighScalabilityStamp) -> cellSet = false cluster = game.Workspace.Terrain line = HighScalabilityLine.InternalLine cMax = game.Workspace.Terrain.MaxExtents.Max cMin = game.Workspace.Terrain.MaxExtents.Min clusterMaterial = 1 -- default is grass clusterType = 0 -- default is brick clusterOrientation = 0 -- default is 0 rotation autoWedgeClusterParts = false if stampData.CurrentParts\FindFirstChild "AutoWedge" autoWedgeClusterParts = true if stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) clusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) if clusterMaterial\IsA "Vector3Value" clusterType = clusterMaterial.Value.Y clusterOrientation = clusterMaterial.Value.Z clusterMaterial = clusterMaterial.Value.X elseif clusterMaterial\IsA "IntValue" clusterMaterial = clusterMaterial.Value if HighScalabilityLine.Adorn.Parent and HighScalabilityLine.Start and ((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0)) startCell = game.Workspace.Terrain\WorldToCell(HighScalabilityLine.Start) xInc = { 0, 0, 0 } yInc = { 0, 0, 0 } zInc = { 0, 0, 0 } incrementVect = { nil, nil, nil } stepVect = * Vector3.new 0, 0, 0 * Vector3.new 0, 0, 0 * Vector3.new 0, 0, 0 worldAxes = * Vector3.new 1, 0, 0 * Vector3.new 0, 1, 0 * Vector3.new 0, 0, 1 lines = {} if HighScalabilityLine.Dimensions > 1 table.insert(lines, HighScalabilityLine.MoreLines[1]) if line and line.magnitude > 0 table.insert(lines, line) if HighScalabilityLine.Dimensions > 2 table.insert(lines, HighScalabilityLine.MoreLines[2]) for i = 1, #lines lines[i] = Vector3.new( math.floor(lines[i].X + 0.5), math.floor(lines[i].Y + 0.5), math.floor(lines[i].Z + 0.5) ) -- round to integers if lines[i].X > 0 xInc[i] = 1 elseif lines[i].X < 0 xInc[i] = -1 if lines[i].Y > 0 yInc[i] = 1 elseif lines[i].Y < 0 yInc[i] = -1 if lines[i].Z > 0 zInc[i] = 1 elseif lines[i].Z < 0 zInc[i] = -1 incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i]) if incrementVect[i].magnitude < 0.9 incrementVect[i] = nil if not lines[2] lines[2] = Vector3.new 0, 0, 0 if not lines[3] lines[3] = Vector3.new 0, 0, 0 waterForceTag = stampData.CurrentParts\FindFirstChild("WaterForceTag", true) waterForceDirectionTag = stampData.CurrentParts\FindFirstChild("WaterForceDirectionTag", true) while stepVect[3].magnitude * 4 <= lines[3].magnitude outerStepVectIndex = 1 while outerStepVectIndex < 4 stepVect[2] = Vector3.new 0, 0, 0 while stepVect[2].magnitude * 4 <= lines[2].magnitude innerStepVectIndex = 1 while innerStepVectIndex < 4 stepVect[1] = Vector3.new 0, 0, 0 while stepVect[1].magnitude * 4 <= lines[1].magnitude stepVectSum = stepVect[1] + stepVect[2] + stepVect[3] cellPos = Vector3int16.new( startCell.X + stepVectSum.X, startCell.Y + stepVectSum.Y, startCell.Z + stepVectSum.Z ) 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 -- check if overlaps player or part okToStampTerrainBlock = checkTerrainBlockCollisions cellPos, checkHighScalabilityStamp if okToStampTerrainBlock if waterForceTag cluster\SetWaterCell( cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value] ) else cluster\SetCell( cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation ) cellSet = true -- auto-wedge it? if autoWedgeClusterParts game.Workspace.Terrain\AutowedgeCells( Region3int16.new( Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1), Vector3int16.new cellPos.x + 1, cellPos.y + 1, cellPos.z + 1 ) ) stepVect[1] = stepVect[1] + incrementVect[1] if incrementVect[2] while innerStepVectIndex < 4 and worldAxes[innerStepVectIndex]\Dot(incrementVect[2]) == 0 innerStepVectIndex += 1 if innerStepVectIndex < 4 stepVect[2] = stepVect[2] + worldAxes[innerStepVectIndex] * worldAxes[innerStepVectIndex]\Dot incrementVect[2] innerStepVectIndex += 1 else stepVect[2] = Vector3.new(1, 0, 0) innerStepVectIndex = 4 -- skip all remaining loops if stepVect[2].magnitude * 4 > lines[2].magnitude innerStepVectIndex = 4 if incrementVect[3] while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]\Dot(incrementVect[3]) == 0 outerStepVectIndex += 1 if outerStepVectIndex < 4 stepVect[3] = stepVect[3] + worldAxes[outerStepVectIndex] * worldAxes[outerStepVectIndex]\Dot incrementVect[3] outerStepVectIndex += 1 else -- skip all remaining loops stepVect[3] = Vector3.new 1, 0, 0 outerStepVectIndex = 4 if stepVect[3].magnitude * 4 > lines[3].magnitude outerStepVectIndex = 4 -- and also get rid of any HighScalabilityLine stuff if it's there HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil -- Mark for undo. if cellSet stampData.CurrentParts.Parent = nil try game\GetService"ChangeHistoryService"\SetWaypoint "StamperMulti" cellSet DoStamperMouseUp = (Mouse) -> if not Mouse error "Error: RbxStamper.DoStamperMouseUp: Mouse is nil" return false if not Mouse\IsA "Mouse" error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className, "should be of type Mouse") return false if not stampData.Dragger error "Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil" return false if not HighScalabilityLine return false local checkHighScalabilityStamp if stampInModel local canStamp isHSLPart = isMegaClusterPart! if isHSLPart and HighScalabilityLine and HighScalabilityLine.Start and HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 -- we have an HSL line, test later canStamp = true checkHighScalabilityStamp = true else canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion) if not canStamp if stampFailedFunc stampFailedFunc! return false -- if unstampable face, then don't let us stamp there! if unstampableSurface flashRedBox! return false -- recheck if we can stamp, as we just moved part canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion) if not canStamp if stampFailedFunc stampFailedFunc! return false -- Prevent part from being stamped on top of a player local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts) -- HotThoth's note: Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the -- value of configFound from the previous onStamperMouseMove call which moved the CurrentParts -- Shouldn't this be true when lastTargetCFrame has been set and false otherwise? configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData) if configFound and not HighScalabilityLine.Adorn.Parent if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) flashRedBox! return false blockingParts = game.Workspace\FindPartsInRegion3( Region3.new(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector), stampData.CurrentParts, 100 ) for b = 1, #blockingParts if isBlocker(blockingParts[b]) flashRedBox! return false alreadyPushedUp = {} -- if no blocking model below, then see if stamping on top of a character for b = 1, #blockingParts if blockingParts[b].Parent and not alreadyPushedUp[blockingParts[b].Parent] and blockingParts[b].Parent\FindFirstChild"Humanoid" and blockingParts[b].Parent\FindFirstChild"Humanoid"\IsA "Humanoid" --------------------------------------------------------------------------- blockingPersonTorso = blockingParts[b].Parent\FindFirstChild "Torso" alreadyPushedUp[blockingParts[b].Parent] = true if blockingPersonTorso -- 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) newY = maxBB.Y + 3 if spaceAboveCharacter blockingPersonTorso, newY, stampData blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new 0, newY - blockingPersonTorso.CFrame.p.Y, 0 else -- if no space, we just error flashRedBox! return false --------------------------------------------------------------------------- elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) -- if no config then only stamp if it's a real HSL! resetHighScalabilityLine! return false -- something will be stamped! so set the "StampedSomething" toggle to true if game\FindFirstChild "Players" if game.Players["LocalPlayer"] if game.Players.LocalPlayer["Character"] localChar = game.Players.LocalPlayer.Character stampTracker = localChar\FindFirstChild "StampTracker" if stampTracker and not stampTracker.Value stampTracker.Value = true -- if we drew a line of mega parts, stamp them out if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart! if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp -- kill the ghost part stampData.CurrentParts.Parent = nil return true -- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff] HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil cluster = game.Workspace.Terrain -- if target point is in cluster, just use cluster\SetCell if isMegaClusterPart! -- if targetCFrame is inside cluster, just set that cell to 1 and return --cellPos = cluster\WorldToCell(targetCFrame.p) local cellPos if stampData.CurrentParts\IsA "Model" cellPos = cluster\WorldToCell(stampData.CurrentParts\GetModelCFrame!.p) else cellPos = cluster\WorldToCell(stampData.CurrentParts.CFrame.p) cMax = game.Workspace.Terrain.MaxExtents.Max cMin = game.Workspace.Terrain.MaxExtents.Min if checkTerrainBlockCollisions(cellPos, false) clusterValues = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) waterForceTag = stampData.CurrentParts\FindFirstChild("WaterForceTag", true) waterForceDirectionTag = stampData.CurrentParts\FindFirstChild("WaterForceDirectionTag", true) 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 if waterForceTag cluster\SetWaterCell( cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value] ) elseif not clusterValues cluster\SetCell( cellPos.X, cellPos.Y, cellPos.Z, cellInfo.Material, cellInfo.clusterType, gInitial90DegreeRotations % 4 ) elseif clusterValues\IsA "Vector3Value" cluster\SetCell( cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value.X, clusterValues.Value.Y, clusterValues.Value.Z ) else cluster\SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value, 0, 0) autoWedgeClusterParts = false if stampData.CurrentParts\FindFirstChild "AutoWedge" autoWedgeClusterParts = true -- auto-wedge it if autoWedgeClusterParts game.Workspace.Terrain\AutowedgeCells( Region3int16.new( Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1), Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1) ) ) -- kill the ghost part stampData.CurrentParts.Parent = nil -- Mark for undo. It has to happen here or the selection display will come back also. try game\GetService"ChangeHistoryService"\SetWaypoint "StamperSingle" return true else -- you tried to stamp a HSL-single part where one does not belong! flashRedBox! return false getPlayer = -> if game\FindFirstChild "Players" if game.Players["LocalPlayer"] return game.Players.LocalPlayer return nil -- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool" if stampData.CurrentParts\IsA "Model" -- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X< manualWeldTable = {} manualWeldParentTable = {} saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable) stampData.CurrentParts\BreakJoints! stampData.CurrentParts\MakeJoints! restoreTheWelds(manualWeldTable, manualWeldParentTable) -- 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) playerIdTag = stampData.CurrentParts\FindFirstChild "PlayerIdTag" playerNameTag = stampData.CurrentParts\FindFirstChild "PlayerNameTag" if playerIdTag? tempPlayerValue = getPlayer! if tempPlayerValue? playerIdTag.Value = tempPlayerValue.userId if playerNameTag? if game\FindFirstChild "Players" and game.Players["LocalPlayer"] tempPlayerValue = game.Players.LocalPlayer if tempPlayerValue? playerNameTag.Value = tempPlayerValue.Name -- ...and tag all inserted models for subsequent origin identification -- if no RobloxModel tag already exists, then add it. if not stampData.CurrentParts\FindFirstChild"RobloxModel"? stringTag = Instance.new "BoolValue" stringTag.Name = "RobloxModel" stringTag.Parent = stampData.CurrentParts if not stampData.CurrentParts\FindFirstChild"RobloxStamper"? stringTag2 = Instance.new "BoolValue" stringTag2.Name = "RobloxStamper" stringTag2.Parent = stampData.CurrentParts else stampData.CurrentParts\BreakJoints! if not stampData.CurrentParts\FindFirstChild"RobloxStamper"? stringTag2 = Instance.new "BoolValue" stringTag2.Name = "RobloxStamper" stringTag2.Parent = stampData.CurrentParts -- make sure all the joints are activated before restoring anchor states if not createJoints game.JointsService\CreateJoinAfterMoveJoints! -- Restore the original properties for all parts being stamped for part, transparency in pairs stampData.TransparencyTable part.Transparency = transparency for part, archivable in pairs stampData.ArchivableTable part.Archivable = archivable for part, material in pairs stampData.MaterialTable part.Material = material for part, collide in pairs stampData.CanCollideTable part.CanCollide = collide for part, anchored in pairs stampData.AnchoredTable part.Anchored = anchored for decal, transparency in pairs stampData.DecalTransparencyTable decal.Transparency = transparency for part, surfaces in pairs stampData.SurfaceTypeTable loadSurfaceTypes(part, surfaces) if isMegaClusterPart! stampData.CurrentParts.Transparency = 0 -- re-enable all seats setSeatEnabledStatus(stampData.CurrentParts, true) stampData.TransparencyTable = nil stampData.ArchivableTable = nil stampData.MaterialTable = nil stampData.CanCollideTable = nil stampData.AnchoredTable = nil stampData.SurfaceTypeTable = nil -- ...and tag all inserted models for subsequent origin identification -- if no RobloxModel tag already exists, then add it. if not stampData.CurrentParts\FindFirstChild"RobloxModel"? stringTag = Instance.new "BoolValue" stringTag.Name = "RobloxModel" stringTag.Parent = stampData.CurrentParts -- and make sure we don't delete it, now that it's not a ghost part if ghostRemovalScript ghostRemovalScript.Parent = nil --Re-enable the scripts for _, script in pairs stampData.DisabledScripts script.Disabled = false --Now that they are all marked enabled, reinsert them into the world so they start running for _, script in pairs stampData.DisabledScripts oldParent = script.Parent script.Parent = nil script\Clone!.Parent = oldParent -- clear out more data stampData.DisabledScripts = nil stampData.Dragger = nil stampData.CurrentParts = nil try game\GetService"ChangeHistoryService"\SetWaypoint "StampedObject" true pauseStamper = -> for i = 1, #mouseCons do -- stop the mouse from doing anything mouseCons[i]\disconnect! mouseCons[i] = nil mouseCons = {} if stampData and stampData.CurrentParts -- remove our ghost part stampData.CurrentParts.Parent = nil stampData.CurrentParts\Remove! resetHighScalabilityLine! game.JointsService\ClearJoinAfterMoveJoints! prepareUnjoinableSurfaces = (modelCFrame, parts, whichSurface) -> 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! isPositive = 1 if whichSurface < 0 isPositive *= -1 whichSurface *= -1 surfaceNormal = isPositive * modelCFrame\vectorToWorldSpace(AXIS_VECTORS[whichSurface]) for i = 1, #parts currPart = parts[i] -- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable surfaceNormalInLocalCoords = currPart.CFrame\vectorToObjectSpace surfaceNormal if math.abs(surfaceNormalInLocalCoords.X) > math.abs surfaceNormalInLocalCoords.Y if math.abs(surfaceNormalInLocalCoords.X) > math.abs surfaceNormalInLocalCoords.Z if surfaceNormalInLocalCoords.X > 0 currPart.RightSurface = "Unjoinable" else currPart.LeftSurface = "Unjoinable" else if surfaceNormalInLocalCoords.Z > 0 currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" else if math.abs(surfaceNormalInLocalCoords.Y) > math.abs surfaceNormalInLocalCoords.Z if surfaceNormalInLocalCoords.Y > 0 currPart.TopSurface = "Unjoinable" else currPart.BottomSurface = "Unjoinable" else if surfaceNormalInLocalCoords.Z > 0 currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" resumeStamper = -> clone, parts = prepareModel modelToStamp if not clone or not parts return -- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable unjoinableTag = clone\FindFirstChild "UnjoinableFaces", true if unjoinableTag for unjoinableSurface in string.gmatch unjoinableTag.Value, "[^,]*" if tonumber unjoinableSurface if clone\IsA "Model" prepareUnjoinableSurfaces clone\GetModelCFrame!, parts, tonumber unjoinableSurface else prepareUnjoinableSurfaces clone.CFrame, parts, tonumber unjoinableSurface stampData.ErrorBox = errorBox clone.Parent = if stampInModel stampInModel else game.Workspace if clone\FindFirstChild "ClusterMaterial", true -- extract all info from vector clusterMaterial = clone\FindFirstChild "ClusterMaterial", true if clusterMaterial\IsA "Vector3Value" cellInfo.Material = clusterMaterial.Value.X cellInfo.clusterType = clusterMaterial.Value.Y cellInfo.clusterOrientation = clusterMaterial.Value.Z elseif clusterMaterial\IsA "IntValue" cellInfo.Material = clusterMaterial.Value try mouseTarget = Mouse.Target if mouseTarget and not mouseTarget.Parent\FindFirstChild"RobloxModel"? game.JointsService\SetJoinAfterMoveTarget mouseTarget else game.JointsService\SetJoinAfterMoveTarget nil game.JointsService\ShowPermissibleJoints! for _, object in pairs stampData.DisabledScripts if object.Name == "GhostRemovalScript" object.Parent = stampData.CurrentParts stampData.Dragger = Instance.new "Dragger" --Begin a movement by faking a MouseDown signal stampData.Dragger\MouseDown parts[1], Vector3.new 0, 0, 0, parts stampData.Dragger\MouseUp! DoStamperMouseMove Mouse table.insert( mouseCons, Mouse.Move\connect -> return if movingLock or stampUpLock movingLock = true DoStamperMouseMove Mouse movingLock = false ) table.insert( mouseCons, Mouse.Button1Down\connect -> DoStamperMouseDown Mouse ) table.insert( mouseCons, Mouse.Button1Up\connect -> stampUpLock = true while movingLock wait! stamped.Value = DoStamperMouseUp Mouse resetHighScalabilityLine! stampUpLock = false ) stamped.Value = false resetStamperState = (newModelToStamp) -> -- if we have a new model, swap it out if newModelToStamp if not newModelToStamp\IsA"Model" and not newModelToStamp\IsA "BasePart" error "resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!" modelToStamp = newModelToStamp -- first clear our state pauseStamper! -- now lets load in the new model resumeStamper! -- load the model initially resetStamperState! -- setup the control table we pass back to the user control.Stamped = stamped -- BoolValue that fires when user stamps control.Paused = false control.LoadNewModel = (newStampModel) -> -- allows us to specify a new stamper model to be used with this stamper if newStampModel and not newStampModel\IsA"Model" and not newStampModel\IsA "BasePart" error "Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!" return nil resetStamperState newStampModel control.ReloadModel = -> -- will automatically set stamper to get a new model of current model and start stamping with new model resetStamperState! control.Pause = -> -- temporarily stops stamping, use resume to start up again if not control.Paused pauseStamper! control.Paused = true else print "RbxStamper Warning: Tried to call Control.Pause! when already paused" control.Resume = -> -- resumes stamping, if currently paused if control.Paused resumeStamper! control.Paused = false else print "RbxStamper Warning: Tried to call Control.Resume! without Pausing First" control.ResetRotation = -> -- resets the model rotation so new models are at default orientation -- gInitial90DegreeRotations = 0 -- 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 -- High-Scalability and with the new model orientation setting methods (model\ResetOrientationToIdentity!) [HotThoth] control.Destroy = -> -- Stops current Stamp operation and destroys control construct for i = 1, #mouseCons mouseCons[i]\disconnect! mouseCons[i] = nil keyCon?\disconnect! game.JointsService\ClearJoinAfterMoveJoints! adorn?\Destroy! adornPart?\Destroy! errorBox?\Destroy! stampData?.Dragger?\Destroy! stampData?.CurrentParts?\Destroy! if control and control["Stamped"] control.Stamped\Destroy! control = nil control t.Help = (funcNameOrFunc) -> --input argument can be a string or a function. Should return a description (of arguments and expected side effects) switch funcNameOrFunc when "GetStampModel", t.GetStampModel "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" when "SetupStamperDragger", t.SetupStamperDragger "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." t