null nil true 0 0 0.349999994 1 0 0 0 1 0 0 0 1 StamperTool http://www.roblox.com/asset/?id=59102781 false -0.5 0.5 0 0 -0.5 0.5 4 0 194 0.0988349915 0.274166554 -0.195800781 0.995120585 -7.11792454e-005 0.0986662284 -1.0218595e-005 0.999999642 0.000824476127 -0.0986662582 -0.000821461435 0.995120227 true 0.5 0.300000012 -0.5 0.5 0 0 -0.5 0.5 0 0 true 256 Handle 0 -0.5 0.5 0 0 0 0 0 -0.5 0.5 3 0 0 0 0 0 1 1 1 0.400000006 1 2 2 http://www.roblox.com/asset/?id=42163552 5 Mesh 0 0 0 0.899999976 0.899999976 0.899999976 http://www.roblox.com/asset/?id=42163513 1 1 1 PlayerOwner null SavedState null 0 0 0 1 0 0 0 1 0 0 0 1 LuaGlobalVariables null 0 0 0 1 0 0 0 1 0 0 0 1 InsertAsset null AssetName AssetId 0 Image StampMode true Updated false SwitchLoaderToDialog false DialogType Stamp AssetImage nil ShowInvalidPlacement false ShowMaxedOut false SwitchMode false Mode Stamped false Moving false ShowAdminCategories false userIDs 1 userID 11744447 ReloadCurrentAsset false ReloadCurrentAsset false MouseClick false true BasePlateGuide local pointDirection = nil local tailLength = 2 -- 2 or 7 --local dSize = -.05 -- .01 or .005 local dSize = 0 -- because we want the targets to all be the same size (tail isn't larger anymore; just "ghost" effect) local dTime = .04 --.025 -- old targetLock pic (square): 48972746 local targetLockSize = .2 local arrowSize = .1 local arrowScreenProportionSize = .95 local arrowMoveFactor = .025 local targetMoveFactor = targetLockSize / 2 - .025 local halfDSize = dSize / 2 local r = game:service("RunService") local vChar = script.Parent if vChar == nil then script.Parent = nil end local vPlay = game.Players:GetPlayerFromCharacter(vChar) if vPlay == nil then script.Parent = nil end local arrowGui = vPlay.PlayerGui:FindFirstChild("GuideArrowGui") if arrowGui == nil then arrowGui = script.GuideArrowGui arrowGui.Parent = vPlay.PlayerGui else script.GuideArrowGui:remove() end local upArrow = arrowGui.UpArrow local downArrow = arrowGui.DownArrow local leftArrow = arrowGui.LeftArrow local rightArrow = arrowGui.RightArrow local targetLock = arrowGui.TargetLock local arrowList = {upArrow, downArrow, leftArrow, rightArrow, targetLock} local goAway = arrowGui.GoAwayButton goAway.Visible = true goAway.Active = true local keepShowing = true goAway.MouseButton1Click:connect(function () keepShowing = false end) local arrowHeightAugment = 0.0 local arrowWidthAugment = 0.0 local targetHeightAugment = 0.0 local targetWidthAugment = 0.0 local targetLockList = {targetLock} for i = 1, tailLength do table.insert(targetLockList, targetLock:Clone()) end targetLockList[1].Size = UDim2.new(targetLockSize, 0, targetLockSize, 0) for i = 1, #targetLockList-1 do targetLockList[i+1].Parent = arrowGui --targetLockList[i+1].Size = UDim2.new(targetLockSize-i*dSize, 0, targetLockSize-i*dSize, 0) end -- switching over to a "ghost-like" transparency effect, but this means we have to upload separate images :( -- if our tail is turned on, manually apply the ghosting to the next two images if tailLength > 0 then targetLockList[2].Image = "http://www.roblox.com/asset/?id=49324811" end if tailLength > 1 then targetLockList[3].Image = "http://www.roblox.com/asset/?id=49324846" end function onResize() -- force arrows into a square resolution xSize = arrowGui.AbsoluteSize.X ySize = arrowGui.AbsoluteSize.Y if xSize >= ySize then arrowHeightAugment = 0.0 -- calculate arrowWidthAugment and new arrow ratio here newArrowUDim = UDim2.new(ySize / xSize * arrowSize, 0, arrowSize, 0) rightArrow.Size = newArrowUDim leftArrow.Size = newArrowUDim upArrow.Size = newArrowUDim downArrow.Size = newArrowUDim for tli = 1, #targetLockList do targetLockList[tli].Size = UDim2.new(ySize / xSize * (targetLockSize-tli*dSize), 0, targetLockSize-tli*dSize, 0) end arrowWidthAugment = (xSize - ySize)*arrowSize*.5 / xSize else arrowWidthAugment = 0.0 -- calculate arrowHeightAugment here newArrowUDim = UDim2.new(arrowSize, 0, xSize / ySize * arrowSize, 0) rightArrow.Size = newArrowUDim leftArrow.Size = newArrowUDim upArrow.Size = newArrowUDim downArrow.Size = newArrowUDim for tli = 1, #targetLockList do targetLockList[tli].Size = UDim2.new(targetLockSize-tli*dSize, 0, xSize / ySize * (targetLockSize-tli*dSize), 0) end arrowHeightAugment = (ySize - xSize)*arrowSize*.5 / ySize end targetWidthAugment = targetLock.Size.X.Scale / 2 - .025 targetHeightAugment = targetLock.Size.Y.Scale / 2 - .025 end arrowGui.Changed:connect(onResize) -- dynamically resize the arrows on screen resize function findMyBasePlate() --if true then return game.Workspace.Base end -- for testing purposes local buildingAreas = game.Workspace.BuildingAreas:GetChildren() for i = 1, #buildingAreas do if buildingAreas[i].Player.Value == script.Parent.Name then return buildingAreas[i]:FindFirstChild("BasePlate") or buildingAreas[i].PlayerArea:FindFirstChild("BasePlate") end end end local myBase = findMyBasePlate() if myBase == nil then print("BasePlateGuide script error: no base plate found!") -- abort if no BasePlate found arrowGui:remove() script:remove() end function setVisible(whichArrow) for i = 1, #arrowList do if arrowList[i] == whichArrow then arrowList[i].Visible = true else arrowList[i].Visible = false end end end function truncate(number, augmentation) if number+augmentation < 0 then return 0 elseif number > (1 - arrowSize)+augmentation then return (1 - arrowSize)+2*augmentation -- furthest extent we want the arrows to reach is .95 - arrowSize else return number+augmentation end end function notInBasePlate(myPos, base) local corner1 = base.Position - base.Size/2 local corner2 = base.Position + base.Size/2 if myPos.X > corner1.X and myPos.Y > corner1.Y and myPos.Z > corner1.Z and myPos.X < corner2.X and myPos.Z < corner2.Z then -- we only care they're somewhere above baseplate return false else return true end end local lastTime = 0 local camera = game.Workspace.CurrentCamera while notInBasePlate(script.Parent.Torso.Position, myBase) and keepShowing do --pointDirection = (myBase.Position - script.Parent.Torso.Position) * Vector3.new(1, 0, 1) camFrame = camera.CoordinateFrame pointDirection = camFrame:vectorToObjectSpace(myBase.Position - camFrame.p) camZ = pointDirection.Z camX = pointDirection.X/math.abs(camZ) camY = pointDirection.Y/math.abs(camZ) --print(camX, camY, camZ) -- was .55 for 610 (Y) and 1.15 for 1286 (X), so seems like scaling factor is universally 1114 on both axes -- nvm: at 1662 x 666, we have ... and .55, so seems like .55 always for Y, then X is scaled according to its proportion to Y local xThreshold = arrowGui.AbsoluteSize.X / arrowGui.AbsoluteSize.Y * .55 local yThreshold = .55 local nCamX = camX / xThreshold local nCamY = camY / yThreshold if (math.abs(nCamX) > 1 or math.abs(nCamY) > 1) or (camZ > 0) then -- camZ test makes sure we only lock on if facing right direction for j = 2, #targetLockList do targetLockList[j].Visible = false end if math.abs(nCamX) > math.abs(nCamY) then --if nCamY > 1 then nCamY = 1 --elseif nCamY < -1 then nCamY = -1 end if camX > 0 then -- should go right rightArrow.Position = UDim2.new(arrowScreenProportionSize - arrowMoveFactor - .025 + 2*arrowWidthAugment, 0, truncate(arrowScreenProportionSize*(1-nCamY)/2 - arrowMoveFactor, arrowHeightAugment), 0) setVisible(rightArrow) else -- should go left leftArrow.Position = UDim2.new(0, 0, truncate(arrowScreenProportionSize*(1-nCamY)/2 - arrowMoveFactor, arrowHeightAugment), 0) setVisible(leftArrow) end else --if nCamX > 1 then nCamX = 1 --elseif nCamX < -1 then nCamX = -1 end if camY > 0 then -- should go up upArrow.Position = UDim2.new(truncate(arrowScreenProportionSize*(nCamX+1)/2 - arrowMoveFactor, arrowWidthAugment), 0, 0, 0) setVisible(upArrow) else -- should go down downArrow.Position = UDim2.new(truncate(arrowScreenProportionSize*(nCamX+1)/2 - arrowMoveFactor, arrowWidthAugment), 0, arrowScreenProportionSize - arrowMoveFactor -.025 + 2*arrowHeightAugment, 0) setVisible(downArrow) end end else -- locked on target --for j = 2, #targetLockList do --targetLockList[j].Visible = true --end --targetLock.Position = UDim2.new(.95*(nCamX+1)/2 - targetMoveFactor, 0, .95*(1-nCamY)/2 - targetMoveFactor, 0) targetLock.Position = UDim2.new(.95*(nCamX+1)/2 - targetWidthAugment, 0, .95*(1-nCamY)/2 - targetHeightAugment, 0) setVisible(targetLock) end --wait() -- update every dTime seconds -- ok to do it this way instead of a ring array, because even though it's less efficient, it's less efficient by only one or two commands [since only have 2 "tails"] local t = r.Stepped:wait() if t - lastTime > dTime then lastTime = t for j = #targetLockList, 2, -1 do targetLockList[j].Position = targetLockList[j-1].Position + UDim2.new(halfDSize, 0, halfDSize, 0) targetLockList[j].Visible = targetLockList[j-1].Visible end end end --[[setVisible(nil) for j = 2, #targetLockList do targetLockList[j].Visible = false end goAway.Visible = false goAway.Active = false ]]-- arrowGui:remove() script:remove() GuideArrowGui false 4288914085 1 4279970357 1 false false http://www.roblox.com/asset/?id=48972729 UpArrow 0 0 0 0 0.0500000007 0 0.0500000007 0 0 false 1 false 4288914085 1 4279970357 1 false false http://www.roblox.com/asset/?id=48972703 RightArrow 0 0 0 0 0.0500000007 0 0.0500000007 0 0 false 1 false 4288914085 1 4279970357 1 false false http://www.roblox.com/asset/?id=48972653 DownArrow 0 0 0 0 0.0500000007 0 0.0500000007 0 0 false 1 false 4288914085 1 4279970357 1 false false http://www.roblox.com/asset/?id=48972682 LeftArrow 0 0 0 0 0.0500000007 0 0.0500000007 0 0 false 1 false 4288914085 1 4279970357 1 false false http://www.roblox.com/asset/?id=49321779 TargetLock 0 0 0 0 0.0500000007 0 0.0500000007 0 0 false 1 true true 4278255360 0 4279970357 1 false false 0 0 false GoAwayButton 0 20 0 40 false 0 150 0 20 0 0 Hide Guide Arrows 4279970357 4278190080 1 0 false 2 1 true 1 StampGUI true 4288914085 1 4279970357 1 false false InsertPanel 0.200000003 2 0.100000001 24 0.600000024 -20 0.639999986 0 0 3 true 1 false 4288914085 1 4279970357 0 false false ItemsFrame 0.239999995 0 0.0850000009 0 0.540000021 0 0.800000012 0 0 0 true 1 false 4294112243 1 4279970357 0 false false InsertAssetButtonExample 0 128 0 64 0 64 0 64 0 0 false 1 true true 4282861383 0.100000001 4279970357 0 false false 0 0 false Button 0.0250000004 0 0.0250000004 0 false 0.949999988 0 0.949999988 0 0 2 4279970357 4278190080 1 0.100000001 false 2 1 true 1 false 4288914085 1 4279970357 1 false false ButtonImage 0 -8 0 -8 1 16 1 16 0 true 2 AssetId 209411115 AssetName Roof - Outer Corner false 4278190080 0 4279970357 0 false false http://www.roblox.com/asset?id=54140547 ConfigIcon 1 -23 1 -24 0 16 0 16 0 false 1 true true 4288914085 0 4279970357 1 false false 2 7 false CancelButton 1 -32 0 -2 false 0 34 0 34 0 1 4294967295 4278190080 1 0 false 2 1 true 1 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54135717 ImageLabel 0 -2 0 -2 0 16 0 16 0 true 1 true true 4288914085 0 4279970357 1 false false 2 5 false SelectSetButton 0 0 0 0 false 0.200000003 0 0.100000001 0 0 1 Select Set 4294967295 4278190080 1 0 false 2 1 false 1 false 4288914085 1 4279970357 0 false false PagingControls 0.239999995 0 0.899999976 0 0.540000021 0 0.100000001 0 0 0 true 1 true true 4288914085 0 4279970357 1 false false 2 7 false PreviousPageButton 0.5 -95 0.5 -20 false 0 60 0 40 0 2 4294967295 4278190080 1 0 false 2 1 true 1 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54138586 ImageLabel 0 8 0 -1 0 18 0 18 0 true 1 false 4284874854 1 4279970357 1 false false 2 7 PageText 0.5 -30 0.5 -20 0 60 0 40 0 1 / 3 4294967295 4278190080 1 0 false 2 1 true 1 true true 4288914085 0 4279970357 1 false false 2 7 false NextPageButton 0.5 35 0.5 -20 false 0 60 0 40 0 2 4294967295 4278190080 1 0 false 2 1 true 1 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54138563 ImageLabel 0 10 0 -1 0 18 0 18 0 true 1 false 4294901862 1 4279970357 0 false false Sets 0 0 0 5 0.230000004 0 1 -5 0 0 true 1 false 4294967295 0.699999988 4279970357 0 false false Line 1 -3 0.0599999987 0 0 3 0.899999976 0 0 0 true 1 false 4288914085 1 4279970357 1 false false 2 7 SetsHeader 0 0 0 0 0 47 0 24 0 Sets 4294967295 4278190080 1 0 false 0 0 true 1 false 4291559577 1 4279970357 1 false false SetsLists 0 0 0.0599999987 0 1 -6 0.939999998 0 0 0 true 1 true false 4294967295 1 4279970357 0 false false 1 6 false SetButtonExample 0 5 0 18 false 1 -5 0 18 0 0 House Decorations 4294967295 4278190080 1 0 true 0 1 false 1 false 4284874803 1 4279970357 1 false false ItemPreview 0.790000021 0 0.0850000009 0 0.209999993 0 0.899999976 0 0 0 true 1 false 4278190080 1 4279970357 0 false false LargePreview 0 0 0 0 1 0 0 170 0 true 3 false 4286578816 1 4279970357 1 false false TextPanel 0 0 0.449999988 0 1 0 0.550000012 0 0 0 true 1 false 4291624857 1 4279970357 0 false false 2 7 RolloverText 0 0 0 0 1 0 0 48 0 Window - Diagonal 4294112243 4278190080 1 0 true 0 0 true 1 false 4288914085 1 4279970357 1 false false ConfigureText 0 0 0 48 1 0 1 -48 0 0 false 1 false 4294901862 1 4279970357 0 false false 2 5 Configure 0 0 0 0 1 0 0 14 0 Configure: 4294112243 4278190080 1 0 true 0 0 true 1 false 4288914085 1 4279970357 0 false false 1 5 Configure1 0 0 0 14 1 0 0 14 0 Speed 4294112243 4278190080 1 0 true 0 0 true 1 false 4288914085 1 4279970357 0 false false 1 5 Configure2 0 0 0 28 1 0 0 14 0 Damage 4294112243 4278190080 1 0 true 0 0 true 1 false 4288914085 1 4279970357 0 false false 1 5 Configure3 0 0 0 42 1 0 0 14 0 Delay 4294112243 4278190080 1 0 true 0 0 true 1 true 4288914085 0 4279970357 1 false false StamperPanel 0.5 -175 1 -135 0 350 0 48 0 3 true 2 true true 4288914085 0 4279970357 1 false false 2 7 false MinimizeButton 1 -32 0 -2 false 0 34 0 34 0 1 4294967295 4278190080 1 0 false 2 1 true 3 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54932670 ImageLabel 0 -3 0 -4 0 16 0 20 0 true 4 false 4288914085 1 4279970357 1 false false StamperButtons 0 0 0 0 1 0 1 0 0 0 true 1 true 4288914085 1 4279970357 1 false false RecentFrame 0 103 0 -6 0 192 0 44 0 0 true 3 true true 4282861383 0.100000001 4279970357 0 false false 0 0 false Button1 0 0 0 -2 false 0 48 0 48 0 2 4294967295 4278190080 1 0.100000001 false 0 0 false 4 false 4288914085 1 4279970357 1 false false http://gametest.roblox.com/Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid=209434543 ButtonImage 0 -8 0 -8 1 16 1 16 0 true 5 false 4288914085 1 4279970357 1 false false 2 5 ShortcutText 0 -7 0 -8 0 7 0 12 0 F 4294967295 4278190080 1 0 false 2 1 true 8 true true 4282861383 0.100000001 4279970357 0 false false 0 0 false Button2 0 48 0 -2 false 0 48 0 48 0 2 4279970357 4278190080 1 0.100000001 false 2 1 false 4 false 4288914085 1 4279970357 1 false false http://gametest.roblox.com/Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid=209434543 ButtonImage 0 -8 0 -8 1 16 1 16 0 true 5 false 4288914085 1 4279970357 1 false false 2 5 ShortcutText 0 -7 0 -8 0 7 0 12 0 G 4294967295 4278190080 1 0 false 2 1 true 8 true true 4282861383 0.100000001 4279970357 0 false false 0 0 false Button3 0 95 0 -3 false 0 48 0 48 0 2 4279970357 4278190080 1 0.100000001 false 2 1 false 4 false 4288914085 1 4279970357 1 false false http://gametest.roblox.com/Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid=209434543 ButtonImage 0 -8 0 -8 1 16 1 16 0 true 5 false 4288914085 1 4279970357 1 false false 2 5 ShortcutText 0 -7 0 -8 0 7 0 12 0 H 4294967295 4278190080 1 0 false 2 1 true 8 true true 4282861383 0.100000001 4279970357 0 false false 0 0 false Button4 0 142 0 -3 false 0 48 0 48 0 2 4279970357 4278190080 1 0.100000001 false 2 1 false 4 false 4288914085 1 4279970357 1 false false http://gametest.roblox.com/Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid=209434543 ButtonImage 0 -8 0 -8 1 16 1 16 0 true 5 false 4288914085 1 4279970357 1 false false 2 5 ShortcutText 0 -7 0 -8 0 7 0 12 0 J 4294967295 4278190080 1 0 false 2 1 true 8 true true 4288914085 0 4279970357 1 false false 2 5 false CloneButton 0 0 0 -6 false 0 45 0 45 0 2 4294967295 4278190080 1 0 false 2 1 true 3 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=51641555 ImageLabel 0 -7 0 -7 1 14 1 14 0 true 4 false 4288914085 1 4279970357 1 false false 2 5 CloneShortcut 0 -7 0 -8 0 8 0 12 0 E 4294967295 4278190080 1 0 false 2 1 true 5 true true 4288914085 0 4279970357 1 false false 2 5 false PartsButton 0 45 0 -6 true 0 45 0 45 0 2 4294967295 4278190080 1 0 false 2 1 true 3 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54966682 ImageLabel 0 -7 0 -7 1 14 1 14 0 true 4 false 4288914085 1 4279970357 1 false false 2 5 PartsShortcut 0 -7 0 -8 0 8 0 12 0 Q 4294967295 4278190080 0 0 false 2 1 true 5 false 4288914085 0 4279970357 1 false false ClonePanel 0 0 0 -8 0 80 1 16 0 2 false 1 false 4288914085 1 4279970357 1 false false 2 5 ClonePanelText 0 -6 0 -8 1 12 1 16 0 Click on a part to clone it 4294967295 4278190080 1 0 true 2 1 false 1 true true 4288914085 0 4279970357 1 false false 2 7 false RestoreButton 0 -25 0 -20 false 0 50 0 25 0 1 4294967295 4278190080 1 0 false 2 1 false 3 false 4288914085 1 4279970357 0 false false http://www.roblox.com/asset?id=54933540 ImageLabel 0 -4 0 -4 1 8 1 8 0 true 4 false StampGuiController local stampGui = script.Parent local insertPanel = stampGui.InsertPanel local stamperPanel = stampGui.StamperPanel local itemPreview = insertPanel.ItemPreview function handleResize() wait() -- neccessary to insure heartbeat happened itemPreview.LargePreview.Size = UDim2.new(1,0,0,itemPreview.AbsoluteSize.X) itemPreview.LargePreview.Position = UDim2.new(0.5,-itemPreview.LargePreview.AbsoluteSize.X/2,0,0) itemPreview.TextPanel.Position = UDim2.new(0,0,0,itemPreview.LargePreview.AbsoluteSize.Y) itemPreview.TextPanel.Size = UDim2.new(1,0,0,itemPreview.AbsoluteSize.Y - itemPreview.LargePreview.AbsoluteSize.Y) end stampGui.Changed:connect(function(prop) if prop == "AbsoluteSize" then handleResize() end end) handleResize() false 4288914085 0 4279970357 1 false false LoadDialog 0.5 -175 0.5 -25 0 350 0 50 1 3 false 1 false 4288914085 1 4279970357 1 false false 2 8 LoadLabel 0 0 0 -8 0 155 0.5 36 0 Loading... 4294506744 4278190080 1 0 false 2 1 true 1 null 21 ErrorBox 0 false false GuiScript -- This script is responsible for working the gui -- basic functions function waitForChild(instance, name) while not instance:findFirstChild(name) do instance.ChildAdded:wait() end end function waitForProperty(instance, name) while not instance[name] do instance.Changed:wait() end end -- Locals local Tool = script.Parent waitForChild(Tool, "LuaGlobalVariables") local variables = Tool.LuaGlobalVariables waitForChild(variables, "InsertAsset") local insertAsset = variables.InsertAsset waitForChild(variables, "SwitchMode") local switchMode = variables.SwitchMode waitForChild(variables, "ShowAdminCategories") local showAdminCategories = variables.ShowAdminCategories waitForChild(variables, "ReloadCurrentAsset") waitForChild(variables,"userIDs") game:GetService("ContentProvider"):Preload("http://www.roblox.com/asset/?id=42163425") game:GetService("ContentProvider"):Preload("http://www.roblox.com/asset/?id=42563487") local BaseUrl = game:GetService("ContentProvider").BaseUrl local buttonHeight = 64 local buttonWidth = buttonHeight local waypointShowing = false local showingPartPicker = true local firstEquip = true local Window local Data local loading = false local startTime = 0 local SetCache = {} local lastEnter = nil -- For Restricting Stamper Tool local buildingPlate local partModel -- wait for all of our set ids to load local userIdsForStamperParts = variables.userIDs -- 7502714 This is UsabilityMan (on gametest) function giveNewUserId(id) local newUserId = Instance.new("IntValue") newUserId.Name = "userID" newUserId.Value = id newUserId.Parent = userIdsForStamperParts userIdsForStamperParts.Value = userIdsForStamperParts.Value + 1 end BaseUrl = BaseUrl:lower() local isRestricted = Instance.new("BoolValue") isRestricted.Name = "IsRestricted" isRestricted.Value = false function isLocalWTRB() waitForChild(game,"StarterGui") if game.StarterGui:FindFirstChild("VersionGui") then if game.StarterGui.VersionGui:FindFirstChild("VersionText", true) then return true end end return false end waitForProperty(game,"PlaceId") if game.PlaceId == 41324860 or isLocalWTRB() then isRestricted.Value = true end isRestricted.Parent = variables giveNewUserId(18881789) giveNewUserId(18881808) if not isRestricted.Value then giveNewUserId(18881829) giveNewUserId(18881853) giveNewUserId(18881866) else giveNewUserId(2409156) giveNewUserId(19238067) giveNewUserId(19238114) end while #userIdsForStamperParts:GetChildren() < userIdsForStamperParts.Value do userIdsForStamperParts.ChildAdded:wait() end userIdsForStamperParts = userIdsForStamperParts:GetChildren() local useAssetVersionId = false local LargeThumbnailUrl local SmallThumbnailUrl local InsertRows = 0 local InsertColumns = 0 local insertButtons = {} local CancelDuringLoad local prevPart = {AssetNameValue = "",AssetIdValue = 0 ,InsertFrameButtonImage = ""} local recentPartStack = {} -- Connection Managers local guiChangedCon = nil local cloneButtonCon = nil local partListClickCon = nil local itemFrameChangedCon = nil local setsNextPageCon = nil local setsPrevPageCon = nil local insertPanelCloseCon = nil local minimizeCon = nil local restoreCon = nil local setButtonCons = {} local insertButtonCons = {} local recentPartStackCons = {} local Mouse = nil local currSetPage = 1 local mode = 0 -- 0 = main dialog, 1 = stamper, 2 = eyedropper waitForChild(script.Parent,"StampGUI") local stamperGui = script.Parent.StampGUI waitForChild(stamperGui,"InsertPanel") waitForChild(stamperGui.InsertPanel, "CancelButton") local currStampGui = nil local maxRecentParts = 4 for i = 1, maxRecentParts do recentPartStack[i] = {AssetNameValue,AssetIdValue,InsertFrameButtonImage} recentPartStack[i].AssetNameValue = "" recentPartStack[i].AssetIdValue = "" recentPartStack[i].InsertFrameButtonImage = "" end ---------------------------------------------------------------------------------------- -- functions function showBaseplateGuideArrows() playerCharacter = Tool.Parent if playerCharacter:FindFirstChild("Humanoid") and not playerCharacter:FindFirstChild("BasePlateGuide") then newGuide = Tool.BasePlateGuide:Clone() newGuide.Parent = playerCharacter newGuide.Disabled = false end end function setAssetUrls() if useAssetVersionId then LargeThumbnailUrl = BaseUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid=" SmallThumbnailUrl = BaseUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&assetversionid=" else LargeThumbnailUrl = BaseUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&aid=" SmallThumbnailUrl = BaseUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&aid=" end end function signalSwitchMode(mode) switchMode.Mode.Value = mode switchMode.Value = true end function goToClone() cancelLoadingWindow() closeInsertPanel() cancelAssetPlacement() mode = 2 --signalStamperScript("",0,"","",false) signalSwitchMode("Clone") currStampGui.StamperPanel.StamperButtons.ClonePanel.Visible = true currStampGui.StamperPanel.StamperButtons.CloneButton.Selected = true if currStampGui.Parent ~= nil then currStampGui.StamperPanel.StamperButtons.ClonePanel:TweenPosition(UDim2.new(0, -88, 0, -8),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.35,true) delay(0.3,function() currStampGui.StamperPanel.StamperButtons.ClonePanel.ClonePanelText.Visible = true end) end end function goToStamp() mode = 1 cancelLoadingWindow() closeInsertPanel() closeClonePanel() end function goToInsertPanel() cancelLoadingWindow() closeClonePanel() pcall(function() currStampGui.InsertPanel.CancelButton.Modal = true end) currStampGui.StamperPanel.StamperButtons.PartsButton.Selected = true if not showingPartPicker then showingPartPicker = true if currStampGui.Parent ~= nil then currStampGui.InsertPanel:TweenPosition(UDim2.new(0.2, 2, 0.1, 24),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.35,true) end else closeInsertPanel() reloadCurrentAsset() end end function closeInsertPanel() pcall(function() currStampGui.InsertPanel.CancelButton.Modal = false end) currStampGui.StamperPanel.StamperButtons.PartsButton.Selected = false showingPartPicker = false if currStampGui.Parent ~= nil then currStampGui.InsertPanel:TweenPosition(UDim2.new(0.2, 2, 1, 24),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.35,true) end end function closeClonePanel() currStampGui.StamperPanel.StamperButtons.ClonePanel.ClonePanelText.Visible = false currStampGui.StamperPanel.StamperButtons.CloneButton.Selected = false if currStampGui.Parent ~= nil then currStampGui.StamperPanel.StamperButtons.ClonePanel:TweenPosition(UDim2.new(0, 0, 0, -8),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.35,true) delay(0.3,function() currStampGui.StamperPanel.StamperButtons.ClonePanel.Visible = false end) end end function cancelAssetPlacement() gInitial90DegreeRotations = 0 Data.Stamp.Cancelled = true Data.Stamp.Dragger = nil if Data.Stamp.Model then Data.Stamp.Model:Remove() Data.Stamp.Model = nil end if Data.Stamp.CurrentParts then for index, object in pairs(Data.Stamp.CurrentParts) do object:Remove() end Data.Stamp.CurrentParts = nil end if Mouse then Mouse.Icon ="rbxasset://textures\\ArrowCursor.png" end game.JointsService:ClearJoinAfterMoveJoints() end function hint(label) -- Pass in a string, it shows a top hint. (Replaces previous hint, if exists) _player = game.Players:GetPlayerFromCharacter(Tool.Parent) if(_player.PlayerGui:FindFirstChild("topHint")~=nil) then local topHint = _player.PlayerGui.topHint topHint.Add.Label.Value = label topHint.Add.Width.Value = 3 -- widest width topHint.Add.Time.Value = 5 topHint.Add.Disabled = true topHint.Add.Disabled = false end end function getPlayer() return game.Players:GetPlayerFromCharacter(script.Parent.Parent) end function getHumanoid() local player = game.Players:GetPlayerFromCharacter(script.Parent.Parent) if player then waitForProperty(player,"Character") waitForChild(player.Character,"Humanoid") return player.Character.Humanoid else return nil end end function buildSetButton(name, setId, setImageId, i, count) local button = currStampGui.InsertPanel.Sets.SetsLists.SetButtonExample:Clone() button.Text = name button.Name = "SetButton" button.Visible = true local setValue = Instance.new("IntValue") setValue.Name = "SetId" setValue.Value = setId setValue.Parent = button local setName = Instance.new("StringValue") setName.Name = "SetName" setName.Value = name setName.Parent = button return button end function previousSetPage() local newIndex = math.max(0, Data.Category[Data.CurrentCategory].Index - (InsertRows * InsertColumns)) setSetIndex(newIndex) end function nextSetPage() local newIndex = math.max(0, Data.Category[Data.CurrentCategory].Index + (InsertRows * InsertColumns)) setSetIndex(newIndex) end function setInsertButtonImageBehavior(insertFrame, visible, name, assetId) if visible then insertFrame.AssetName.Value = name insertFrame.AssetId.Value = assetId local newImageUrl = SmallThumbnailUrl .. assetId if newImageUrl ~= insertFrame.Button.ButtonImage.Image then delay(0,function() game:GetService("ContentProvider"):Preload(SmallThumbnailUrl .. assetId) insertFrame.Button.ButtonImage.Image = SmallThumbnailUrl .. assetId end) end table.insert(insertButtonCons, insertFrame.Button.MouseButton1Click:connect(function() beginInsertAssetGui(insertFrame.AssetName.Value, insertFrame.AssetId.Value, insertFrame.Button.ButtonImage.Image, 1) end) ) insertFrame.Visible = true else insertFrame.Visible = false end end function setSetIndex(dataOffset) Data.Category[Data.CurrentCategory].Index = dataOffset InsertRows = math.floor(currStampGui.InsertPanel.ItemsFrame.AbsoluteSize.Y/buttonHeight) InsertColumns = math.floor(currStampGui.InsertPanel.ItemsFrame.AbsoluteSize.X/buttonWidth) local PageSize = InsertRows * InsertColumns local contents = Data.Category[Data.CurrentCategory].Contents if contents then local numOfPages = math.ceil((#contents)/(PageSize)) local currPage = math.ceil((dataOffset/PageSize) + 1) currStampGui.InsertPanel.PagingControls.PageText.Text = tostring(currPage) .. "/" .. tostring(numOfPages) currStampGui.InsertPanel.PagingControls.PageText.Visible = (numOfPages > 1) currStampGui.InsertPanel.PagingControls.PreviousPageButton.Visible = (numOfPages > 1) and dataOffset > 1 currStampGui.InsertPanel.PagingControls.NextPageButton.Visible = (numOfPages > 1) and ((dataOffset - 1) + PageSize) < (#contents) -- remove our buttons and their connections for i = 1, #insertButtons do insertButtons[i]:remove() end for i = 1, #insertButtonCons do pcall(function() insertButtonCons[i]:disconnect() end) end insertButtonCons = {} insertButtons = {} local arrayPosition = 1 for y = 1, InsertRows do for x = 1, InsertColumns do local buttonPosition = UDim2.new(0,(buttonWidth)*(x-1),0, (buttonHeight)*(y-1)) local buttonCon insertButtons[arrayPosition], buttonCon = buildInsertButton(buttonPosition) table.insert(insertButtonCons,buttonCon) insertButtons[arrayPosition].Parent = currStampGui.InsertPanel.ItemsFrame arrayPosition = arrayPosition + 1 end end Data.InsertButtons = insertButtons for index = 1, PageSize do if insertButtons[index] then if contents[index + dataOffset] then local assetId if useAssetVersionId then assetId = contents[index + dataOffset].AssetVersionId else assetId = contents[index + dataOffset].AssetId end setInsertButtonImageBehavior(insertButtons[index], true, contents[index + dataOffset].Name, assetId) else setInsertButtonImageBehavior(insertButtons[index], false) end end end else currStampGui.InsertPanel.PagingControls.PreviousPageButton.Visible = false currStampGui.InsertPanel.PagingControls.NextPageButton.Visible = false end end function moveLoadingLeft() if (tick() - startTime > 5) and (currStampGui.Parent ~= nil) then loading = false cancelAssetPlacement() goToInsertPanel() end if loading then currStampGui.LoadDialog.LoadLabel:TweenPosition(UDim2.new(0, 0, 0, -8),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.7,true,function() moveLoadingRight() end) end end function moveLoadingRight() if (tick() - startTime > 5) and (currStampGui.Parent ~= nil) then loading = false cancelAssetPlacement() goToInsertPanel() end if loading then currStampGui.LoadDialog.LoadLabel:TweenPosition(UDim2.new(0, 180, 0, -8),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.7,true,function() moveLoadingLeft() end) end end function moveLoadingWindow() startTime = tick() currStampGui.LoadDialog.Visible = true loading = true moveLoadingRight() end function cancelLoadingWindow() currStampGui.LoadDialog.Visible = false loading = false end function selectCategoryPage(buttons, page) if buttons ~= Data.CurrentCategory then if Data.CurrentCategory then for key, button in pairs(Data.CurrentCategory) do button.Visible = false end end Data.CurrentCategory = buttons if Data.Category[Data.CurrentCategory] == nil then Data.Category[Data.CurrentCategory] = {} if #buttons > 0 then selectSet(buttons[1], buttons[1].SetName.Value, buttons[1].SetId.Value, 0) end else Data.Category[Data.CurrentCategory].Button = nil selectSet(Data.Category[Data.CurrentCategory].ButtonFrame, Data.Category[Data.CurrentCategory].SetName, Data.Category[Data.CurrentCategory].SetId, Data.Category[Data.CurrentCategory].Index) end if Data.Main.FrameHeight then if Data.Category[Data.CurrentCategory].SetIndex then layoutSetButtons(Data.Main.FrameHeight, Data.Category[Data.CurrentCategory].SetIndex) else layoutSetButtons(Data.Main.FrameHeight, 1) end end end end function selectSet(button, setName, setId, setIndex) if button and Data.Category[Data.CurrentCategory] ~= nil then if button ~= Data.Category[Data.CurrentCategory].Button then Data.Category[Data.CurrentCategory].Button = button if SetCache[setId] == nil then SetCache[setId] = game:GetService("InsertService"):GetCollection(setId) end Data.Category[Data.CurrentCategory].Contents = SetCache[setId] Data.Category[Data.CurrentCategory].SetName = setName Data.Category[Data.CurrentCategory].SetId = setId end setSetIndex(setIndex) end end function selectCategory(button, category) selectCategoryPage(category, 0) end function processCategory(sets, setPanel) local setButtons = {} local numSkipped = 0 for index, object in pairs(sets) do if not showAdminCategories.Value and object.Name == "Beta" then -- skip this if not an admin numSkipped = numSkipped + 1 else setButtons[index - numSkipped] = buildSetButton(object.Name, object.CategoryId, object.ImageAssetId, index - numSkipped, #sets) setButtons[index - numSkipped].Parent = setPanel end end return setButtons end function setsNextPageClick(totalSetPages, gridSize) -- set our logic/gui correctly if currSetPage >= totalSetPages then return end currSetPage = currSetPage + 1 currStampGui.InsertPanel.PagingControls.PageText.Text = tostring(currSetPage) .. "/" .. tostring(totalSetPages) if currSetPage == totalSetPages then currStampGui.InsertPanel.PagingControls.NextPageButton.Visible = false currStampGui.InsertPanel.PagingControls.PreviousPageButton.Visible = true else Window.Sets.PagingControls.NextPageButton.Visible = true end -- actually update the items makeCurrentSetsPageVisible(gridSize) end function setsPrevPageClick(totalSetPages, gridSize) -- set our logic/gui correctly if currSetPage <= 1 then return end currSetPage = currSetPage - 1 currStampGui.InsertPanel.PagingControls.PageText.Text = tostring(currSetPage) .. "/" .. tostring(totalSetPages) if currSetPage == 1 then currStampGui.InsertPanel.PagingControls.NextPageButton.Visible = true currStampGui.InsertPanel.PagingControls.PreviousPageButton.Visible = false else currStampGui.InsertPanel.PagingControls.PreviousPageButton.Visible = true end -- actually update the items makeCurrentSetsPageVisible(gridSize) end function resetAllSetButtonSelection() local setButtons = Window.Sets.SetsLists:GetChildren() for i = 1, #setButtons do setButtons[i].Selected = false setButtons[i].BackgroundTransparency = 1 setButtons[i].TextColor3 = Color3.new(1,1,1) setButtons[i].BackgroundColor3 = Color3.new(1,1,1) end end function populateSetsFrame() local categories = #Data.UserCategoryButtons local robloxMaxCat = categories -- don't do anything until window is visible (otherwise we won't layout anything!) while Window.Sets.SetsLists.AbsoluteSize.X <= 0 do Window.Sets.SetsLists.Changed:wait() end while Window.Sets.SetsLists.SetButtonExample.AbsoluteSize.X <= 0 do Window.Sets.SetsLists.SetButtonExample.Changed:wait() end local totalColumns = math.floor(Window.Sets.SetsLists.AbsoluteSize.X/Window.Sets.SetsLists.SetButtonExample.AbsoluteSize.X) local totalRows = math.floor(Window.Sets.SetsLists.AbsoluteSize.Y/Window.Sets.SetsLists.SetButtonExample.AbsoluteSize.Y) local currRow = 0 local buttonVisible = true local masterCategory = 1 for i = 1, categories do local userCategory = masterCategory -- needed to maintain local scope for categories in event listeners below local button = Window.Sets.SetsLists.SetButtonExample:clone() button.Name = "Set" .. tostring(Data.UserCategoryButtons[userCategory].SetName.Value) .. "Button" button.Parent = Window.Sets.SetsLists button.Position = UDim2.new(0,5,0,currRow * button.AbsoluteSize.Y) button.Visible = buttonVisible button.Text = tostring(Data.UserCategoryButtons[userCategory].SetName.Value) if i == 1 then button.Selected = true button.BackgroundColor3 = Color3.new(0,204/255,0) button.TextColor3 = Color3.new(0,0,0) button.BackgroundTransparency = 0 end button.MouseEnter:connect(function() if not button.Selected then button.BackgroundTransparency = 0 button.TextColor3 = Color3.new(0,0,0) end end) button.MouseLeave:connect(function() if not button.Selected then button.BackgroundTransparency = 1 button.TextColor3 = Color3.new(1,1,1) end end) setButtonCons[i] = button.MouseButton1Click:connect(function() resetAllSetButtonSelection() button.Selected = not button.Selected button.BackgroundColor3 = Color3.new(0,204/255,0) button.TextColor3 = Color3.new(0,0,0) button.BackgroundTransparency = 0 selectSet(button, button.Text, Data.UserCategoryButtons[userCategory].SetId.Value, 0) end) masterCategory = masterCategory + 1 currRow = currRow + 1 end -- don't use example button as the first set! local example = currStampGui.InsertPanel.Sets.SetsLists.SetButtonExample currStampGui.InsertPanel.Sets.SetsLists.SetButtonExample.Parent = nil local buttons = currStampGui.InsertPanel.Sets.SetsLists:GetChildren() example.Parent = currStampGui.InsertPanel.Sets.SetsLists -- set first category as loaded for default selectSet(buttons[1], buttons[1].Text, Data.UserCategoryButtons[1].SetId.Value, 0) selectCategory(buttons[1], Data.UserCategoryButtons) end function layoutSetButtons(frameHeight, setIndex) Data.Main.FrameHeight = frameHeight Data.Main.InsertSets = math.floor(frameHeight / (height*2)) if #Data.CurrentCategory > Data.Main.InsertSets then --Steal one entry since we have too many things Data.Main.InsertSets = Data.Main.InsertSets - 1 end Data.Category[Data.CurrentCategory].SetIndex = setIndex end function showLargePreview(insertButton) if insertButton:FindFirstChild("AssetId") then delay(0,function() game:GetService("ContentProvider"):Preload(LargeThumbnailUrl .. tostring(insertButton.AssetId.Value)) currStampGui.InsertPanel.ItemPreview.LargePreview.Image = LargeThumbnailUrl .. tostring(insertButton.AssetId.Value) end) end if insertButton:FindFirstChild("AssetName") then currStampGui.InsertPanel.ItemPreview.TextPanel.RolloverText.Text = insertButton.AssetName.Value end end function buildInsertButton(buttonPosition) local insertButton = currStampGui.InsertPanel.ItemsFrame.InsertAssetButtonExample:clone() insertButton.Position = buttonPosition insertButton.Name = "InsertAssetButton" insertButton.Visible = true local mouseEnterCon = insertButton.MouseEnter:connect(function() lastEnter = insertButton delay(0.1,function() if lastEnter == insertButton then showLargePreview(insertButton) end end) end) return insertButton, mouseEnterCon end function minimizeStamperPanel() currStampGui.StamperPanel.StamperButtons.Visible = false currStampGui.StamperPanel.MinimizeButton.Visible = false if currStampGui.Parent ~= nil then currStampGui.StamperPanel:TweenSizeAndPosition(UDim2.new(0,0,0,0), UDim2.new(0.5,0,1,-92),Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.5,true) delay(0.5,function() currStampGui.StamperPanel.RestoreButton.Visible = true end) end end function restoreStamperPanel() currStampGui.StamperPanel.RestoreButton.Visible = false if currStampGui.Parent ~= nil then currStampGui.StamperPanel:TweenSizeAndPosition(UDim2.new(0,350,0,48), UDim2.new(0.5,-175,1,-135), Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,0.5,true) delay(0.5,function() currStampGui.StamperPanel.StamperButtons.Visible = true currStampGui.StamperPanel.MinimizeButton.Visible = true end) end end function setUpStamperGui() pcall(function() currStampGui.InsertPanel.CancelButton.Modal = true end) Window.Sets = currStampGui.InsertPanel.Sets cloneButtonCon = currStampGui.StamperPanel.StamperButtons.CloneButton.MouseButton1Click:connect(goToClone) partListClickCon = currStampGui.StamperPanel.StamperButtons.PartsButton.MouseButton1Click:connect(goToInsertPanel) Data.Main = {} Data.Category = {} Data.Stamp = {} Data.BaseCategoryButtons = {} local userData = {} for id = 1, #userIdsForStamperParts do local newUserData = game:GetService("InsertService"):GetUserCategories(userIdsForStamperParts[id].Value) if newUserData and #newUserData > 2 then -- start at #3 to skip over My Decals and My Models for each account for category = 3, #newUserData do if newUserData[category].Name == "High Scalability" then table.insert(userData,1,newUserData[category]) else table.insert(userData, newUserData[category]) end end end end if userData then Data.UserCategoryButtons = processCategory(userData, setPanel) end guiChangedCon = currStampGui.Changed:connect(function(prop) if prop == "AbsoluteSize" then wait() setSetIndex(0) end end) InsertRows = math.floor(currStampGui.InsertPanel.ItemsFrame.AbsoluteSize.Y/buttonHeight) InsertColumns = math.floor(currStampGui.InsertPanel.ItemsFrame.AbsoluteSize.X/buttonWidth) populateSetsFrame() setsPrevPageCon = currStampGui.InsertPanel.PagingControls.PreviousPageButton.MouseButton1Click:connect(function() previousSetPage() end) setsNextPageCon = currStampGui.InsertPanel.PagingControls.NextPageButton.MouseButton1Click:connect(function() nextSetPage() end) insertPanelCloseCon = currStampGui.InsertPanel.CancelButton.MouseButton1Click:connect(function() closeInsertPanel() closeClonePanel() reloadCurrentAsset() end) minimizeCon = currStampGui.StamperPanel.MinimizeButton.MouseButton1Click:connect(function() minimizeStamperPanel() end) restoreCon = currStampGui.StamperPanel.RestoreButton.MouseButton1Click:connect(function() restoreStamperPanel() end) end -- signal to scripts we are ready to start manipulating objects function signalStamperScript(assetName, assetId, image, stampMode) insertAsset.AssetName.Value = assetName insertAsset.AssetId.Value = assetId insertAsset.Image.Value = image insertAsset.StampMode.Value = stampMode insertAsset.Updated.Value = true end function reloadCurrentAsset() variables.ReloadCurrentAsset.Value = true end function beginInsertAssetGui(assetName, assetId, image, stampMode) Data.Stamp.StampMode = stampMode closeInsertPanel() signalStamperScript("",0,"","",false) moveLoadingWindow() cancelAssetPlacement() signalStamperScript(assetName, assetId, image, stampMode) end function cancelAssetLoad() Data.Loading.Cancelled = true insertComplete() gInitial90DegreeRotations = 0 end function inBounds2(part) -- part must have a position property local xOne= buildingPlate.Position.x + buildingPlate.Size.x/2 local xTwo = buildingPlate.Position.x - buildingPlate.Size.x/2 local zOne = buildingPlate.Position.z + buildingPlate.Size.z/2 local zTwo = buildingPlate.Position.z - buildingPlate.Size.z/2 if part.Position.x > xOne or part.Position.x < xTwo then return false end if part.Position.z > zOne or part.Position.z < zTwo then return false end return true end -- For Restricting Stamper Tool (isRestricted) function showHelp_pointToBuildingplate() if(buildingPlate==nil) then hint("All building areas are taken. If you want to build, leave and join again.") else -- only show one waypoint at a time (because kids will click a million times outside their plate) hint("Stamper Tool only works in your area.") if(not waypointShowing) then waypointShowing = true local _character = Tool.Parent waitForChild(_character, "Torso") _player = game.Players:GetPlayerFromCharacter(_character) _player.PlayerGui.showBaseplateWaypoint.target.Value = buildingPlate _player.PlayerGui.showBaseplateWaypoint.Disabled = true _player.PlayerGui.showBaseplateWaypoint.Disabled = false -- Wait until character moves in bounds (check every 2 seconds) while(not inBounds2(_character.Torso) and isEquipped) do wait(2) end -- Then hide the waypoint hideHelp_pointToBuildingplate() end end end function showHelp_tooManyParts() hint("You have reached maximum number of parts! Delete some to put more down.") end function hideHelp_pointToBuildingplate() waypointShowing = false _player.PlayerGui.hideBaseplateWaypoint.Disabled = true _player.PlayerGui.hideBaseplateWaypoint.Disabled = false end function setUpRestrictions() playerModel = game.Workspace.ActiveParts:FindFirstChild(player.Name .. "'s parts") local takenAreas = game.Workspace.BuildingAreas:GetChildren() waitForChild(player, "playerNumber") if(player.playerNumber.Value == 0) then buildingPlate = nil partModel = nil else waitForChild(game.Workspace, "BuildingAreas") local buildingAreas = game.Workspace.BuildingAreas waitForChild(buildingAreas, "Area"..tostring(player.playerNumber.Value)) local targetArea = buildingAreas:FindFirstChild("Area"..tostring(player.playerNumber.Value)) waitForChild(targetArea, "PlayerArea") waitForChild(targetArea.PlayerArea, "BasePlate") buildingPlate = targetArea.PlayerArea.BasePlate partModel = targetArea.PlayerArea end -- Check if player is standing in bounds, if not show error local _character = Tool.Parent waitForChild(_character, "Torso") _player = game.Players:GetPlayerFromCharacter(_character) if(buildingPlate~=nil) then if(not inBounds2(_character.Torso)) then showHelp_pointToBuildingplate() end else -- You have no building plate. hint("All building areas are taken. If you want to build, leave and join again.") end end function onInsertKeyDown(key) if loading then return end -- don't try to switch while we're loading key = string.lower(key) -- go to mru buttons if key == 'f' then mruButtonClick(1) elseif key == 'g' then mruButtonClick(2) elseif key == 'h' then mruButtonClick(3) elseif key == 'j' then mruButtonClick(4) end end function onEquippedLocal(mouse) player = getPlayer() if not player then return end if Tool.PlayerOwner.Value and Tool.PlayerOwner.Value ~= player then return end -- For Restricting Stamper Tool if isRestricted.Value then setUpRestrictions() end Mouse = mouse if not firstEquip and currStampGui and Tool.SavedState.Value and Tool.PlayerOwner.Value == getPlayer() and Data and Data.FullyLoaded then currStampGui.Parent = getPlayer().PlayerGui if mode == 1 then -- if we were stamping, keep going -- if we signal a negative asset, then that means keep going using whatever was in recent memory [don't reload from insert service] signalStamperScript(insertAsset.AssetName.Value, -1, insertAsset.Image.Value, true) elseif mode == 2 then -- time to clone goToClone() end else if firstEquip then Tool.PlayerOwner.Value = player firstEquip = false end CancelDuringLoad = false resetCons() Data = {} Data.FullyLoaded = false Window = {} Window.Sets = {} currStampGui = stamperGui:clone() currStampGui.Parent = getPlayer().PlayerGui wait() setUpStamperGui() if not(CancelDuringLoad) then currStampGui.Parent = getPlayer().PlayerGui Tool.SavedState.Value = currStampGui end Data.FullyLoaded = true end Mouse.KeyDown:connect(onInsertKeyDown) end function onUnequippedLocal() if currStampGui then Tool.SavedState.Value = currStampGui currStampGui.Parent = nil end pcall(function() cancelAssetPlacement() Data.Loading.Cancelled = true end) CancelDuringLoad = true end function killConnection(connection) if connection then connection:disconnect() end end function resetCons() killConnection(guiChangedCon) killConnection(cloneButtonCon) killConnection(partListClickCon) killConnection(itemFrameChangedCon) killConnection(setsPrevPageCon) killConnection(setsNextPageCon) killConnection(insertPanelCloseCon) killConnection(minimizeCon) killConnection(restoreCon) end function onAncestryChanged(child,parent) if Tool.PlayerOwner.Value and not Tool:IsDescendantOf(Tool.PlayerOwner.Value) and not Tool:IsDescendantOf(Tool.PlayerOwner.Value.Character) then --Tool was dropped in some way, so we need to nuke our external state Tool.SavedState.Value = nil resetCons() end end function getMaxNumOfRecentParts() return maxRecentParts end function pushRecentStackBack() for i = getMaxNumOfRecentParts() - 1, 1, -1 do recentPartStack[i + 1].AssetNameValue = recentPartStack[i].AssetNameValue recentPartStack[i + 1].AssetIdValue = recentPartStack[i].AssetIdValue recentPartStack[i + 1].InsertFrameButtonImage = recentPartStack[i].InsertFrameButtonImage end end function clearMRUList() local buttons = currStampGui.StamperPanel.StamperButtons.RecentFrame:GetChildren() for i = 1, #buttons do if recentPartStackCons[i] then recentPartStackCons[i]:disconnect() end buttons[i].Visible = false end end function mruButtonClick(position) if recentPartStack[position].AssetIdValue == insertAsset.AssetId.Value then return end -- already stamping part -- get rid of old part signalStamperScript("",0,"","",false) --Show the dialog window moveLoadingWindow() signalStamperScript(recentPartStack[position].AssetNameValue, recentPartStack[position].AssetIdValue, recentPartStack[position].InsertFrameButtonImage, true) end function insertMRUButton(position) local mruButton = currStampGui.StamperPanel.StamperButtons.RecentFrame:FindFirstChild("Button" .. tostring(position)) mruButton.ButtonImage.Image = recentPartStack[position].InsertFrameButtonImage if mruButton.ButtonImage.Image == "" then mruButton.Text = recentPartStack[position].AssetNameValue end if recentPartStackCons[position] then pcall(function() recentPartStackCons[position]:Disconnect() end) end recentPartStackCons[position] = mruButton.MouseButton1Click:connect(function() mruButtonClick(position) end) mruButton.Visible = true end function refreshRecentParts() if insertAsset.Image.Value == "" then return end -- we don't have an asset, get out of here if not recentPartStack then return end -- if somehow not created yet (recentPartStack[i] calls below were nil somehow) local numOfRecentParts = getMaxNumOfRecentParts() for i = 1,numOfRecentParts do if insertAsset.AssetId and recentPartStack[i] and insertAsset.AssetId.Value == recentPartStack[i].AssetIdValue then -- already have part, don't push back return end end pushRecentStackBack() -- update our stack to show previously allocated part recentPartStack[1].AssetNameValue = insertAsset.AssetName.Value recentPartStack[1].AssetIdValue = insertAsset.AssetId.Value recentPartStack[1].InsertFrameButtonImage = insertAsset.Image.Value clearMRUList() for i = 1, numOfRecentParts do if recentPartStack[i] and recentPartStack[i].AssetIdValue ~= "" then insertMRUButton(i) end end end function updateGui(type) if type == "Main" then goToInsertPanel() elseif type == "EyeDropper" then goToClone() elseif type == "SideDialog" then goToStamp() end end ---------------------------------------------------------------------------------------- -- Lua Start Execution setAssetUrls() Tool.Equipped:connect(onEquippedLocal) Tool.Unequipped:connect(onUnequippedLocal) Tool.AncestryChanged:connect(onAncestryChanged) waitForChild(variables, "SwitchLoaderToDialog") variables.SwitchLoaderToDialog.Changed:connect(function(prop) if variables.SwitchLoaderToDialog.Value == true then updateGui(variables.SwitchLoaderToDialog.DialogType.Value) variables.SwitchLoaderToDialog.Value = false end end) waitForChild(variables, "ShowInvalidPlacement") variables.ShowInvalidPlacement.Changed:connect(function(prop) if variables.ShowInvalidPlacement.Value == true then showHelp_pointToBuildingplate() showBaseplateGuideArrows() variables.ShowInvalidPlacement.Value = false end end) waitForChild(variables, "ShowMaxedOut") variables.ShowMaxedOut.Changed:connect(function(prop) if variables.ShowMaxedOut.Value == true then showHelp_tooManyParts() variables.ShowMaxedOut.Value = false end end) waitForChild(variables, "Stamped") variables.Stamped.Changed:connect(function() if variables.Stamped.Value == true then refreshRecentParts() end end) ---------------------------------------------------------------------------------------- true Readme --[[ Stamper Tool v1.0.0 Welcome to the stamper tool readme! Currently, this doc only exists to show the Stamper Tool Version! ]] false StampScript -- basic functions function waitForChild(instance, name) while not instance:findFirstChild(name) do instance.ChildAdded:wait() end end ---------------------------------------------------------------------------------------- -- Locals local Tool = script.Parent local Mouse local mouseMoveCon local mouseButton1DownCon local mouseButton1UpCon local cameraChangeCon local walking = false local pressedEsc = false local billBoardOwnerGui = nil local cluster = game.Workspace:FindFirstChild("Terrain") local gInitial90DegreeRotations = 0 local gStaticTrans = 1 local gDesiredTrans = 0.7 local transFadeInTime = 0.5 local fadeInDelayTime = 0.5 local eyedropperOffGridTolerance = 0.01 local insertBoundingBoxOverlapVector = Vector3.new(1, 1, 1) -- we can still stamp if our character extrudes into the target stamping space by 1 or fewer units local useAssetVersionId = false -- for high-scalability display local adornPart = Instance.new("Part") adornPart.Parent = nil adornPart.formFactor = "Custom" adornPart.Size = Vector3.new(4, 4, 4) adornPart.CFrame = CFrame.new() local adorn = Instance.new("SelectionBox") adorn.Color = BrickColor.new("Toothpaste") adorn.Adornee = adornPart adorn.Visible = true adorn.Transparency = 0 adorn.Name = "HighScalabilityStamperLine" adorn.Parent = nil local terrainSelectionBox = Instance.new("Part") terrainSelectionBox.Parent = nil terrainSelectionBox.formFactor = "Custom" terrainSelectionBox.Size = Vector3.new(4, 4, 4) terrainSelectionBox.CFrame = CFrame.new() local HighScalabilityLine = {} HighScalabilityLine.Start = nil HighScalabilityLine.End = nil HighScalabilityLine.Adorn = adorn HighScalabilityLine.AdornPart = adornPart HighScalabilityLine.InternalLine = nil HighScalabilityLine.NewHint = true -- for higher dimensional megacluster part stamping HighScalabilityLine.MorePoints = {nil, nil} HighScalabilityLine.MoreLines = {nil, nil} HighScalabilityLine.Dimensions = 1 waitForChild(Tool,"LuaGlobalVariables") local variables = Tool.LuaGlobalVariables waitForChild(variables,"ShowInvalidPlacement") waitForChild(variables, "Stamped") waitForChild(Tool,"ErrorBox") waitForChild(variables, "ShowAdminCategories") local errorBox = Tool.ErrorBox waitForChild(variables, "IsRestricted") waitForChild(variables, "MouseClick") click = variables.MouseClick local Data = {} Data.Stamp = {} Data.Loading = {} local guiScriptIsLoadingSomething = false local unstampableSurface = false local eyeDropperConnection, eyeDropperMoveConnection local playerModel local player local lastTargetCFrame = nil local lastTargetTerrainOrientation = 0 -- For Restricting Stamper Tool local isRestricted = variables.IsRestricted.Value local adminAccess = variables.ShowAdminCategories.Value -- For Delete highlighting local selectionBox local currentSelection local currentSelectionColors = {} if isRestricted then waitForChild(game.Workspace, "BaseplateBumpers") end ---------------------------------------------------------------------------------------- -- Functions function hint(label) -- Pass in a string, it shows a top hint. (Replaces previous hint, if exists) _player = game.Players:GetPlayerFromCharacter(Tool.Parent) if(_player.PlayerGui:FindFirstChild("topHint")~=nil) then local topHint = _player.PlayerGui.topHint topHint.Add.Label.Value = label topHint.Add.Width.Value = 3 -- widest width topHint.Add.Time.Value = 5 topHint.Add.Disabled = true topHint.Add.Disabled = false end end function getClosestColorToTerrainMaterial(terrainValue) if terrainValue == 1 then return BrickColor.new("Bright green") elseif terrainValue == 2 then return BrickColor.new("Bright yellow") elseif terrainValue == 3 then return BrickColor.new("Bright red") elseif terrainValue == 4 then return BrickColor.new("Medium stone grey") else return BrickColor.new("Bright green") end end local manualWeldTable = {} local manualWeldParentTable = {} function saveTheWelds(object) if object:IsA("ManualWeld") or object:IsA("Rotate") then table.insert(manualWeldTable, object) table.insert(manualWeldParentTable, object.Parent) else local children = object:GetChildren() for i = 1, #children do saveTheWelds(children[i]) end end end function restoreTheWelds() for i = 1, #manualWeldTable do manualWeldTable[i].Parent = manualWeldParentTable[i] end end function findSeatsInModel(parent, seatTable) if not parent then return end if parent.className == "Seat" or parent.className == "VehicleSeat" then table.insert(seatTable, parent) end local myChildren = parent:GetChildren() for j = 1, #myChildren do findSeatsInModel(myChildren[j], seatTable) end end function setSeatEnabledStatus(model, isEnabled) local seatList = {} findSeatsInModel(model, seatList) if isEnabled then -- remove any welds called "SeatWeld" in seats for i = 1, #seatList do local nextSeat = seatList[i]:FindFirstChild("SeatWeld") while nextSeat do nextSeat:Remove() nextSeat = seatList[i]:FindFirstChild("SeatWeld") end end 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 do local fakeWeld = Instance.new("Weld") fakeWeld.Name = "SeatWeld" fakeWeld.Parent = seatList[i] end end end function UnlockInstances(object) if object:IsA("BasePart") then object.Locked = false end for index,child in pairs(object:GetChildren()) do UnlockInstances(child) end end function generateOwnerGui(playerName) local gui = Instance.new("BillboardGui") gui.Name = "PlayerStamperTagGui" gui.StudsOffset = Vector3.new(0,1,0) gui.ExtentsOffset = Vector3.new(0,1,0) gui.Size = UDim2.new(5,0,2,0) pcall(function() gui.PlayerToHideFrom = game.Players.LocalPlayer end) local frame = Instance.new("Frame") frame.BackgroundColor3 = Color3.new(0,0,0) frame.BackgroundTransparency = 0.5 frame.Name = "OwnerFrame" frame.Size = UDim2.new(1,0,1,0) frame.Parent = gui local ownerName = Instance.new("TextLabel") ownerName.Name = "OwnerName" ownerName.Size = UDim2.new(1,0,1,0) ownerName.Text = playerName ownerName.Font = Enum.Font.ArialBold ownerName.FontSize = Enum.FontSize.Size14 ownerName.TextWrap = true ownerName.TextColor3 = Color3.new(1,1,1) ownerName.TextStrokeTransparency = 0 ownerName.BackgroundTransparency = 1 ownerName.Parent = frame return gui end function getPlayer() return game.Players:GetPlayerFromCharacter(script.Parent.Parent) end function beginInsertAssetStamp(assetName, assetId, image, stampMode) -- trying to stop assets, gone back to Main Stamp Dialog if assetId == 0 then guiScriptIsLoadingSomething = true cancelAssetPlacement() return end if assetId < 0 then guiScriptIsLoadingSomething = true cancelAssetPlacement() setupDraggableClone() wait() -- need this so onInsertMouseMove() can sync up with setupDraggableClone() onInsertMouseMove() guiScriptIsLoadingSomething = false return end -- 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 then loader = coroutine.create(function() root = game:GetService("InsertService"):LoadAssetVersion(assetId) loading = false end) coroutine.resume(loader) else loader = coroutine.create(function() root = game:GetService("InsertService"):LoadAsset(assetId) loading = false end) coroutine.resume(loader) end local lastGameTime = 0 local totalTime = 0 local maxWait = 8 while loading and totalTime < maxWait do lastGameTime = tick() wait(1) totalTime = totalTime + tick() - lastGameTime end loading = false if totalTime >= maxWait or pressedEsc then Data.Loading.Cancelled = true pressedEsc = false else Data.Loading.Cancelled = false end if Data.Loading.Cancelled then --The user got bored and wandered off --Just delete the model from the world... a shame we loaded it when they got bored -- 12/28/2010: Putting this inside a pcall (on gametest, first time equipping stamper, this was being called with root=nil.) (Jahr) pcall(function() root:Remove() end) signalInsertComplete("Main") else if root == nil then signalInsertComplete("Main") return end if not root:IsA("Model") then signalInsertComplete("Main") return end local instances = root:GetChildren() if #instances == 0 then root:Remove() signalInsertComplete("Main") return end --Unlock all parts that are inserted, to make sure they are editable UnlockInstances(root) --Continue the insert process root.Name = "InsertedObject" .. assetId --Examine the contents and decide what it looks like for pos, instance in pairs(instances) do --Single instance objects might be treated special, decals/skyboxes if instance:IsA("Decal") then --Current system here stops after finding one Decal (and gives you Decal tool) --We should do the same (probably) beginInsertDecal(instance) root:Remove() Window.Stamp.Frame.Visible = true return elseif instance:IsA("Team") then instance.Parent = game:GetService("Teams") elseif instance:IsA("SpawnLocation") then -- uh............. elseif instance:IsA("HopperBin") then -- Must go into the starterPack, prompt user? elseif instance:IsA("Tool") then -- Ask them if it should go in StarterPack? elseif instance:IsA("Sky") then local lightingService = game:GetService("Lighting") for index,child in pairs(lightingService:GetChildren()) do if child:IsA("Sky") then child:Remove(); end end instance.Parent = lightingService return else end end if #root:GetChildren() == 0 then root:Remove() signalInsertComplete("Main") return end signalInsertComplete("SideDialog") cancelAssetPlacement() Data.Stamp.Model = root setupDraggableClone() guiScriptIsLoadingSomething = false end end function beginInsertDecal(decal) Data.Stamp.DecalSelection = Instance.new("SurfaceSelection") Data.Stamp.DecalSelection.Color = BrickColor.new("Bright orange") Data.Stamp.DecalSelection.archivable = false Data.Stamp.DecalSelection.Parent = getPlayer().PlayerGui --Save the decal in our Lua code for later use Data.Stamp.Decal = decal Data.Stamp.Decal.Parent = nil end -- signal to gui to switch frames function signalInsertComplete(type) if type == "SideDialog" then variables.SwitchLoaderToDialog.DialogType.Value = "SideDialog" variables.SwitchLoaderToDialog.Value = true elseif type == "Main" then variables.SwitchLoaderToDialog.DialogType.Value = "Main" variables.SwitchLoaderToDialog.Value = true elseif type == "EyeDropper" then variables.SwitchLoaderToDialog.DialogType.Value = "EyeDropper" variables.SwitchLoaderToDialog.Value = true end -- needed to make sure we disconnect eyedroper handler if type ~= "EyeDropper" then if eyeDropperConnection then eyeDropperConnection:disconnect() eyeDropperConnection = nil end if eyeDropperMoveConnection then eyeDropperMoveConnection:disconnect() end if Mouse and not mouseButton1UpCon then mouseButton1UpCon = Mouse.Button1Up:connect(onInsertMouseButton1Up) end end clearSelection() end function p(assetName, assetId, image, stampMode) -- trying to stop assets, gone back to Main Stamp Dialog if assetId == 0 then cancelAssetPlacement() return end -- 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 then loader = coroutine.create(function() root = game:GetService("InsertService"):LoadAssetVersion(assetId) loading = false end) coroutine.resume(loader) else loader = coroutine.create(function() root = game:GetService("InsertService"):LoadAsset(assetId) loading = false end) coroutine.resume(loader) end local lastGameTime = 0 local totalTime = 0 local maxWait = 8 while loading and totalTime < maxWait do lastGameTime = tick() wait(1) totalTime = totalTime + tick() - lastGameTime end loading = false if totalTime >= maxWait or pressedEsc then Data.Loading.Cancelled = true pressedEsc = false else Data.Loading.Cancelled = false end if Data.Loading.Cancelled then --The user got bored and wandered off --Just delete the model from the world... a shame we loaded it when they got bored -- 12/28/2010: Putting this inside a pcall (on gametest, first time equipping stamper, this was being called with root=nil.) (Jahr) pcall(function() root:Remove() end) signalInsertComplete("Main") else local instances = root:GetChildren() if #instances == 0 then root:Remove() signalInsertComplete("Main") return end --Unlock all parts that are inserted, to make sure they are editable UnlockInstances(root) --Continue the insert process root.Name = "InsertedObject" .. assetId --Examine the contents and decide what it looks like for pos, instance in pairs(instances) do --Single instance objects might be treated special, decals/skyboxes if instance:IsA("Decal") then --Current system here stops after finding one Decal (and gives you Decal tool) --We should do the same (probably) beginInsertDecal(instance) root:Remove() Window.Stamp.Frame.Visible = true return elseif instance:IsA("Team") then instance.Parent = game:GetService("Teams") elseif instance:IsA("SpawnLocation") then -- uh............. elseif instance:IsA("HopperBin") then -- Must go into the starterPack, prompt user? elseif instance:IsA("Tool") then -- Ask them if it should go in StarterPack? elseif instance:IsA("Sky") then local lightingService = game:GetService("Lighting") for index,child in pairs(lightingService:GetChildren()) do if child:IsA("Sky") then child:Remove(); end end instance.Parent = lightingService return else end end if #root:GetChildren() == 0 then root:Remove() signalInsertComplete("Main") return end signalInsertComplete("SideDialog") cancelAssetPlacement() Data.Stamp.Model = root setupDraggableClone() end end function positionPartsAtCFrame3(partOrModel, aCFrame) local insertCFrame if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then --insertCFrame = Data.Stamp.CurrentParts[1]:GetChildren()[1].CFrame -- we assume model has at least one part in it; need to find first part i = 1 while (i < (#Data.Stamp.CurrentParts[1]:GetChildren()) and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("Part") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("TrussPart") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("WedgePart") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("CornerWedgePart")) do i = i + 1 end insertCFrame = Data.Stamp.CurrentParts[1]:GetChildren()[i].CFrame for i, object in pairs(Data.Stamp.CurrentParts[1]:GetChildren()) do if object:IsA("Flag") then object = object.Handle end if (object:IsA("Part") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("TrussPart") or object:IsA("Seat") or object:IsA("VehicleSeat")) then local posPartInWorld = object.Position local posPart1InWorld = insertCFrame.p local newPosPartInWorld = posPartInWorld - posPart1InWorld + aCFrame.p local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = object.CFrame:components() object.CFrame = CFrame.new(newPosPartInWorld.x, newPosPartInWorld.y, newPosPartInWorld.z, R00, R01, R02, R10, R11, R12, R20, R21, R22) end end else Data.Stamp.CurrentParts[1].CFrame = aCFrame end end -- For Restricting Stamper Tool (isRestricted) function inBounds(object) for part, transparency in pairs(object) do if part:IsA("Part") or part:IsA("WedgePart") or part:IsA("CornerWedgePart") or part:IsA("TrussPart") then if not partInBounds(part) then return false end elseif part:IsA("Model") then local primPart = object.PrimaryPart if not partInBounds(primPart) then return false end end end return true end function partInBounds(part) if part == nil then return false end local xOne= buildingPlate.Position.x + buildingPlate.Size.x/2 local xTwo = buildingPlate.Position.x - buildingPlate.Size.x/2 local zOne = buildingPlate.Position.z + buildingPlate.Size.z/2 local zTwo = buildingPlate.Position.z - buildingPlate.Size.z/2 if part.Position.x > xOne or part.Position.x < xTwo then return false end if part.Position.z > zOne or part.Position.z < zTwo then return false end return true end function canSelectObject(part) return part and not (part.Locked) and part:IsA("BasePart") and (part.Position - Tool.Parent.Head.Position).Magnitude < 60 end function canEyeDropperObject(part) local stamperTag = part.Parent:FindFirstChild("RobloxStamper") if stamperTag == nil then stamperTag = part:FindFirstChild("RobloxStamper") end return part and not (part.Locked) and part:IsA("BasePart") and (part.Position - Tool.Parent.Head.Position).Magnitude < 60 and stamperTag ~= nil end function isOnGrid(partOrModel) -- first check to see if off-grid, and if so, prevent eyedropperage local modelExtentsInWorldCoords if partOrModel:IsA("Model") then modelExtentsInWorldCoords = partOrModel:GetModelCFrame():vectorToWorldSpace(partOrModel:GetModelSize()) else modelExtentsInWorldCoords = partOrModel.CFrame:vectorToWorldSpace(partOrModel.Size) end -- we now simply check to see if the above property fits in a 4x4x4 gridspace offX = math.fmod(math.abs(modelExtentsInWorldCoords.X), 4) offY = math.fmod(math.abs(modelExtentsInWorldCoords.Y), 4) offZ = math.fmod(math.abs(modelExtentsInWorldCoords.Z), 4) local numberOfAxesOffGrid = 0 if math.min(offX, 4 - offX) > eyedropperOffGridTolerance then numberOfAxesOffGrid = numberOfAxesOffGrid + 1 end if math.min(offY, 4 - offY) > eyedropperOffGridTolerance then numberOfAxesOffGrid = numberOfAxesOffGrid + 1 end if math.min(offZ, 4 - offZ) > eyedropperOffGridTolerance then numberOfAxesOffGrid = numberOfAxesOffGrid + 1 end if numberOfAxesOffGrid > 1 then return false -- we allow one axis to be not fit to grid, since any rotation must necessarily affect >= 2 axes (some of the models are like 4x4x6 :( ). else return true end end -- below is a helper function to help get the model surface instead of the part surface [for allowing a side to elect out of making joints automatically] function calcRayHitTime(rayStart, raySlope, intersectionPlane) if math.abs(raySlope) < .01 then return 0 end -- 0 slope --> we just say intersection time is 0, and sidestep this dimension -- rayStart + t*raySlope = intersectionPlane, so t = (intersectionPlane - rayStart) / raySlope return (intersectionPlane - rayStart) / raySlope end function modelTargetSurface(partOrModel, rayStart, rayEnd) if not partOrModel then return 0 end local modelCFrame = nil local modelSize = nil if partOrModel:IsA("Model") then modelCFrame = partOrModel:GetModelCFrame() modelSize = partOrModel:GetModelSize() else modelCFrame = partOrModel.CFrame modelSize = partOrModel.Size end local mouseRayStart = modelCFrame:pointToObjectSpace(rayStart) local mouseRayEnd = modelCFrame:pointToObjectSpace(rayEnd) local mouseSlope = mouseRayEnd - mouseRayStart local xPositive = 1 local yPositive = 1 local zPositive = 1 if mouseSlope.X > 0 then xPositive = -1 end if mouseSlope.Y > 0 then yPositive = -1 end if mouseSlope.Z > 0 then zPositive = -1 end -- find which surface the transformed mouse ray hits (using modelSize): local xHitTime = calcRayHitTime(mouseRayStart.X, mouseSlope.X, modelSize.X/2 * xPositive) local yHitTime = calcRayHitTime(mouseRayStart.Y, mouseSlope.Y, modelSize.Y/2 * yPositive) local zHitTime = calcRayHitTime(mouseRayStart.Z, mouseSlope.Z, modelSize.Z/2 * zPositive) local hitFace = 0 --if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 then if xHitTime > yHitTime then if xHitTime > zHitTime then -- xFace is hit hitFace = 1*xPositive else -- zFace is hit hitFace = 3*zPositive end else if yHitTime > zHitTime then -- yFace is hit hitFace = 2*yPositive else -- zFace is hit hitFace = 3*zPositive end end return hitFace end -- helper function for truncating to 45-degree angles on a 2D plane function truncateToCircleEighth(bigValue, littleValue) local big = math.abs(bigValue) local little = math.abs(littleValue) local hypotenuse = math.sqrt(big*big + little*little) local frac = little / hypotenuse local bigSign = 1 local littleSign = 1 if bigValue < 0 then bigSign = -1 end if littleValue < 0 then littleSign = -1 end if frac > .382683432 then -- between 22.5 and 45 degrees, so truncate to 45-degree tilt return .707106781 * hypotenuse * bigSign, .707106781 * hypotenuse * littleSign else -- between 0 and 22.5 degrees, so truncate to 0-degree tilt return hypotenuse * bigSign, 0 end end function onInsertMouseMove() if Data.Stamp.MovingLock then return end -- check to see if mouse is still active, and return otherwise! if not pcall(function () if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then return true else return false end end) then print("ERRORED OUT") game.JointsService:SetJoinAfterMoveTarget(nil) Mouse = nil return end if isRestricted and Mouse then Mouse.TargetFilter = game.Workspace.BaseplateBumpers end Data.Stamp.MovingLock = true if Data.Stamp.Dragger == nil then if Data.Stamp.Model ~= nil then if isRestricted then if checkPartLimit() then setupDraggableClone() else variables.ShowMaxedOut.Value = true end else setupDraggableClone() end end else -- REM TL: Data.Stamp.Dragger:MouseMove(Mouse.UnitRay) -- don't move with dragger - will move in one step on mouse down -- draw ghost at acceptable positions configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Data.Stamp.TransparencyTable) if configFound then if autoAlignToFace() and targetSurface ~= 1 and targetSurface ~= 4 then -- pre-rotate the flag or portrait so it's aligned correctly --local currCFrame --if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then currCFrame = Data.Stamp.CurrentParts[1]:GetModelCFrame() --else currCFrame = Data.Stamp.CurrentParts[1].CFrame end local numRotations = 0 -- update this according to how many rotations you need to get it to target surface if targetSurface == 3 then numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace() elseif targetSurface == 0 then numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace() elseif targetSurface == 5 then numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace() elseif targetSurface == 2 then numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace() end local ry = math.pi/2 local rotCF = CFrame.fromEulerAnglesXYZ(0, ry*numRotations, 0) gInitial90DegreeRotations = gInitial90DegreeRotations + numRotations if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then for i, object in pairs(Data.Stamp.CurrentParts[1]:GetChildren()) do if object:IsA("Flag") then object = object.Handle end if object:IsA("Part") or object:IsA("TrussPart") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("Seat") or object:IsA("VehicleSeat") then object.CFrame = rotCF * object.CFrame end end else Data.Stamp.CurrentParts[1].CFrame = rotCF * Data.Stamp.CurrentParts[1].CFrame end end -- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE local minBB, maxBB = getBoundingBoxInWorldCoordinates(Data.Stamp.CurrentParts[1]) -- need to offset by distance to be dragged local currModelCFrame if Data.Stamp.CurrentParts[1]:IsA("Model") then -- we assume model has at least one part in it; need to find first part i = 1 while i < (#Data.Stamp.CurrentParts[1]:GetChildren()) and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("BasePart") do i = i + 1 end currModelCFrame = Data.Stamp.CurrentParts[1]:GetChildren()[i].CFrame else currModelCFrame = Data.Stamp.CurrentParts[1].CFrame end minBB = minBB + targetCFrame.p - currModelCFrame.p maxBB = maxBB + targetCFrame.p - currModelCFrame.p -- don't drag into terrain if clusterPartsInRegion(minBB+insertBoundingBoxOverlapVector, maxBB-insertBoundingBoxOverlapVector) then if lastTargetCFrame then positionPartsAtCFrame3(thingToDrag, lastTargetCFrame) if (Data.Stamp.CurrentParts[1].Name == "MegaClusterCube") and (Data.Stamp.CurrentParts[1]:FindFirstChild("ClusterMaterial")) and (Data.Stamp.CurrentParts[1].ClusterMaterial:IsA("Vector3Value")) then local clusterMat = Data.Stamp.CurrentParts[1].ClusterMaterial clusterMat.Value = Vector3.new(clusterMat.Value.X, clusterMat.Value.Y, lastTargetTerrainOrientation) end end Data.Stamp.MovingLock = false return end local blockingParts = game.Workspace:FindPartsInRegion3(Region3.new(minBB+insertBoundingBoxOverlapVector, maxBB-insertBoundingBoxOverlapVector), currentSelection, 100) for b = 1, #blockingParts do -- put code back here if we want to prevent stamper from dragging ghost parts into other stamped models (once Region3 fix goes out) end positionPartsAtCFrame3(Data.Stamp.CurrentParts[1], targetCFrame) lastTargetCFrame = targetCFrame -- successful positioning, so update 'dat cframe if (Data.Stamp.CurrentParts[1].Name == "MegaClusterCube") and (Data.Stamp.CurrentParts[1]:FindFirstChild("ClusterMaterial")) and (Data.Stamp.CurrentParts[1].ClusterMaterial:IsA("Vector3Value")) then lastTargetTerrainOrientation = Data.Stamp.CurrentParts[1].ClusterMaterial.Value.Z end -- auto break joints code if Mouse and Mouse.Target and Mouse.Target.Parent then local modelInfo = Mouse.Target:FindFirstChild("RobloxModel") if not modelInfo then modelInfo = Mouse.Target.Parent:FindFirstChild("RobloxModel") end local myModelInfo = Data.Stamp.CurrentParts[1]:FindFirstChild("UnstampableFaces") --if (modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then -- need better targetSurface calcs if (true) then local breakingFaces = "" local myBreakingFaces = "" if modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces") then breakingFaces = modelInfo.Parent.UnstampableFaces.Value end if myModelInfo then myBreakingFaces = myModelInfo.Value end local hitFace = 0 if modelInfo then hitFace = modelTargetSurface(modelInfo.Parent, game.Workspace.CurrentCamera.CoordinateFrame.p, Mouse.Hit.p) end -- are we stamping TO an unstampable surface? for bf in string.gmatch(breakingFaces, "[^,]+") do if hitFace == tonumber(bf) then -- return before we hit the JointsService code below! unstampableSurface = true game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache Data.Stamp.MovingLock = false return end end -- now we have to cast the ray back in the other direction to find the surface we're stamping FROM hitFace = modelTargetSurface(Data.Stamp.CurrentParts[1], Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- are we stamping WITH an unstampable surface? for bf in string.gmatch(myBreakingFaces, "[^,]+") do if hitFace == tonumber(bf) then unstampableSurface = true game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache Data.Stamp.MovingLock = false return end end -- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame -- targetSurface: 1 is top, 4 is bottom, end end end -- to show joints during the mouse move unstampableSurface = false game.JointsService:SetJoinAfterMoveInstance(Data.Stamp.CurrentParts[1]) -- most common mouse inactive error occurs here, so check mouse active one more time in a pcall if not pcall(function () if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then return true else return false end end) then print("ERRORED OUT") game.JointsService:ClearJoinAfterMoveJoints() Mouse = nil Data.Stamp.MovingLock = false return end if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then game.JointsService:SetJoinAfterMoveTarget(Mouse.Target) else game.JointsService:SetJoinAfterMoveTarget(nil) end game.JointsService:ShowPermissibleJoints() -- here we allow for a line of high-scalability parts if (Data.Stamp.CurrentParts[1] and Data.Stamp.CurrentParts[1].Name == "MegaClusterCube") and HighScalabilityLine.Start then HighScalabilityLine.End = Data.Stamp.CurrentParts[1].CFrame.p local line local line2 = Vector3.new(0, 0, 0) local line3 = Vector3.new(0, 0, 0) if HighScalabilityLine.Dimensions == 1 then -- 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) then if math.abs(line.X) < math.abs(line.Z) then -- limit to Y/Z plane, domination unknown local newY, newZ if (math.abs(line.Y) > math.abs(line.Z)) then newY, newZ = truncateToCircleEighth(line.Y, line.Z) else newZ, newY = truncateToCircleEighth(line.Z, line.Y) end 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) end else if math.abs(line.Y) < math.abs(line.Z) then -- limit to X/Z plane, domination unknown local newX, newZ if math.abs(line.X) > math.abs(line.Z) then newX, newZ = truncateToCircleEighth(line.X, line.Z) else newZ, newX = truncateToCircleEighth(line.Z, line.X) end 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) end end HighScalabilityLine.InternalLine = line elseif HighScalabilityLine.Dimensions == 2 then 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 = line2 - line.unit*line.unit:Dot(line2) tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line) -- then zero out whichever is the smaller component local yAxis = tempCFrame:vectorToWorldSpace(Vector3.new(0, 1, 0)) local xAxis = tempCFrame:vectorToWorldSpace(Vector3.new(1, 0, 0)) local xComp = xAxis:Dot(line2) local yComp = yAxis:Dot(line2) if math.abs(yComp) > math.abs(xComp) then line2 = line2 - xAxis*xComp else line2 = line2 - yAxis*yComp end HighScalabilityLine.InternalLine = line2 elseif HighScalabilityLine.Dimensions == 3 then line = HighScalabilityLine.MoreLines[1] line2 = HighScalabilityLine.MoreLines[2] line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2] -- zero out all components of previous lines line3 = line3 - line.unit*line.unit:Dot(line3) line3 = line3 - line2.unit*line2.unit:Dot(line3) HighScalabilityLine.InternalLine = line3 end -- resize the "line" graphic to be the correct size and orientation tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line) if HighScalabilityLine.Dimensions == 1 then -- 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 local 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) end -- make player able to see this ish if player then HighScalabilityLine.Adorn.Parent = player.PlayerGui end if HighScalabilityLine.NewHint then if HighScalabilityLine.Dimensions == 1 and line and (line.magnitude > 4) then hint("Press C to stamp in 2D") HighScalabilityLine.NewHint = false elseif HighScalabilityLine.Dimensions == 2 and line2 and (line2.magnitude > 4) then hint("Press C again to stamp in 3D") HighScalabilityLine.NewHint = false elseif HighScalabilityLine.Dimensions == 3 then HighScalabilityLine.NewHint = false end -- if we do cyclic, then let them know they're going back to line stamping end end end Data.Stamp.MovingLock = false end function onInsertMouseButton1Down() if Data.Stamp.Dragger or Data.Stamp.Decal then Data.Stamp.MouseDown = true end if (Data.Stamp.CurrentParts and Data.Stamp.CurrentParts[1] and Data.Stamp.CurrentParts[1].Name == "MegaClusterCube") and not eyeDropperConnection then -- only let them drag if they start dragging on the cluster! if Mouse and Mouse.Target and Mouse.Target:IsA("Terrain") then HighScalabilityLine.Dimensions = 1 HighScalabilityLine.Start = Data.Stamp.CurrentParts[1].CFrame.p end end end function cancelAssetPlacement() HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil gInitial90DegreeRotations = 0 Data.Stamp.Cancelled = true if Data.Stamp["Model"] then Data.Stamp.Model.Parent = nil end if Data.Stamp.CurrentParts then for index, object in pairs(Data.Stamp.CurrentParts) do object.Parent = nil end Data.Stamp.CurrentParts.Parent = nil end if Data.Stamp.DecalSelection then Data.Stamp.DecalSelection:Remove() Data.Stamp.DecalSelection = nil end if Data.Stamp.Decal then Data.Stamp.Decal:Remove() Data.Stamp.Decal = nil end if Mouse then Mouse.Icon = "http://www.roblox.com/asset?id=66887745" end game.JointsService:ClearJoinAfterMoveJoints() end function collectParts(object, baseParts, scripts, decals) if object:IsA("BasePart") then baseParts[#baseParts+1] = object elseif object:IsA("Script") then scripts[#scripts+1] = object elseif object:IsA("Decal") then decals[#decals+1] = object end for index,child in pairs(object:GetChildren()) do collectParts(child, baseParts, scripts, decals) end end function getTargetPartBoundingBox(targetPart) if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then return getBoundingBox2(targetPart.Parent) else return getBoundingBox2(targetPart) end end function 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. local minVec = Vector3.new(math.huge, math.huge, math.huge) local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge) if partOrModel:IsA("Part") or partOrModel:IsA("WedgePart") or partOrModel:IsA("CornerWedgePart") or partOrModel:IsA("TrussPart")then minVec = -0.5 * partOrModel.Size maxVec = -minVec elseif partOrModel:IsA("Terrain") then minVec = Vector3.new(-2, -2, -2) maxVec = Vector3.new(2, 2, 2) else local part1 = partOrModel:GetChildren()[1] if partOrModel:IsA("Tool") then part1 = partOrModel.Handle if not part1 then return end end if part1:IsA("Flag") then part1 = partOrModel:FindFirstChild("Part") if not part1 then return end end for i, object in pairs(partOrModel:GetChildren()) do if (object:IsA("Part") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("TrussPart")) then boxMinInWorld = object.CFrame:pointToWorldSpace(-0.5 * object.Size) boxMinInPart1 = part1.CFrame:pointToObjectSpace(boxMinInWorld) boxMaxInWorld = object.CFrame:pointToWorldSpace(0.5 * object.Size) boxMaxInPart1 = part1.CFrame:pointToObjectSpace(boxMaxInWorld) local minX = minVec.x local minY = minVec.y local minZ = minVec.z local maxX = maxVec.x local maxY = maxVec.y local maxZ = maxVec.z if boxMinInPart1.x < minVec.x then minX = boxMinInPart1.x end if boxMinInPart1.y < minVec.y then minY = boxMinInPart1.y end if boxMinInPart1.z < minVec.z then minZ = boxMinInPart1.z end if boxMaxInPart1.x < minX then minX = boxMaxInPart1.x end if boxMaxInPart1.y < minY then minY = boxMaxInPart1.y end if boxMaxInPart1.z < minZ then minZ = boxMaxInPart1.z end if boxMinInPart1.x > maxVec.x then maxX = boxMinInPart1.x end if boxMinInPart1.y > maxVec.y then maxY = boxMinInPart1.y end if boxMinInPart1.z > maxVec.z then maxZ = boxMinInPart1.z end if boxMaxInPart1.x > maxX then maxX = boxMaxInPart1.x end if boxMaxInPart1.y > maxY then maxY = boxMaxInPart1.y end if boxMaxInPart1.z > maxZ then maxZ = boxMaxInPart1.z end minVec = Vector3.new(minX, minY, minZ) maxVec = Vector3.new(maxX, maxY, maxZ) end end end -- Adjust bounding box to reflect what the model or part author wants in terms of justification local justifyValue = partOrModel:FindFirstChild("Justification") if justifyValue ~= nil then -- 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 = minVec - 0.5 * adjustment * justify maxVec = maxVec + 0.5 * adjustment * (two - justify) end return minVec, maxVec end function getBoundingBoxInWorldCoordinates(partOrModel) local minVec = Vector3.new(math.huge, math.huge, math.huge) local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge) if partOrModel:IsA("BasePart")then 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 partOrModel:IsA("Terrain") then -- we shouldn't have to deal with this case --minVec = Vector3.new(-2, -2, -2) --maxVec = Vector3.new(2, 2, 2) else local part1 = partOrModel:GetChildren()[1] for i, object in pairs(partOrModel:GetChildren()) do if object:IsA("BasePart") then boxMinInWorld = object.CFrame:pointToWorldSpace(-0.5 * object.Size) boxMaxInWorld = object.CFrame:pointToWorldSpace(0.5 * object.Size) local minX = minVec.x local minY = minVec.y local minZ = minVec.z local maxX = maxVec.x local maxY = maxVec.y local maxZ = maxVec.z if boxMinInWorld.x < minX then minX = boxMinInWorld.x end if boxMinInWorld.y < minY then minY = boxMinInWorld.y end if boxMinInWorld.z < minZ then minZ = boxMinInWorld.z end if boxMaxInWorld.x < minX then minX = boxMaxInWorld.x end if boxMaxInWorld.y < minY then minY = boxMaxInWorld.y end if boxMaxInWorld.z < minZ then minZ = boxMaxInWorld.z end if boxMinInWorld.x > maxX then maxX = boxMinInWorld.x end if boxMinInWorld.y > maxY then maxY = boxMinInWorld.y end if boxMinInWorld.z > maxZ then maxZ = boxMinInWorld.z end if boxMaxInWorld.x > maxX then maxX = boxMaxInWorld.x end if boxMaxInWorld.y > maxY then maxY = boxMaxInWorld.y end if boxMaxInWorld.z > maxZ then maxZ = boxMaxInWorld.z end minVec = Vector3.new(minX, minY, minZ) maxVec = Vector3.new(maxX, maxY, maxZ) end end end return minVec, maxVec end function getClosestAlignedWorldDirection(aVector3InWorld) local xDir = Vector3.new(1,0,0) local yDir = Vector3.new(0,1,0) local zDir = Vector3.new(0,0,1) local xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z local yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z local 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) then if xDot > 0 then return 0 else return 3 end elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs(zDot) then if yDot > 0 then return 1 else return 4 end else if zDot > 0 then return 2 else return 5 end end end function getMouseTargetCFrame(targetPart) if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then if targetPart.Parent:IsA("Tool") then return targetPart.Parent.Handle.CFrame else return targetPart.Parent:GetChildren()[1].CFrame end else return targetPart.CFrame end end function surfaceToVector(surf) local vect = 1 if surf < 0 then surf = surf * -1 vect = vect * -1 end if surf == 1 then return vect*Vector3.new(1, 0, 0) elseif surf == 2 then return vect*Vector3.new(0, 1, 0) elseif surf == 3 then return vect*Vector3.new(0, 0, 1) elseif Mouse then return Vector3.FromNormalId(Mouse.TargetSurface) end -- if we somehow got a "0", then we just revert to old behavior return Vector3.new(0,0,0) end function findConfigAtMouseTarget(partsTable) -- *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. local grid = 4.0 local admissibleConfig = false local targetConfig = CFrame.new(0,0,0) local minBB, maxBB = getBoundingBox2(Data.Stamp.CurrentParts[1]) local diagBB = maxBB - minBB local insertCFrame if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then i = 1 while (i < (#Data.Stamp.CurrentParts[1]:GetChildren()) and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("Part") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("TrussPart") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("WedgePart") and not Data.Stamp.CurrentParts[1]:GetChildren()[i]:IsA("CornerWedgePart")) do i = i + 1 end insertCFrame = Data.Stamp.CurrentParts[1]:GetChildren()[i].CFrame else insertCFrame = Data.Stamp.CurrentParts[1].CFrame end if not isRestricted and Mouse then if Data.Stamp.CurrentParts[1]:IsA("Tool") then Mouse.TargetFilter = Data.Stamp.CurrentParts[1].Handle else Mouse.TargetFilter = Data.Stamp.CurrentParts[1] end end local targetPart = nil local success = pcall(function() targetPart = Mouse.Target end) if not success or targetPart == nil then return admissibleConfig, targetConfig end -- test mouse hit location local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart) local diagBBTarget = maxBBTarget - minBBTarget local targetCFrame = getMouseTargetCFrame(targetPart) local hitCFrame = CFrame.new(0,0,0) if Mouse then hitCFrame = Mouse.Hit end local mouseHitInWorld = hitCFrame.p -- find which axis of the insertion objects should match with the target surface -- this should use targetPart CFrame, not the model CFrame --[[ attempt at fixing Mouse.TargetSurface below... local targetModel = targetPart if not targetPart:FindFirstChild("RobloxModel") and targetPart.Parent and targetPart.Parent:FindFirstChild("RobloxModel") then targetModel = targetPart.Parent end local correctedTargetSurfaceVector = surfaceToVector(modelTargetSurface(targetModel, game.Workspace.CurrentCamera.CoordinateFrame.p, mouseHitInWorld)) local targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(correctedTargetSurfaceVector) --]] if targetPart:IsA("Terrain") then if not cluster then cluster = game.Workspace.Terrain end cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld) targetCFrame = CFrame.new(cluster:CellCenterToWorld(cellID.x, cellID.y, cellID.z)) end local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld) local targetVectorInWorld = Vector3.new(0,0,0) if Mouse then targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface)) end local targetRefPointInTarget local clampToSurface if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 then 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 then 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 then 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 then 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 then 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) end targetRefPointInTarget = targetRefPointInTarget * (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget) insertRefPointInInsert = 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 local delta = mouseHitInTarget - targetRefPointInTarget local deltaClamped = Vector3.new(grid * math.modf(delta.x/grid), grid * math.modf(delta.y/grid), grid * math.modf(delta.z/grid)) deltaClamped = deltaClamped * clampToSurface local targetTouchInTarget = deltaClamped + targetRefPointInTarget local TargetTouchRelToWorld = targetCFrame:pointToWorldSpace(targetTouchInTarget) local InsertTouchInWorld = insertCFrame:vectorToWorldSpace(insertRefPointInInsert) local posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld local x, y, z, 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 return admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld) end function checkPartLimit() local numPoints = player.PointsUsed.Value local maxPoints = player.MaxPoints.Value if numPoints < maxPoints then return true else return false end return true end function setupDraggableClone() if eyeDropperConnection then eyeDropperConnection:disconnect() eyeDropperConnection = nil end --if not Mouse.Target then return end click.Value = false if Data.Stamp.CurrentParts then for i = 1, #Data.Stamp.CurrentParts do if Data.Stamp.CurrentParts[i].Parent ~= nil then Data.Stamp.CurrentParts[i].Parent = partModel end end end if Data.Stamp["Model"] == nil then return end local clone = Data.Stamp.Model:Clone() local scripts = {} local parts = {} local decals = {} collectParts(clone, parts, scripts, decals) if #parts > 0 then Data.Stamp.DisabledScripts = {} Data.Stamp.TransparencyTable = {} Data.Stamp.MaterialTable = {} Data.Stamp.CanCollideTable = {} Data.Stamp.AnchoredTable = {} Data.Stamp.DecalTransparencyTable = {} for index,script in pairs(scripts) do if not(script.Disabled) then script.Disabled = true Data.Stamp.DisabledScripts[#Data.Stamp.DisabledScripts +1] = script end end for index, part in pairs(parts) do Data.Stamp.TransparencyTable[part] = part.Transparency part.Transparency = gStaticTrans + (1-gStaticTrans)*part.Transparency Data.Stamp.MaterialTable[part] = part.Material part.Material = Enum.Material.Plastic Data.Stamp.CanCollideTable[part] = part.CanCollide part.CanCollide = false Data.Stamp.AnchoredTable[part] = part.Anchored part.Anchored = true part.archivable = false delay(0,function() local con = nil local exitLoop = false con = click.Changed:connect(function() if click.Value then con:disconnect() exitLoop = true end end) wait(fadeInDelayTime) -- give it some time to be completely transparent if exitLoop then return end -- if we already stamped, we don't need to do the rest of this local begTime = tick() local currTime = begTime while (currTime - begTime) < transFadeInTime and part and part:IsA("BasePart") and part.Transparency > gDesiredTrans and not exitLoop do local newTrans = 1 - (((currTime - begTime)/transFadeInTime) * (gStaticTrans - gDesiredTrans)) if Data.Stamp.TransparencyTable[part] then part.Transparency = newTrans + (1-newTrans) * Data.Stamp.TransparencyTable[part] end wait(0.03) currTime = tick() end if part and part:IsA("BasePart") and not exitLoop then if Data.Stamp.TransparencyTable[part] then part.Transparency = gDesiredTrans + (1-gDesiredTrans)*Data.Stamp.TransparencyTable[part] end end if con then con:disconnect() end end) end for index, decal in pairs(decals) do Data.Stamp.DecalTransparencyTable[decal] = decal.Transparency decal.Transparency = gDesiredTrans + (1-gDesiredTrans)*decal.Transparency end clone.Parent = game.Workspace -- For Restricting Stamper Tool if(isRestricted) then -- mark a particular spot in BaseplateBumpers, so we can remove any stragglers later, when player leaves local cloneInsertionSpot = game.Workspace.BaseplateBumpers:FindFirstChild(player.Name) if cloneInsertionSpot == nil then cloneInsertionSpot = Instance.new("Model") cloneInsertionSpot.Name = player.Name cloneInsertionSpot.Parent = game.Workspace.BaseplateBumpers end clone.Parent = cloneInsertionSpot end -- disable all seats setSeatEnabledStatus(clone, true) setSeatEnabledStatus(clone, false) Data.Stamp.CurrentParts = clone:GetChildren() -- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame" if autoAlignToFace() then if Data.Stamp.CurrentParts[1].PrimaryPart then modelInverse = Data.Stamp.CurrentParts[1].PrimaryPart.CFrame:inverse() else modelInverse = Data.Stamp.CurrentParts[1]:GetModelCFrame():inverse() end for i, object in pairs(Data.Stamp.CurrentParts[1]:GetChildren()) do if object:IsA("Flag") then object = object.Handle end if object:IsA("Part") or object:IsA("TrussPart") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("Seat") or object:IsA("VehicleSeat") then object.CFrame = modelInverse*object.CFrame end end gInitial90DegreeRotations = 0 else -- pre-rotate if necessary local ry = gInitial90DegreeRotations * math.pi/2 local rotCF = CFrame.fromEulerAnglesXYZ(0, ry, 0) if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then for i, object in pairs(Data.Stamp.CurrentParts[1]:GetChildren()) do if object:IsA("Flag") then object = object.Handle end if object:IsA("Part") or object:IsA("TrussPart") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("Seat") or object:IsA("VehicleSeat") then object.CFrame = rotCF * object.CFrame end end else Data.Stamp.CurrentParts[1].CFrame = rotCF * Data.Stamp.CurrentParts[1].CFrame end end -- After rotating, update the position configFound, targetCFrame = findConfigAtMouseTarget(Data.Stamp.TransparencyTable) if configFound then positionPartsAtCFrame3(Data.Stamp.TransparencyTable, targetCFrame) end -- to show joints during the mouse move game.JointsService:SetJoinAfterMoveInstance(Data.Stamp.CurrentParts[1]) local mouseTarget = nil pcall(function() mouseTarget = Mouse.Target end) if mouseTarget and mouseTarget.Parent:FindFirstChild("RobloxModel") == nil then game.JointsService:SetJoinAfterMoveTarget(mouseTarget) else game.JointsService:SetJoinAfterMoveTarget(nil) end game.JointsService:ShowPermissibleJoints() for index, object in pairs(Data.Stamp.CurrentParts) do object.Parent = clone.Parent end clone:Remove() lastTargetCFrame = nil if billBoardOwnerGui then if #parts == 1 then billBoardOwnerGui.Parent = parts[1] else billBoardOwnerGui.Parent = parts[1].Parent end end HighScalabilityLine.NewHint = true -- show hint if they start dragging a high-scalability part Data.Stamp.Dragger = Instance.new("Dragger") --Begin a movement by faking a MouseDown signal Data.Stamp.Dragger:MouseDown(parts[1], Vector3.new(0,0,0), parts) Data.Stamp.Dragger:MouseUp() else --Nothing draggable in the Model Data.Stamp.Model:Remove() Data.Stamp.Model = nil Data.Stamp.TransparencyTable = nil Data.Stamp.MaterialTable = nil Data.Stamp.CanCollideTable = nil Data.Stamp.AnchoredTable = nil Data.Stamp.DisabledScripts = nil end end function noManualWelds(part) local partChildren = part:GetChildren() for i = 1, #partChildren do if partChildren[i]:IsA("ManualWeld") or partChildren[i]:IsA("Rotate") then return false end end return true end local debris = game:GetService("Debris") function flashRedBox() errorBox.Parent = player.PlayerGui if Data.Stamp.CurrentParts[1]:IsA("Tool") then errorBox.Adornee = Data.Stamp.CurrentParts[1].Handle else errorBox.Adornee = Data.Stamp.CurrentParts[1] end delay(0,function() for i = 1, 3 do errorBox.Visible = true wait(0.13) errorBox.Visible = false wait(0.13) end errorBox.Adornee = nil errorBox.Parent = Tool end) end -- below function should work as a Region3 query, returning true if a single cluster part is within this region function clusterPartsInRegion(startVector, endVector) if not cluster then return false end local startCell = cluster:WorldToCell(startVector) local endCell = cluster:WorldToCell(endVector) local startX = startCell.X local startY = startCell.Y local startZ = startCell.Z local endX = endCell.X local endY = endCell.Y local endZ = endCell.Z if startX < cluster.MaxExtents.Min.X then startX = cluster.MaxExtents.Min.X end if startY < cluster.MaxExtents.Min.Y then startY = cluster.MaxExtents.Min.Y end if startZ < cluster.MaxExtents.Min.Z then startZ = cluster.MaxExtents.Min.Z end if endX > cluster.MaxExtents.Max.X then endX = cluster.MaxExtents.Max.X end if endY > cluster.MaxExtents.Max.Y then endY = cluster.MaxExtents.Max.Y end if endZ > cluster.MaxExtents.Max.Z then endZ = cluster.MaxExtents.Max.Z end for x = startX, endX do for y = startY, endY do for z = startZ, endZ do if (cluster:GetCell(x, y, z).Value) > 0 then return true end end end end return false end -- 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 function spaceAboveCharacter(charTorso, newTorsoY) local partsAboveChar = game.Workspace:FindPartsInRegion3(Region3.new(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75), Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)), charTorso.Parent, 100) for j = 1, #partsAboveChar do if partsAboveChar[j].CanCollide and not partsAboveChar[j]:IsDescendantOf(Data.Stamp.CurrentParts[1]) then return false end end if clusterPartsInRegion(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75), Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)) then return false end return true end -- returns whether or not we want to cancel the stamp because we're blocked by this part function isBlocker(part) if not part then return false end if not part.Parent then return false end if part:FindFirstChild("Humanoid") then return false end if part:FindFirstChild("RobloxStamper") or part:FindFirstChild("RobloxModel") then return true end if part:IsA("Part") and not part.CanCollide then return false end if part == game.Workspace then return true end if part == game.Lighting then return false end return isBlocker(part.Parent) end function onInsertMouseButton1Up() if guiScriptIsLoadingSomething or variables.InsertAsset.Updated.Value then return end -- don't try to stamp while we're loading! if eyeDropperConnection then eyeDropperConnection:disconnect() eyeDropperConnection = nil return end if Data.Stamp.MouseDown then Data.Stamp.MouseDown = false if Data.Stamp.Dragger then --Place the object where the mouse is currently positioned -- For Restricting Stamper Tool while Data.Stamp.MouseLock do wait() end onInsertMouseMove() Data.Stamp.MouseLock = true if(isRestricted) then -- if player tries to stamp out of bounds, tell them they can't if not inBounds(Data.Stamp.TransparencyTable) then variables.ShowInvalidPlacement.Value = true Data.Stamp.MouseLock = false HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil return end end -- if unstampable face, then don't let us stamp there! if unstampableSurface then Data.Stamp.MouseLock = false HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil flashRedBox() return end -- Prevent part from being stamped on top of a player local minBB, maxBB = getBoundingBoxInWorldCoordinates(Data.Stamp.CurrentParts[1]) local configFound, targetCFrame = findConfigAtMouseTarget(Data.Stamp.TransparencyTable) if configFound and not HighScalabilityLine.Adorn.Parent then if clusterPartsInRegion(minBB+insertBoundingBoxOverlapVector, maxBB-insertBoundingBoxOverlapVector) then Data.Stamp.MouseLock = false flashRedBox() return end local blockingParts = game.Workspace:FindPartsInRegion3(Region3.new(minBB+insertBoundingBoxOverlapVector, maxBB-insertBoundingBoxOverlapVector), Data.Stamp.CurrentParts[1], 100) for b = 1, #blockingParts do -- below if we only want to block stamping on self --if blockingParts[b].Parent == script.Parent.Parent then return end -- below if we want to block stamping on self and also stamping that intersects a model we've previously stamped on our baseplate -- should work as soon as Region3 queries are fixed -- NOTE TO SELF: See if partModel applies when isRestricted is false --if blockingParts[b]:IsDescendantOf(partModel) or blockingParts[b].Parent == script.Parent.Parent then Data.Stamp.MouseLock = false flashRedBox() break end --return end if isBlocker(blockingParts[b]) then Data.Stamp.MouseLock = false flashRedBox() return end-- still error if overlapping another model [should return here too!] end local alreadyPushedUp = {} -- if no blocking model below, then see if stamping on top of a character for b = 1, #blockingParts do --if blockingParts[b].Parent == script.Parent.Parent then -- local blockingPersonTorso = script.Parent.Parent:FindFirstChild("Torso") if blockingParts[b].Parent and not alreadyPushedUp[blockingParts[b].Parent] and blockingParts[b].Parent:FindFirstChild("Humanoid") and blockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then local blockingPersonTorso = blockingParts[b].Parent:FindFirstChild("Torso") alreadyPushedUp[blockingParts[b].Parent] = true if blockingPersonTorso then -- 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) local newY = maxBB.Y + 3 if spaceAboveCharacter(blockingPersonTorso, newY) then blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0) else -- if no space, we just error Data.Stamp.MouseLock = false flashRedBox() return -- should return here too! end end --break end end elseif (not configFound) then HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil Data.Stamp.MouseLock = false return end -- if no config then don't stamp! -- something will be stamped! so set the "StampedSomething" toggle to true local localChar = game.Players.LocalPlayer.Character if localChar then local stampTracker = localChar:FindFirstChild("StampTracker") if stampTracker and not stampTracker.Value then stampTracker.Value = true end end -- also, show hints again if high scalability part HighScalabilityLine.NewHint = true if Data.Stamp.Model and Data.Stamp.Model:GetChildren()[1] and Data.Stamp.Model:GetChildren()[1].Name == "MegaClusterCube" then local line = HighScalabilityLine.InternalLine local cMax = game.Workspace.Terrain.MaxExtents.Max local cMin = game.Workspace.Terrain.MaxExtents.Min local clusterMaterial = 1 -- default is grass local clusterType = 0 -- default is brick local clusterOrientation = 0 -- default is 0 rotation local autoWedgeClusterParts = false if Data.Stamp.CurrentParts[1]:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end if Data.Stamp.CurrentParts[1]:FindFirstChild("ClusterMaterial") then clusterMaterial = Data.Stamp.CurrentParts[1].ClusterMaterial if (clusterMaterial:IsA("Vector3Value")) then -- extract all info from vector clusterType = clusterMaterial.Value.Y clusterOrientation = clusterMaterial.Value.Z clusterMaterial = clusterMaterial.Value.X else clusterMaterial = clusterMaterial.Value end end if HighScalabilityLine.Adorn.Parent and HighScalabilityLine.Start and ((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0)) then local startCell = game.Workspace.Terrain:WorldToCell(HighScalabilityLine.Start) local xInc = {0,0,0} local yInc = {0,0,0} local zInc = {0,0,0} local incrementVect = {nil, nil, nil} local stepVect = {Vector3.new(0, 0, 0), Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)} local worldAxes = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)} local lines = {} if HighScalabilityLine.Dimensions > 1 then table.insert(lines, HighScalabilityLine.MoreLines[1]) end if line and line.magnitude > 0 then table.insert(lines, line) end if HighScalabilityLine.Dimensions > 2 then table.insert(lines, HighScalabilityLine.MoreLines[2]) end for i = 1, #lines do 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 if lines[i].X > 0 then xInc[i] = 1 elseif lines[i].X < 0 then xInc[i] = -1 end if lines[i].Y > 0 then yInc[i] = 1 elseif lines[i].Y < 0 then yInc[i] = -1 end if lines[i].Z > 0 then zInc[i] = 1 elseif lines[i].Z < 0 then zInc[i] = -1 end incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i]) if incrementVect[i].magnitude < .9 then incrementVect[i] = nil end end if not lines[2] then lines[2] = Vector3.new(0, 0, 0) end if not lines[3] then lines[3] = Vector3.new(0, 0, 0) end while (stepVect[3].magnitude*4 <= lines[3].magnitude) do local outerStepVectIndex = 1 while outerStepVectIndex < 4 do stepVect[2] = Vector3.new(0, 0, 0) while (stepVect[2].magnitude*4 <= lines[2].magnitude) do local innerStepVectIndex = 1 while innerStepVectIndex < 4 do stepVect[1] = Vector3.new(0, 0, 0) while (stepVect[1].magnitude*4 <= lines[1].magnitude) do local stepVectSum = stepVect[1] + stepVect[2] + stepVect[3] local 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 then -- check if overlaps player or part: local cellCenter = game.Workspace.Terrain:CellCenterToWorld(cellPos.X, cellPos.Y, cellPos.Z) local cellBlockingParts = game.Workspace:FindPartsInRegion3(Region3.new(cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector, cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector), Data.Stamp.CurrentParts[1], 100) local skipThisCell = false for b = 1, #cellBlockingParts do if isBlocker(cellBlockingParts[b]) then skipThisCell = true break end end if not skipThisCell then -- pop players up above any set cells local alreadyPushedUp = {} -- if no blocking model below, then see if stamping on top of a character for b = 1, #cellBlockingParts do if cellBlockingParts[b].Parent and not alreadyPushedUp[cellBlockingParts[b].Parent] and cellBlockingParts[b].Parent:FindFirstChild("Humanoid") and cellBlockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then local blockingPersonTorso = cellBlockingParts[b].Parent:FindFirstChild("Torso") alreadyPushedUp[cellBlockingParts[b].Parent] = true if blockingPersonTorso then -- 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) local newY = cellCenter.Y + 5 if spaceAboveCharacter(blockingPersonTorso, newY) then 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 end end end end end if not skipThisCell then -- if we STILL aren't skipping... then we're good to go! -- set it cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation) -- auto-wedge it? if (autoWedgeClusterParts) then 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))) end end end stepVect[1] = stepVect[1] + incrementVect[1] end if incrementVect[2] then while innerStepVectIndex < 4 and worldAxes[innerStepVectIndex]:Dot(incrementVect[2]) == 0 do innerStepVectIndex = innerStepVectIndex + 1 end if innerStepVectIndex < 4 then stepVect[2] = stepVect[2] + worldAxes[innerStepVectIndex]*worldAxes[innerStepVectIndex]:Dot(incrementVect[2]) end innerStepVectIndex = innerStepVectIndex + 1 else stepVect[2] = Vector3.new(1, 0, 0) innerStepVectIndex = 4 end -- skip all remaining loops if (stepVect[2].magnitude*4 > lines[2].magnitude) then innerStepVectIndex = 4 end end end wait() if incrementVect[3] then while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) == 0 do outerStepVectIndex = outerStepVectIndex + 1 end if outerStepVectIndex < 4 then stepVect[3] = stepVect[3] + worldAxes[outerStepVectIndex]*worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) end outerStepVectIndex = outerStepVectIndex + 1 else stepVect[3] = Vector3.new(1, 0, 0) outerStepVectIndex = 4 end -- skip all remaining loops if (stepVect[3].magnitude*4 > lines[3].magnitude) then outerStepVectIndex = 4 end end end -- and also get rid of any HighScalabilityLine stuff if it's there HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil Data.Stamp.MouseLock = false return end -- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff] HighScalabilityLine.Start = nil HighScalabilityLine.Adorn.Parent = nil -- if target point is in cluster, just use cluster:SetCell if cluster then -- if targetCFrame is inside cluster, just set that cell to 1 and return local cellPos = cluster:WorldToCell(targetCFrame.p) 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 cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation) -- auto-wedge it if (autoWedgeClusterParts) then 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))) end Data.Stamp.MouseLock = false return end end end -- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then if Data.Stamp.CurrentParts[1]:IsA("Model") then -- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X< manualWeldTable = {} manualWeldParentTable = {} saveTheWelds(Data.Stamp.CurrentParts[1]) Data.Stamp.CurrentParts[1]:BreakJoints() Data.Stamp.CurrentParts[1]:MakeJoints() restoreTheWelds() end -- 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 = Data.Stamp.CurrentParts[1]:FindFirstChild("PlayerIdTag") playerNameTag = Data.Stamp.CurrentParts[1]:FindFirstChild("PlayerNameTag") if playerIdTag ~= nil then tempPlayerValue = getPlayer() if tempPlayerValue ~= nil then playerIdTag.Value = tempPlayerValue.userId end end if playerNameTag ~= nil then tempPlayerValue = getPlayer() if tempPlayerValue ~= nil then playerNameTag.Value = tempPlayerValue.Name end end -- ...and tag all inserted models for subsequent origin identification -- if no RobloxModel tag already exists, then add it. if Data.Stamp.CurrentParts[1]:FindFirstChild("RobloxModel") == nil then local stringTag = Instance.new("BoolValue", Data.Stamp.CurrentParts[1]) stringTag.Name = "RobloxModel" if Data.Stamp.CurrentParts[1]:FindFirstChild("RobloxStamper") == nil then local stringTag2 = Instance.new("BoolValue", Data.Stamp.CurrentParts[1]) stringTag2.Name = "RobloxStamper" end end else Data.Stamp.CurrentParts[1]:BreakJoints() if Data.Stamp.CurrentParts[1]:FindFirstChild("RobloxStamper") == nil then local stringTag2 = Instance.new("BoolValue", Data.Stamp.CurrentParts[1]) stringTag2.Name = "RobloxStamper" end end -- make sure all the joints are activated before restoring anchor states if not createJoints then game.JointsService:CreateJoinAfterMoveJoints() end click.Value = true --Fix the transparency and material of all the parts for part, transparency in pairs(Data.Stamp.TransparencyTable) do part.Transparency = transparency part.archivable = true end for part, material in pairs(Data.Stamp.MaterialTable) do part.Material = material end for part, collide in pairs(Data.Stamp.CanCollideTable) do part.CanCollide = collide end for part, anchored in pairs(Data.Stamp.AnchoredTable) do part.Anchored = anchored end for decal, transparency in pairs(Data.Stamp.DecalTransparencyTable) do decal.Transparency = transparency end if (Data.Stamp.CurrentParts[1] and Data.Stamp.CurrentParts[1].Name == "MegaClusterCube") then Data.Stamp.CurrentParts[1].Transparency = 0 end -- re-enable all seats setSeatEnabledStatus(Data.Stamp.CurrentParts[1], true) Data.Stamp.TransparencyTable = nil Data.Stamp.MaterialTable = nil Data.Stamp.CanCollideTable = nil Data.Stamp.AnchoredTable = nil -- ...and tag all inserted models for subsequent origin identification -- if no RobloxModel tag already exists, then add it. if Data.Stamp.CurrentParts[1]:FindFirstChild("RobloxModel") == nil then local stringTag = Instance.new("BoolValue", Data.Stamp.CurrentParts[1]) stringTag.Name = "RobloxModel" end -- set our object back to the player's plate Data.Stamp.CurrentParts[1].Parent = partModel --Re-enable the scripts for index,script in pairs(Data.Stamp.DisabledScripts) do script.Disabled = false end --Now that they are all marked enabled, reinsert them into the world so they start running for index,script in pairs(Data.Stamp.DisabledScripts) do local oldParent = script.Parent script.Parent = nil script:Clone().Parent = oldParent end Data.Stamp.DisabledScripts = nil Data.Stamp.Dragger = nil Data.Stamp.CurrentParts.Parent = nil Data.Stamp.CurrentParts = nil Data.Stamp.MouseLock = false --Now set up a new instance of the object to allow a second copy to be stamped down variables.Stamped.Value = true variables.Stamped.Value = false if isRestricted then if checkPartLimit() then setupDraggableClone() else variables.ShowMaxedOut.Value = true end else setupDraggableClone() end end end end ------------------------ EyeDropper Code ------------------------------------------- function onEyeDropperMouseButton1Down() if eyeDropperMoveConnection then eyeDropperMoveConnection:disconnect() end clearSelection() if Mouse then Mouse.Icon = "http://www.roblox.com/asset?id=66887745" end -- deny any attempt to clone something that wasn't stamped using the Stamper tool if not Mouse or not Mouse.Target then startEyeDropperOperation() return end local isTerrainEyedroppering = false if Mouse.Target == game.Workspace.Terrain then -- want to eyedropper a terrain piece; gotta do a little extra footwork print("Eyedroppering Terrain Piece") local newTerrainPiece -- gotta make the fake part here local hitCell = game.Workspace.Terrain:WorldToCellPreferSolid(Mouse.Hit.p) local cellMat, cellType, cellOrient = game.Workspace.Terrain:GetCell(hitCell.x, hitCell.y, hitCell.z) if (cellType.Value == 1 or cellType.Value == 4) then newTerrainPiece = Instance.new("WedgePart") newTerrainPiece.formFactor = "Custom" elseif (cellType.Value == 2) then newTerrainPiece = Instance.new("CornerWedgePart") else newTerrainPiece = Instance.new("Part") newTerrainPiece.formFactor = "Custom" end 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.Value) local sideways = 0 local flipped = math.pi if cellType.Value == 4 then sideways = -math.pi/2 end if cellType.Value == 2 or cellType.Value == 3 then flipped = 0 end newTerrainPiece.CFrame = CFrame.Angles(0, math.pi/2*cellOrient.Value + flipped, sideways) if cellType.Value == 3 then local 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 end local materialTag = Instance.new("Vector3Value") materialTag.Value = Vector3.new(cellMat.Value, cellType.Value, cellOrient.Value) materialTag.Name = "ClusterMaterial" materialTag.Parent = newTerrainPiece local tempModel = Instance.new("Model") newTerrainPiece.Parent = tempModel Data.Stamp.Model = tempModel Data.Stamp.Model:BreakJoints() isTerrainEyedroppering = true end local stamperTag = Mouse.Target.Parent:FindFirstChild("RobloxStamper") if stamperTag == nil then stamperTag = Mouse.Target:FindFirstChild("RobloxStamper") end if stamperTag == nil and not isTerrainEyedroppering then startEyeDropperOperation() return end local eyeDropperInstance -- find out if the target part is part of a Roblox Set Model local robloxModelTag = Mouse.Target.Parent:FindFirstChild("RobloxModel") if robloxModelTag ~= nil then eyeDropperInstance = Mouse.Target.Parent else eyeDropperInstance = Mouse.Target end -- do not allow certain objects to be captured with eye-dropper -- for now, locked parts if eyeDropperInstance:IsA("Part") and eyeDropperInstance.Locked and not isTerrainEyedroppering then startEyeDropperOperation() else if not isTerrainEyedroppering then local cloneInstance = eyeDropperInstance:clone() local tempModel = Instance.new("Model") cloneInstance.Parent = tempModel -- once more, we make sure it's on grid before eyedroppering if not isOnGrid(tempModel) then startEyeDropperOperation() return end Data.Stamp.Model = tempModel Data.Stamp.Model:BreakJoints() end -- will create and position clone without requiring user to move the mouse if isRestricted then if checkPartLimit() then setupDraggableClone() if Mouse and not mouseButton1UpCon then mouseButton1UpCon = Mouse.Button1Up:connect(onInsertMouseButton1Up) end else variables.ShowMaxedOut.Value = true end else setupDraggableClone() if Mouse and not mouseButton1UpCon then mouseButton1UpCon = Mouse.Button1Up:connect(onInsertMouseButton1Up) end end variables.SwitchLoaderToDialog.AssetImage.Value = "0" variables.SwitchLoaderToDialog.DialogType.Value = "SideDialog" variables.SwitchLoaderToDialog.Value = true end end function onEyeDropperMouseMove() if not(inGui) and not(inPalette) then if not Mouse or not Mouse.Target then clearSelection() return end local part = Mouse.Target if part:IsA("Terrain") and Mouse.Hit then selectionBox.Color = BrickColor.Green() setTerrainSelection(Mouse.Hit.p) elseif canEyeDropperObject(part) then local model = findModel(part) if model and isOnGrid(model) then selectionBox.Color = BrickColor.Green() setSelection(model) elseif (not model) and isOnGrid(part) then selectionBox.Color = BrickColor.Green() setSelection(part) else clearSelection() end else clearSelection() end end end function startEyeDropperOperation() cancelAssetPlacement() pressedEsc = false signalInsertComplete("EyeDropper") if mouseButton1UpCon then mouseButton1UpCon:disconnect() mouseButton1UpCon = nil end if eyeDropperConnection then eyeDropperConnection:disconnect() eyeDropperConnection = nil end if eyeDropperMoveConnection then eyeDropperMoveConnection:disconnect() end if Mouse then --Mouse.Icon ="rbxasset://textures//DropperCursor.png" Mouse.Icon = "http://www.roblox.com/asset?id=67163166" eyeDropperConnection = Mouse.Button1Up:connect(onEyeDropperMouseButton1Down) eyeDropperMoveConnection = Mouse.Move:connect(onEyeDropperMouseMove) end end function findModel(part) if isRestricted then while part ~= nil do if part.className == "Model" and part.Name ~= playerModel.Name and part.Name ~= "GarbageParts" then return part elseif part.Name == playerModel.Name or part.Name == "GarbageParts" then return nil end part = part.Parent end return nil else while part ~= game.Workspace do if part:FindFirstChild("RobloxModel") then return part end part = part.Parent end return nil end end ------------------------ End EyeDropper Code --------------------------------------- ------------------------ Start Selection Highlighting Code -------------------------- function setTerrainSelection(point) if selectionBox then local cell = game.Workspace.Terrain:WorldToCellPreferSolid(point) local cellCenter = game.Workspace.Terrain:CellCenterToWorld(cell.X, cell.Y, cell.Z) terrainSelectionBox.CFrame = CFrame.new(cellCenter) selectionBox.Adornee = terrainSelectionBox end end function setSelection(partOrModel) if partOrModel ~= currentSelection then clearSelection() currentSelection = partOrModel selectionBox.Adornee = currentSelection end end function clearSelection() if currentSelection ~= nil then for part, color in pairs(currentSelectionColors) do part.BrickColor = color end selectionBox.Adornee = nil end currentSelectionColors = {} -- I put these inside if statements, because we can't assume these exist. (Jahr, 12-29-2010) if currentSelection then currentSelection = nil end if selectionBox then selectionBox.Adornee = nil end end ------------------------ End Selection Highlighting Code -------------------------- function autoAlignToFace() local aatf = Data.Stamp.CurrentParts[1]:FindFirstChild("AutoAlignToFace") if aatf then return aatf.Value else return false end end function autoAlignHelper() local model = Data.Stamp.CurrentParts[1] local aatfTag = model:FindFirstChild("AutoAlignToFace") if not aatfTag then aatfTag = Instance.new("IntValue") aatfTag.Name = "AutoAlignToFace" aatfTag.Parent = Data.Stamp.CurrentParts[1] aatfTag.Value = 3 end aatfTag.Value = aatfTag.Value + 1 if aatfTag.Value > 3 then aatfTag.Value = 0 end end function unstampableFaceHelper() if not Mouse or not Mouse.Target then return end local model = Mouse.Target if not model then return end if not model:FindFirstChild("RobloxModel") then model = model.Parent end if not model then return end if not model:FindFirstChild("RobloxModel") then return end local ufhTag = model:FindFirstChild("UnstampableFaces") if not ufhTag then ufhTag = Instance.new("StringValue") ufhTag.Name = "UnstampableFaces" ufhTag.Parent = model ufhTag.Value = "" end local hitFace = modelTargetSurface(model, game.Workspace.CurrentCamera.CoordinateFrame.p, Mouse.Hit.p) -- put string list into table form breakingFaceList = {} for bf = -3, 3 do breakingFaceList[bf] = false end for bf in string.gmatch(ufhTag.Value, "[^,]+") do breakingFaceList[tonumber(bf)] = true end -- toggle value of hit face breakingFaceList[hitFace] = not breakingFaceList[hitFace] -- put table form back into string value ufhTag.Value = "" local seenAValueSoFar = false for bf = -3, 3 do if bf ~= 0 then -- ignore 0 face, since that doesn't exist if breakingFaceList[bf] then if seenAValueSoFar then ufhTag.Value = ufhTag.Value .. "," .. tostring(bf) else ufhTag.Value = tostring(bf) seenAValueSoFar = true end end end end end function justificationHelper(whichAxis) local model = Data.Stamp.CurrentParts[1] local justTag = model:FindFirstChild("Justification") if not justTag then justTag = Instance.new("Vector3Value") justTag.Name = "Justification" justTag.Parent = Data.Stamp.CurrentParts[1] justTag.Value = Vector3.new(1, 1, 1) end local oldValue = justTag.Value if whichAxis == 1 then if oldValue.X == 2 then justTag.Value = Vector3.new(0, oldValue.Y, oldValue.Z) else justTag.Value = Vector3.new(oldValue.X+1, oldValue.Y, oldValue.Z) end elseif whichAxis == 2 then if oldValue.Y == 2 then justTag.Value = Vector3.new(oldValue.X, 0, oldValue.Z) else justTag.Value = Vector3.new(oldValue.X, oldValue.Y+1, oldValue.Z) end elseif whichAxis == 3 then if oldValue.Z == 2 then justTag.Value = Vector3.new(oldValue.X, oldValue.Y, 0) else justTag.Value = Vector3.new(oldValue.X, oldValue.Y, oldValue.Z+1) end end end function onInsertKeyDown(key) key = string.lower(key) if Data.Stamp.Dragger then if key == 'c' and Data.Stamp.CurrentParts[1].Name == "MegaClusterCube" and HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 and HighScalabilityLine.Dimensions < 3 then HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1 HighScalabilityLine.NewHint = true end if key == 'r' and not autoAlignToFace() then -- Update orientation value if this is a fake terrain part if Data.Stamp.CurrentParts[1].Name == "MegaClusterCube" then local clusterValues = Data.Stamp.CurrentParts[1]:FindFirstChild("ClusterMaterial") if clusterValues and clusterValues:IsA("Vector3Value") then clusterValues.Value = Vector3.new(clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z+1)%4) end end -- Rotate the parts or all the parts in the model local ry = math.pi/2 local rotCF = CFrame.fromEulerAnglesXYZ(0, ry, 0) gInitial90DegreeRotations = gInitial90DegreeRotations + 1 if Data.Stamp.CurrentParts[1]:IsA("Model") or Data.Stamp.CurrentParts[1]:IsA("Tool") then for i, object in pairs(Data.Stamp.CurrentParts[1]:GetChildren()) do if object:IsA("Flag") then object = object.Handle end if object:IsA("Part") or object:IsA("TrussPart") or object:IsA("WedgePart") or object:IsA("CornerWedgePart") or object:IsA("Seat") or object:IsA("VehicleSeat") then object.CFrame = rotCF * object.CFrame end end else Data.Stamp.CurrentParts[1].CFrame = rotCF * Data.Stamp.CurrentParts[1].CFrame end -- After rotating, update the position configFound, targetCFrame = findConfigAtMouseTarget(Data.Stamp.TransparencyTable) if configFound then positionPartsAtCFrame3(Data.Stamp.CurrentParts[1], targetCFrame) -- update everything else in MouseMove onInsertMouseMove() end end end if key == 'e' then startEyeDropperOperation() elseif key == 'q' then pressedEsc = true if eyeDropperConnection then eyeDropperConnection:disconnect() eyeDropperConnection = nil end if eyeDropperMoveConnection then eyeDropperMoveConnection:disconnect() end if Mouse and not mouseButton1UpCon then mouseButton1UpCon = Mouse.Button1Up:connect(onInsertMouseButton1Up) end clearSelection() cancelAssetPlacement() Data.Loading.Cancelled = true signalInsertComplete("Main") end -- admin-only tools: helpful functions for construction purposes if adminAccess then if key == 'L' or key == 'l' then -- autoalign helper if Data.Stamp.Dragger then autoAlignHelper() onInsertMouseMove() end elseif key == 'U' or key == 'u' then -- unstampable face helper unstampableFaceHelper() --elseif key == 'C' or key == 'c' then -- if Data.Stamp.Dragger then justificationHelper() end -- the 'c' stands for "Centering" since "j" is already reserved by GuiScript --end elseif key == 'X' or key == 'x' then -- change x justification if Data.Stamp.Dragger then justificationHelper(1) onInsertMouseMove() end elseif key == 'Y' or key == 'y' then -- change y justification if Data.Stamp.Dragger then justificationHelper(2) onInsertMouseMove() end elseif key == 'Z' or key == 'z' then -- change z justification if Data.Stamp.Dragger then justificationHelper(3) onInsertMouseMove() end end end end function onEquippedLocal(newMouse) Mouse = newMouse player = getPlayer() -- if equip goes through while in backpack (so getPlayer() returns nil), we don't want to process anything else if not player then return end --[[if game:FindFirstChild("NetworkClient") and game.CoreGui.Version >= 7 then only show billboards in online mode if not billBoardOwnerGui then billBoardOwnerGui = generateOwnerGui(player.Name) end end]] if isRestricted then if game.Workspace:FindFirstChild("BuildingAreas") then local areas = game.Workspace.BuildingAreas:GetChildren() for i = 1, #areas do if areas[i]:FindFirstChild("Player") and areas[i].Player.Value == Tool.Parent.Name then playerModel = areas[i]:FindFirstChild("PlayerArea") break end end end else playerModel = game.Workspace -- give them a topHint gui too, if they don't have it since the place won't already have it if not player.PlayerGui:FindFirstChild("topHint") then local topHintGui = script.Parent:FindFirstChild("topHint") if topHintGui then topHintGui:Clone().Parent = player.PlayerGui end end end if not playerModel then return end -- used to move object when walking cameraChangeCon = game.Players.LocalPlayer.Character.Humanoid.Running:connect(function(speed) if speed > 0 then walking = true while walking and cameraChangeCon do onInsertMouseMove() wait(1.0/30.0) end else walking = false end end) if Mouse then mouseMoveCon = newMouse.Move:connect(onInsertMouseMove) mouseButton1DownCon = newMouse.Button1Down:connect(onInsertMouseButton1Down) mouseButton1UpCon = newMouse.Button1Up:connect(onInsertMouseButton1Up) newMouse.KeyDown:connect(onInsertKeyDown) end if(isRestricted) then local takenAreas = game.Workspace.BuildingAreas:GetChildren() waitForChild(player, "playerNumber") if(player.playerNumber.Value == 0) then buildingPlate = nil partModel = nil else waitForChild(game.Workspace, "BuildingAreas") local buildingAreas = game.Workspace.BuildingAreas waitForChild(buildingAreas, "Area"..tostring(player.playerNumber.Value)) local targetArea = buildingAreas:FindFirstChild("Area"..tostring(player.playerNumber.Value)) waitForChild(targetArea, "PlayerArea") waitForChild(targetArea.PlayerArea, "BasePlate") buildingPlate = targetArea.PlayerArea.BasePlate partModel = targetArea.PlayerArea end else partModel = game.Workspace end selectionBox = Instance.new("SelectionBox") selectionBox.Name = "Model Delete Selection" selectionBox.Color = BrickColor.Red() selectionBox.Adornee = nil selectionBox.Parent = player.PlayerGui alreadyMoving = false end function onUnequipped() Mouse = nil if mouseMoveCon then mouseMoveCon:disconnect() end if mouseButton1DownCon then mouseButton1DownCon:disconnect() end if mouseButton1UpCon then mouseButton1UpCon:disconnect() end if cameraChangeCon then cameraChangeCon:disconnect() cameraChangeCon = nil end if billBoardOwnerGui then billBoardOwnerGui:Remove() end cancelAssetPlacement() clearSelection() if selectionBox then selectionBox:Remove() end end ---------------------------------------------------------------------------------------- -- Lua Start Script Tool.Equipped:connect(function(newMouse) onEquippedLocal(newMouse) end) waitForChild(variables,"InsertAsset") waitForChild(variables.InsertAsset, "Updated") variables.InsertAsset.Updated.Changed:connect(function(prop) if variables.InsertAsset.Updated.Value == true then pressedEsc = false beginInsertAssetStamp(variables.InsertAsset.AssetName.Value, variables.InsertAsset.AssetId.Value, variables.InsertAsset.Image.Value, variables.InsertAsset.StampMode.Value) variables.InsertAsset.Updated.Value = false end end) waitForChild(variables, "SwitchMode") waitForChild(variables.SwitchMode, "Mode") variables.SwitchMode.Changed:connect(function() if variables.SwitchMode.Value == true then if variables.SwitchMode.Mode.Value == "Clone" then startEyeDropperOperation() end variables.SwitchMode.Value = false end end) waitForChild(variables, "ReloadCurrentAsset") variables.ReloadCurrentAsset.Changed:connect(function() if variables.ReloadCurrentAsset.Value == true then setupDraggableClone() variables.ReloadCurrentAsset.Value = false end end) Tool.Unequipped:connect(function() onUnequipped() end) ---------------------------------------------------------------------------------------- topHint false 4288914085 1 4279970357 1 false false _preview 0 0 0 0 1 0 1 0 0 false 1 false 4288914085 0 4279970357 1 false false Bkg 0.5 -250 0 10 0 400 0 40 0 3 true 1 true true 4288914085 1 4279970357 1 false false 0 5 false Label 0.25 0 0 10 false 0.5 0 0 40 0 0 Saved changes 4294967295 4278190080 1 0 false 2 1 true 1 true Add -- This script's job is to destroy previous topHint instance (if exists) and clone and run a new _topHint script (naming it "topHint") -- When this script is finished, it disables and resets itself. function waitForChild(instance, name) while not instance:FindFirstChild(name) do instance.ChildAdded:wait() end end function waitForNoChild(instance, name) while instance:FindFirstChild(name) do instance.ChildRemoved:wait() end end function waitForAnyChild(instance) while not instance:GetChildren()[1] do instance.ChildAdded:wait() end end waitForChild(script, "Label") waitForChild(script, "Time") waitForChild(script, "Width") waitForChild(script.Parent, "_topHint") waitForChild(script.Parent._topHint, "Label") waitForChild(script.Parent._topHint, "HideTime") waitForChild(script.Parent._topHint, "Width") local width = 0.55--script.Width.Value --width = math.min(0.55,width/script.Parent.AbsoluteSize.X) local quickReplace = true -- If there's already an instance, do you quickly replace the text and width (true), or animate out then in? (false) if (script.Parent:FindFirstChild("topHint")~=nil) then -- If there's already an instance... if(quickReplace) then -- If quickReplace is true... local bkg = script.Parent.Bkg local label = script.Parent.Label local ys = bkg.Position.Y.Scale local yo = bkg.Position.Y.Offset bkg.Position = UDim2.new((1 - width)/2,0, ys, yo) bkg.Size = UDim2.new(width,0, 0, 40) label.Text = script.Label.Value script.Parent.topHint.HideTime.Value = time() + script.Time.Value else -- If quickReplace is false... script.Parent.Delete.Disabled = false -- Wait for instance to be removed waitForNoChild(script.Parent, "topHint") waitForNoChild(script.Parent, "Bkg") waitForNoChild(script.Parent, "Label") end else -- There's no previous instance. -- Clone and enable a new instance local topHint = script.Parent._topHint:Clone() topHint.Parent = script.Parent topHint.Name = "topHint" topHint.Label.Value = script.Label.Value topHint.HideTime.Value = time() + script.Time.Value topHint.Width.Value = width topHint.Disabled = false end script.Disabled = true Width 0 Label Time 0 true _topHint function waitForChild(instance, name) while not instance:FindFirstChild(name) do instance.ChildAdded:wait() end end function waitForNoChild(instance, name) while instance:FindFirstChild(name) do instance.ChildRemoved:wait() end end function waitForAnyChild(instance) while not instance:GetChildren()[1] do instance.ChildAdded:wait() end end -- This script creates the graphics for the new topHint, and animates it. -- Before calling this script, we've already made sure: there's no other "topHint", "Bkg", and "Label" -- Then, if time~=0, it waits "time" and then calls "Remove" script waitForChild(script, "Label") waitForChild(script, "HideTime") waitForChild(script, "Width") local container = script.Parent local labelText = script.Label.Value local hideTime = script.HideTime.Value local width = script.Width.Value -- SETTINGS local tweenTime = 0.3 -- animation time (seconds) local topY = -50 local bottomY = 10 local easing = "Sine.easeInOut" -- local bkg = Instance.new("Frame") bkg.Parent = container bkg.Name = "Bkg" bkg.Position = UDim2.new((1 - width)/2,0, ys, yo) bkg.Size = UDim2.new(width,0, 0, 40) bkg.Style = "RobloxRound" function destroy() script.Parent.Delete.Disabled = false end local label = Instance.new("TextButton") label.Parent = container label.Name = "Label" label.BackgroundTransparency = 1 label.FontSize = "Size14" label.Position = UDim2.new((1 - width)/2,0, 0, topY) label.Size = UDim2.new(width,0, 0, 40) label.Text = labelText label.TextColor3 = Color3.new(1, 1, 1) label.MouseButton1Click:connect(destroy) bkg:TweenPosition(UDim2.new(bkg.Position.X.Scale,bkg.Position.X.Offset,bkg.Position.Y.Scale,bottomY), Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,tweenTime,true) label:TweenPosition(UDim2.new(label.Position.X.Scale,label.Position.X.Offset,label.Position.Y.Scale,bottomY), Enum.EasingDirection.InOut,Enum.EasingStyle.Sine,tweenTime,true) if(hideTime~=0) then while(time() < hideTime) do wait(0.1) end destroy() end Width 0 Label HideTime 0 true Delete function waitForChild(instance, name) while not instance:FindFirstChild(name) do instance.ChildAdded:wait() end end function waitForNoChild(instance, name) while instance:FindFirstChild(name) do instance.ChildRemoved:wait() end end function waitForAnyChild(instance) while not instance:GetChildren()[1] do instance.ChildAdded:wait() end end local topHint = script.Parent:FindFirstChild("topHint") local bkg = script.Parent:FindFirstChild("Bkg") local label = script.Parent:FindFirstChild("Label") -- SETTINGS local tweenTime = 0.3 -- animation time (seconds) local topY = -50 -- top position (Y Offset) local easing = "Back.easeInOut" -- if(topHint~=nil) then topHint:Remove() end if(bkg~=nil) then bkg:TweenPosition(UDim2.new(bkg.Position.X.Scale,bkg.Position.X.Offset,bkg.Position.Y.Scale,topY), Enum.EasingDirection.InOut,Enum.EasingStyle.Quad,tweenTime,true) end if(label~=nil) then label:TweenPosition(UDim2.new(label.Position.X.Scale,label.Position.X.Offset,label.Position.Y.Scale,topY), Enum.EasingDirection.InOut,Enum.EasingStyle.Quad,tweenTime,true) end wait(tweenTime) if(bkg~=nil) then bkg:Remove() end if(label~=nil) then label:Remove() end script.Disabled = true true _readme -- -- Top Hint -- -- * This is a string of text that's top-centered in the window. (White text on black background) -- * It should be used for messages from the client, and never from the place creator. -- -- FEATURES: -- * Auto-hides previous messages before showing yours. -- * Always centered, regardless of length. -- * Clicking the message hides it. -- -- USE IT FOR: -- * Quick task-related (non-essential) hints -- * Showing the current modal state -- * Tool hints (for the currently equipped tool) -- * Keyboard shortcuts -- -- NEVER USE IT FOR: -- * Essential information (Use a popup instead) -- * Messages from users or place creators. This is for client messages only. -- -- ARGUMENTS: -- Label: The message -- Width: Text and background width (pixels) -- This should be removed when textWidth is added to Lua -- Time: Seconds before hiding itself (If 0, it never hides) -- -- USAGE: local topHint = script.Parent.topHint -- path to topHint ScreenGui topHint.Add.Label.Value = "Press D to return to main menu." topHint.Add.Width.Value = 290 topHint.Add.Time.Value = 7 topHint.Add.Disabled = true -- flip it off then on, in case it's currently running. topHint.Add.Disabled = false -- -- TO HIDE MANUALLY: topHint.Delete.Disabled = false -- Clicking the message also hides it.