true
null
nil
-
LoadLibrary
-
RbxGui
{1B6E0F76-A20C-48BA-998C-CADBA0FD0A53}
= 3 then
local spacing = .1 / numButtons
local buttonSize = .9 / numButtons
buttonNum = 1
while buttonNum <= numButtons do
buttonObjs[buttonNum].Position = UDim2.new(spacing*buttonNum + (buttonNum-1) * buttonSize, 0, yPos.Scale, yPos.Offset)
buttonObjs[buttonNum].Size = UDim2.new(buttonSize, 0, ySize.Scale, ySize.Offset)
buttonNum = buttonNum + 1
end
end
end
local function setSliderPos(newAbsPosX,slider,sliderPosition,bar,steps)
local newStep = steps - 1 --otherwise we really get one more step than we want
local relativePosX = math.min(1, math.max(0, (newAbsPosX - bar.AbsolutePosition.X) / bar.AbsoluteSize.X ))
local wholeNum, remainder = math.modf(relativePosX * newStep)
if remainder > 0.5 then
wholeNum = wholeNum + 1
end
relativePosX = wholeNum/newStep
local result = math.ceil(relativePosX * newStep)
if sliderPosition.Value ~= (result + 1) then --only update if we moved a step
sliderPosition.Value = result + 1
slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
end
end
local function cancelSlide(areaSoak)
areaSoak.Visible = false
end
t.CreateStyledMessageDialog = function(title, message, style, buttons)
local frame = Instance.new("Frame")
frame.Size = UDim2.new(0.5, 0, 0, 165)
frame.Position = UDim2.new(0.25, 0, 0.5, -72.5)
frame.Name = "MessageDialog"
frame.Active = true
frame.Style = Enum.FrameStyle.RobloxRound
local styleImage = Instance.new("ImageLabel")
styleImage.Name = "StyleImage"
styleImage.BackgroundTransparency = 1
styleImage.Position = UDim2.new(0,5,0,15)
if style == "error" or style == "Error" then
styleImage.Size = UDim2.new(0, 71, 0, 71)
styleImage.Image = "https://www.roblox.com/asset/?id=42565285"
elseif style == "notify" or style == "Notify" then
styleImage.Size = UDim2.new(0, 71, 0, 71)
styleImage.Image = "https://www.roblox.com/asset/?id=42604978"
elseif style == "confirm" or style == "Confirm" then
styleImage.Size = UDim2.new(0, 74, 0, 76)
styleImage.Image = "https://www.roblox.com/asset/?id=42557901"
else
return t.CreateMessageDialog(title,message,buttons)
end
styleImage.Parent = frame
local titleLabel = Instance.new("TextLabel")
titleLabel.Name = "Title"
titleLabel.Text = title
titleLabel.TextStrokeTransparency = 0
titleLabel.BackgroundTransparency = 1
titleLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
titleLabel.Position = UDim2.new(0, 80, 0, 0)
titleLabel.Size = UDim2.new(1, -80, 0, 40)
titleLabel.Font = Enum.Font.ArialBold
titleLabel.FontSize = Enum.FontSize.Size36
titleLabel.TextXAlignment = Enum.TextXAlignment.Center
titleLabel.TextYAlignment = Enum.TextYAlignment.Center
titleLabel.Parent = frame
local messageLabel = Instance.new("TextLabel")
messageLabel.Name = "Message"
messageLabel.Text = message
messageLabel.TextStrokeTransparency = 0
messageLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
messageLabel.Position = UDim2.new(0.025, 80, 0, 45)
messageLabel.Size = UDim2.new(0.95, -80, 0, 55)
messageLabel.BackgroundTransparency = 1
messageLabel.Font = Enum.Font.Arial
messageLabel.FontSize = Enum.FontSize.Size18
messageLabel.TextWrap = true
messageLabel.TextXAlignment = Enum.TextXAlignment.Left
messageLabel.TextYAlignment = Enum.TextYAlignment.Top
messageLabel.Parent = frame
CreateButtons(frame, buttons, UDim.new(0, 105), UDim.new(0, 40) )
return frame
end
t.CreateMessageDialog = function(title, message, buttons)
local frame = Instance.new("Frame")
frame.Size = UDim2.new(0.5, 0, 0.5, 0)
frame.Position = UDim2.new(0.25, 0, 0.25, 0)
frame.Name = "MessageDialog"
frame.Active = true
frame.Style = Enum.FrameStyle.RobloxRound
local titleLabel = Instance.new("TextLabel")
titleLabel.Name = "Title"
titleLabel.Text = title
titleLabel.BackgroundTransparency = 1
titleLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
titleLabel.Position = UDim2.new(0, 0, 0, 0)
titleLabel.Size = UDim2.new(1, 0, 0.15, 0)
titleLabel.Font = Enum.Font.ArialBold
titleLabel.FontSize = Enum.FontSize.Size36
titleLabel.TextXAlignment = Enum.TextXAlignment.Center
titleLabel.TextYAlignment = Enum.TextYAlignment.Center
titleLabel.Parent = frame
local messageLabel = Instance.new("TextLabel")
messageLabel.Name = "Message"
messageLabel.Text = message
messageLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
messageLabel.Position = UDim2.new(0.025, 0, 0.175, 0)
messageLabel.Size = UDim2.new(0.95, 0, .55, 0)
messageLabel.BackgroundTransparency = 1
messageLabel.Font = Enum.Font.Arial
messageLabel.FontSize = Enum.FontSize.Size18
messageLabel.TextWrap = true
messageLabel.TextXAlignment = Enum.TextXAlignment.Left
messageLabel.TextYAlignment = Enum.TextYAlignment.Top
messageLabel.Parent = frame
CreateButtons(frame, buttons, UDim.new(0.8,0), UDim.new(0.15, 0))
return frame
end
-- written by jmargh
-- to be used for the new settings menu
t.CreateScrollingDropDownMenu = function(onSelectedCallback, size, position, baseZ)
local maxVisibleList = 6
local baseZIndex = 0
if type(baseZ) == 'number' then
baseZIndex = baseZ
end
local dropDownMenu = {}
local currentList = nil
local updateFunc = nil
local frame = Instance.new('Frame')
frame.Name = "DropDownMenuFrame"
frame.Size = size
frame.Position = position
frame.BackgroundTransparency = 1
dropDownMenu.Frame = frame
local currentSelectionName = Instance.new('TextButton')
currentSelectionName.Name = "CurrentSelectionName"
currentSelectionName.Size = UDim2.new(1, 0, 1, 0)
currentSelectionName.BackgroundTransparency = 1
currentSelectionName.Font = Enum.Font.SourceSansBold
currentSelectionName.FontSize = Enum.FontSize.Size18
currentSelectionName.TextXAlignment = Enum.TextXAlignment.Left
currentSelectionName.TextYAlignment = Enum.TextYAlignment.Center
currentSelectionName.TextColor3 = Color3.new(0.5, 0.5, 0.5)
currentSelectionName.TextWrap = true
currentSelectionName.ZIndex = baseZIndex
currentSelectionName.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
currentSelectionName.Text = "Choose One"
currentSelectionName.Parent = frame
dropDownMenu.CurrentSelectionButton = currentSelectionName
local icon = Instance.new('ImageLabel')
icon.Name = "DropDownIcon"
icon.Size = UDim2.new(0, 16, 0, 12)
icon.Position = UDim2.new(1, -17, 0.5, -6)
icon.Image = 'rbxasset://textures/ui/dropdown_arrow.png'
icon.BackgroundTransparency = 1
icon.ZIndex = baseZIndex
icon.Parent = currentSelectionName
local listMenu = nil
local scrollingBackground = nil
local visibleCount = 0
local isOpen = false
local function onEntrySelected()
icon.Rotation = 0
scrollingBackground:TweenSize(UDim2.new(1, 0, 0, currentSelectionName.AbsoluteSize.y), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true)
--
listMenu.ScrollBarThickness = 0
listMenu:TweenSize(UDim2.new(1, -16, 0, 24), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true, function()
if not isOpen then
listMenu.Visible = false
scrollingBackground.Visible = false
end
end)
isOpen = false
end
currentSelectionName.MouseButton1Click:connect(function()
if not currentSelectionName.Active or #currentList == 0 then return end
if isOpen then
onEntrySelected()
return
end
--
isOpen = true
icon.Rotation = 180
if listMenu then listMenu.Visible = true end
if scrollingBackground then scrollingBackground.Visible = true end
--
if scrollingBackground then
scrollingBackground:TweenSize(UDim2.new(1, 0, 0, visibleCount * 24 + 8), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true)
end
if listMenu then
listMenu:TweenSize(UDim2.new(1, -16, 0, visibleCount * 24), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true, function()
listMenu.ScrollBarThickness = 6
end)
end
end)
--[[ Public API ]]--
dropDownMenu.IsOpen = function()
return isOpen
end
dropDownMenu.Close = function()
onEntrySelected()
end
dropDownMenu.Reset = function()
isOpen = false
icon.Rotation = 0
listMenu.ScrollBarThickness = 0
listMenu.Size = UDim2.new(1, -16, 0, 24)
listMenu.Visible = false
scrollingBackground.Visible = false
end
dropDownMenu.SetVisible = function(isVisible)
if frame then
frame.Visible = isVisible
end
end
dropDownMenu.UpdateZIndex = function(newZIndexBase)
currentSelectionName.ZIndex = newZIndexBase
icon.ZIndex = newZIndexBase
if scrollingBackground then scrollingBackground.ZIndex = newZIndexBase + 1 end
if listMenu then
listMenu.ZIndex = newZIndexBase + 2
for _,child in pairs(listMenu:GetChildren()) do
child.ZIndex = newZIndexBase + 4
end
end
end
dropDownMenu.SetActive = function(isActive)
currentSelectionName.Active = isActive
end
dropDownMenu.SetSelectionText = function(text)
currentSelectionName.Text = text
end
dropDownMenu.CreateList = function(list)
currentSelectionName.Text = "Choose One"
if listMenu then listMenu:Destroy() end
if scrollingBackground then scrollingBackground:Destroy() end
--
currentList = list
local length = #list
visibleCount = math.min(maxVisibleList, length)
local listMenuOffset = visibleCount * 24
listMenu = Instance.new('ScrollingFrame')
listMenu.Name = "ListMenu"
listMenu.Size = UDim2.new(1, -16, 0, 24)
listMenu.Position = UDim2.new(0, 12, 0, 32)
listMenu.CanvasSize = UDim2.new(0, 0, 0, length * 24)
listMenu.BackgroundTransparency = 1
listMenu.BorderSizePixel = 0
listMenu.ZIndex = baseZIndex + 2
listMenu.Visible = false
listMenu.Active = true
listMenu.BottomImage = 'rbxasset://textures/ui/scroll-bottom.png'
listMenu.MidImage = 'rbxasset://textures/ui/scroll-middle.png'
listMenu.TopImage = 'rbxasset://textures/ui/scroll-top.png'
listMenu.ScrollBarThickness = 0
listMenu.Parent = frame
scrollingBackground = Instance.new('TextButton')
scrollingBackground.Name = "ScrollingBackground"
scrollingBackground.Size = UDim2.new(1, 0, 0, currentSelectionName.AbsoluteSize.y)
scrollingBackground.Position = UDim2.new(0, 0, 0, 28)
scrollingBackground.BackgroundColor3 = Color3.new(1, 1, 1)
scrollingBackground.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
scrollingBackground.ZIndex = baseZIndex + 1
scrollingBackground.Text = ""
scrollingBackground.Visible = false
scrollingBackground.AutoButtonColor = false
scrollingBackground.Parent = frame
for i = 1, length do
local entry = list[i]
local btn = Instance.new('TextButton')
btn.Name = entry
btn.Size = UDim2.new(1, 0, 0, 24)
btn.Position = UDim2.new(0, 0, 0, (i - 1) * 24)
btn.BackgroundTransparency = 0
btn.BackgroundColor3 = Color3.new(1, 1, 1)
btn.BorderSizePixel = 0
btn.Font = Enum.Font.SourceSans
btn.FontSize = Enum.FontSize.Size18
btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
btn.TextXAlignment = Enum.TextXAlignment.Left
btn.TextYAlignment = Enum.TextYAlignment.Center
btn.Text = entry
btn.ZIndex = baseZIndex + 4
btn.AutoButtonColor = false
btn.Parent = listMenu
btn.MouseButton1Click:connect(function()
currentSelectionName.Text = btn.Text
onEntrySelected()
btn.Font = Enum.Font.SourceSans
btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
btn.BackgroundColor3 = Color3.new(1, 1, 1)
onSelectedCallback(btn.Text)
end)
btn.MouseEnter:connect(function()
btn.TextColor3 = Color3.new(1, 1, 1)
btn.BackgroundColor3 = Color3.new(0.75, 0.75, 0.75)
end)
btn.MouseLeave:connect(function()
btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
btn.BackgroundColor3 = Color3.new(1, 1, 1)
end)
end
end
return dropDownMenu
end
t.CreateDropDownMenu = function(items, onSelect, forRoblox, whiteSkin, baseZ)
local baseZIndex = 0
if (type(baseZ) == "number") then
baseZIndex = baseZ
end
local width = UDim.new(0, 100)
local height = UDim.new(0, 32)
local xPos = 0.055
local frame = Instance.new("Frame")
local textColor = Color3.new(1,1,1)
if (whiteSkin) then
textColor = Color3.new(0.5, 0.5, 0.5)
end
frame.Name = "DropDownMenu"
frame.BackgroundTransparency = 1
frame.Size = UDim2.new(width, height)
local dropDownMenu = Instance.new("TextButton")
dropDownMenu.Name = "DropDownMenuButton"
dropDownMenu.TextWrap = true
dropDownMenu.TextColor3 = textColor
dropDownMenu.Text = "Choose One"
dropDownMenu.Font = Enum.Font.ArialBold
dropDownMenu.FontSize = Enum.FontSize.Size18
dropDownMenu.TextXAlignment = Enum.TextXAlignment.Left
dropDownMenu.TextYAlignment = Enum.TextYAlignment.Center
dropDownMenu.BackgroundTransparency = 1
dropDownMenu.AutoButtonColor = true
if (whiteSkin) then
dropDownMenu.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
else
dropDownMenu.Style = Enum.ButtonStyle.RobloxButton
end
dropDownMenu.Size = UDim2.new(1,0,1,0)
dropDownMenu.Parent = frame
dropDownMenu.ZIndex = 2 + baseZIndex
local dropDownIcon = Instance.new("ImageLabel")
dropDownIcon.Name = "Icon"
dropDownIcon.Active = false
if (whiteSkin) then
dropDownIcon.Image = "rbxasset://textures/ui/dropdown_arrow.png"
dropDownIcon.Size = UDim2.new(0,16,0,12)
dropDownIcon.Position = UDim2.new(1,-17,0.5, -6)
else
dropDownIcon.Image = "https://www.roblox.com/asset/?id=45732894"
dropDownIcon.Size = UDim2.new(0,11,0,6)
dropDownIcon.Position = UDim2.new(1,-11,0.5, -2)
end
dropDownIcon.BackgroundTransparency = 1
dropDownIcon.Parent = dropDownMenu
dropDownIcon.ZIndex = 2 + baseZIndex
local itemCount = #items
local dropDownItemCount = #items
local useScrollButtons = false
if dropDownItemCount > 6 then
useScrollButtons = true
dropDownItemCount = 6
end
local droppedDownMenu = Instance.new("TextButton")
droppedDownMenu.Name = "List"
droppedDownMenu.Text = ""
droppedDownMenu.BackgroundTransparency = 1
--droppedDownMenu.AutoButtonColor = true
if (whiteSkin) then
droppedDownMenu.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
else
droppedDownMenu.Style = Enum.ButtonStyle.RobloxButton
end
droppedDownMenu.Visible = false
droppedDownMenu.Active = true --Blocks clicks
droppedDownMenu.Position = UDim2.new(0,0,0,0)
droppedDownMenu.Size = UDim2.new(1,0, (1 + dropDownItemCount)*.8, 0)
droppedDownMenu.Parent = frame
droppedDownMenu.ZIndex = 2 + baseZIndex
local choiceButton = Instance.new("TextButton")
choiceButton.Name = "ChoiceButton"
choiceButton.BackgroundTransparency = 1
choiceButton.BorderSizePixel = 0
choiceButton.Text = "ReplaceMe"
choiceButton.TextColor3 = textColor
choiceButton.TextXAlignment = Enum.TextXAlignment.Left
choiceButton.TextYAlignment = Enum.TextYAlignment.Center
choiceButton.BackgroundColor3 = Color3.new(1, 1, 1)
choiceButton.Font = Enum.Font.Arial
choiceButton.FontSize = Enum.FontSize.Size18
if useScrollButtons then
choiceButton.Size = UDim2.new(1,-13, .8/((dropDownItemCount + 1)*.8),0)
else
choiceButton.Size = UDim2.new(1, 0, .8/((dropDownItemCount + 1)*.8),0)
end
choiceButton.TextWrap = true
choiceButton.ZIndex = 2 + baseZIndex
local areaSoak = Instance.new("TextButton")
areaSoak.Name = "AreaSoak"
areaSoak.Text = ""
areaSoak.BackgroundTransparency = 1
areaSoak.Active = true
areaSoak.Size = UDim2.new(1,0,1,0)
areaSoak.Visible = false
areaSoak.ZIndex = 3 + baseZIndex
local dropDownSelected = false
local scrollUpButton
local scrollDownButton
local scrollMouseCount = 0
local setZIndex = function(baseZIndex)
droppedDownMenu.ZIndex = baseZIndex +1
if scrollUpButton then
scrollUpButton.ZIndex = baseZIndex + 3
end
if scrollDownButton then
scrollDownButton.ZIndex = baseZIndex + 3
end
local children = droppedDownMenu:GetChildren()
if children then
for i, child in ipairs(children) do
if child.Name == "ChoiceButton" then
child.ZIndex = baseZIndex + 2
elseif child.Name == "ClickCaptureButton" then
child.ZIndex = baseZIndex
end
end
end
end
local scrollBarPosition = 1
local updateScroll = function()
if scrollUpButton then
scrollUpButton.Active = scrollBarPosition > 1
end
if scrollDownButton then
scrollDownButton.Active = scrollBarPosition + dropDownItemCount <= itemCount
end
local children = droppedDownMenu:GetChildren()
if not children then return end
local childNum = 1
for i, obj in ipairs(children) do
if obj.Name == "ChoiceButton" then
if childNum < scrollBarPosition or childNum >= scrollBarPosition + dropDownItemCount then
obj.Visible = false
else
obj.Position = UDim2.new(0,0,((childNum-scrollBarPosition+1)*.8)/((dropDownItemCount+1)*.8),0)
obj.Visible = true
end
obj.TextColor3 = textColor
obj.BackgroundTransparency = 1
childNum = childNum + 1
end
end
end
local toggleVisibility = function()
dropDownSelected = not dropDownSelected
areaSoak.Visible = not areaSoak.Visible
dropDownMenu.Visible = not dropDownSelected
droppedDownMenu.Visible = dropDownSelected
if dropDownSelected then
setZIndex(4 + baseZIndex)
else
setZIndex(2 + baseZIndex)
end
if useScrollButtons then
updateScroll()
end
end
droppedDownMenu.MouseButton1Click:connect(toggleVisibility)
local updateSelection = function(text)
local foundItem = false
local children = droppedDownMenu:GetChildren()
local childNum = 1
if children then
for i, obj in ipairs(children) do
if obj.Name == "ChoiceButton" then
if obj.Text == text then
obj.Font = Enum.Font.ArialBold
foundItem = true
scrollBarPosition = childNum
if (whiteSkin) then
obj.TextColor3 = Color3.new(90/255,142/255,233/255)
end
else
obj.Font = Enum.Font.Arial
if (whiteSkin) then
obj.TextColor3 = textColor
end
end
childNum = childNum + 1
end
end
end
if not text then
dropDownMenu.Text = "Choose One"
scrollBarPosition = 1
else
if not foundItem then
error("Invalid Selection Update -- " .. text)
end
if scrollBarPosition + dropDownItemCount > itemCount + 1 then
scrollBarPosition = itemCount - dropDownItemCount + 1
end
dropDownMenu.Text = text
end
end
local function scrollDown()
if scrollBarPosition + dropDownItemCount <= itemCount then
scrollBarPosition = scrollBarPosition + 1
updateScroll()
return true
end
return false
end
local function scrollUp()
if scrollBarPosition > 1 then
scrollBarPosition = scrollBarPosition - 1
updateScroll()
return true
end
return false
end
if useScrollButtons then
--Make some scroll buttons
scrollUpButton = Instance.new("ImageButton")
scrollUpButton.Name = "ScrollUpButton"
scrollUpButton.BackgroundTransparency = 1
scrollUpButton.Image = "rbxasset://textures/ui/scrollbuttonUp.png"
scrollUpButton.Size = UDim2.new(0,17,0,17)
scrollUpButton.Position = UDim2.new(1,-11,(1*.8)/((dropDownItemCount+1)*.8),0)
scrollUpButton.MouseButton1Click:connect(
function()
scrollMouseCount = scrollMouseCount + 1
end)
scrollUpButton.MouseLeave:connect(
function()
scrollMouseCount = scrollMouseCount + 1
end)
scrollUpButton.MouseButton1Down:connect(
function()
scrollMouseCount = scrollMouseCount + 1
scrollUp()
local val = scrollMouseCount
wait(0.5)
while val == scrollMouseCount do
if scrollUp() == false then
break
end
wait(0.1)
end
end)
scrollUpButton.Parent = droppedDownMenu
scrollDownButton = Instance.new("ImageButton")
scrollDownButton.Name = "ScrollDownButton"
scrollDownButton.BackgroundTransparency = 1
scrollDownButton.Image = "rbxasset://textures/ui/scrollbuttonDown.png"
scrollDownButton.Size = UDim2.new(0,17,0,17)
scrollDownButton.Position = UDim2.new(1,-11,1,-11)
scrollDownButton.Parent = droppedDownMenu
scrollDownButton.MouseButton1Click:connect(
function()
scrollMouseCount = scrollMouseCount + 1
end)
scrollDownButton.MouseLeave:connect(
function()
scrollMouseCount = scrollMouseCount + 1
end)
scrollDownButton.MouseButton1Down:connect(
function()
scrollMouseCount = scrollMouseCount + 1
scrollDown()
local val = scrollMouseCount
wait(0.5)
while val == scrollMouseCount do
if scrollDown() == false then
break
end
wait(0.1)
end
end)
local scrollbar = Instance.new("ImageLabel")
scrollbar.Name = "ScrollBar"
scrollbar.Image = "rbxasset://textures/ui/scrollbar.png"
scrollbar.BackgroundTransparency = 1
scrollbar.Size = UDim2.new(0, 18, (dropDownItemCount*.8)/((dropDownItemCount+1)*.8), -(17) - 11 - 4)
scrollbar.Position = UDim2.new(1,-11,(1*.8)/((dropDownItemCount+1)*.8),17+2)
scrollbar.Parent = droppedDownMenu
end
for i,item in ipairs(items) do
-- needed to maintain local scope for items in event listeners below
local button = choiceButton:clone()
if forRoblox then
button.RobloxLocked = true
end
button.Text = item
button.Parent = droppedDownMenu
if (whiteSkin) then
button.TextColor3 = textColor
end
button.MouseButton1Click:connect(function()
--Remove Highlight
if (not whiteSkin) then
button.TextColor3 = Color3.new(1,1,1)
end
button.BackgroundTransparency = 1
updateSelection(item)
onSelect(item)
toggleVisibility()
end)
button.MouseEnter:connect(function()
--Add Highlight
if (not whiteSkin) then
button.TextColor3 = Color3.new(0,0,0)
end
button.BackgroundTransparency = 0
end)
button.MouseLeave:connect(function()
--Remove Highlight
if (not whiteSkin) then
button.TextColor3 = Color3.new(1,1,1)
end
button.BackgroundTransparency = 1
end)
end
--This does the initial layout of the buttons
updateScroll()
frame.AncestryChanged:connect(function(child,parent)
if parent == nil then
areaSoak.Parent = nil
else
areaSoak.Parent = getLayerCollectorAncestor(frame)
end
end)
dropDownMenu.MouseButton1Click:connect(toggleVisibility)
areaSoak.MouseButton1Click:connect(toggleVisibility)
return frame, updateSelection
end
t.CreatePropertyDropDownMenu = function(instance, property, enum)
local items = enum:GetEnumItems()
local names = {}
local nameToItem = {}
for i,obj in ipairs(items) do
names[i] = obj.Name
nameToItem[obj.Name] = obj
end
local frame
local updateSelection
frame, updateSelection = t.CreateDropDownMenu(names, function(text) instance[property] = nameToItem[text] end)
ScopedConnect(frame, instance, "Changed",
function(prop)
if prop == property then
updateSelection(instance[property].Name)
end
end,
function()
updateSelection(instance[property].Name)
end)
return frame
end
t.GetFontHeight = function(font, fontSize)
if font == nil or fontSize == nil then
error("Font and FontSize must be non-nil")
end
local fontSizeInt = tonumber(fontSize.Name:match("%d+")) -- Clever hack to extract the size from the enum itself.
if font == Enum.Font.Legacy then -- Legacy has a 50% bigger size.
return math.ceil(fontSizeInt*1.5)
else -- Size is literally just the fontSizeInt
return fontSizeInt
end
end
local function layoutGuiObjectsHelper(frame, guiObjects, settingsTable)
local totalPixels = frame.AbsoluteSize.Y
local pixelsRemaining = frame.AbsoluteSize.Y
for i, child in ipairs(guiObjects) do
if child:IsA("TextLabel") or child:IsA("TextButton") then
local isLabel = child:IsA("TextLabel")
if isLabel then
pixelsRemaining = pixelsRemaining - settingsTable["TextLabelPositionPadY"]
else
pixelsRemaining = pixelsRemaining - settingsTable["TextButtonPositionPadY"]
end
child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, pixelsRemaining)
if child.TextFits and child.TextBounds.Y < pixelsRemaining then
child.Visible = true
if isLabel then
child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.TextBounds.Y + settingsTable["TextLabelSizePadY"])
else
child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.TextBounds.Y + settingsTable["TextButtonSizePadY"])
end
while not child.TextFits do
child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.AbsoluteSize.Y + 1)
end
pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
if isLabel then
pixelsRemaining = pixelsRemaining - settingsTable["TextLabelPositionPadY"]
else
pixelsRemaining = pixelsRemaining - settingsTable["TextButtonPositionPadY"]
end
else
child.Visible = false
pixelsRemaining = -1
end
else
--GuiObject
child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
child.Visible = (pixelsRemaining >= 0)
end
end
end
t.LayoutGuiObjects = function(frame, guiObjects, settingsTable)
if not frame:IsA("GuiObject") then
error("Frame must be a GuiObject")
end
for i, child in ipairs(guiObjects) do
if not child:IsA("GuiObject") then
error("All elements that are layed out must be of type GuiObject")
end
end
if not settingsTable then
settingsTable = {}
end
if not settingsTable["TextLabelSizePadY"] then
settingsTable["TextLabelSizePadY"] = 0
end
if not settingsTable["TextLabelPositionPadY"] then
settingsTable["TextLabelPositionPadY"] = 0
end
if not settingsTable["TextButtonSizePadY"] then
settingsTable["TextButtonSizePadY"] = 12
end
if not settingsTable["TextButtonPositionPadY"] then
settingsTable["TextButtonPositionPadY"] = 2
end
--Wrapper frame takes care of styled objects
local wrapperFrame = Instance.new("Frame")
wrapperFrame.Name = "WrapperFrame"
wrapperFrame.BackgroundTransparency = 1
wrapperFrame.Size = UDim2.new(1,0,1,0)
wrapperFrame.Parent = frame
for i, child in ipairs(guiObjects) do
child.Parent = wrapperFrame
end
local recalculate = function()
wait()
layoutGuiObjectsHelper(wrapperFrame, guiObjects, settingsTable)
end
frame.Changed:connect(
function(prop)
if prop == "AbsoluteSize" then
--Wait a heartbeat for it to sync in
recalculate(nil)
end
end)
frame.AncestryChanged:connect(recalculate)
layoutGuiObjectsHelper(wrapperFrame, guiObjects, settingsTable)
end
t.CreateSlider = function(steps,width,position)
local sliderGui = Instance.new("Frame")
sliderGui.Size = UDim2.new(1,0,1,0)
sliderGui.BackgroundTransparency = 1
sliderGui.Name = "SliderGui"
local sliderSteps = Instance.new("IntValue")
sliderSteps.Name = "SliderSteps"
sliderSteps.Value = steps
sliderSteps.Parent = sliderGui
local areaSoak = Instance.new("TextButton")
areaSoak.Name = "AreaSoak"
areaSoak.Text = ""
areaSoak.BackgroundTransparency = 1
areaSoak.Active = false
areaSoak.Size = UDim2.new(1,0,1,0)
areaSoak.Visible = false
areaSoak.ZIndex = 4
sliderGui.AncestryChanged:connect(function(child,parent)
if parent == nil then
areaSoak.Parent = nil
else
areaSoak.Parent = getLayerCollectorAncestor(sliderGui)
end
end)
local sliderPosition = Instance.new("IntValue")
sliderPosition.Name = "SliderPosition"
sliderPosition.Value = 0
sliderPosition.Parent = sliderGui
local id = math.random(1,100)
local bar = Instance.new("TextButton")
bar.Text = ""
bar.AutoButtonColor = false
bar.Name = "Bar"
bar.BackgroundColor3 = Color3.new(0,0,0)
if type(width) == "number" then
bar.Size = UDim2.new(0,width,0,5)
else
bar.Size = UDim2.new(0,200,0,5)
end
bar.BorderColor3 = Color3.new(95/255,95/255,95/255)
bar.ZIndex = 2
bar.Parent = sliderGui
if position["X"] and position["X"]["Scale"] and position["X"]["Offset"] and position["Y"] and position["Y"]["Scale"] and position["Y"]["Offset"] then
bar.Position = position
end
local slider = Instance.new("ImageButton")
slider.Name = "Slider"
slider.BackgroundTransparency = 1
slider.Image = "rbxasset://textures/ui/Slider.png"
slider.Position = UDim2.new(0,0,0.5,-10)
slider.Size = UDim2.new(0,20,0,20)
slider.ZIndex = 3
slider.Parent = bar
local areaSoakMouseMoveCon = nil
areaSoak.MouseLeave:connect(function()
if areaSoak.Visible then
cancelSlide(areaSoak)
end
end)
areaSoak.MouseButton1Up:connect(function()
if areaSoak.Visible then
cancelSlide(areaSoak)
end
end)
slider.MouseButton1Down:connect(function()
areaSoak.Visible = true
if areaSoakMouseMoveCon then areaSoakMouseMoveCon:disconnect() end
areaSoakMouseMoveCon = areaSoak.MouseMoved:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
end)
slider.MouseButton1Up:connect(function() cancelSlide(areaSoak) end)
sliderPosition.Changed:connect(function(prop)
sliderPosition.Value = math.min(steps, math.max(1,sliderPosition.Value))
local relativePosX = (sliderPosition.Value - 1) / (steps - 1)
slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
end)
bar.MouseButton1Down:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
return sliderGui, sliderPosition, sliderSteps
end
t.CreateSliderNew = function(steps,width,position)
local sliderGui = Instance.new("Frame")
sliderGui.Size = UDim2.new(1,0,1,0)
sliderGui.BackgroundTransparency = 1
sliderGui.Name = "SliderGui"
local sliderSteps = Instance.new("IntValue")
sliderSteps.Name = "SliderSteps"
sliderSteps.Value = steps
sliderSteps.Parent = sliderGui
local areaSoak = Instance.new("TextButton")
areaSoak.Name = "AreaSoak"
areaSoak.Text = ""
areaSoak.BackgroundTransparency = 1
areaSoak.Active = false
areaSoak.Size = UDim2.new(1,0,1,0)
areaSoak.Visible = false
areaSoak.ZIndex = 6
sliderGui.AncestryChanged:connect(function(child,parent)
if parent == nil then
areaSoak.Parent = nil
else
areaSoak.Parent = getLayerCollectorAncestor(sliderGui)
end
end)
local sliderPosition = Instance.new("IntValue")
sliderPosition.Name = "SliderPosition"
sliderPosition.Value = 0
sliderPosition.Parent = sliderGui
local id = math.random(1,100)
local sliderBarImgHeight = 7
local sliderBarCapImgWidth = 4
local bar = Instance.new("ImageButton")
bar.BackgroundTransparency = 1
bar.Image = "rbxasset://textures/ui/Slider-BKG-Center.png"
bar.Name = "Bar"
local displayWidth = 200
if type(width) == "number" then
bar.Size = UDim2.new(0,width - (sliderBarCapImgWidth * 2),0,sliderBarImgHeight)
displayWidth = width - (sliderBarCapImgWidth * 2)
else
bar.Size = UDim2.new(0,200,0,sliderBarImgHeight)
end
bar.ZIndex = 3
bar.Parent = sliderGui
if position["X"] and position["X"]["Scale"] and position["X"]["Offset"] and position["Y"] and position["Y"]["Scale"] and position["Y"]["Offset"] then
bar.Position = position
end
local barLeft = bar:clone()
barLeft.Name = "BarLeft"
barLeft.Image = "rbxasset://textures/ui/Slider-BKG-Left-Cap.png"
barLeft.Size = UDim2.new(0, sliderBarCapImgWidth, 0, sliderBarImgHeight)
barLeft.Position = UDim2.new(position.X.Scale, position.X.Offset - sliderBarCapImgWidth, position.Y.Scale, position.Y.Offset)
barLeft.Parent = sliderGui
barLeft.ZIndex = 3
local barRight = barLeft:clone()
barRight.Name = "BarRight"
barRight.Image = "rbxasset://textures/ui/Slider-BKG-Right-Cap.png"
barRight.Position = UDim2.new(position.X.Scale, position.X.Offset + displayWidth, position.Y.Scale, position.Y.Offset)
barRight.Parent = sliderGui
local fillLeft = barLeft:clone()
fillLeft.Name = "FillLeft"
fillLeft.Image = "rbxasset://textures/ui/Slider-Fill-Left-Cap.png"
fillLeft.Parent = sliderGui
fillLeft.ZIndex = 4
local fill = fillLeft:clone()
fill.Name = "Fill"
fill.Image = "rbxasset://textures/ui/Slider-Fill-Center.png"
fill.Parent = bar
fill.ZIndex = 4
fill.Position = UDim2.new(0, 0, 0, 0)
fill.Size = UDim2.new(0.5, 0, 1, 0)
-- bar.Visible = false
local slider = Instance.new("ImageButton")
slider.Name = "Slider"
slider.BackgroundTransparency = 1
slider.Image = "rbxasset://textures/ui/slider_new_tab.png"
slider.Position = UDim2.new(0,0,0.5,-14)
slider.Size = UDim2.new(0,28,0,28)
slider.ZIndex = 5
slider.Parent = bar
local areaSoakMouseMoveCon = nil
areaSoak.MouseLeave:connect(function()
if areaSoak.Visible then
cancelSlide(areaSoak)
end
end)
areaSoak.MouseButton1Up:connect(function()
if areaSoak.Visible then
cancelSlide(areaSoak)
end
end)
slider.MouseButton1Down:connect(function()
areaSoak.Visible = true
if areaSoakMouseMoveCon then areaSoakMouseMoveCon:disconnect() end
areaSoakMouseMoveCon = areaSoak.MouseMoved:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
end)
slider.MouseButton1Up:connect(function() cancelSlide(areaSoak) end)
sliderPosition.Changed:connect(function(prop)
sliderPosition.Value = math.min(steps, math.max(1,sliderPosition.Value))
local relativePosX = (sliderPosition.Value - 1) / (steps - 1)
slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
fill.Size = UDim2.new(relativePosX, 0, 1, 0)
end)
bar.MouseButton1Down:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
fill.MouseButton1Down:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
fillLeft.MouseButton1Down:connect(function(x,y)
setSliderPos(x,slider,sliderPosition,bar,steps)
end)
return sliderGui, sliderPosition, sliderSteps
end
t.CreateTrueScrollingFrame = function()
local lowY = nil
local highY = nil
local dragCon = nil
local upCon = nil
local internalChange = false
local descendantsChangeConMap = {}
local scrollingFrame = Instance.new("Frame")
scrollingFrame.Name = "ScrollingFrame"
scrollingFrame.Active = true
scrollingFrame.Size = UDim2.new(1,0,1,0)
scrollingFrame.ClipsDescendants = true
local controlFrame = Instance.new("Frame")
controlFrame.Name = "ControlFrame"
controlFrame.BackgroundTransparency = 1
controlFrame.Size = UDim2.new(0,18,1,0)
controlFrame.Position = UDim2.new(1,-20,0,0)
controlFrame.Parent = scrollingFrame
local scrollBottom = Instance.new("BoolValue")
scrollBottom.Value = false
scrollBottom.Name = "ScrollBottom"
scrollBottom.Parent = controlFrame
local scrollUp = Instance.new("BoolValue")
scrollUp.Value = false
scrollUp.Name = "scrollUp"
scrollUp.Parent = controlFrame
local scrollUpButton = Instance.new("TextButton")
scrollUpButton.Name = "ScrollUpButton"
scrollUpButton.Text = ""
scrollUpButton.AutoButtonColor = false
scrollUpButton.BackgroundColor3 = Color3.new(0,0,0)
scrollUpButton.BorderColor3 = Color3.new(1,1,1)
scrollUpButton.BackgroundTransparency = 0.5
scrollUpButton.Size = UDim2.new(0,18,0,18)
scrollUpButton.ZIndex = 2
scrollUpButton.Parent = controlFrame
for i = 1, 6 do
local triFrame = Instance.new("Frame")
triFrame.BorderColor3 = Color3.new(1,1,1)
triFrame.Name = "tri" .. tostring(i)
triFrame.ZIndex = 3
triFrame.BackgroundTransparency = 0.5
triFrame.Size = UDim2.new(0,12 - ((i -1) * 2),0,0)
triFrame.Position = UDim2.new(0,3 + (i -1),0.5,2 - (i -1))
triFrame.Parent = scrollUpButton
end
scrollUpButton.MouseEnter:connect(function()
scrollUpButton.BackgroundTransparency = 0.1
local upChildren = scrollUpButton:GetChildren()
for i = 1, #upChildren do
upChildren[i].BackgroundTransparency = 0.1
end
end)
scrollUpButton.MouseLeave:connect(function()
scrollUpButton.BackgroundTransparency = 0.5
local upChildren = scrollUpButton:GetChildren()
for i = 1, #upChildren do
upChildren[i].BackgroundTransparency = 0.5
end
end)
local scrollDownButton = scrollUpButton:clone()
scrollDownButton.Name = "ScrollDownButton"
scrollDownButton.Position = UDim2.new(0,0,1,-18)
local downChildren = scrollDownButton:GetChildren()
for i = 1, #downChildren do
downChildren[i].Position = UDim2.new(0,3 + (i -1),0.5,-2 + (i - 1))
end
scrollDownButton.MouseEnter:connect(function()
scrollDownButton.BackgroundTransparency = 0.1
local downChildren = scrollDownButton:GetChildren()
for i = 1, #downChildren do
downChildren[i].BackgroundTransparency = 0.1
end
end)
scrollDownButton.MouseLeave:connect(function()
scrollDownButton.BackgroundTransparency = 0.5
local downChildren = scrollDownButton:GetChildren()
for i = 1, #downChildren do
downChildren[i].BackgroundTransparency = 0.5
end
end)
scrollDownButton.Parent = controlFrame
local scrollTrack = Instance.new("Frame")
scrollTrack.Name = "ScrollTrack"
scrollTrack.BackgroundTransparency = 1
scrollTrack.Size = UDim2.new(0,18,1,-38)
scrollTrack.Position = UDim2.new(0,0,0,19)
scrollTrack.Parent = controlFrame
local scrollbar = Instance.new("TextButton")
scrollbar.BackgroundColor3 = Color3.new(0,0,0)
scrollbar.BorderColor3 = Color3.new(1,1,1)
scrollbar.BackgroundTransparency = 0.5
scrollbar.AutoButtonColor = false
scrollbar.Text = ""
scrollbar.Active = true
scrollbar.Name = "ScrollBar"
scrollbar.ZIndex = 2
scrollbar.BackgroundTransparency = 0.5
scrollbar.Size = UDim2.new(0, 18, 0.1, 0)
scrollbar.Position = UDim2.new(0,0,0,0)
scrollbar.Parent = scrollTrack
local scrollNub = Instance.new("Frame")
scrollNub.Name = "ScrollNub"
scrollNub.BorderColor3 = Color3.new(1,1,1)
scrollNub.Size = UDim2.new(0,10,0,0)
scrollNub.Position = UDim2.new(0.5,-5,0.5,0)
scrollNub.ZIndex = 2
scrollNub.BackgroundTransparency = 0.5
scrollNub.Parent = scrollbar
local newNub = scrollNub:clone()
newNub.Position = UDim2.new(0.5,-5,0.5,-2)
newNub.Parent = scrollbar
local lastNub = scrollNub:clone()
lastNub.Position = UDim2.new(0.5,-5,0.5,2)
lastNub.Parent = scrollbar
scrollbar.MouseEnter:connect(function()
scrollbar.BackgroundTransparency = 0.1
scrollNub.BackgroundTransparency = 0.1
newNub.BackgroundTransparency = 0.1
lastNub.BackgroundTransparency = 0.1
end)
scrollbar.MouseLeave:connect(function()
scrollbar.BackgroundTransparency = 0.5
scrollNub.BackgroundTransparency = 0.5
newNub.BackgroundTransparency = 0.5
lastNub.BackgroundTransparency = 0.5
end)
local mouseDrag = Instance.new("ImageButton")
mouseDrag.Active = false
mouseDrag.Size = UDim2.new(1.5, 0, 1.5, 0)
mouseDrag.AutoButtonColor = false
mouseDrag.BackgroundTransparency = 1
mouseDrag.Name = "mouseDrag"
mouseDrag.Position = UDim2.new(-0.25, 0, -0.25, 0)
mouseDrag.ZIndex = 10
local function positionScrollBar(x,y,offset)
local oldPos = scrollbar.Position
if y < scrollTrack.AbsolutePosition.y then
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,0,0)
return (oldPos ~= scrollbar.Position)
end
local relativeSize = scrollbar.AbsoluteSize.Y/scrollTrack.AbsoluteSize.Y
if y > (scrollTrack.AbsolutePosition.y + scrollTrack.AbsoluteSize.y) then
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,1 - relativeSize,0)
return (oldPos ~= scrollbar.Position)
end
local newScaleYPos = (y - scrollTrack.AbsolutePosition.y - offset)/scrollTrack.AbsoluteSize.y
if newScaleYPos + relativeSize > 1 then
newScaleYPos = 1 - relativeSize
scrollBottom.Value = true
scrollUp.Value = false
elseif newScaleYPos <= 0 then
newScaleYPos = 0
scrollUp.Value = true
scrollBottom.Value = false
else
scrollUp.Value = false
scrollBottom.Value = false
end
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,newScaleYPos,0)
return (oldPos ~= scrollbar.Position)
end
local function drillDownSetHighLow(instance)
if not instance or not instance:IsA("GuiObject") then return end
if instance == controlFrame then return end
if instance:IsDescendantOf(controlFrame) then return end
if not instance.Visible then return end
if lowY and lowY > instance.AbsolutePosition.Y then
lowY = instance.AbsolutePosition.Y
elseif not lowY then
lowY = instance.AbsolutePosition.Y
end
if highY and highY < (instance.AbsolutePosition.Y + instance.AbsoluteSize.Y) then
highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
elseif not highY then
highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
end
local children = instance:GetChildren()
for i = 1, #children do
drillDownSetHighLow(children[i])
end
end
local function resetHighLow()
local firstChildren = scrollingFrame:GetChildren()
for i = 1, #firstChildren do
drillDownSetHighLow(firstChildren[i])
end
end
local function recalculate()
internalChange = true
local percentFrame = 0
if scrollbar.Position.Y.Scale > 0 then
if scrollbar.Visible then
percentFrame = scrollbar.Position.Y.Scale/((scrollTrack.AbsoluteSize.Y - scrollbar.AbsoluteSize.Y)/scrollTrack.AbsoluteSize.Y)
else
percentFrame = 0
end
end
if percentFrame > 0.99 then percentFrame = 1 end
local hiddenYAmount = (scrollingFrame.AbsoluteSize.Y - (highY - lowY)) * percentFrame
local guiChildren = scrollingFrame:GetChildren()
for i = 1, #guiChildren do
if guiChildren[i] ~= controlFrame then
guiChildren[i].Position = UDim2.new(guiChildren[i].Position.X.Scale,guiChildren[i].Position.X.Offset,
0, math.ceil(guiChildren[i].AbsolutePosition.Y) - math.ceil(lowY) + hiddenYAmount)
end
end
lowY = nil
highY = nil
resetHighLow()
internalChange = false
end
local function setSliderSizeAndPosition()
if not highY or not lowY then return end
local totalYSpan = math.abs(highY - lowY)
if totalYSpan == 0 then
scrollbar.Visible = false
scrollDownButton.Visible = false
scrollUpButton.Visible = false
if dragCon then dragCon:disconnect() dragCon = nil end
if upCon then upCon:disconnect() upCon = nil end
return
end
local percentShown = scrollingFrame.AbsoluteSize.Y/totalYSpan
if percentShown >= 1 then
scrollbar.Visible = false
scrollDownButton.Visible = false
scrollUpButton.Visible = false
recalculate()
else
scrollbar.Visible = true
scrollDownButton.Visible = true
scrollUpButton.Visible = true
scrollbar.Size = UDim2.new(scrollbar.Size.X.Scale,scrollbar.Size.X.Offset,percentShown,0)
end
local percentPosition = (scrollingFrame.AbsolutePosition.Y - lowY)/totalYSpan
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,percentPosition,-scrollbar.AbsoluteSize.X/2)
if scrollbar.AbsolutePosition.y < scrollTrack.AbsolutePosition.y then
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,0,0)
end
if (scrollbar.AbsolutePosition.y + scrollbar.AbsoluteSize.Y) > (scrollTrack.AbsolutePosition.y + scrollTrack.AbsoluteSize.y) then
local relativeSize = scrollbar.AbsoluteSize.Y/scrollTrack.AbsoluteSize.Y
scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,1 - relativeSize,0)
end
end
local buttonScrollAmountPixels = 7
local reentrancyGuardScrollUp = false
local function doScrollUp()
if reentrancyGuardScrollUp then return end
reentrancyGuardScrollUp = true
if positionScrollBar(0,scrollbar.AbsolutePosition.Y - buttonScrollAmountPixels,0) then
recalculate()
end
reentrancyGuardScrollUp = false
end
local reentrancyGuardScrollDown = false
local function doScrollDown()
if reentrancyGuardScrollDown then return end
reentrancyGuardScrollDown = true
if positionScrollBar(0,scrollbar.AbsolutePosition.Y + buttonScrollAmountPixels,0) then
recalculate()
end
reentrancyGuardScrollDown = false
end
local function scrollUp(mouseYPos)
if scrollUpButton.Active then
scrollStamp = tick()
local current = scrollStamp
local upCon
upCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
upCon:disconnect()
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
doScrollUp()
wait(0.2)
local t = tick()
local w = 0.1
while scrollStamp == current do
doScrollUp()
if mouseYPos and mouseYPos > scrollbar.AbsolutePosition.y then
break
end
if not scrollUpButton.Active then break end
if tick()-t > 5 then
w = 0
elseif tick()-t > 2 then
w = 0.06
end
wait(w)
end
end
end
local function scrollDown(mouseYPos)
if scrollDownButton.Active then
scrollStamp = tick()
local current = scrollStamp
local downCon
downCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
downCon:disconnect()
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
doScrollDown()
wait(0.2)
local t = tick()
local w = 0.1
while scrollStamp == current do
doScrollDown()
if mouseYPos and mouseYPos < (scrollbar.AbsolutePosition.y + scrollbar.AbsoluteSize.x) then
break
end
if not scrollDownButton.Active then break end
if tick()-t > 5 then
w = 0
elseif tick()-t > 2 then
w = 0.06
end
wait(w)
end
end
end
scrollbar.MouseButton1Down:connect(function(x,y)
if scrollbar.Active then
scrollStamp = tick()
local mouseOffset = y - scrollbar.AbsolutePosition.y
if dragCon then dragCon:disconnect() dragCon = nil end
if upCon then upCon:disconnect() upCon = nil end
local prevY = y
local reentrancyGuardMouseScroll = false
dragCon = mouseDrag.MouseMoved:connect(function(x,y)
if reentrancyGuardMouseScroll then return end
reentrancyGuardMouseScroll = true
if positionScrollBar(x,y,mouseOffset) then
recalculate()
end
reentrancyGuardMouseScroll = false
end)
upCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
dragCon:disconnect(); dragCon = nil
upCon:disconnect(); drag = nil
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
end
end)
local scrollMouseCount = 0
scrollUpButton.MouseButton1Down:connect(function()
scrollUp()
end)
scrollUpButton.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
scrollDownButton.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
scrollDownButton.MouseButton1Down:connect(function()
scrollDown()
end)
scrollbar.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
local function heightCheck(instance)
if highY and (instance.AbsolutePosition.Y + instance.AbsoluteSize.Y) > highY then
highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
elseif not highY then
highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
end
setSliderSizeAndPosition()
end
local function highLowRecheck()
local oldLowY = lowY
local oldHighY = highY
lowY = nil
highY = nil
resetHighLow()
if (lowY ~= oldLowY) or (highY ~= oldHighY) then
setSliderSizeAndPosition()
end
end
local function descendantChanged(this, prop)
if internalChange then return end
if not this.Visible then return end
if prop == "Size" or prop == "Position" then
wait()
highLowRecheck()
end
end
scrollingFrame.DescendantAdded:connect(function(instance)
if not instance:IsA("GuiObject") then return end
if instance.Visible then
wait() -- wait a heartbeat for sizes to reconfig
highLowRecheck()
end
descendantsChangeConMap[instance] = instance.Changed:connect(function(prop) descendantChanged(instance, prop) end)
end)
scrollingFrame.DescendantRemoving:connect(function(instance)
if not instance:IsA("GuiObject") then return end
if descendantsChangeConMap[instance] then
descendantsChangeConMap[instance]:disconnect()
descendantsChangeConMap[instance] = nil
end
wait() -- wait a heartbeat for sizes to reconfig
highLowRecheck()
end)
scrollingFrame.Changed:connect(function(prop)
if prop == "AbsoluteSize" then
if not highY or not lowY then return end
highLowRecheck()
setSliderSizeAndPosition()
end
end)
return scrollingFrame, controlFrame
end
t.CreateScrollingFrame = function(orderList,scrollStyle)
local frame = Instance.new("Frame")
frame.Name = "ScrollingFrame"
frame.BackgroundTransparency = 1
frame.Size = UDim2.new(1,0,1,0)
local scrollUpButton = Instance.new("ImageButton")
scrollUpButton.Name = "ScrollUpButton"
scrollUpButton.BackgroundTransparency = 1
scrollUpButton.Image = "rbxasset://textures/ui/scrollbuttonUp.png"
scrollUpButton.Size = UDim2.new(0,17,0,17)
local scrollDownButton = Instance.new("ImageButton")
scrollDownButton.Name = "ScrollDownButton"
scrollDownButton.BackgroundTransparency = 1
scrollDownButton.Image = "rbxasset://textures/ui/scrollbuttonDown.png"
scrollDownButton.Size = UDim2.new(0,17,0,17)
local scrollbar = Instance.new("ImageButton")
scrollbar.Name = "ScrollBar"
scrollbar.Image = "rbxasset://textures/ui/scrollbar.png"
scrollbar.BackgroundTransparency = 1
scrollbar.Size = UDim2.new(0, 18, 0, 150)
local scrollStamp = 0
local scrollDrag = Instance.new("ImageButton")
scrollDrag.Image = "https://www.roblox.com/asset/?id=61367186"
scrollDrag.Size = UDim2.new(1, 0, 0, 16)
scrollDrag.BackgroundTransparency = 1
scrollDrag.Name = "ScrollDrag"
scrollDrag.Active = true
scrollDrag.Parent = scrollbar
local mouseDrag = Instance.new("ImageButton")
mouseDrag.Active = false
mouseDrag.Size = UDim2.new(1.5, 0, 1.5, 0)
mouseDrag.AutoButtonColor = false
mouseDrag.BackgroundTransparency = 1
mouseDrag.Name = "mouseDrag"
mouseDrag.Position = UDim2.new(-0.25, 0, -0.25, 0)
mouseDrag.ZIndex = 10
local style = "simple"
if scrollStyle and tostring(scrollStyle) then
style = scrollStyle
end
local scrollPosition = 1
local rowSize = 0
local howManyDisplayed = 0
local layoutGridScrollBar = function()
howManyDisplayed = 0
local guiObjects = {}
if orderList then
for i, child in ipairs(orderList) do
if child.Parent == frame then
table.insert(guiObjects, child)
end
end
else
local children = frame:GetChildren()
if children then
for i, child in ipairs(children) do
if child:IsA("GuiObject") then
table.insert(guiObjects, child)
end
end
end
end
if #guiObjects == 0 then
scrollUpButton.Active = false
scrollDownButton.Active = false
scrollDrag.Active = false
scrollPosition = 1
return
end
if scrollPosition > #guiObjects then
scrollPosition = #guiObjects
end
if scrollPosition < 1 then scrollPosition = 1 end
local totalPixelsY = frame.AbsoluteSize.Y
local pixelsRemainingY = frame.AbsoluteSize.Y
local totalPixelsX = frame.AbsoluteSize.X
local xCounter = 0
local rowSizeCounter = 0
local setRowSize = true
local pixelsBelowScrollbar = 0
local pos = #guiObjects
local currentRowY = 0
pos = scrollPosition
--count up from current scroll position to fill out grid
while pos <= #guiObjects and pixelsBelowScrollbar < totalPixelsY do
xCounter = xCounter + guiObjects[pos].AbsoluteSize.X
--previous pos was the end of a row
if xCounter >= totalPixelsX then
pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
currentRowY = 0
xCounter = guiObjects[pos].AbsoluteSize.X
end
if guiObjects[pos].AbsoluteSize.Y > currentRowY then
currentRowY = guiObjects[pos].AbsoluteSize.Y
end
pos = pos + 1
end
--Count wherever current row left off
pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
currentRowY = 0
pos = scrollPosition - 1
xCounter = 0
--objects with varying X,Y dimensions can rarely cause minor errors
--rechecking every new scrollPosition is necessary to avoid 100% of errors
--count backwards from current scrollPosition to see if we can add more rows
while pixelsBelowScrollbar + currentRowY < totalPixelsY and pos >= 1 do
xCounter = xCounter + guiObjects[pos].AbsoluteSize.X
rowSizeCounter = rowSizeCounter + 1
if xCounter >= totalPixelsX then
rowSize = rowSizeCounter - 1
rowSizeCounter = 0
xCounter = guiObjects[pos].AbsoluteSize.X
if pixelsBelowScrollbar + currentRowY <= totalPixelsY then
--It fits, so back up our scroll position
pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
if scrollPosition <= rowSize then
scrollPosition = 1
break
else
scrollPosition = scrollPosition - rowSize
end
currentRowY = 0
else
break
end
end
if guiObjects[pos].AbsoluteSize.Y > currentRowY then
currentRowY = guiObjects[pos].AbsoluteSize.Y
end
pos = pos - 1
end
--Do check last time if pos = 0
if (pos == 0) and (pixelsBelowScrollbar + currentRowY <= totalPixelsY) then
scrollPosition = 1
end
xCounter = 0
--pos = scrollPosition
rowSizeCounter = 0
setRowSize = true
local lastChildSize = 0
local xOffset,yOffset = 0
if guiObjects[1] then
yOffset = math.ceil(math.floor(math.fmod(totalPixelsY,guiObjects[1].AbsoluteSize.X))/2)
xOffset = math.ceil(math.floor(math.fmod(totalPixelsX,guiObjects[1].AbsoluteSize.Y))/2)
end
for i, child in ipairs(guiObjects) do
if i < scrollPosition then
--print("Hiding " .. child.Name)
child.Visible = false
else
if pixelsRemainingY < 0 then
--print("Out of Space " .. child.Name)
child.Visible = false
else
--print("Laying out " .. child.Name)
--GuiObject
if setRowSize then rowSizeCounter = rowSizeCounter + 1 end
if xCounter + child.AbsoluteSize.X >= totalPixelsX then
if setRowSize then
rowSize = rowSizeCounter - 1
setRowSize = false
end
xCounter = 0
pixelsRemainingY = pixelsRemainingY - child.AbsoluteSize.Y
end
child.Position = UDim2.new(child.Position.X.Scale,xCounter + xOffset, 0, totalPixelsY - pixelsRemainingY + yOffset)
xCounter = xCounter + child.AbsoluteSize.X
child.Visible = ((pixelsRemainingY - child.AbsoluteSize.Y) >= 0)
if child.Visible then
howManyDisplayed = howManyDisplayed + 1
end
lastChildSize = child.AbsoluteSize
end
end
end
scrollUpButton.Active = (scrollPosition > 1)
if lastChildSize == 0 then
scrollDownButton.Active = false
else
scrollDownButton.Active = ((pixelsRemainingY - lastChildSize.Y) < 0)
end
scrollDrag.Active = #guiObjects > howManyDisplayed
scrollDrag.Visible = scrollDrag.Active
end
local layoutSimpleScrollBar = function()
local guiObjects = {}
howManyDisplayed = 0
if orderList then
for i, child in ipairs(orderList) do
if child.Parent == frame then
table.insert(guiObjects, child)
end
end
else
local children = frame:GetChildren()
if children then
for i, child in ipairs(children) do
if child:IsA("GuiObject") then
table.insert(guiObjects, child)
end
end
end
end
if #guiObjects == 0 then
scrollUpButton.Active = false
scrollDownButton.Active = false
scrollDrag.Active = false
scrollPosition = 1
return
end
if scrollPosition > #guiObjects then
scrollPosition = #guiObjects
end
local totalPixels = frame.AbsoluteSize.Y
local pixelsRemaining = frame.AbsoluteSize.Y
local pixelsBelowScrollbar = 0
local pos = #guiObjects
while pixelsBelowScrollbar < totalPixels and pos >= 1 do
if pos >= scrollPosition then
pixelsBelowScrollbar = pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y
else
if pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y <= totalPixels then
--It fits, so back up our scroll position
pixelsBelowScrollbar = pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y
if scrollPosition <= 1 then
scrollPosition = 1
break
else
--local ("Backing up ScrollPosition from -- " ..scrollPosition)
scrollPosition = scrollPosition - 1
end
else
break
end
end
pos = pos - 1
end
pos = scrollPosition
for i, child in ipairs(guiObjects) do
if i < scrollPosition then
--print("Hiding " .. child.Name)
child.Visible = false
else
if pixelsRemaining < 0 then
--print("Out of Space " .. child.Name)
child.Visible = false
else
--print("Laying out " .. child.Name)
--GuiObject
child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
if (pixelsRemaining >= 0) then
child.Visible = true
howManyDisplayed = howManyDisplayed + 1
else
child.Visible = false
end
end
end
end
scrollUpButton.Active = (scrollPosition > 1)
scrollDownButton.Active = (pixelsRemaining < 0)
scrollDrag.Active = #guiObjects > howManyDisplayed
scrollDrag.Visible = scrollDrag.Active
end
local moveDragger = function()
local guiObjects = 0
local children = frame:GetChildren()
if children then
for i, child in ipairs(children) do
if child:IsA("GuiObject") then
guiObjects = guiObjects + 1
end
end
end
if not scrollDrag.Parent then return end
local dragSizeY = scrollDrag.Parent.AbsoluteSize.y * (1/(guiObjects - howManyDisplayed + 1))
if dragSizeY < 16 then dragSizeY = 16 end
scrollDrag.Size = UDim2.new(scrollDrag.Size.X.Scale,scrollDrag.Size.X.Offset,scrollDrag.Size.Y.Scale,dragSizeY)
local relativeYPos = (scrollPosition - 1)/(guiObjects - (howManyDisplayed))
if relativeYPos > 1 then relativeYPos = 1
elseif relativeYPos < 0 then relativeYPos = 0 end
local absYPos = 0
if relativeYPos ~= 0 then
absYPos = (relativeYPos * scrollbar.AbsoluteSize.y) - (relativeYPos * scrollDrag.AbsoluteSize.y)
end
scrollDrag.Position = UDim2.new(scrollDrag.Position.X.Scale,scrollDrag.Position.X.Offset,scrollDrag.Position.Y.Scale,absYPos)
end
local reentrancyGuard = false
local recalculate = function()
if reentrancyGuard then
return
end
reentrancyGuard = true
wait()
local success, err = nil
if style == "grid" then
success, err = pcall(function() layoutGridScrollBar() end)
elseif style == "simple" then
success, err = pcall(function() layoutSimpleScrollBar() end)
end
if not success then print(err) end
moveDragger()
reentrancyGuard = false
end
local doScrollUp = function()
scrollPosition = (scrollPosition) - rowSize
if scrollPosition < 1 then scrollPosition = 1 end
recalculate(nil)
end
local doScrollDown = function()
scrollPosition = (scrollPosition) + rowSize
recalculate(nil)
end
local scrollUp = function(mouseYPos)
if scrollUpButton.Active then
scrollStamp = tick()
local current = scrollStamp
local upCon
upCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
upCon:disconnect()
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
doScrollUp()
wait(0.2)
local t = tick()
local w = 0.1
while scrollStamp == current do
doScrollUp()
if mouseYPos and mouseYPos > scrollDrag.AbsolutePosition.y then
break
end
if not scrollUpButton.Active then break end
if tick()-t > 5 then
w = 0
elseif tick()-t > 2 then
w = 0.06
end
wait(w)
end
end
end
local scrollDown = function(mouseYPos)
if scrollDownButton.Active then
scrollStamp = tick()
local current = scrollStamp
local downCon
downCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
downCon:disconnect()
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
doScrollDown()
wait(0.2)
local t = tick()
local w = 0.1
while scrollStamp == current do
doScrollDown()
if mouseYPos and mouseYPos < (scrollDrag.AbsolutePosition.y + scrollDrag.AbsoluteSize.x) then
break
end
if not scrollDownButton.Active then break end
if tick()-t > 5 then
w = 0
elseif tick()-t > 2 then
w = 0.06
end
wait(w)
end
end
end
local y = 0
scrollDrag.MouseButton1Down:connect(function(x,y)
if scrollDrag.Active then
scrollStamp = tick()
local mouseOffset = y - scrollDrag.AbsolutePosition.y
local dragCon
local upCon
dragCon = mouseDrag.MouseMoved:connect(function(x,y)
local barAbsPos = scrollbar.AbsolutePosition.y
local barAbsSize = scrollbar.AbsoluteSize.y
local dragAbsSize = scrollDrag.AbsoluteSize.y
local barAbsOne = barAbsPos + barAbsSize - dragAbsSize
y = y - mouseOffset
y = y < barAbsPos and barAbsPos or y > barAbsOne and barAbsOne or y
y = y - barAbsPos
local guiObjects = 0
local children = frame:GetChildren()
if children then
for i, child in ipairs(children) do
if child:IsA("GuiObject") then
guiObjects = guiObjects + 1
end
end
end
local doublePercent = y/(barAbsSize-dragAbsSize)
local rowDiff = rowSize
local totalScrollCount = guiObjects - (howManyDisplayed - 1)
local newScrollPosition = math.floor((doublePercent * totalScrollCount) + 0.5) + rowDiff
if newScrollPosition < scrollPosition then
rowDiff = -rowDiff
end
if newScrollPosition < 1 then
newScrollPosition = 1
end
scrollPosition = newScrollPosition
recalculate(nil)
end)
upCon = mouseDrag.MouseButton1Up:connect(function()
scrollStamp = tick()
mouseDrag.Parent = nil
dragCon:disconnect(); dragCon = nil
upCon:disconnect(); drag = nil
end)
mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
end
end)
local scrollMouseCount = 0
scrollUpButton.MouseButton1Down:connect(
function()
scrollUp()
end)
scrollUpButton.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
scrollDownButton.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
scrollDownButton.MouseButton1Down:connect(
function()
scrollDown()
end)
scrollbar.MouseButton1Up:connect(function()
scrollStamp = tick()
end)
scrollbar.MouseButton1Down:connect(
function(x,y)
if y > (scrollDrag.AbsoluteSize.y + scrollDrag.AbsolutePosition.y) then
scrollDown(y)
elseif y < (scrollDrag.AbsolutePosition.y) then
scrollUp(y)
end
end)
frame.ChildAdded:connect(function()
recalculate(nil)
end)
frame.ChildRemoved:connect(function()
recalculate(nil)
end)
frame.Changed:connect(
function(prop)
if prop == "AbsoluteSize" then
--Wait a heartbeat for it to sync in
recalculate(nil)
end
end)
frame.AncestryChanged:connect(function() recalculate(nil) end)
return frame, scrollUpButton, scrollDownButton, recalculate, scrollbar
end
local function binaryGrow(min, max, fits)
if min > max then
return min
end
local biggestLegal = min
while min <= max do
local mid = min + math.floor((max - min) / 2)
if fits(mid) and (biggestLegal == nil or biggestLegal < mid) then
biggestLegal = mid
--Try growing
min = mid + 1
else
--Doesn't fit, shrink
max = mid - 1
end
end
return biggestLegal
end
local function binaryShrink(min, max, fits)
if min > max then
return min
end
local smallestLegal = max
while min <= max do
local mid = min + math.floor((max - min) / 2)
if fits(mid) and (smallestLegal == nil or smallestLegal > mid) then
smallestLegal = mid
--It fits, shrink
max = mid - 1
else
--Doesn't fit, grow
min = mid + 1
end
end
return smallestLegal
end
local function getGuiOwner(instance)
while instance ~= nil do
if instance:IsA("ScreenGui") or instance:IsA("BillboardGui") then
return instance
end
instance = instance.Parent
end
return nil
end
t.AutoTruncateTextObject = function(textLabel)
local text = textLabel.Text
local fullLabel = textLabel:Clone()
fullLabel.Name = "Full" .. textLabel.Name
fullLabel.BorderSizePixel = 0
fullLabel.BackgroundTransparency = 0
fullLabel.Text = text
fullLabel.TextXAlignment = Enum.TextXAlignment.Center
fullLabel.Position = UDim2.new(0,-3,0,0)
fullLabel.Size = UDim2.new(0,100,1,0)
fullLabel.Visible = false
fullLabel.Parent = textLabel
local shortText = nil
local mouseEnterConnection = nil
local mouseLeaveConnection= nil
local checkForResize = function()
if getGuiOwner(textLabel) == nil then
return
end
textLabel.Text = text
if textLabel.TextFits then
--Tear down the rollover if it is active
if mouseEnterConnection then
mouseEnterConnection:disconnect()
mouseEnterConnection = nil
end
if mouseLeaveConnection then
mouseLeaveConnection:disconnect()
mouseLeaveConnection = nil
end
else
local len = string.len(text)
textLabel.Text = text .. "~"
--Shrink the text
local textSize = binaryGrow(0, len,
function(pos)
if pos == 0 then
textLabel.Text = "~"
else
textLabel.Text = string.sub(text, 1, pos) .. "~"
end
return textLabel.TextFits
end)
shortText = string.sub(text, 1, textSize) .. "~"
textLabel.Text = shortText
--Make sure the fullLabel fits
if not fullLabel.TextFits then
--Already too small, grow it really bit to start
fullLabel.Size = UDim2.new(0, 10000, 1, 0)
end
--Okay, now try to binary shrink it back down
local fullLabelSize = binaryShrink(textLabel.AbsoluteSize.X,fullLabel.AbsoluteSize.X,
function(size)
fullLabel.Size = UDim2.new(0, size, 1, 0)
return fullLabel.TextFits
end)
fullLabel.Size = UDim2.new(0,fullLabelSize+6,1,0)
--Now setup the rollover effects, if they are currently off
if mouseEnterConnection == nil then
mouseEnterConnection = textLabel.MouseEnter:connect(
function()
fullLabel.ZIndex = textLabel.ZIndex + 1
fullLabel.Visible = true
--textLabel.Text = ""
end)
end
if mouseLeaveConnection == nil then
mouseLeaveConnection = textLabel.MouseLeave:connect(
function()
fullLabel.Visible = false
--textLabel.Text = shortText
end)
end
end
end
textLabel.AncestryChanged:connect(checkForResize)
textLabel.Changed:connect(
function(prop)
if prop == "AbsoluteSize" then
checkForResize()
end
end)
checkForResize()
local function changeText(newText)
text = newText
fullLabel.Text = text
checkForResize()
end
return textLabel, changeText
end
local function TransitionTutorialPages(fromPage, toPage, transitionFrame, currentPageValue)
if fromPage then
fromPage.Visible = false
if transitionFrame.Visible == false then
transitionFrame.Size = fromPage.Size
transitionFrame.Position = fromPage.Position
end
else
if transitionFrame.Visible == false then
transitionFrame.Size = UDim2.new(0.0,50,0.0,50)
transitionFrame.Position = UDim2.new(0.5,-25,0.5,-25)
end
end
transitionFrame.Visible = true
currentPageValue.Value = nil
local newSize, newPosition
if toPage then
--Make it visible so it resizes
toPage.Visible = true
newSize = toPage.Size
newPosition = toPage.Position
toPage.Visible = false
else
newSize = UDim2.new(0.0,50,0.0,50)
newPosition = UDim2.new(0.5,-25,0.5,-25)
end
transitionFrame:TweenSizeAndPosition(newSize, newPosition, Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.3, true,
function(state)
if state == Enum.TweenStatus.Completed then
transitionFrame.Visible = false
if toPage then
toPage.Visible = true
currentPageValue.Value = toPage
end
end
end)
end
t.CreateTutorial = function(name, tutorialKey, createButtons)
local frame = Instance.new("Frame")
frame.Name = "Tutorial-" .. name
frame.BackgroundTransparency = 1
frame.Size = UDim2.new(0.6, 0, 0.6, 0)
frame.Position = UDim2.new(0.2, 0, 0.2, 0)
local transitionFrame = Instance.new("Frame")
transitionFrame.Name = "TransitionFrame"
transitionFrame.Style = Enum.FrameStyle.RobloxRound
transitionFrame.Size = UDim2.new(0.6, 0, 0.6, 0)
transitionFrame.Position = UDim2.new(0.2, 0, 0.2, 0)
transitionFrame.Visible = false
transitionFrame.Parent = frame
local currentPageValue = Instance.new("ObjectValue")
currentPageValue.Name = "CurrentTutorialPage"
currentPageValue.Value = nil
currentPageValue.Parent = frame
local boolValue = Instance.new("BoolValue")
boolValue.Name = "Buttons"
boolValue.Value = createButtons
boolValue.Parent = frame
local pages = Instance.new("Frame")
pages.Name = "Pages"
pages.BackgroundTransparency = 1
pages.Size = UDim2.new(1,0,1,0)
pages.Parent = frame
local function getVisiblePageAndHideOthers()
local visiblePage = nil
local children = pages:GetChildren()
if children then
for i,child in ipairs(children) do
if child.Visible then
if visiblePage then
child.Visible = false
else
visiblePage = child
end
end
end
end
return visiblePage
end
local showTutorial = function(alwaysShow)
if alwaysShow or UserSettings().GameSettings:GetTutorialState(tutorialKey) == false then
print("Showing tutorial-",tutorialKey)
local currentTutorialPage = getVisiblePageAndHideOthers()
local firstPage = pages:FindFirstChild("TutorialPage1")
if firstPage then
TransitionTutorialPages(currentTutorialPage, firstPage, transitionFrame, currentPageValue)
else
error("Could not find TutorialPage1")
end
end
end
local dismissTutorial = function()
local currentTutorialPage = getVisiblePageAndHideOthers()
if currentTutorialPage then
TransitionTutorialPages(currentTutorialPage, nil, transitionFrame, currentPageValue)
end
UserSettings().GameSettings:SetTutorialState(tutorialKey, true)
end
local gotoPage = function(pageNum)
local page = pages:FindFirstChild("TutorialPage" .. pageNum)
local currentTutorialPage = getVisiblePageAndHideOthers()
TransitionTutorialPages(currentTutorialPage, page, transitionFrame, currentPageValue)
end
return frame, showTutorial, dismissTutorial, gotoPage
end
local function CreateBasicTutorialPage(name, handleResize, skipTutorial, giveDoneButton)
local frame = Instance.new("Frame")
frame.Name = "TutorialPage"
frame.Style = Enum.FrameStyle.RobloxRound
frame.Size = UDim2.new(0.6, 0, 0.6, 0)
frame.Position = UDim2.new(0.2, 0, 0.2, 0)
frame.Visible = false
local frameHeader = Instance.new("TextLabel")
frameHeader.Name = "Header"
frameHeader.Text = name
frameHeader.BackgroundTransparency = 1
frameHeader.FontSize = Enum.FontSize.Size24
frameHeader.Font = Enum.Font.ArialBold
frameHeader.TextColor3 = Color3.new(1,1,1)
frameHeader.TextXAlignment = Enum.TextXAlignment.Center
frameHeader.TextWrap = true
frameHeader.Size = UDim2.new(1,-55, 0, 22)
frameHeader.Position = UDim2.new(0,0,0,0)
frameHeader.Parent = frame
local skipButton = Instance.new("ImageButton")
skipButton.Name = "SkipButton"
skipButton.AutoButtonColor = false
skipButton.BackgroundTransparency = 1
skipButton.Image = "rbxasset://textures/ui/closeButton.png"
skipButton.MouseButton1Click:connect(function()
skipTutorial()
end)
skipButton.MouseEnter:connect(function()
skipButton.Image = "rbxasset://textures/ui/closeButton_dn.png"
end)
skipButton.MouseLeave:connect(function()
skipButton.Image = "rbxasset://textures/ui/closeButton.png"
end)
skipButton.Size = UDim2.new(0, 25, 0, 25)
skipButton.Position = UDim2.new(1, -25, 0, 0)
skipButton.Parent = frame
if giveDoneButton then
local doneButton = Instance.new("TextButton")
doneButton.Name = "DoneButton"
doneButton.Style = Enum.ButtonStyle.RobloxButtonDefault
doneButton.Text = "Done"
doneButton.TextColor3 = Color3.new(1,1,1)
doneButton.Font = Enum.Font.ArialBold
doneButton.FontSize = Enum.FontSize.Size18
doneButton.Size = UDim2.new(0,100,0,50)
doneButton.Position = UDim2.new(0.5,-50,1,-50)
if skipTutorial then
doneButton.MouseButton1Click:connect(function() skipTutorial() end)
end
doneButton.Parent = frame
end
local innerFrame = Instance.new("Frame")
innerFrame.Name = "ContentFrame"
innerFrame.BackgroundTransparency = 1
innerFrame.Position = UDim2.new(0,0,0,25)
innerFrame.Parent = frame
local nextButton = Instance.new("TextButton")
nextButton.Name = "NextButton"
nextButton.Text = "Next"
nextButton.TextColor3 = Color3.new(1,1,1)
nextButton.Font = Enum.Font.Arial
nextButton.FontSize = Enum.FontSize.Size18
nextButton.Style = Enum.ButtonStyle.RobloxButtonDefault
nextButton.Size = UDim2.new(0,80, 0, 32)
nextButton.Position = UDim2.new(0.5, 5, 1, -32)
nextButton.Active = false
nextButton.Visible = false
nextButton.Parent = frame
local prevButton = Instance.new("TextButton")
prevButton.Name = "PrevButton"
prevButton.Text = "Previous"
prevButton.TextColor3 = Color3.new(1,1,1)
prevButton.Font = Enum.Font.Arial
prevButton.FontSize = Enum.FontSize.Size18
prevButton.Style = Enum.ButtonStyle.RobloxButton
prevButton.Size = UDim2.new(0,80, 0, 32)
prevButton.Position = UDim2.new(0.5, -85, 1, -32)
prevButton.Active = false
prevButton.Visible = false
prevButton.Parent = frame
if giveDoneButton then
innerFrame.Size = UDim2.new(1,0,1,-75)
else
innerFrame.Size = UDim2.new(1,0,1,-22)
end
local parentConnection = nil
local function basicHandleResize()
if frame.Visible and frame.Parent then
local maxSize = math.min(frame.Parent.AbsoluteSize.X, frame.Parent.AbsoluteSize.Y)
handleResize(200,maxSize)
end
end
frame.Changed:connect(
function(prop)
if prop == "Parent" then
if parentConnection ~= nil then
parentConnection:disconnect()
parentConnection = nil
end
if frame.Parent and frame.Parent:IsA("GuiObject") then
parentConnection = frame.Parent.Changed:connect(
function(parentProp)
if parentProp == "AbsoluteSize" then
wait()
basicHandleResize()
end
end)
basicHandleResize()
end
end
if prop == "Visible" then
basicHandleResize()
end
end)
return frame, innerFrame
end
t.CreateTextTutorialPage = function(name, text, skipTutorialFunc)
local frame = nil
local contentFrame = nil
local textLabel = Instance.new("TextLabel")
textLabel.BackgroundTransparency = 1
textLabel.TextColor3 = Color3.new(1,1,1)
textLabel.Text = text
textLabel.TextWrap = true
textLabel.TextXAlignment = Enum.TextXAlignment.Left
textLabel.TextYAlignment = Enum.TextYAlignment.Center
textLabel.Font = Enum.Font.Arial
textLabel.FontSize = Enum.FontSize.Size14
textLabel.Size = UDim2.new(1,0,1,0)
local function handleResize(minSize, maxSize)
size = binaryShrink(minSize, maxSize,
function(size)
frame.Size = UDim2.new(0, size, 0, size)
return textLabel.TextFits
end)
frame.Size = UDim2.new(0, size, 0, size)
frame.Position = UDim2.new(0.5, -size/2, 0.5, -size/2)
end
frame, contentFrame = CreateBasicTutorialPage(name, handleResize, skipTutorialFunc)
textLabel.Parent = contentFrame
return frame
end
t.CreateImageTutorialPage = function(name, imageAsset, x, y, skipTutorialFunc, giveDoneButton)
local frame = nil
local contentFrame = nil
local imageLabel = Instance.new("ImageLabel")
imageLabel.BackgroundTransparency = 1
imageLabel.Image = imageAsset
imageLabel.Size = UDim2.new(0,x,0,y)
imageLabel.Position = UDim2.new(0.5,-x/2,0.5,-y/2)
local function handleResize(minSize, maxSize)
size = binaryShrink(minSize, maxSize,
function(size)
return size >= x and size >= y
end)
if size >= x and size >= y then
imageLabel.Size = UDim2.new(0,x, 0,y)
imageLabel.Position = UDim2.new(0.5,-x/2, 0.5, -y/2)
else
if x > y then
--X is limiter, so
imageLabel.Size = UDim2.new(1,0,y/x,0)
imageLabel.Position = UDim2.new(0,0, 0.5 - (y/x)/2, 0)
else
--Y is limiter
imageLabel.Size = UDim2.new(x/y,0,1, 0)
imageLabel.Position = UDim2.new(0.5-(x/y)/2, 0, 0, 0)
end
end
size = size + 50
frame.Size = UDim2.new(0, size, 0, size)
frame.Position = UDim2.new(0.5, -size/2, 0.5, -size/2)
end
frame, contentFrame = CreateBasicTutorialPage(name, handleResize, skipTutorialFunc, giveDoneButton)
imageLabel.Parent = contentFrame
return frame
end
t.AddTutorialPage = function(tutorial, tutorialPage)
local transitionFrame = tutorial.TransitionFrame
local currentPageValue = tutorial.CurrentTutorialPage
if not tutorial.Buttons.Value then
tutorialPage.NextButton.Parent = nil
tutorialPage.PrevButton.Parent = nil
end
local children = tutorial.Pages:GetChildren()
if children and #children > 0 then
tutorialPage.Name = "TutorialPage" .. (#children+1)
local previousPage = children[#children]
if not previousPage:IsA("GuiObject") then
error("All elements under Pages must be GuiObjects")
end
if tutorial.Buttons.Value then
if previousPage.NextButton.Active then
error("NextButton already Active on previousPage, please only add pages with RbxGui.AddTutorialPage function")
end
previousPage.NextButton.MouseButton1Click:connect(
function()
TransitionTutorialPages(previousPage, tutorialPage, transitionFrame, currentPageValue)
end)
previousPage.NextButton.Active = true
previousPage.NextButton.Visible = true
if tutorialPage.PrevButton.Active then
error("PrevButton already Active on tutorialPage, please only add pages with RbxGui.AddTutorialPage function")
end
tutorialPage.PrevButton.MouseButton1Click:connect(
function()
TransitionTutorialPages(tutorialPage, previousPage, transitionFrame, currentPageValue)
end)
tutorialPage.PrevButton.Active = true
tutorialPage.PrevButton.Visible = true
end
tutorialPage.Parent = tutorial.Pages
else
--First child
tutorialPage.Name = "TutorialPage1"
tutorialPage.Parent = tutorial.Pages
end
end
t.CreateSetPanel = function(userIdsForSets, objectSelected, dialogClosed, size, position, showAdminCategories, useAssetVersionId)
if not userIdsForSets then
error("CreateSetPanel: userIdsForSets (first arg) is nil, should be a table of number ids")
end
if type(userIdsForSets) ~= "table" and type(userIdsForSets) ~= "userdata" then
error("CreateSetPanel: userIdsForSets (first arg) is of type " ..type(userIdsForSets) .. ", should be of type table or userdata")
end
if not objectSelected then
error("CreateSetPanel: objectSelected (second arg) is nil, should be a callback function!")
end
if type(objectSelected) ~= "function" then
error("CreateSetPanel: objectSelected (second arg) is of type " .. type(objectSelected) .. ", should be of type function!")
end
if dialogClosed and type(dialogClosed) ~= "function" then
error("CreateSetPanel: dialogClosed (third arg) is of type " .. type(dialogClosed) .. ", should be of type function!")
end
if showAdminCategories == nil then -- by default, don't show beta sets
showAdminCategories = false
end
local arrayPosition = 1
local insertButtons = {}
local insertButtonCons = {}
local contents = nil
local setGui = nil
-- used for water selections
local waterForceDirection = "NegX"
local waterForce = "None"
local waterGui, waterTypeChangedEvent = nil
local Data = {}
Data.CurrentCategory = nil
Data.Category = {}
local SetCache = {}
local userCategoryButtons = nil
local buttonWidth = 64
local buttonHeight = buttonWidth
local SmallThumbnailUrl = nil
local LargeThumbnailUrl = nil
local BaseUrl = game:GetService("ContentProvider").BaseUrl:lower()
local AssetGameUrl = string.gsub(BaseUrl, "www", "assetgame")
if useAssetVersionId then
LargeThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid="
SmallThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&assetversionid="
else
LargeThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&aid="
SmallThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&aid="
end
local function drillDownSetZIndex(parent, index)
local children = parent:GetChildren()
for i = 1, #children do
if children[i]:IsA("GuiObject") then
children[i].ZIndex = index
end
drillDownSetZIndex(children[i], index)
end
end
-- for terrain stamping
local currTerrainDropDownFrame = nil
local terrainShapes = {"Block","Vertical Ramp","Corner Wedge","Inverse Corner Wedge","Horizontal Ramp","Auto-Wedge"}
local terrainShapeMap = {}
for i = 1, #terrainShapes do
terrainShapeMap[terrainShapes[i]] = i - 1
end
terrainShapeMap[terrainShapes[#terrainShapes]] = 6
local function createWaterGui()
local waterForceDirections = {"NegX","X","NegY","Y","NegZ","Z"}
local waterForces = {"None", "Small", "Medium", "Strong", "Max"}
local waterFrame = Instance.new("Frame")
waterFrame.Name = "WaterFrame"
waterFrame.Style = Enum.FrameStyle.RobloxSquare
waterFrame.Size = UDim2.new(0,150,0,110)
waterFrame.Visible = false
local waterForceLabel = Instance.new("TextLabel")
waterForceLabel.Name = "WaterForceLabel"
waterForceLabel.BackgroundTransparency = 1
waterForceLabel.Size = UDim2.new(1,0,0,12)
waterForceLabel.Font = Enum.Font.ArialBold
waterForceLabel.FontSize = Enum.FontSize.Size12
waterForceLabel.TextColor3 = Color3.new(1,1,1)
waterForceLabel.TextXAlignment = Enum.TextXAlignment.Left
waterForceLabel.Text = "Water Force"
waterForceLabel.Parent = waterFrame
local waterForceDirLabel = waterForceLabel:Clone()
waterForceDirLabel.Name = "WaterForceDirectionLabel"
waterForceDirLabel.Text = "Water Force Direction"
waterForceDirLabel.Position = UDim2.new(0,0,0,50)
waterForceDirLabel.Parent = waterFrame
local waterTypeChangedEvent = Instance.new("BindableEvent",waterFrame)
waterTypeChangedEvent.Name = "WaterTypeChangedEvent"
local waterForceDirectionSelectedFunc = function(newForceDirection)
waterForceDirection = newForceDirection
waterTypeChangedEvent:Fire({waterForce, waterForceDirection})
end
local waterForceSelectedFunc = function(newForce)
waterForce = newForce
waterTypeChangedEvent:Fire({waterForce, waterForceDirection})
end
local waterForceDirectionDropDown, forceWaterDirectionSelection = t.CreateDropDownMenu(waterForceDirections, waterForceDirectionSelectedFunc)
waterForceDirectionDropDown.Size = UDim2.new(1,0,0,25)
waterForceDirectionDropDown.Position = UDim2.new(0,0,1,3)
forceWaterDirectionSelection("NegX")
waterForceDirectionDropDown.Parent = waterForceDirLabel
local waterForceDropDown, forceWaterForceSelection = t.CreateDropDownMenu(waterForces, waterForceSelectedFunc)
forceWaterForceSelection("None")
waterForceDropDown.Size = UDim2.new(1,0,0,25)
waterForceDropDown.Position = UDim2.new(0,0,1,3)
waterForceDropDown.Parent = waterForceLabel
return waterFrame, waterTypeChangedEvent
end
-- Helper Function that contructs gui elements
local function createSetGui()
local setGui = Instance.new("ScreenGui")
setGui.Name = "SetGui"
local setPanel = Instance.new("Frame")
setPanel.Name = "SetPanel"
setPanel.Active = true
setPanel.BackgroundTransparency = 1
if position then
setPanel.Position = position
else
setPanel.Position = UDim2.new(0.2, 29, 0.1, 24)
end
if size then
setPanel.Size = size
else
setPanel.Size = UDim2.new(0.6, -58, 0.64, 0)
end
setPanel.Style = Enum.FrameStyle.RobloxRound
setPanel.ZIndex = 6
setPanel.Parent = setGui
-- Children of SetPanel
local itemPreview = Instance.new("Frame")
itemPreview.Name = "ItemPreview"
itemPreview.BackgroundTransparency = 1
itemPreview.Position = UDim2.new(0.8,5,0.085,0)
itemPreview.Size = UDim2.new(0.21,0,0.9,0)
itemPreview.ZIndex = 6
itemPreview.Parent = setPanel
-- Children of ItemPreview
local textPanel = Instance.new("Frame")
textPanel.Name = "TextPanel"
textPanel.BackgroundTransparency = 1
textPanel.Position = UDim2.new(0,0,0.45,0)
textPanel.Size = UDim2.new(1,0,0.55,0)
textPanel.ZIndex = 6
textPanel.Parent = itemPreview
-- Children of TextPanel
local rolloverText = Instance.new("TextLabel")
rolloverText.Name = "RolloverText"
rolloverText.BackgroundTransparency = 1
rolloverText.Size = UDim2.new(1,0,0,48)
rolloverText.ZIndex = 6
rolloverText.Font = Enum.Font.ArialBold
rolloverText.FontSize = Enum.FontSize.Size24
rolloverText.Text = ""
rolloverText.TextColor3 = Color3.new(1,1,1)
rolloverText.TextWrap = true
rolloverText.TextXAlignment = Enum.TextXAlignment.Left
rolloverText.TextYAlignment = Enum.TextYAlignment.Top
rolloverText.Parent = textPanel
local largePreview = Instance.new("ImageLabel")
largePreview.Name = "LargePreview"
largePreview.BackgroundTransparency = 1
largePreview.Image = ""
largePreview.Size = UDim2.new(1,0,0,170)
largePreview.ZIndex = 6
largePreview.Parent = itemPreview
local sets = Instance.new("Frame")
sets.Name = "Sets"
sets.BackgroundTransparency = 1
sets.Position = UDim2.new(0,0,0,5)
sets.Size = UDim2.new(0.23,0,1,-5)
sets.ZIndex = 6
sets.Parent = setPanel
-- Children of Sets
local line = Instance.new("Frame")
line.Name = "Line"
line.BackgroundColor3 = Color3.new(1,1,1)
line.BackgroundTransparency = 0.7
line.BorderSizePixel = 0
line.Position = UDim2.new(1,-3,0.06,0)
line.Size = UDim2.new(0,3,0.9,0)
line.ZIndex = 6
line.Parent = sets
local setsLists, controlFrame = t.CreateTrueScrollingFrame()
setsLists.Size = UDim2.new(1,-6,0.94,0)
setsLists.Position = UDim2.new(0,0,0.06,0)
setsLists.BackgroundTransparency = 1
setsLists.Name = "SetsLists"
setsLists.ZIndex = 6
setsLists.Parent = sets
drillDownSetZIndex(controlFrame, 7)
local setsHeader = Instance.new("TextLabel")
setsHeader.Name = "SetsHeader"
setsHeader.BackgroundTransparency = 1
setsHeader.Size = UDim2.new(0,47,0,24)
setsHeader.ZIndex = 6
setsHeader.Font = Enum.Font.ArialBold
setsHeader.FontSize = Enum.FontSize.Size24
setsHeader.Text = "Sets"
setsHeader.TextColor3 = Color3.new(1,1,1)
setsHeader.TextXAlignment = Enum.TextXAlignment.Left
setsHeader.TextYAlignment = Enum.TextYAlignment.Top
setsHeader.Parent = sets
local cancelButton = Instance.new("TextButton")
cancelButton.Name = "CancelButton"
cancelButton.Position = UDim2.new(1,-32,0,-2)
cancelButton.Size = UDim2.new(0,34,0,34)
cancelButton.Style = Enum.ButtonStyle.RobloxButtonDefault
cancelButton.ZIndex = 6
cancelButton.Text = ""
cancelButton.Modal = true
cancelButton.Parent = setPanel
-- Children of Cancel Button
local cancelImage = Instance.new("ImageLabel")
cancelImage.Name = "CancelImage"
cancelImage.BackgroundTransparency = 1
cancelImage.Image = "https://www.roblox.com/asset/?id=54135717"
cancelImage.Position = UDim2.new(0,-2,0,-2)
cancelImage.Size = UDim2.new(0,16,0,16)
cancelImage.ZIndex = 6
cancelImage.Parent = cancelButton
return setGui
end
local function createSetButton(text)
local setButton = Instance.new("TextButton")
if text then setButton.Text = text
else setButton.Text = "" end
setButton.AutoButtonColor = false
setButton.BackgroundTransparency = 1
setButton.BackgroundColor3 = Color3.new(1,1,1)
setButton.BorderSizePixel = 0
setButton.Size = UDim2.new(1,-5,0,18)
setButton.ZIndex = 6
setButton.Visible = false
setButton.Font = Enum.Font.Arial
setButton.FontSize = Enum.FontSize.Size18
setButton.TextColor3 = Color3.new(1,1,1)
setButton.TextXAlignment = Enum.TextXAlignment.Left
return setButton
end
local function buildSetButton(name, setId, setImageId, i, count)
local button = createSetButton(name)
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
local function processCategory(sets)
local setButtons = {}
local numSkipped = 0
for i = 1, #sets do
if not showAdminCategories and sets[i].Name == "Beta" then
numSkipped = numSkipped + 1
else
setButtons[i - numSkipped] = buildSetButton(sets[i].Name, sets[i].CategoryId, sets[i].ImageAssetId, i - numSkipped, #sets)
end
end
return setButtons
end
local function handleResize()
wait() -- neccessary to insure heartbeat happened
local itemPreview = setGui.SetPanel.ItemPreview
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
local function makeInsertAssetButton()
local insertAssetButtonExample = Instance.new("Frame")
insertAssetButtonExample.Name = "InsertAssetButtonExample"
insertAssetButtonExample.Position = UDim2.new(0,128,0,64)
insertAssetButtonExample.Size = UDim2.new(0,64,0,64)
insertAssetButtonExample.BackgroundTransparency = 1
insertAssetButtonExample.ZIndex = 6
insertAssetButtonExample.Visible = false
local assetId = Instance.new("IntValue")
assetId.Name = "AssetId"
assetId.Value = 0
assetId.Parent = insertAssetButtonExample
local assetName = Instance.new("StringValue")
assetName.Name = "AssetName"
assetName.Value = ""
assetName.Parent = insertAssetButtonExample
local button = Instance.new("TextButton")
button.Name = "Button"
button.Text = ""
button.Style = Enum.ButtonStyle.RobloxButton
button.Position = UDim2.new(0.025,0,0.025,0)
button.Size = UDim2.new(0.95,0,0.95,0)
button.ZIndex = 6
button.Parent = insertAssetButtonExample
local buttonImage = Instance.new("ImageLabel")
buttonImage.Name = "ButtonImage"
buttonImage.Image = ""
buttonImage.Position = UDim2.new(0,-7,0,-7)
buttonImage.Size = UDim2.new(1,14,1,14)
buttonImage.BackgroundTransparency = 1
buttonImage.ZIndex = 7
buttonImage.Parent = button
local configIcon = buttonImage:clone()
configIcon.Name = "ConfigIcon"
configIcon.Visible = false
configIcon.Position = UDim2.new(1,-23,1,-24)
configIcon.Size = UDim2.new(0,16,0,16)
configIcon.Image = ""
configIcon.ZIndex = 6
configIcon.Parent = insertAssetButtonExample
return insertAssetButtonExample
end
local function showLargePreview(insertButton)
if insertButton:FindFirstChild("AssetId") then
delay(0,function()
game:GetService("ContentProvider"):Preload(LargeThumbnailUrl .. tostring(insertButton.AssetId.Value))
setGui.SetPanel.ItemPreview.LargePreview.Image = LargeThumbnailUrl .. tostring(insertButton.AssetId.Value)
end)
end
if insertButton:FindFirstChild("AssetName") then
setGui.SetPanel.ItemPreview.TextPanel.RolloverText.Text = insertButton.AssetName.Value
end
end
local function selectTerrainShape(shape)
if currTerrainDropDownFrame then
objectSelected(tostring(currTerrainDropDownFrame.AssetName.Value), tonumber(currTerrainDropDownFrame.AssetId.Value), shape)
end
end
local function createTerrainTypeButton(name, parent)
local dropDownTextButton = Instance.new("TextButton")
dropDownTextButton.Name = name .. "Button"
dropDownTextButton.Font = Enum.Font.ArialBold
dropDownTextButton.FontSize = Enum.FontSize.Size14
dropDownTextButton.BorderSizePixel = 0
dropDownTextButton.TextColor3 = Color3.new(1,1,1)
dropDownTextButton.Text = name
dropDownTextButton.TextXAlignment = Enum.TextXAlignment.Left
dropDownTextButton.BackgroundTransparency = 1
dropDownTextButton.ZIndex = parent.ZIndex + 1
dropDownTextButton.Size = UDim2.new(0,parent.Size.X.Offset - 2,0,16)
dropDownTextButton.Position = UDim2.new(0,1,0,0)
dropDownTextButton.MouseEnter:connect(function()
dropDownTextButton.BackgroundTransparency = 0
dropDownTextButton.TextColor3 = Color3.new(0,0,0)
end)
dropDownTextButton.MouseLeave:connect(function()
dropDownTextButton.BackgroundTransparency = 1
dropDownTextButton.TextColor3 = Color3.new(1,1,1)
end)
dropDownTextButton.MouseButton1Click:connect(function()
dropDownTextButton.BackgroundTransparency = 1
dropDownTextButton.TextColor3 = Color3.new(1,1,1)
if dropDownTextButton.Parent and dropDownTextButton.Parent:IsA("GuiObject") then
dropDownTextButton.Parent.Visible = false
end
selectTerrainShape(terrainShapeMap[dropDownTextButton.Text])
end)
return dropDownTextButton
end
local function createTerrainDropDownMenu(zIndex)
local dropDown = Instance.new("Frame")
dropDown.Name = "TerrainDropDown"
dropDown.BackgroundColor3 = Color3.new(0,0,0)
dropDown.BorderColor3 = Color3.new(1,0,0)
dropDown.Size = UDim2.new(0,200,0,0)
dropDown.Visible = false
dropDown.ZIndex = zIndex
dropDown.Parent = setGui
for i = 1, #terrainShapes do
local shapeButton = createTerrainTypeButton(terrainShapes[i],dropDown)
shapeButton.Position = UDim2.new(0,1,0,(i - 1) * (shapeButton.Size.Y.Offset))
shapeButton.Parent = dropDown
dropDown.Size = UDim2.new(0,200,0,dropDown.Size.Y.Offset + (shapeButton.Size.Y.Offset))
end
dropDown.MouseLeave:connect(function()
dropDown.Visible = false
end)
end
local function createDropDownMenuButton(parent)
local dropDownButton = Instance.new("ImageButton")
dropDownButton.Name = "DropDownButton"
dropDownButton.Image = "https://www.roblox.com/asset/?id=67581509"
dropDownButton.BackgroundTransparency = 1
dropDownButton.Size = UDim2.new(0,16,0,16)
dropDownButton.Position = UDim2.new(1,-24,0,6)
dropDownButton.ZIndex = parent.ZIndex + 2
dropDownButton.Parent = parent
if not setGui:FindFirstChild("TerrainDropDown") then
createTerrainDropDownMenu(8)
end
dropDownButton.MouseButton1Click:connect(function()
setGui.TerrainDropDown.Visible = true
setGui.TerrainDropDown.Position = UDim2.new(0,parent.AbsolutePosition.X,0,parent.AbsolutePosition.Y)
currTerrainDropDownFrame = parent
end)
end
local function buildInsertButton()
local insertButton = makeInsertAssetButton()
insertButton.Name = "InsertAssetButton"
insertButton.Visible = true
if Data.Category[Data.CurrentCategory].SetName == "High Scalability" then
createDropDownMenuButton(insertButton)
end
local lastEnter = nil
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
local function realignButtonGrid(columns)
local x = 0
local y = 0
for i = 1, #insertButtons do
insertButtons[i].Position = UDim2.new(0, buttonWidth * x, 0, buttonHeight * y)
x = x + 1
if x >= columns then
x = 0
y = y + 1
end
end
end
local 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)
if insertFrame:findFirstChild("Button") then
insertFrame.Button.ButtonImage.Image = SmallThumbnailUrl .. assetId
end
end)
end
table.insert(insertButtonCons,
insertFrame.Button.MouseButton1Click:connect(function()
-- special case for water, show water selection gui
local isWaterSelected = (name == "Water") and (Data.Category[Data.CurrentCategory].SetName == "High Scalability")
waterGui.Visible = isWaterSelected
if isWaterSelected then
objectSelected(name, tonumber(assetId), nil)
else
objectSelected(name, tonumber(assetId))
end
end)
)
insertFrame.Visible = true
else
insertFrame.Visible = false
end
end
local function loadSectionOfItems(setGui, rows, columns)
local pageSize = rows * columns
if arrayPosition > #contents then return end
local origArrayPos = arrayPosition
local yCopy = 0
for i = 1, pageSize + 1 do
if arrayPosition >= #contents + 1 then
break
end
local buttonCon
insertButtons[arrayPosition], buttonCon = buildInsertButton()
table.insert(insertButtonCons,buttonCon)
insertButtons[arrayPosition].Parent = setGui.SetPanel.ItemsFrame
arrayPosition = arrayPosition + 1
end
realignButtonGrid(columns)
local indexCopy = origArrayPos
for index = origArrayPos, arrayPosition do
if insertButtons[index] then
if contents[index] then
-- we don't want water to have a drop down button
if contents[index].Name == "Water" then
if Data.Category[Data.CurrentCategory].SetName == "High Scalability" then
insertButtons[index]:FindFirstChild("DropDownButton",true):Destroy()
end
end
local assetId
if useAssetVersionId then
assetId = contents[index].AssetVersionId
else
assetId = contents[index].AssetId
end
setInsertButtonImageBehavior(insertButtons[index], true, contents[index].Name, assetId)
else
break
end
else
break
end
indexCopy = index
end
end
local function setSetIndex()
Data.Category[Data.CurrentCategory].Index = 0
rows = 7
columns = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.X/buttonWidth)
contents = Data.Category[Data.CurrentCategory].Contents
if contents then
-- remove our buttons and their connections
for i = 1, #insertButtons do
insertButtons[i]:remove()
end
for i = 1, #insertButtonCons do
if insertButtonCons[i] then insertButtonCons[i]:disconnect() end
end
insertButtonCons = {}
insertButtons = {}
arrayPosition = 1
loadSectionOfItems(setGui, rows, columns)
end
end
local 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()
end
end
local 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
end
end
local function selectCategory(category)
selectCategoryPage(category, 0)
end
local function resetAllSetButtonSelection()
local setButtons = setGui.SetPanel.Sets.SetsLists:GetChildren()
for i = 1, #setButtons do
if setButtons[i]:IsA("TextButton") then
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
end
local function populateSetsFrame()
local currRow = 0
for i = 1, #userCategoryButtons do
local button = userCategoryButtons[i]
button.Visible = true
button.Position = UDim2.new(0,5,0,currRow * button.Size.Y.Offset)
button.Parent = setGui.SetPanel.Sets.SetsLists
if i == 1 then -- we will have this selected by default, so show it
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)
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, userCategoryButtons[i].SetId.Value, 0)
end)
currRow = currRow + 1
end
local buttons = setGui.SetPanel.Sets.SetsLists:GetChildren()
-- set first category as loaded for default
if buttons then
for i = 1, #buttons do
if buttons[i]:IsA("TextButton") then
selectSet(buttons[i], buttons[i].Text, userCategoryButtons[i].SetId.Value, 0)
selectCategory(userCategoryButtons)
break
end
end
end
end
setGui = createSetGui()
waterGui, waterTypeChangedEvent = createWaterGui()
waterGui.Position = UDim2.new(0,55,0,0)
waterGui.Parent = setGui
setGui.Changed:connect(function(prop) -- this resizes the preview image to always be the right size
if prop == "AbsoluteSize" then
handleResize()
setSetIndex()
end
end)
local scrollFrame, controlFrame = t.CreateTrueScrollingFrame()
scrollFrame.Size = UDim2.new(0.54,0,0.85,0)
scrollFrame.Position = UDim2.new(0.24,0,0.085,0)
scrollFrame.Name = "ItemsFrame"
scrollFrame.ZIndex = 6
scrollFrame.Parent = setGui.SetPanel
scrollFrame.BackgroundTransparency = 1
drillDownSetZIndex(controlFrame,7)
controlFrame.Parent = setGui.SetPanel
controlFrame.Position = UDim2.new(0.76, 5, 0, 0)
local debounce = false
controlFrame.ScrollBottom.Changed:connect(function(prop)
if controlFrame.ScrollBottom.Value == true then
if debounce then return end
debounce = true
loadSectionOfItems(setGui, rows, columns)
debounce = false
end
end)
local userData = {}
for id = 1, #userIdsForSets do
local newUserData = game:GetService("InsertService"):GetUserSets(userIdsForSets[id])
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 -- we want high scalability parts to show first
table.insert(userData,1,newUserData[category])
else
table.insert(userData, newUserData[category])
end
end
end
end
if userData then
userCategoryButtons = processCategory(userData)
end
rows = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.Y/buttonHeight)
columns = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.X/buttonWidth)
populateSetsFrame()
setGui.SetPanel.CancelButton.MouseButton1Click:connect(function()
setGui.SetPanel.Visible = false
if dialogClosed then dialogClosed() end
end)
local setVisibilityFunction = function(visible)
if visible then
setGui.SetPanel.Visible = true
else
setGui.SetPanel.Visible = false
end
end
local getVisibilityFunction = function()
if setGui then
if setGui:FindFirstChild("SetPanel") then
return setGui.SetPanel.Visible
end
end
return false
end
return setGui, setVisibilityFunction, getVisibilityFunction, waterTypeChangedEvent
end
t.CreateTerrainMaterialSelector = function(size,position)
local terrainMaterialSelectionChanged = Instance.new("BindableEvent")
terrainMaterialSelectionChanged.Name = "TerrainMaterialSelectionChanged"
local selectedButton = nil
local frame = Instance.new("Frame")
frame.Name = "TerrainMaterialSelector"
if size then
frame.Size = size
else
frame.Size = UDim2.new(0, 245, 0, 230)
end
if position then
frame.Position = position
end
frame.BorderSizePixel = 0
frame.BackgroundColor3 = Color3.new(0,0,0)
frame.Active = true
terrainMaterialSelectionChanged.Parent = frame
local waterEnabled = true -- todo: turn this on when water is ready
local materialToImageMap = {}
local materialNames = {"Grass", "Sand", "Brick", "Granite", "Asphalt", "Iron", "Aluminum", "Gold", "Plank", "Log", "Gravel", "Cinder Block", "Stone Wall", "Concrete", "Plastic (red)", "Plastic (blue)"}
if waterEnabled then
table.insert(materialNames,"Water")
end
local currentMaterial = 1
function getEnumFromName(choice)
if choice == "Grass" then return 1 end
if choice == "Sand" then return 2 end
if choice == "Erase" then return 0 end
if choice == "Brick" then return 3 end
if choice == "Granite" then return 4 end
if choice == "Asphalt" then return 5 end
if choice == "Iron" then return 6 end
if choice == "Aluminum" then return 7 end
if choice == "Gold" then return 8 end
if choice == "Plank" then return 9 end
if choice == "Log" then return 10 end
if choice == "Gravel" then return 11 end
if choice == "Cinder Block" then return 12 end
if choice == "Stone Wall" then return 13 end
if choice == "Concrete" then return 14 end
if choice == "Plastic (red)" then return 15 end
if choice == "Plastic (blue)" then return 16 end
if choice == "Water" then return 17 end
end
function getNameFromEnum(choice)
if choice == Enum.CellMaterial.Grass or choice == 1 then return "Grass"end
if choice == Enum.CellMaterial.Sand or choice == 2 then return "Sand" end
if choice == Enum.CellMaterial.Empty or choice == 0 then return "Erase" end
if choice == Enum.CellMaterial.Brick or choice == 3 then return "Brick" end
if choice == Enum.CellMaterial.Granite or choice == 4 then return "Granite" end
if choice == Enum.CellMaterial.Asphalt or choice == 5 then return "Asphalt" end
if choice == Enum.CellMaterial.Iron or choice == 6 then return "Iron" end
if choice == Enum.CellMaterial.Aluminum or choice == 7 then return "Aluminum" end
if choice == Enum.CellMaterial.Gold or choice == 8 then return "Gold" end
if choice == Enum.CellMaterial.WoodPlank or choice == 9 then return "Plank" end
if choice == Enum.CellMaterial.WoodLog or choice == 10 then return "Log" end
if choice == Enum.CellMaterial.Gravel or choice == 11 then return "Gravel" end
if choice == Enum.CellMaterial.CinderBlock or choice == 12 then return "Cinder Block" end
if choice == Enum.CellMaterial.MossyStone or choice == 13 then return "Stone Wall" end
if choice == Enum.CellMaterial.Cement or choice == 14 then return "Concrete" end
if choice == Enum.CellMaterial.RedPlastic or choice == 15 then return "Plastic (red)" end
if choice == Enum.CellMaterial.BluePlastic or choice == 16 then return "Plastic (blue)" end
if waterEnabled then
if choice == Enum.CellMaterial.Water or choice == 17 then return "Water" end
end
end
local function updateMaterialChoice(choice)
currentMaterial = getEnumFromName(choice)
terrainMaterialSelectionChanged:Fire(currentMaterial)
end
-- we so need a better way to do this
for i,v in pairs(materialNames) do
materialToImageMap[v] = {}
if v == "Grass" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=56563112"
elseif v == "Sand" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=62356652"
elseif v == "Brick" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=65961537"
elseif v == "Granite" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532153"
elseif v == "Asphalt" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532038"
elseif v == "Iron" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532093"
elseif v == "Aluminum" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531995"
elseif v == "Gold" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532118"
elseif v == "Plastic (red)" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531848"
elseif v == "Plastic (blue)" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531924"
elseif v == "Plank" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532015"
elseif v == "Log" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532051"
elseif v == "Gravel" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532206"
elseif v == "Cinder Block" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532103"
elseif v == "Stone Wall" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531804"
elseif v == "Concrete" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532059"
elseif v == "Water" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=81407474"
else materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=66887593" -- fill in the rest here!!
end
end
local scrollFrame, scrollUp, scrollDown, recalculateScroll = t.CreateScrollingFrame(nil,"grid")
scrollFrame.Size = UDim2.new(0.85,0,1,0)
scrollFrame.Position = UDim2.new(0,0,0,0)
scrollFrame.Parent = frame
scrollUp.Parent = frame
scrollUp.Visible = true
scrollUp.Position = UDim2.new(1,-19,0,0)
scrollDown.Parent = frame
scrollDown.Visible = true
scrollDown.Position = UDim2.new(1,-19,1,-17)
local function goToNewMaterial(buttonWrap, materialName)
updateMaterialChoice(materialName)
buttonWrap.BackgroundTransparency = 0
selectedButton.BackgroundTransparency = 1
selectedButton = buttonWrap
end
local function createMaterialButton(name)
local buttonWrap = Instance.new("TextButton")
buttonWrap.Text = ""
buttonWrap.Size = UDim2.new(0,32,0,32)
buttonWrap.BackgroundColor3 = Color3.new(1,1,1)
buttonWrap.BorderSizePixel = 0
buttonWrap.BackgroundTransparency = 1
buttonWrap.AutoButtonColor = false
buttonWrap.Name = tostring(name)
local imageButton = Instance.new("ImageButton")
imageButton.AutoButtonColor = false
imageButton.BackgroundTransparency = 1
imageButton.Size = UDim2.new(0,30,0,30)
imageButton.Position = UDim2.new(0,1,0,1)
imageButton.Name = tostring(name)
imageButton.Parent = buttonWrap
imageButton.Image = materialToImageMap[name].Regular
local enumType = Instance.new("NumberValue")
enumType.Name = "EnumType"
enumType.Parent = buttonWrap
enumType.Value = 0
imageButton.MouseEnter:connect(function()
buttonWrap.BackgroundTransparency = 0
end)
imageButton.MouseLeave:connect(function()
if selectedButton ~= buttonWrap then
buttonWrap.BackgroundTransparency = 1
end
end)
imageButton.MouseButton1Click:connect(function()
if selectedButton ~= buttonWrap then
goToNewMaterial(buttonWrap, tostring(name))
end
end)
return buttonWrap
end
for i = 1, #materialNames do
local imageButton = createMaterialButton(materialNames[i])
if materialNames[i] == "Grass" then -- always start with grass as the default
selectedButton = imageButton
imageButton.BackgroundTransparency = 0
end
imageButton.Parent = scrollFrame
end
local forceTerrainMaterialSelection = function(newMaterialType)
if not newMaterialType then return end
if currentMaterial == newMaterialType then return end
local matName = getNameFromEnum(newMaterialType)
local buttons = scrollFrame:GetChildren()
for i = 1, #buttons do
if buttons[i].Name == "Plastic (blue)" and matName == "Plastic (blue)" then goToNewMaterial(buttons[i],matName) return end
if buttons[i].Name == "Plastic (red)" and matName == "Plastic (red)" then goToNewMaterial(buttons[i],matName) return end
if string.find(buttons[i].Name, matName) then
goToNewMaterial(buttons[i],matName)
return
end
end
end
frame.Changed:connect(function ( prop )
if prop == "AbsoluteSize" then
recalculateScroll()
end
end)
recalculateScroll()
return frame, terrainMaterialSelectionChanged, forceTerrainMaterialSelection
end
t.CreateLoadingFrame = function(name,size,position)
game:GetService("ContentProvider"):Preload("https://www.roblox.com/asset/?id=35238053")
local loadingFrame = Instance.new("Frame")
loadingFrame.Name = "LoadingFrame"
loadingFrame.Style = Enum.FrameStyle.RobloxRound
if size then loadingFrame.Size = size
else loadingFrame.Size = UDim2.new(0,300,0,160) end
if position then loadingFrame.Position = position
else loadingFrame.Position = UDim2.new(0.5, -150, 0.5,-80) end
local loadingBar = Instance.new("Frame")
loadingBar.Name = "LoadingBar"
loadingBar.BackgroundColor3 = Color3.new(0,0,0)
loadingBar.BorderColor3 = Color3.new(79/255,79/255,79/255)
loadingBar.Position = UDim2.new(0,0,0,41)
loadingBar.Size = UDim2.new(1,0,0,30)
loadingBar.Parent = loadingFrame
local loadingGreenBar = Instance.new("ImageLabel")
loadingGreenBar.Name = "LoadingGreenBar"
loadingGreenBar.Image = "https://www.roblox.com/asset/?id=35238053"
loadingGreenBar.Position = UDim2.new(0,0,0,0)
loadingGreenBar.Size = UDim2.new(0,0,1,0)
loadingGreenBar.Visible = false
loadingGreenBar.Parent = loadingBar
local loadingPercent = Instance.new("TextLabel")
loadingPercent.Name = "LoadingPercent"
loadingPercent.BackgroundTransparency = 1
loadingPercent.Position = UDim2.new(0,0,1,0)
loadingPercent.Size = UDim2.new(1,0,0,14)
loadingPercent.Font = Enum.Font.Arial
loadingPercent.Text = "0%"
loadingPercent.FontSize = Enum.FontSize.Size14
loadingPercent.TextColor3 = Color3.new(1,1,1)
loadingPercent.Parent = loadingBar
local cancelButton = Instance.new("TextButton")
cancelButton.Name = "CancelButton"
cancelButton.Position = UDim2.new(0.5,-60,1,-40)
cancelButton.Size = UDim2.new(0,120,0,40)
cancelButton.Font = Enum.Font.Arial
cancelButton.FontSize = Enum.FontSize.Size18
cancelButton.TextColor3 = Color3.new(1,1,1)
cancelButton.Text = "Cancel"
cancelButton.Style = Enum.ButtonStyle.RobloxButton
cancelButton.Parent = loadingFrame
local loadingName = Instance.new("TextLabel")
loadingName.Name = "loadingName"
loadingName.BackgroundTransparency = 1
loadingName.Size = UDim2.new(1,0,0,18)
loadingName.Position = UDim2.new(0,0,0,2)
loadingName.Font = Enum.Font.Arial
loadingName.Text = name
loadingName.TextColor3 = Color3.new(1,1,1)
loadingName.TextStrokeTransparency = 1
loadingName.FontSize = Enum.FontSize.Size18
loadingName.Parent = loadingFrame
local cancelButtonClicked = Instance.new("BindableEvent")
cancelButtonClicked.Name = "CancelButtonClicked"
cancelButtonClicked.Parent = cancelButton
cancelButton.MouseButton1Click:connect(function()
cancelButtonClicked:Fire()
end)
local updateLoadingGuiPercent = function(percent, tweenAction, tweenLength)
if percent and type(percent) ~= "number" then
error("updateLoadingGuiPercent expects number as argument, got",type(percent),"instead")
end
local newSize = nil
if percent < 0 then
newSize = UDim2.new(0,0,1,0)
elseif percent > 1 then
newSize = UDim2.new(1,0,1,0)
else
newSize = UDim2.new(percent,0,1,0)
end
if tweenAction then
if not tweenLength then
error("updateLoadingGuiPercent is set to tween new percentage, but got no tween time length! Please pass this in as third argument")
end
if (newSize.X.Scale > 0) then
loadingGreenBar.Visible = true
loadingGreenBar:TweenSize( newSize,
Enum.EasingDirection.Out,
Enum.EasingStyle.Quad,
tweenLength,
true)
else
loadingGreenBar:TweenSize( newSize,
Enum.EasingDirection.Out,
Enum.EasingStyle.Quad,
tweenLength,
true,
function()
if (newSize.X.Scale < 0) then
loadingGreenBar.Visible = false
end
end)
end
else
loadingGreenBar.Size = newSize
loadingGreenBar.Visible = (newSize.X.Scale > 0)
end
end
loadingGreenBar.Changed:connect(function(prop)
if prop == "Size" then
loadingPercent.Text = tostring( math.ceil(loadingGreenBar.Size.X.Scale * 100) ) .. "%"
end
end)
return loadingFrame, updateLoadingGuiPercent, cancelButtonClicked
end
t.CreatePluginFrame = function (name,size,position,scrollable,parent)
local function createMenuButton(size,position,text,fontsize,name,parent)
local button = Instance.new("TextButton",parent)
button.AutoButtonColor = false
button.Name = name
button.BackgroundTransparency = 1
button.Position = position
button.Size = size
button.Font = Enum.Font.ArialBold
button.FontSize = fontsize
button.Text = text
button.TextColor3 = Color3.new(1,1,1)
button.BorderSizePixel = 0
button.BackgroundColor3 = Color3.new(20/255,20/255,20/255)
button.MouseEnter:connect(function ( )
if button.Selected then return end
button.BackgroundTransparency = 0
end)
button.MouseLeave:connect(function ( )
if button.Selected then return end
button.BackgroundTransparency = 1
end)
return button
end
local dragBar = Instance.new("Frame",parent)
dragBar.Name = tostring(name) .. "DragBar"
dragBar.BackgroundColor3 = Color3.new(39/255,39/255,39/255)
dragBar.BorderColor3 = Color3.new(0,0,0)
if size then
dragBar.Size = UDim2.new(size.X.Scale,size.X.Offset,0,20) + UDim2.new(0,20,0,0)
else
dragBar.Size = UDim2.new(0,183,0,20)
end
if position then
dragBar.Position = position
end
dragBar.Active = true
dragBar.Draggable = true
--dragBar.Visible = false
dragBar.MouseEnter:connect(function ( )
dragBar.BackgroundColor3 = Color3.new(49/255,49/255,49/255)
end)
dragBar.MouseLeave:connect(function ( )
dragBar.BackgroundColor3 = Color3.new(39/255,39/255,39/255)
end)
-- plugin name label
local pluginNameLabel = Instance.new("TextLabel",dragBar)
pluginNameLabel.Name = "BarNameLabel"
pluginNameLabel.Text = " " .. tostring(name)
pluginNameLabel.TextColor3 = Color3.new(1,1,1)
pluginNameLabel.TextStrokeTransparency = 0
pluginNameLabel.Size = UDim2.new(1,0,1,0)
pluginNameLabel.Font = Enum.Font.ArialBold
pluginNameLabel.FontSize = Enum.FontSize.Size18
pluginNameLabel.TextXAlignment = Enum.TextXAlignment.Left
pluginNameLabel.BackgroundTransparency = 1
-- close button
local closeButton = createMenuButton(UDim2.new(0,15,0,17),UDim2.new(1,-16,0.5,-8),"X",Enum.FontSize.Size14,"CloseButton",dragBar)
local closeEvent = Instance.new("BindableEvent")
closeEvent.Name = "CloseEvent"
closeEvent.Parent = closeButton
closeButton.MouseButton1Click:connect(function ()
closeEvent:Fire()
closeButton.BackgroundTransparency = 1
end)
-- help button
local helpButton = createMenuButton(UDim2.new(0,15,0,17),UDim2.new(1,-51,0.5,-8),"?",Enum.FontSize.Size14,"HelpButton",dragBar)
local helpFrame = Instance.new("Frame",dragBar)
helpFrame.Name = "HelpFrame"
helpFrame.BackgroundColor3 = Color3.new(0,0,0)
helpFrame.Size = UDim2.new(0,300,0,552)
helpFrame.Position = UDim2.new(1,5,0,0)
helpFrame.Active = true
helpFrame.BorderSizePixel = 0
helpFrame.Visible = false
helpButton.MouseButton1Click:connect(function( )
helpFrame.Visible = not helpFrame.Visible
if helpFrame.Visible then
helpButton.Selected = true
helpButton.BackgroundTransparency = 0
local screenGui = getLayerCollectorAncestor(helpFrame)
if screenGui then
if helpFrame.AbsolutePosition.X + helpFrame.AbsoluteSize.X > screenGui.AbsoluteSize.X then --position on left hand side
helpFrame.Position = UDim2.new(0,-5 - helpFrame.AbsoluteSize.X,0,0)
else -- position on right hand side
helpFrame.Position = UDim2.new(1,5,0,0)
end
else
helpFrame.Position = UDim2.new(1,5,0,0)
end
else
helpButton.Selected = false
helpButton.BackgroundTransparency = 1
end
end)
local minimizeButton = createMenuButton(UDim2.new(0,16,0,17),UDim2.new(1,-34,0.5,-8),"-",Enum.FontSize.Size14,"MinimizeButton",dragBar)
minimizeButton.TextYAlignment = Enum.TextYAlignment.Top
local minimizeFrame = Instance.new("Frame",dragBar)
minimizeFrame.Name = "MinimizeFrame"
minimizeFrame.BackgroundColor3 = Color3.new(73/255,73/255,73/255)
minimizeFrame.BorderColor3 = Color3.new(0,0,0)
minimizeFrame.Position = UDim2.new(0,0,1,0)
if size then
minimizeFrame.Size = UDim2.new(size.X.Scale,size.X.Offset,0,50) + UDim2.new(0,20,0,0)
else
minimizeFrame.Size = UDim2.new(0,183,0,50)
end
minimizeFrame.Visible = false
local minimizeBigButton = Instance.new("TextButton",minimizeFrame)
minimizeBigButton.Position = UDim2.new(0.5,-50,0.5,-20)
minimizeBigButton.Name = "MinimizeButton"
minimizeBigButton.Size = UDim2.new(0,100,0,40)
minimizeBigButton.Style = Enum.ButtonStyle.RobloxButton
minimizeBigButton.Font = Enum.Font.ArialBold
minimizeBigButton.FontSize = Enum.FontSize.Size18
minimizeBigButton.TextColor3 = Color3.new(1,1,1)
minimizeBigButton.Text = "Show"
local separatingLine = Instance.new("Frame",dragBar)
separatingLine.Name = "SeparatingLine"
separatingLine.BackgroundColor3 = Color3.new(115/255,115/255,115/255)
separatingLine.BorderSizePixel = 0
separatingLine.Position = UDim2.new(1,-18,0.5,-7)
separatingLine.Size = UDim2.new(0,1,0,14)
local otherSeparatingLine = separatingLine:clone()
otherSeparatingLine.Position = UDim2.new(1,-35,0.5,-7)
otherSeparatingLine.Parent = dragBar
local widgetContainer = Instance.new("Frame",dragBar)
widgetContainer.Name = "WidgetContainer"
widgetContainer.BackgroundTransparency = 1
widgetContainer.Position = UDim2.new(0,0,1,0)
widgetContainer.BorderColor3 = Color3.new(0,0,0)
if not scrollable then
widgetContainer.BackgroundTransparency = 0
widgetContainer.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
end
if size then
if scrollable then
widgetContainer.Size = size
else
widgetContainer.Size = UDim2.new(0,dragBar.AbsoluteSize.X,size.Y.Scale,size.Y.Offset)
end
else
if scrollable then
widgetContainer.Size = UDim2.new(0,163,0,400)
else
widgetContainer.Size = UDim2.new(0,dragBar.AbsoluteSize.X,0,400)
end
end
if position then
widgetContainer.Position = position + UDim2.new(0,0,0,20)
end
local frame,control,verticalDragger = nil
if scrollable then
--frame for widgets
frame,control = t.CreateTrueScrollingFrame()
frame.Size = UDim2.new(1, 0, 1, 0)
frame.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
frame.BorderColor3 = Color3.new(0,0,0)
frame.Active = true
frame.Parent = widgetContainer
control.Parent = dragBar
control.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
control.BorderSizePixel = 0
control.BackgroundTransparency = 0
control.Position = UDim2.new(1,-21,1,1)
if size then
control.Size = UDim2.new(0,21,size.Y.Scale,size.Y.Offset)
else
control.Size = UDim2.new(0,21,0,400)
end
control:FindFirstChild("ScrollDownButton").Position = UDim2.new(0,0,1,-20)
local fakeLine = Instance.new("Frame",control)
fakeLine.Name = "FakeLine"
fakeLine.BorderSizePixel = 0
fakeLine.BackgroundColor3 = Color3.new(0,0,0)
fakeLine.Size = UDim2.new(0,1,1,1)
fakeLine.Position = UDim2.new(1,0,0,0)
verticalDragger = Instance.new("TextButton",widgetContainer)
verticalDragger.ZIndex = 2
verticalDragger.AutoButtonColor = false
verticalDragger.Name = "VerticalDragger"
verticalDragger.BackgroundColor3 = Color3.new(50/255,50/255,50/255)
verticalDragger.BorderColor3 = Color3.new(0,0,0)
verticalDragger.Size = UDim2.new(1,20,0,20)
verticalDragger.Position = UDim2.new(0,0,1,0)
verticalDragger.Active = true
verticalDragger.Text = ""
local scrubFrame = Instance.new("Frame",verticalDragger)
scrubFrame.Name = "ScrubFrame"
scrubFrame.BackgroundColor3 = Color3.new(1,1,1)
scrubFrame.BorderSizePixel = 0
scrubFrame.Position = UDim2.new(0.5,-5,0.5,0)
scrubFrame.Size = UDim2.new(0,10,0,1)
scrubFrame.ZIndex = 5
local scrubTwo = scrubFrame:clone()
scrubTwo.Position = UDim2.new(0.5,-5,0.5,-2)
scrubTwo.Parent = verticalDragger
local scrubThree = scrubFrame:clone()
scrubThree.Position = UDim2.new(0.5,-5,0.5,2)
scrubThree.Parent = verticalDragger
local areaSoak = Instance.new("TextButton",getLayerCollectorAncestor(parent))
areaSoak.Name = "AreaSoak"
areaSoak.Size = UDim2.new(1,0,1,0)
areaSoak.BackgroundTransparency = 1
areaSoak.BorderSizePixel = 0
areaSoak.Text = ""
areaSoak.ZIndex = 10
areaSoak.Visible = false
areaSoak.Active = true
local draggingVertical = false
local startYPos = nil
verticalDragger.MouseEnter:connect(function ()
verticalDragger.BackgroundColor3 = Color3.new(60/255,60/255,60/255)
end)
verticalDragger.MouseLeave:connect(function ()
verticalDragger.BackgroundColor3 = Color3.new(50/255,50/255,50/255)
end)
verticalDragger.MouseButton1Down:connect(function(x,y)
draggingVertical = true
areaSoak.Visible = true
startYPos = y
end)
areaSoak.MouseButton1Up:connect(function ( )
draggingVertical = false
areaSoak.Visible = false
end)
areaSoak.MouseMoved:connect(function(x,y)
if not draggingVertical then return end
local yDelta = y - startYPos
if not control.ScrollDownButton.Visible and yDelta > 0 then
return
end
if (widgetContainer.Size.Y.Offset + yDelta) < 150 then
widgetContainer.Size = UDim2.new(widgetContainer.Size.X.Scale, widgetContainer.Size.X.Offset,widgetContainer.Size.Y.Scale,150)
control.Size = UDim2.new (0,21,0,150)
return
end
startYPos = y
if widgetContainer.Size.Y.Offset + yDelta >= 0 then
widgetContainer.Size = UDim2.new(widgetContainer.Size.X.Scale, widgetContainer.Size.X.Offset,widgetContainer.Size.Y.Scale,widgetContainer.Size.Y.Offset + yDelta)
control.Size = UDim2.new(0,21,0,control.Size.Y.Offset + yDelta )
end
end)
end
local function switchMinimize()
minimizeFrame.Visible = not minimizeFrame.Visible
if scrollable then
frame.Visible = not frame.Visible
verticalDragger.Visible = not verticalDragger.Visible
control.Visible = not control.Visible
else
widgetContainer.Visible = not widgetContainer.Visible
end
if minimizeFrame.Visible then
minimizeButton.Text = "+"
else
minimizeButton.Text = "-"
end
end
minimizeBigButton.MouseButton1Click:connect(function ( )
switchMinimize()
end)
minimizeButton.MouseButton1Click:connect(function( )
switchMinimize()
end)
if scrollable then
return dragBar, frame, helpFrame, closeEvent
else
return dragBar, widgetContainer, helpFrame, closeEvent
end
end
t.Help =
function(funcNameOrFunc)
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
if funcNameOrFunc == "CreatePropertyDropDownMenu" or funcNameOrFunc == t.CreatePropertyDropDownMenu then
return "Function CreatePropertyDropDownMenu. " ..
"Arguments: (instance, propertyName, enumType). " ..
"Side effect: returns a container with a drop-down-box that is linked to the 'property' field of 'instance' which is of type 'enumType'"
end
if funcNameOrFunc == "CreateDropDownMenu" or funcNameOrFunc == t.CreateDropDownMenu then
return "Function CreateDropDownMenu. " ..
"Arguments: (items, onItemSelected). " ..
"Side effect: Returns 2 results, a container to the gui object and a 'updateSelection' function for external updating. The container is a drop-down-box created around a list of items"
end
if funcNameOrFunc == "CreateMessageDialog" or funcNameOrFunc == t.CreateMessageDialog then
return "Function CreateMessageDialog. " ..
"Arguments: (title, message, buttons). " ..
"Side effect: Returns a gui object of a message box with 'title' and 'message' as passed in. 'buttons' input is an array of Tables contains a 'Text' and 'Function' field for the text/callback of each button"
end
if funcNameOrFunc == "CreateStyledMessageDialog" or funcNameOrFunc == t.CreateStyledMessageDialog then
return "Function CreateStyledMessageDialog. " ..
"Arguments: (title, message, style, buttons). " ..
"Side effect: Returns a gui object of a message box with 'title' and 'message' as passed in. 'buttons' input is an array of Tables contains a 'Text' and 'Function' field for the text/callback of each button, 'style' is a string, either Error, Notify or Confirm"
end
if funcNameOrFunc == "GetFontHeight" or funcNameOrFunc == t.GetFontHeight then
return "Function GetFontHeight. " ..
"Arguments: (font, fontSize). " ..
"Side effect: returns the size in pixels of the given font + fontSize"
end
if funcNameOrFunc == "LayoutGuiObjects" or funcNameOrFunc == t.LayoutGuiObjects then
end
if funcNameOrFunc == "CreateScrollingFrame" or funcNameOrFunc == t.CreateScrollingFrame then
return "Function CreateScrollingFrame. " ..
"Arguments: (orderList, style) " ..
"Side effect: returns 4 objects, (scrollFrame, scrollUpButton, scrollDownButton, recalculateFunction). 'scrollFrame' can be filled with GuiObjects. It will lay them out and allow scrollUpButton/scrollDownButton to interact with them. Orderlist is optional (and specifies the order to layout the children. Without orderlist, it uses the children order. style is also optional, and allows for a 'grid' styling if style is passed 'grid' as a string. recalculateFunction can be called when a relayout is needed (when orderList changes)"
end
if funcNameOrFunc == "CreateTrueScrollingFrame" or funcNameOrFunc == t.CreateTrueScrollingFrame then
return "Function CreateTrueScrollingFrame. " ..
"Arguments: (nil) " ..
"Side effect: returns 2 objects, (scrollFrame, controlFrame). 'scrollFrame' can be filled with GuiObjects, and they will be clipped if not inside the frame's bounds. controlFrame has children scrollup and scrolldown, as well as a slider. controlFrame can be parented to any guiobject and it will readjust itself to fit."
end
if funcNameOrFunc == "AutoTruncateTextObject" or funcNameOrFunc == t.AutoTruncateTextObject then
return "Function AutoTruncateTextObject. " ..
"Arguments: (textLabel) " ..
"Side effect: returns 2 objects, (textLabel, changeText). The 'textLabel' input is modified to automatically truncate text (with ellipsis), if it gets too small to fit. 'changeText' is a function that can be used to change the text, it takes 1 string as an argument"
end
if funcNameOrFunc == "CreateSlider" or funcNameOrFunc == t.CreateSlider then
return "Function CreateSlider. " ..
"Arguments: (steps, width, position) " ..
"Side effect: returns 2 objects, (sliderGui, sliderPosition). The 'steps' argument specifies how many different positions the slider can hold along the bar. 'width' specifies in pixels how wide the bar should be (modifiable afterwards if desired). 'position' argument should be a UDim2 for slider positioning. 'sliderPosition' is an IntValue whose current .Value specifies the specific step the slider is currently on."
end
if funcNameOrFunc == "CreateSliderNew" or funcNameOrFunc == t.CreateSliderNew then
return "Function CreateSliderNew. " ..
"Arguments: (steps, width, position) " ..
"Side effect: returns 2 objects, (sliderGui, sliderPosition). The 'steps' argument specifies how many different positions the slider can hold along the bar. 'width' specifies in pixels how wide the bar should be (modifiable afterwards if desired). 'position' argument should be a UDim2 for slider positioning. 'sliderPosition' is an IntValue whose current .Value specifies the specific step the slider is currently on."
end
if funcNameOrFunc == "CreateLoadingFrame" or funcNameOrFunc == t.CreateLoadingFrame then
return "Function CreateLoadingFrame. " ..
"Arguments: (name, size, position) " ..
"Side effect: Creates a gui that can be manipulated to show progress for a particular action. Name appears above the loading bar, and size and position are udim2 values (both size and position are optional arguments). Returns 3 arguments, the first being the gui created. The second being updateLoadingGuiPercent, which is a bindable function. This function takes one argument (two optionally), which should be a number between 0 and 1, representing the percentage the loading gui should be at. The second argument to this function is a boolean value that if set to true will tween the current percentage value to the new percentage value, therefore our third argument is how long this tween should take. Our third returned argument is a BindableEvent, that when fired means that someone clicked the cancel button on the dialog."
end
if funcNameOrFunc == "CreateTerrainMaterialSelector" or funcNameOrFunc == t.CreateTerrainMaterialSelector then
return "Function CreateTerrainMaterialSelector. " ..
"Arguments: (size, position) " ..
"Side effect: Size and position are UDim2 values that specifies the selector's size and position. Both size and position are optional arguments. This method returns 3 objects (terrainSelectorGui, terrainSelected, forceTerrainSelection). terrainSelectorGui is just the gui object that we generate with this function, parent it as you like. TerrainSelected is a BindableEvent that is fired whenever a new terrain type is selected in the gui. ForceTerrainSelection is a function that takes an argument of Enum.CellMaterial and will force the gui to show that material as currently selected."
end
end
return t
]]>
-
RbxStamper
{2222EB6D-D42F-4AD4-9463-563FF09E133C}
=0 and t <=1) then
local intersection = ((endPos - startPos) * t) + startPos
cellPos = game:GetService("Workspace").Terrain:WorldToCell(intersection)
hit = true
end
end
return cellPos, hit
end
-- Purpose:
-- Checks for terrain touched by the mouse hit.
-- Will do a plane intersection if no terrain is touched.
--
-- mouse - Mouse to check the .hit for.
--
-- Return:
-- cellPos - Cell position hit. Nil if none.
function GetTerrainForMouse(mouse)
-- There was no target, so all it could be is a plane intersection.
-- Check for a plane intersection. If there isn't one then nothing will get hit.
local cell = game:GetService("Workspace").Terrain:WorldToCellPreferSolid(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
local planeLoc = nil
local hit = nil
-- If nothing was hit, do the plane intersection.
if 0 == game:GetService("Workspace").Terrain:GetCell(cell.X, cell.Y, cell.Z).Value then
cell = nil
planeLoc, hit = PlaneIntersection(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
if hit then
cell = planeLoc
end
end
return cell
end
-- setup helper functions
local insertBoundingBoxOverlapVector = Vector3.new(.3, .3, .3) -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units
-- rotates a model by yAngle radians about the global y-axis
local function rotatePartAndChildren(part, rotCF, offsetFromOrigin)
-- rotate this thing, if it's a part
if part:IsA("BasePart") then
part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin
end
-- recursively do the same to all children
local partChildren = part:GetChildren()
for c = 1, #partChildren do rotatePartAndChildren(partChildren[c], rotCF, offsetFromOrigin) end
end
local function modelRotate(model, yAngle)
local rotCF = CFrame.Angles(0, yAngle, 0)
local offsetFromOrigin = model:GetModelCFrame().p
rotatePartAndChildren(model, rotCF, offsetFromOrigin)
end
local 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
local function clusterPartsInRegion(startVector, endVector)
local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
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
local 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
local 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
local function autoAlignToFace(parts)
local aatf = parts:FindFirstChild("AutoAlignToFace")
if aatf then return aatf.Value else return false end
end
local 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
local function positionPartsAtCFrame3(aCFrame, currentParts)
local insertCFrame = nil
if not currentParts then return currentParts end
if currentParts and (currentParts:IsA("Model") or currentParts:IsA("Tool")) then
insertCFrame = currentParts:GetModelCFrame()
currentParts:TranslateBy(aCFrame.p - insertCFrame.p)
else
currentParts.CFrame = aCFrame
end
return currentParts
end
local 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
return (intersectionPlane - rayStart) / raySlope
end
local 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
local 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("Terrain") then
minVec = Vector3.new(-2, -2, -2)
maxVec = Vector3.new(2, 2, 2)
elseif partOrModel:IsA("BasePart") then
minVec = -0.5 * partOrModel.Size
maxVec = -minVec
else
maxVec = partOrModel:GetModelSize()*0.5
minVec = -maxVec
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
local justify = justifyValue.Value
local two = Vector3.new(2, 2, 2)
local actualBox = maxVec - minVec - Vector3.new(0.01, 0.01, 0.01)
local containingGridBox = Vector3.new(4 * math.ceil(actualBox.x/4), 4 * math.ceil(actualBox.y/4), 4 * math.ceil(actualBox.z/4))
local adjustment = containingGridBox - actualBox
minVec = minVec - 0.5 * adjustment * justify
maxVec = maxVec + 0.5 * adjustment * (two - justify)
end
return minVec, maxVec
end
local 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") and not partOrModel:IsA("Terrain") then
local vec1 = partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
local 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 vec1 = partOrModel:GetModelCFrame():pointToWorldSpace(-0.5 * partOrModel:GetModelSize())
local vec2 = partOrModel:GetModelCFrame():pointToWorldSpace(0.5 * partOrModel:GetModelSize())
minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
end
return minVec, maxVec
end
local function getTargetPartBoundingBox(targetPart)
if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
return getBoundingBox2(targetPart.Parent)
else
return getBoundingBox2(targetPart)
end
end
local 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:GetModelCFrame() end
else
return targetPart.CFrame
end
end
local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this 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:GetService("Lighting") then return false end
return isBlocker(part.Parent)
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
local function spaceAboveCharacter(charTorso, newTorsoY, stampData)
local partsAboveChar = game:GetService("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(stampData.CurrentParts) 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
local function findConfigAtMouseTarget(Mouse, stampData)
-- *Critical Assumption* :
-- This function assumes the target CF axes are orthogonal with the target bounding box faces
-- And, it assumes the insert CF axes are orthongonal with the insert bounding box faces
-- Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor
-- will it work for parts in a model that are not orthogonally aligned with the model's CF.
if not Mouse then return nil end -- This can happen sometimes, return if so
if not stampData then error("findConfigAtMouseTarget: stampData is nil") return nil end
if not stampData["CurrentParts"] then return nil end
local grid = 4.0
local admissibleConfig = false
local targetConfig = CFrame.new(0,0,0)
local minBB, maxBB = getBoundingBox2(stampData.CurrentParts)
local diagBB = maxBB - minBB
local insertCFrame
if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
insertCFrame = stampData.CurrentParts:GetModelCFrame()
else
insertCFrame = stampData.CurrentParts.CFrame
end
if Mouse then
if stampData.CurrentParts:IsA("Tool") then
Mouse.TargetFilter = stampData.CurrentParts.Handle
else
Mouse.TargetFilter = stampData.CurrentParts
end
end
local hitPlane = false
local targetPart = nil
local success = pcall(function() targetPart = Mouse.Target end)
if not success then-- or targetPart == nil then
return admissibleConfig, targetConfig
end
local mouseHitInWorld = Vector3.new(0, 0, 0)
if Mouse then
mouseHitInWorld = Vector3.new(Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z)
end
local cellPos = nil
-- Nothing was hit, so check for the default plane.
if nil == targetPart then
cellPos = GetTerrainForMouse(Mouse)
if nil == cellPos then
hitPlane = false
return admissibleConfig, targetConfig
else
targetPart = game:GetService("Workspace").Terrain
hitPlane = true
-- Take into account error that will occur.
cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z)
mouseHitInWorld = game:GetService("Workspace").Terrain:CellCenterToWorld(cellPos.x, cellPos.y, cellPos.z)
end
end
-- test mouse hit location
local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart)
local diagBBTarget = maxBBTarget - minBBTarget
local targetCFrame = getMouseTargetCFrame(targetPart)
if targetPart:IsA("Terrain") then
local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld)
if hitPlane then
cellID = cellPos
end
targetCFrame = CFrame.new(game:GetService("Workspace").Terrain:CellCenterToWorld(cellID.x, cellID.y, cellID.z))
end
local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld)
local targetVectorInWorld = Vector3.new(0,0,0)
if Mouse then
-- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME! (.TargetSurface is in terms of the part CFrame, so this would break, right? [HotThoth])
-- (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse)
--targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))
targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface)) -- better, but model cframe would be best
--[[if targetPart.Parent:IsA("Model") then
local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right
local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
if hitFace > 0 then
targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace])
elseif hitFace < 0 then
targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace])
end
end]]
end
local targetRefPointInTarget
local clampToSurface
local insertRefPointInInsert
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
local 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
local function saveTheWelds(object, manualWeldTable, manualWeldParentTable)
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], manualWeldTable, manualWeldParentTable)
end
end
end
local function restoreTheWelds(manualWeldTable, manualWeldParentTable)
for i = 1, #manualWeldTable do
manualWeldTable[i].Parent = manualWeldParentTable[i]
end
end
t.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model and stamper metadata
if not EditRegion then return true, false end
local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel)
if minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X/2 or
minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y/2 or
minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z/2 then
return false, false
end
if maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X/2 or
maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y/2 or
maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z/2 then
return false, false
end
return true, false
end
t.GetStampModel = function(assetId, terrainShape, useAssetVersionId)
if assetId == 0 then
return nil, "No Asset"
end
if assetId < 0 then
return nil, "Negative Asset"
end
local function UnlockInstances(object)
if object:IsA("BasePart") then
object.Locked = false
end
for index,child in pairs(object:GetChildren()) do
UnlockInstances(child)
end
end
local 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("Sand red")
elseif terrainValue == 5 then
return BrickColor.new("Black")
elseif terrainValue == 6 then
return BrickColor.new("Dark stone grey")
elseif terrainValue == 7 then
return BrickColor.new("Sand blue")
elseif terrainValue == 8 then
return BrickColor.new("Deep orange")
elseif terrainValue == 9 then
return BrickColor.new("Dark orange")
elseif terrainValue == 10 then
return BrickColor.new("Reddish brown")
elseif terrainValue == 11 then
return BrickColor.new("Light orange")
elseif terrainValue == 12 then
return BrickColor.new("Light stone grey")
elseif terrainValue == 13 then
return BrickColor.new("Sand green")
elseif terrainValue == 14 then
return BrickColor.new("Medium stone grey")
elseif terrainValue == 15 then
return BrickColor.new("Really red")
elseif terrainValue == 16 then
return BrickColor.new("Really blue")
elseif terrainValue == 17 then
return BrickColor.new("Bright blue")
else
return BrickColor.new("Bright green")
end
end
local function setupFakeTerrainPart(cellMat, cellType, cellOrient)
local newTerrainPiece = nil
if (cellType == 1 or cellType == 4) then newTerrainPiece = Instance.new("WedgePart")
elseif (cellType == 2) then newTerrainPiece = Instance.new("CornerWedgePart")
else newTerrainPiece = Instance.new("Part") 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)
local sideways = 0
local flipped = math.pi
if cellType == 4 then sideways = -math.pi/2 end
if cellType == 2 or cellType == 3 then flipped = 0 end
newTerrainPiece.CFrame = CFrame.Angles(0, math.pi/2*cellOrient + flipped, sideways)
if cellType == 3 then
local inverseCornerWedgeMesh = Instance.new("SpecialMesh")
inverseCornerWedgeMesh.MeshType = "FileMesh"
inverseCornerWedgeMesh.MeshId = "https://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, cellType, cellOrient)
materialTag.Name = "ClusterMaterial"
materialTag.Parent = newTerrainPiece
return newTerrainPiece
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 then
return nil, "Load Time Fail"
end
if root == nil then
return nil, "Load Asset Fail"
end
if not root:IsA("Model") then
return nil, "Load Type Fail"
end
local instances = root:GetChildren()
if #instances == 0 then
return nil, "Empty Model Fail"
end
--Unlock all parts that are inserted, to make sure they are editable
UnlockInstances(root)
--Continue the insert process
root = root:GetChildren()[1]
--Examine the contents and decide what it looks like
for pos, instance in pairs(instances) do
if instance:IsA("Team") then
instance.Parent = game:GetService("Teams")
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
end
end
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if root:FindFirstChild("RobloxModel") == nil then
local stringTag = Instance.new("BoolValue", root)
stringTag.Name = "RobloxModel"
if root:FindFirstChild("RobloxStamper") == nil then
local stringTag2 = Instance.new("BoolValue", root)
stringTag2.Name = "RobloxStamper"
end
end
if terrainShape then
if root.Name == "MegaClusterCube" then
if (terrainShape == 6) then -- insert an autowedging tag
local autowedgeTag = Instance.new("BoolValue")
autowedgeTag.Name = "AutoWedge"
autowedgeTag.Parent = root
else
local clusterTag = root:FindFirstChild("ClusterMaterial")
if clusterTag then
if clusterTag:IsA("Vector3Value") then
root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z)
else
root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0)
end
else
root = setupFakeTerrainPart(1, terrainShape, 0)
end
end
end
end
return root
end
t.SetupStamperDragger = function(modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc)
if not modelToStamp then
error("SetupStamperDragger: modelToStamp (first arg) is nil! Should be a stamper model")
return nil
end
if not modelToStamp:IsA("Model") and not modelToStamp:IsA("BasePart") then
error("SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!")
return nil
end
if not Mouse then
error("SetupStamperDragger: Mouse (second arg) is nil! Should be a mouse object")
return nil
end
if not Mouse:IsA("Mouse") then
error("SetupStamperDragger: Mouse (second arg) is not of type Mouse!")
return nil
end
local stampInModel = nil
local allowedStampRegion = nil
local stampFailedFunc = nil
if StampInModel then
if not StampInModel:IsA("Model") then
error("SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'")
return nil
end
if not AllowedStampRegion then
error("SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined")
return nil
end
stampFailedFunc = StampFailedFunc
stampInModel = StampInModel
allowedStampRegion = AllowedStampRegion
end
-- Init all state variables
local gInitial90DegreeRotations = 0
local stampData = nil
local mouseTarget = nil
local errorBox = Instance.new("SelectionBox")
errorBox.Color = BrickColor.new("Bright red")
errorBox.Transparency = 0
errorBox.Archivable = false
-- for megacluster MEGA STAMPING
local adornPart = Instance.new("Part")
adornPart.Parent = nil
adornPart.Size = Vector3.new(4, 4, 4)
adornPart.CFrame = CFrame.new()
adornPart.Archivable = false
local adorn = Instance.new("SelectionBox")
adorn.Color = BrickColor.new("Toothpaste")
adorn.Adornee = adornPart
adorn.Visible = true
adorn.Transparency = 0
adorn.Name = "HighScalabilityStamperLine"
adorn.Archivable = false
local HighScalabilityLine = {}
HighScalabilityLine.Start = nil
HighScalabilityLine.End = nil
HighScalabilityLine.Adorn = adorn
HighScalabilityLine.AdornPart = adornPart
HighScalabilityLine.InternalLine = nil
HighScalabilityLine.NewHint = true
HighScalabilityLine.MorePoints = {nil, nil}
HighScalabilityLine.MoreLines = {nil, nil}
HighScalabilityLine.Dimensions = 1
local control = {}
local movingLock = false
local stampUpLock = false
local unstampableSurface = false
local mouseCons = {}
local keyCon = nil
local stamped = Instance.new("BoolValue")
stamped.Archivable = false
stamped.Value = false
local lastTarget = {}
lastTarget.TerrainOrientation = 0
lastTarget.CFrame = 0
local cellInfo = {}
cellInfo.Material = 1
cellInfo.clusterType = 0
cellInfo.clusterOrientation = 0
local function isMegaClusterPart()
if not stampData then return false end
if not stampData.CurrentParts then return false end
return ( stampData.CurrentParts:FindFirstChild("ClusterMaterial",true) or (stampData.CurrentParts.Name == "MegaClusterCube") )
end
local function DoHighScalabilityRegionSelect()
local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube")
if not megaCube then
if not stampData.CurrentParts.Name == "MegaClusterCube" then
return
else
megaCube = stampData.CurrentParts
end
end
HighScalabilityLine.End = megaCube.CFrame.p
local line = nil
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)
local 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
local 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
local gui = nil
if game:GetService("Players")["LocalPlayer"] then
gui = game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui")
if gui and gui:IsA("PlayerGui") then
if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
elseif HighScalabilityLine.Dimensions > 1 then
HighScalabilityLine.Adorn.Parent = gui
end
end
end
if gui == nil then -- we are in studio
gui = game:GetService("CoreGui")
if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
elseif HighScalabilityLine.Dimensions > 1 then
HighScalabilityLine.Adorn.Parent = gui
end
end
end
local function DoStamperMouseMove(Mouse)
if not Mouse then
error("Error: RbxStamper.DoStamperMouseMove: Mouse is nil")
return
end
if not Mouse:IsA("Mouse") then
error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className,"should be of type Mouse")
return
end
-- There wasn't a target (no part or terrain), so check for plane intersection.
if not Mouse.Target then
local cellPos = GetTerrainForMouse(Mouse)
if nil == cellPos then
return
end
end
if not stampData then
return
end
-- don't move with dragger - will move in one step on mouse down
-- draw ghost at acceptable positions
local configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Mouse, stampData)
if not configFound then
error("RbxStamper.DoStamperMouseMove No configFound, returning")
return
end
local numRotations = 0 -- update this according to how many rotations you need to get it to target surface
if autoAlignToFace(stampData.CurrentParts) and targetSurface ~= 1 and targetSurface ~= 4 then -- pre-rotate the flag or portrait so it's aligned correctly
if targetSurface == 3 then numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 0 then numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 5 then numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 2 then numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
end
end
local ry = math.pi/2
gInitial90DegreeRotations = gInitial90DegreeRotations + numRotations
if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
--stampData.CurrentParts:Rotate(0, ry*numRotations, 0)
modelRotate(stampData.CurrentParts, ry*numRotations)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry*numRotations, 0) * stampData.CurrentParts.CFrame
end
-- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE
local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
-- need to offset by distance to be dragged
local currModelCFrame = nil
if stampData.CurrentParts:IsA("Model") then
currModelCFrame = stampData.CurrentParts:GetModelCFrame()
else
currModelCFrame = stampData.CurrentParts.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 lastTarget.CFrame then
if (stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)) then
local theClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if theClusterMaterial:IsA("Vector3Value") then
local stampClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if stampClusterMaterial then
stampClusterMaterial = theClusterMaterial
end
end
end
end
return
end
-- if we are stamping a terrain part, make sure it goes on the grid! Otherwise preview block could be placed off grid, but stamped on grid
if isMegaClusterPart() then
local cellToStamp = game:GetService("Workspace").Terrain:WorldToCell(targetCFrame.p)
local newCFramePosition = game:GetService("Workspace").Terrain:CellCenterToWorld(cellToStamp.X, cellToStamp.Y, cellToStamp.Z)
local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = targetCFrame:components()
targetCFrame = CFrame.new(newCFramePosition.X,newCFramePosition.Y,newCFramePosition.Z,R00, R01, R02, R10, R11, R12, R20, R21, R22)
end
positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe
if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
local clusterMat = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMat:IsA("Vector3Value") then
lastTarget.TerrainOrientation = clusterMat.Value.Z
end
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 = stampData.CurrentParts: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:GetService("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:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
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(stampData.CurrentParts, Mouse.Hit.p, game:GetService("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:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
return
end
end
-- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
-- targetSurface: 1 is top, 4 is bottom,
end
end
-- to show joints during the mouse move
unstampableSurface = false
game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
-- 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
else
return
end
end)
then
error("Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check")
game:GetService("JointsService"):ClearJoinAfterMoveJoints()
Mouse = nil
return
end
if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
game:GetService("JointsService"):SetJoinAfterMoveTarget(Mouse.Target)
else
game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
end
game:GetService("JointsService"):ShowPermissibleJoints()
-- here we allow for a line of high-scalability parts
if isMegaClusterPart() and HighScalabilityLine and HighScalabilityLine.Start then
DoHighScalabilityRegionSelect()
end
end
local function setupKeyListener(key, Mouse)
if control and control["Paused"] then return end -- don't do this if we have no stamp
key = string.lower(key)
if key == 'r' and not autoAlignToFace(stampData.CurrentParts) then -- rotate the model
gInitial90DegreeRotations = gInitial90DegreeRotations + 1
-- Update orientation value if this is a fake terrain part
local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterValues and clusterValues:IsA("Vector3Value") then
clusterValues.Value = Vector3.new(clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z + 1) % 4)
end
-- Rotate the parts or all the parts in the model
local ry = math.pi/2
if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
--stampData.CurrentParts:Rotate(0, ry, 0)
modelRotate(stampData.CurrentParts, ry)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
end
-- After rotating, update the position
configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
if configFound then
positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
-- update everything else in MouseMove
DoStamperMouseMove(Mouse)
end
elseif key == 'c' then -- try to expand our high scalability dragger dimension
if 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
end
end
keyCon = Mouse.KeyDown:connect(function(key) -- init key connection (keeping code close to func)
setupKeyListener(key, Mouse)
end)
local function resetHighScalabilityLine()
if HighScalabilityLine then
HighScalabilityLine.Start = nil
HighScalabilityLine.End = nil
HighScalabilityLine.InternalLine = nil
HighScalabilityLine.NewHint = true
end
end
local function flashRedBox()
local gui = game:GetService("CoreGui")
if game:GetService("Players") then
if game:GetService("Players")["LocalPlayer"] then
if game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui") then
gui = game:GetService("Players").LocalPlayer.PlayerGui
end
end
end
if not stampData["ErrorBox"] then return end
stampData.ErrorBox.Parent = gui
if stampData.CurrentParts:IsA("Tool") then
stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
else
stampData.ErrorBox.Adornee = stampData.CurrentParts
end
delay(0,function()
for i = 1, 3 do
if stampData["ErrorBox"] then stampData.ErrorBox.Visible = true end
wait(0.13)
if stampData["ErrorBox"] then stampData.ErrorBox.Visible = false end
wait(0.13)
end
if stampData["ErrorBox"] then
stampData.ErrorBox.Adornee = nil
stampData.ErrorBox.Parent = nil
end
end)
end
local function DoStamperMouseDown(Mouse)
if not Mouse then
error("Error: RbxStamper.DoStamperMouseDown: Mouse is nil")
return
end
if not Mouse:IsA("Mouse") then
error("Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className,"should be of type Mouse")
return
end
if not stampData then
return
end
if isMegaClusterPart() then
if Mouse and HighScalabilityLine then
local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube", true)
local terrain = game:GetService("Workspace").Terrain
if megaCube then
HighScalabilityLine.Dimensions = 1
local tempCell = terrain:WorldToCell(megaCube.CFrame.p)
HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
return
else
HighScalabilityLine.Dimensions = 1
local tempCell = terrain:WorldToCell(stampData.CurrentParts.CFrame.p)
HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
return
end
end
end
end
local function loadSurfaceTypes(part, surfaces)
part.TopSurface = surfaces[1]
part.BottomSurface = surfaces[2]
part.LeftSurface = surfaces[3]
part.RightSurface = surfaces[4]
part.FrontSurface = surfaces[5]
part.BackSurface = surfaces[6]
end
local function saveSurfaceTypes(part, myTable)
local tempTable = {}
tempTable[1] = part.TopSurface
tempTable[2] = part.BottomSurface
tempTable[3] = part.LeftSurface
tempTable[4] = part.RightSurface
tempTable[5] = part.FrontSurface
tempTable[6] = part.BackSurface
myTable[part] = tempTable
end
local function makeSurfaceUnjoinable(part, surface)
-- TODO: FILL OUT!
end
local function prepareModel(model)
if not model then return nil end
local gDesiredTrans = 0.7
local gStaticTrans = 1
local clone = model:Clone()
local scripts = {}
local parts = {}
local decals = {}
stampData = {}
stampData.DisabledScripts = {}
stampData.TransparencyTable = {}
stampData.MaterialTable = {}
stampData.CanCollideTable = {}
stampData.AnchoredTable = {}
stampData.ArchivableTable = {}
stampData.DecalTransparencyTable = {}
stampData.SurfaceTypeTable = {}
collectParts(clone, parts, scripts, decals)
if #parts <= 0 then return nil, "no parts found in modelToStamp" end
for index,script in pairs(scripts) do
if not(script.Disabled) then
script.Disabled = true
stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script
end
end
for index, part in pairs(parts) do
stampData.TransparencyTable[part] = part.Transparency
part.Transparency = gStaticTrans + (1 - gStaticTrans) * part.Transparency
stampData.MaterialTable[part] = part.Material
part.Material = Enum.Material.Plastic
stampData.CanCollideTable[part] = part.CanCollide
part.CanCollide = false
stampData.AnchoredTable[part] = part.Anchored
part.Anchored = true
stampData.ArchivableTable[part] = part.Archivable
part.Archivable = false
saveSurfaceTypes(part, stampData.SurfaceTypeTable)
local fadeInDelayTime = 0.5
local transFadeInTime = 0.5
delay(0,function()
wait(fadeInDelayTime) -- give it some time to be completely transparent
local begTime = tick()
local currTime = begTime
while (currTime - begTime) < transFadeInTime and part and part:IsA("BasePart") and part.Transparency > gDesiredTrans do
local newTrans = 1 - (((currTime - begTime)/transFadeInTime) * (gStaticTrans - gDesiredTrans))
if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part]
end
wait(0.03)
currTime = tick()
end
if part and part:IsA("BasePart") then
if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part]
end
end
end)
end
for index, decal in pairs(decals) do
stampData.DecalTransparencyTable[decal] = decal.Transparency
decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency
end
-- disable all seats
setSeatEnabledStatus(clone, true)
setSeatEnabledStatus(clone, false)
stampData.CurrentParts = clone
-- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame"
if autoAlignToFace(clone) then
stampData.CurrentParts:ResetOrientationToIdentity()
gInitial90DegreeRotations = 0
else -- pre-rotate if necessary
local ry = gInitial90DegreeRotations * math.pi/2
if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
--stampData.CurrentParts:Rotate(0, ry, 0)
modelRotate(stampData.CurrentParts, ry)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
end
end
-- since we're cloning the old model instead of the new one, we will need to update the orientation based on the original value AND how many more
-- rotations we expect since then [either that or we need to store the just-stamped clusterMaterial.Value.Z somewhere]. This should fix the terrain rotation
-- issue (fingers crossed) [HotThoth]
local clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMaterial and clusterMaterial:IsA("Vector3Value") then
clusterMaterial.Value = Vector3.new(clusterMaterial.Value.X, clusterMaterial.Value.Y, (clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4)
end
-- After rotating, update the position
local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
if configFound then
stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
end
-- to show joints during the mouse move
game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
return clone, parts
end
local function checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
local cellCenterToWorld = game:GetService("Workspace").Terrain.CellCenterToWorld
local cellCenter = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
local cellBlockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector, cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector), stampData.CurrentParts, 100)
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, stampData) 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!
local canSetCell = true
if checkHighScalabilityStamp then -- check to see if cell is in region, if not we'll skip set
if allowedStampRegion then
local cellPos = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
if cellPos.X + 2 > allowedStampRegion.CFrame.p.X + allowedStampRegion.Size.X/2 then
canSetCell = false
elseif cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X/2 then
canSetCell = false
elseif cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y/2 then
canSetCell = false
elseif cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y/2 then
canSetCell = false
elseif cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z/2 then
canSetCell = false
elseif cellPos.Z - 2 < allowedStampRegion.CFrame.p.Z - allowedStampRegion.Size.Z/2 then
canSetCell = false
end
end
end
return canSetCell
end
return false
end
local function ResolveMegaClusterStamp(checkHighScalabilityStamp)
local cellSet = false
local cluser = game:GetService("Workspace").Terrain
local line = HighScalabilityLine.InternalLine
local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
local cMin = game:GetService("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 stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMaterial:IsA("Vector3Value") then
clusterType = clusterMaterial.Value.Y
clusterOrientation = clusterMaterial.Value.Z
clusterMaterial = clusterMaterial.Value.X
elseif clusterMaterial:IsA("IntValue") then
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:GetService("Workspace").Terrain:WorldToCell(HighScalabilityLine.Start)
local xInc = {0,0,0}
local yInc = {0,0,0}
local zInc = {0,0,0}
local cluster = game:GetService("Workspace").Terrain
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
local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
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 okToStampTerrainBlock = checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
if okToStampTerrainBlock then
if waterForceTag then
cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
else
cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation)
end
cellSet = true
-- auto-wedge it?
if (autoWedgeClusterParts) then
game:GetService("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 -- skip all remaining loops
end
if (stepVect[2].magnitude*4 > lines[2].magnitude) then innerStepVectIndex = 4 end
end
end
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 -- skip all remaining loops
stepVect[3] = Vector3.new(1, 0, 0) outerStepVectIndex = 4
end
if (stepVect[3].magnitude*4 > lines[3].magnitude) then outerStepVectIndex = 4 end
end
end
end
-- and also get rid of any HighScalabilityLine stuff if it's there
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
-- Mark for undo.
if cellSet then
stampData.CurrentParts.Parent = nil
pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StamperMulti") end)
end
return cellSet
end
local function DoStamperMouseUp(Mouse)
if not Mouse then
error("Error: RbxStamper.DoStamperMouseUp: Mouse is nil")
return false
end
if not Mouse:IsA("Mouse") then
error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className,"should be of type Mouse")
return false
end
if not stampData.Dragger then
error("Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil")
return false
end
if not HighScalabilityLine then
return false
end
local checkHighScalabilityStamp = nil
if stampInModel then
local canStamp = nil
local isHSLPart = isMegaClusterPart()
if isHSLPart and
HighScalabilityLine and
HighScalabilityLine.Start and
HighScalabilityLine.InternalLine and
HighScalabilityLine.InternalLine.magnitude > 0 then -- we have an HSL line, test later
canStamp = true
checkHighScalabilityStamp = true
else
canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
end
if not canStamp then
if stampFailedFunc then
stampFailedFunc()
end
return false
end
end
-- if unstampable face, then don't let us stamp there!
if unstampableSurface then
flashRedBox()
return false
end
-- recheck if we can stamp, as we just moved part
local canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
if not canStamp then
if stampFailedFunc then
stampFailedFunc()
end
return false
end
-- Prevent part from being stamped on top of a player
local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
-- HotThoth's note: Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the
-- value of configFound from the previous onStamperMouseMove call which moved the CurrentParts
-- Shouldn't this be true when lastTargetCFrame has been set and false otherwise?
configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
if configFound and not HighScalabilityLine.Adorn.Parent then
if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
flashRedBox()
return false
end
local blockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(minBB + insertBoundingBoxOverlapVector,
maxBB - insertBoundingBoxOverlapVector),
stampData.CurrentParts,
100)
for b = 1, #blockingParts do
if isBlocker(blockingParts[b]) then
flashRedBox()
return false
end
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 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, stampData) then
blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
else
-- if no space, we just error
flashRedBox()
return false
end
end
---------------------------------------------------------------------------
end
end
elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) then -- if no config then only stamp if it's a real HSL!
resetHighScalabilityLine()
return false
end
-- something will be stamped! so set the "StampedSomething" toggle to true
if game:GetService("Players")["LocalPlayer"] then
if game:GetService("Players").LocalPlayer["Character"] then
local localChar = game:GetService("Players").LocalPlayer.Character
local stampTracker = localChar:FindFirstChild("StampTracker")
if stampTracker and not stampTracker.Value then
stampTracker.Value = true
end
end
end
-- if we drew a line of mega parts, stamp them out
if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart() then
if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp then
-- kill the ghost part
stampData.CurrentParts.Parent = nil
return true
end
end
-- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
local cluster = game:GetService("Workspace").Terrain
-- if target point is in cluster, just use cluster:SetCell
if isMegaClusterPart() then
-- if targetCFrame is inside cluster, just set that cell to 1 and return
--local cellPos = cluster:WorldToCell(targetCFrame.p)
local cellPos
if stampData.CurrentParts:IsA("Model") then cellPos = cluster:WorldToCell(stampData.CurrentParts:GetModelCFrame().p)
else cellPos = cluster:WorldToCell(stampData.CurrentParts.CFrame.p) end
local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
if checkTerrainBlockCollisions(cellPos, false) then
local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
if cellPos.X >= cMin.X and cellPos.Y >= cMin.Y and cellPos.Z >= cMin.Z and cellPos.X < cMax.X and cellPos.Y < cMax.Y and cellPos.Z < cMax.Z then
if waterForceTag then
cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
elseif not clusterValues then
cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, cellInfo.Material, cellInfo.clusterType, gInitial90DegreeRotations % 4)
elseif clusterValues:IsA("Vector3Value") then
cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value.X, clusterValues.Value.Y, clusterValues.Value.Z)
else
cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value, 0, 0)
end
local autoWedgeClusterParts = false
if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
-- auto-wedge it
if (autoWedgeClusterParts) then
game:GetService("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
-- kill the ghost part
stampData.CurrentParts.Parent = nil
-- Mark for undo. It has to happen here or the selection display will come back also.
pcall(function() game:GetService("ChangeHistoryService"):SetWaypoint("StamperSingle") end)
return true
end
else
-- you tried to stamp a HSL-single part where one does not belong!
flashRedBox()
return false
end
end
local function getPlayer()
if game:GetService("Players")["LocalPlayer"] then
return game:GetService("Players").LocalPlayer
end
return nil
end
-- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints
if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
if stampData.CurrentParts:IsA("Model") then
-- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X<
local manualWeldTable = {}
local manualWeldParentTable = {}
saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable)
stampData.CurrentParts:BreakJoints()
stampData.CurrentParts:MakeJoints()
restoreTheWelds(manualWeldTable, manualWeldParentTable)
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)
local playerIdTag = stampData.CurrentParts:FindFirstChild("PlayerIdTag")
local playerNameTag = stampData.CurrentParts:FindFirstChild("PlayerNameTag")
if playerIdTag ~= nil then
local tempPlayerValue = getPlayer()
if tempPlayerValue ~= nil then playerIdTag.Value = tempPlayerValue.UserId end
end
if playerNameTag ~= nil then
if game:GetService("Players")["LocalPlayer"] then
local tempPlayerValue = game:GetService("Players").LocalPlayer
if tempPlayerValue ~= nil then playerNameTag.Value = tempPlayerValue.Name end
end
end
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
stringTag.Name = "RobloxModel"
if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
stringTag2.Name = "RobloxStamper"
end
end
else
stampData.CurrentParts:BreakJoints()
if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
stringTag2.Name = "RobloxStamper"
end
end
-- make sure all the joints are activated before restoring anchor states
game:GetService("JointsService"):CreateJoinAfterMoveJoints()
-- Restore the original properties for all parts being stamped
for part, transparency in pairs(stampData.TransparencyTable) do
part.Transparency = transparency
end
for part, archivable in pairs(stampData.ArchivableTable) do
part.Archivable = archivable
end
for part, material in pairs(stampData.MaterialTable) do
part.Material = material
end
for part, collide in pairs(stampData.CanCollideTable) do
part.CanCollide = collide
end
for part, anchored in pairs(stampData.AnchoredTable) do
part.Anchored = anchored
end
for decal, transparency in pairs(stampData.DecalTransparencyTable) do
decal.Transparency = transparency
end
for part, surfaces in pairs(stampData.SurfaceTypeTable) do
loadSurfaceTypes(part, surfaces)
end
if isMegaClusterPart() then
stampData.CurrentParts.Transparency = 0
end
-- re-enable all seats
setSeatEnabledStatus(stampData.CurrentParts, true)
stampData.TransparencyTable = nil
stampData.ArchivableTable = nil
stampData.MaterialTable = nil
stampData.CanCollideTable = nil
stampData.AnchoredTable = nil
stampData.SurfaceTypeTable = nil
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
stringTag.Name = "RobloxModel"
end
--Re-enable the scripts
for index,script in pairs(stampData.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(stampData.DisabledScripts) do
local oldParent = script.Parent
script.Parent = nil
script:Clone().Parent = oldParent
end
-- clear out more data
stampData.DisabledScripts = nil
stampData.Dragger = nil
stampData.CurrentParts = nil
pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StampedObject") end)
return true
end
local function pauseStamper()
for i = 1, #mouseCons do -- stop the mouse from doing anything
mouseCons[i]:disconnect()
mouseCons[i] = nil
end
mouseCons = {}
if stampData and stampData.CurrentParts then -- remove our ghost part
stampData.CurrentParts.Parent = nil
stampData.CurrentParts:Remove()
end
resetHighScalabilityLine()
game:GetService("JointsService"):ClearJoinAfterMoveJoints()
end
local function prepareUnjoinableSurfaces(modelCFrame, parts, whichSurface)
local AXIS_VECTORS = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)} -- maybe last one is negative? TODO: check this!
local isPositive = 1
if whichSurface < 0 then isPositive = isPositive * -1 whichSurface = whichSurface*-1 end
local surfaceNormal = isPositive * modelCFrame:vectorToWorldSpace(AXIS_VECTORS[whichSurface])
for i = 1, #parts do
local currPart = parts[i]
-- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
local surfaceNormalInLocalCoords = currPart.CFrame:vectorToObjectSpace(surfaceNormal)
if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Y) then
if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Z) then
if surfaceNormalInLocalCoords.X > 0 then currPart.RightSurface = "Unjoinable" else currPart.LeftSurface = "Unjoinable" end
else
if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
end
else
if math.abs(surfaceNormalInLocalCoords.Y) > math.abs(surfaceNormalInLocalCoords.Z) then
if surfaceNormalInLocalCoords.Y > 0 then currPart.TopSurface = "Unjoinable" else currPart.BottomSurface = "Unjoinable" end
else
if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
end
end
end
end
local function resumeStamper()
local clone, parts = prepareModel(modelToStamp)
if not clone or not parts then
return
end
-- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
local unjoinableTag = clone:FindFirstChild("UnjoinableFaces", true)
if unjoinableTag then
for unjoinableSurface in string.gmatch(unjoinableTag.Value, "[^,]*") do
if tonumber(unjoinableSurface) then
if clone:IsA("Model") then
prepareUnjoinableSurfaces(clone:GetModelCFrame(), parts, tonumber(unjoinableSurface))
else
prepareUnjoinableSurfaces(clone.CFrame, parts, tonumber(unjoinableSurface))
end
end
end
end
stampData.ErrorBox = errorBox
if stampInModel then
clone.Parent = stampInModel
else
clone.Parent = game:GetService("Workspace")
end
if clone:FindFirstChild("ClusterMaterial", true) then -- extract all info from vector
local clusterMaterial = clone:FindFirstChild("ClusterMaterial", true)
if (clusterMaterial:IsA("Vector3Value")) then
cellInfo.Material = clusterMaterial.Value.X
cellInfo.clusterType = clusterMaterial.Value.Y
cellInfo.clusterOrientation = clusterMaterial.Value.Z
elseif clusterMaterial:IsA("IntValue") then
cellInfo.Material = clusterMaterial.Value
end
end
pcall(function() mouseTarget = Mouse.Target end)
if mouseTarget and mouseTarget.Parent:FindFirstChild("RobloxModel") == nil then
game:GetService("JointsService"):SetJoinAfterMoveTarget(mouseTarget)
else
game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
end
game:GetService("JointsService"):ShowPermissibleJoints()
for index, object in pairs(stampData.DisabledScripts) do
if object.Name == "GhostRemovalScript" then
object.Parent = stampData.CurrentParts
end
end
stampData.Dragger = Instance.new("Dragger")
--Begin a movement by faking a MouseDown signal
stampData.Dragger:MouseDown(parts[1], Vector3.new(0,0,0), parts)
stampData.Dragger:MouseUp()
DoStamperMouseMove(Mouse)
table.insert(mouseCons,Mouse.Move:connect(function()
if movingLock or stampUpLock then return end
movingLock = true
DoStamperMouseMove(Mouse)
movingLock = false
end))
table.insert(mouseCons,Mouse.Button1Down:connect(function()
DoStamperMouseDown(Mouse)
end))
table.insert(mouseCons,Mouse.Button1Up:connect(function()
stampUpLock = true
while movingLock do wait() end
stamped.Value = DoStamperMouseUp(Mouse)
resetHighScalabilityLine()
stampUpLock = false
end))
stamped.Value = false
end
local function resetStamperState(newModelToStamp)
-- if we have a new model, swap it out
if newModelToStamp then
if not newModelToStamp:IsA("Model") and not newModelToStamp:IsA("BasePart") then
error("resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!")
end
modelToStamp = newModelToStamp
end
-- first clear our state
pauseStamper()
-- now lets load in the new model
resumeStamper()
end
-- load the model initially
resetStamperState()
-- setup the control table we pass back to the user
control.Stamped = stamped -- BoolValue that fires when user stamps
control.Paused = false
control.LoadNewModel = function(newStampModel) -- allows us to specify a new stamper model to be used with this stamper
if newStampModel and not newStampModel:IsA("Model") and not newStampModel:IsA("BasePart") then
error("Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!")
return nil
end
resetStamperState(newStampModel)
end
control.ReloadModel = function() -- will automatically set stamper to get a new model of current model and start stamping with new model
resetStamperState()
end
control.Pause = function() -- temporarily stops stamping, use resume to start up again
if not control.Paused then
pauseStamper()
control.Paused = true
else
print("RbxStamper Warning: Tried to call Control.Pause() when already paused")
end
end
control.Resume = function() -- resumes stamping, if currently paused
if control.Paused then
resumeStamper()
control.Paused = false
else
print("RbxStamper Warning: Tried to call Control.Resume() without Pausing First")
end
end
control.ResetRotation = function() -- resets the model rotation so new models are at default orientation
-- gInitial90DegreeRotations = 0
-- Note: This function will not always work quite the way we want it to; we will have to build this out further so it works with
-- High-Scalability and with the new model orientation setting methods (model:ResetOrientationToIdentity()) [HotThoth]
end
control.Destroy = function() -- Stops current Stamp operation and destroys control construct
for i = 1, #mouseCons do
mouseCons[i]:disconnect()
mouseCons[i] = nil
end
if keyCon then
keyCon:disconnect()
end
game:GetService("JointsService"):ClearJoinAfterMoveJoints()
if adorn then adorn:Destroy() end
if adornPart then adornPart:Destroy() end
if errorBox then errorBox:Destroy() end
if stampData then
if stampData["Dragger"] then
stampData.Dragger:Destroy()
end
if stampData.CurrentParts then
stampData.CurrentParts:Destroy()
end
end
if control and control["Stamped"] then
control.Stamped:Destroy()
end
control = nil
end
return control
end
t.Help =
function(funcNameOrFunc)
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
if funcNameOrFunc == "GetStampModel" or funcNameOrFunc == t.GetStampModel then
return "Function GetStampModel. Arguments: assetId, useAssetVersionId. assetId is the asset to load in, define useAssetVersionId as true if assetId is a version id instead of a relative assetId. Side effect: returns a model of the assetId, or a string with error message if something fails"
end
if funcNameOrFunc == "SetupStamperDragger" or funcNameOrFunc == t.SetupStamperDragger then
return "Function SetupStamperDragger. Side Effect: Creates 4x4 stamping mechanism for building out parts quickly. Arguments: ModelToStamp, Mouse, LegalStampCheckFunction. ModelToStamp should be a Model or Part, preferrably loaded from RbxStamper.GetStampModel and should have extents that are multiples of 4. Mouse should be a mouse object (obtained from things such as Tool.OnEquipped), used to drag parts around 'stamp' them out. LegalStampCheckFunction is optional, used as a callback with a table argument (table is full of instances about to be stamped). Function should return either true or false, false stopping the stamp action."
end
end
return t
]]>
-
RbxUtility
{DF8B2557-F46D-42DC-AF33-22F13A7D6296}
0 then
if math.floor(k) == k then
return true
end
end
return false
end
for k,v in pairs(t) do
if not isindex(k) then
return false, '{', '}'
else
count = math.max(count, k)
end
end
return true, '[', ']', count
end
function JsonWriter:WriteTable(t)
local ba, st, et, n = self:IsArray(t)
self:Append(st)
if ba then
for i = 1, n do
self:Write(t[i])
if i < n then
self:Append(',')
end
end
else
local first = true;
for k, v in pairs(t) do
if not first then
self:Append(',')
end
first = false;
self:ParseString(k)
self:Append(':')
self:Write(v)
end
end
self:Append(et)
end
function JsonWriter:WriteError(o)
error(string.format(
"Encoding of %s unsupported",
tostring(o)))
end
function JsonWriter:WriteFunction(o)
if o == Null then
self:WriteNil()
else
self:WriteError(o)
end
end
local StringReader = {
s = "",
i = 0
}
function StringReader:New(s)
local o = {}
setmetatable(o, self)
self.__index = self
o.s = s or o.s
return o
end
function StringReader:Peek()
local i = self.i + 1
if i <= #self.s then
return string.sub(self.s, i, i)
end
return nil
end
function StringReader:Next()
self.i = self.i+1
if self.i <= #self.s then
return string.sub(self.s, self.i, self.i)
end
return nil
end
function StringReader:All()
return self.s
end
local JsonReader = {
escapes = {
['t'] = '\t',
['n'] = '\n',
['f'] = '\f',
['r'] = '\r',
['b'] = '\b',
}
}
function JsonReader:New(s)
local o = {}
o.reader = StringReader:New(s)
setmetatable(o, self)
self.__index = self
return o;
end
function JsonReader:Read()
self:SkipWhiteSpace()
local peek = self:Peek()
if peek == nil then
error(string.format(
"Nil string: '%s'",
self:All()))
elseif peek == '{' then
return self:ReadObject()
elseif peek == '[' then
return self:ReadArray()
elseif peek == '"' then
return self:ReadString()
elseif string.find(peek, "[%+%-%d]") then
return self:ReadNumber()
elseif peek == 't' then
return self:ReadTrue()
elseif peek == 'f' then
return self:ReadFalse()
elseif peek == 'n' then
return self:ReadNull()
elseif peek == '/' then
self:ReadComment()
return self:Read()
else
return nil
end
end
function JsonReader:ReadTrue()
self:TestReservedWord{'t','r','u','e'}
return true
end
function JsonReader:ReadFalse()
self:TestReservedWord{'f','a','l','s','e'}
return false
end
function JsonReader:ReadNull()
self:TestReservedWord{'n','u','l','l'}
return nil
end
function JsonReader:TestReservedWord(t)
for i, v in ipairs(t) do
if self:Next() ~= v then
error(string.format(
"Error reading '%s': %s",
table.concat(t),
self:All()))
end
end
end
function JsonReader:ReadNumber()
local result = self:Next()
local peek = self:Peek()
while peek ~= nil and string.find(
peek,
"[%+%-%d%.eE]") do
result = result .. self:Next()
peek = self:Peek()
end
result = tonumber(result)
if result == nil then
error(string.format(
"Invalid number: '%s'",
result))
else
return result
end
end
function JsonReader:ReadString()
local result = ""
assert(self:Next() == '"')
while self:Peek() ~= '"' do
local ch = self:Next()
if ch == '\\' then
ch = self:Next()
if self.escapes[ch] then
ch = self.escapes[ch]
end
end
result = result .. ch
end
assert(self:Next() == '"')
local fromunicode = function(m)
return string.char(tonumber(m, 16))
end
return string.gsub(
result,
"u%x%x(%x%x)",
fromunicode)
end
function JsonReader:ReadComment()
assert(self:Next() == '/')
local second = self:Next()
if second == '/' then
self:ReadSingleLineComment()
elseif second == '*' then
self:ReadBlockComment()
else
error(string.format(
"Invalid comment: %s",
self:All()))
end
end
function JsonReader:ReadBlockComment()
local done = false
while not done do
local ch = self:Next()
if ch == '*' and self:Peek() == '/' then
done = true
end
if not done and
ch == '/' and
self:Peek() == "*" then
error(string.format(
"Invalid comment: %s, '/*' illegal.",
self:All()))
end
end
self:Next()
end
function JsonReader:ReadSingleLineComment()
local ch = self:Next()
while ch ~= '\r' and ch ~= '\n' do
ch = self:Next()
end
end
function JsonReader:ReadArray()
local result = {}
assert(self:Next() == '[')
local done = false
if self:Peek() == ']' then
done = true;
end
while not done do
local item = self:Read()
result[#result+1] = item
self:SkipWhiteSpace()
if self:Peek() == ']' then
done = true
end
if not done then
local ch = self:Next()
if ch ~= ',' then
error(string.format(
"Invalid array: '%s' due to: '%s'",
self:All(), ch))
end
end
end
assert(']' == self:Next())
return result
end
function JsonReader:ReadObject()
local result = {}
assert(self:Next() == '{')
local done = false
if self:Peek() == '}' then
done = true
end
while not done do
local key = self:Read()
if type(key) ~= "string" then
error(string.format(
"Invalid non-string object key: %s",
key))
end
self:SkipWhiteSpace()
local ch = self:Next()
if ch ~= ':' then
error(string.format(
"Invalid object: '%s' due to: '%s'",
self:All(),
ch))
end
self:SkipWhiteSpace()
local val = self:Read()
result[key] = val
self:SkipWhiteSpace()
if self:Peek() == '}' then
done = true
end
if not done then
ch = self:Next()
if ch ~= ',' then
error(string.format(
"Invalid array: '%s' near: '%s'",
self:All(),
ch))
end
end
end
assert(self:Next() == "}")
return result
end
function JsonReader:SkipWhiteSpace()
local p = self:Peek()
while p ~= nil and string.find(p, "[%s/]") do
if p == '/' then
self:ReadComment()
else
self:Next()
end
p = self:Peek()
end
end
function JsonReader:Peek()
return self.reader:Peek()
end
function JsonReader:Next()
return self.reader:Next()
end
function JsonReader:All()
return self.reader:All()
end
function Encode(o)
local writer = JsonWriter:New()
writer:Write(o)
return writer:ToString()
end
function Decode(s)
local reader = JsonReader:New(s)
return reader:Read()
end
function Null()
return Null
end
-------------------- End JSON Parser ------------------------
t.DecodeJSON = function(jsonString)
pcall(function() warn("RbxUtility.DecodeJSON is deprecated, please use Game:GetService('HttpService'):JSONDecode() instead.") end)
if type(jsonString) == "string" then
return Decode(jsonString)
end
print("RbxUtil.DecodeJSON expects string argument!")
return nil
end
t.EncodeJSON = function(jsonTable)
pcall(function() warn("RbxUtility.EncodeJSON is deprecated, please use Game:GetService('HttpService'):JSONEncode() instead.") end)
return Encode(jsonTable)
end
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
--------------------------------------------Terrain Utilities Begin-----------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
--makes a wedge at location x, y, z
--sets cell x, y, z to default material if parameter is provided, if not sets cell x, y, z to be whatever material it previously w
--returns true if made a wedge, false if the cell remains a block
t.MakeWedge = function(x, y, z, defaultmaterial)
return game:GetService("Terrain"):AutoWedgeCell(x,y,z)
end
t.SelectTerrainRegion = function(regionToSelect, color, selectEmptyCells, selectionParent)
local terrain = game:GetService("Workspace"):FindFirstChild("Terrain")
if not terrain then return end
assert(regionToSelect)
assert(color)
if not type(regionToSelect) == "Region3" then
error("regionToSelect (first arg), should be of type Region3, but is type",type(regionToSelect))
end
if not type(color) == "BrickColor" then
error("color (second arg), should be of type BrickColor, but is type",type(color))
end
-- frequently used terrain calls (speeds up call, no lookup necessary)
local GetCell = terrain.GetCell
local WorldToCellPreferSolid = terrain.WorldToCellPreferSolid
local CellCenterToWorld = terrain.CellCenterToWorld
local emptyMaterial = Enum.CellMaterial.Empty
-- container for all adornments, passed back to user
local selectionContainer = Instance.new("Model")
selectionContainer.Name = "SelectionContainer"
selectionContainer.Archivable = false
if selectionParent then
selectionContainer.Parent = selectionParent
else
selectionContainer.Parent = game:GetService("Workspace")
end
local updateSelection = nil -- function we return to allow user to update selection
local currentKeepAliveTag = nil -- a tag that determines whether adorns should be destroyed
local aliveCounter = 0 -- helper for currentKeepAliveTag
local lastRegion = nil -- used to stop updates that do nothing
local adornments = {} -- contains all adornments
local reusableAdorns = {}
local selectionPart = Instance.new("Part")
selectionPart.Name = "SelectionPart"
selectionPart.Transparency = 1
selectionPart.Anchored = true
selectionPart.Locked = true
selectionPart.CanCollide = false
selectionPart.Size = Vector3.new(4.2,4.2,4.2)
local selectionBox = Instance.new("SelectionBox")
-- srs translation from region3 to region3int16
local function Region3ToRegion3int16(region3)
local theLowVec = region3.CFrame.p - (region3.Size/2) + Vector3.new(2,2,2)
local lowCell = WorldToCellPreferSolid(terrain,theLowVec)
local theHighVec = region3.CFrame.p + (region3.Size/2) - Vector3.new(2,2,2)
local highCell = WorldToCellPreferSolid(terrain, theHighVec)
local highIntVec = Vector3int16.new(highCell.x,highCell.y,highCell.z)
local lowIntVec = Vector3int16.new(lowCell.x,lowCell.y,lowCell.z)
return Region3int16.new(lowIntVec,highIntVec)
end
-- helper function that creates the basis for a selection box
function createAdornment(theColor)
local selectionPartClone = nil
local selectionBoxClone = nil
if #reusableAdorns > 0 then
selectionPartClone = reusableAdorns[1]["part"]
selectionBoxClone = reusableAdorns[1]["box"]
table.remove(reusableAdorns,1)
selectionBoxClone.Visible = true
else
selectionPartClone = selectionPart:Clone()
selectionPartClone.Archivable = false
selectionBoxClone = selectionBox:Clone()
selectionBoxClone.Archivable = false
selectionBoxClone.Adornee = selectionPartClone
selectionBoxClone.Parent = selectionContainer
selectionBoxClone.Adornee = selectionPartClone
selectionBoxClone.Parent = selectionContainer
end
if theColor then
selectionBoxClone.Color = theColor
end
return selectionPartClone, selectionBoxClone
end
-- iterates through all current adornments and deletes any that don't have latest tag
function cleanUpAdornments()
for cellPos, adornTable in pairs(adornments) do
if adornTable.KeepAlive ~= currentKeepAliveTag then -- old news, we should get rid of this
adornTable.SelectionBox.Visible = false
table.insert(reusableAdorns,{part = adornTable.SelectionPart, box = adornTable.SelectionBox})
adornments[cellPos] = nil
end
end
end
-- helper function to update tag
function incrementAliveCounter()
aliveCounter = aliveCounter + 1
if aliveCounter > 1000000 then
aliveCounter = 0
end
return aliveCounter
end
-- finds full cells in region and adorns each cell with a box, with the argument color
function adornFullCellsInRegion(region, color)
local regionBegin = region.CFrame.p - (region.Size/2) + Vector3.new(2,2,2)
local regionEnd = region.CFrame.p + (region.Size/2) - Vector3.new(2,2,2)
local cellPosBegin = WorldToCellPreferSolid(terrain, regionBegin)
local cellPosEnd = WorldToCellPreferSolid(terrain, regionEnd)
currentKeepAliveTag = incrementAliveCounter()
for y = cellPosBegin.y, cellPosEnd.y do
for z = cellPosBegin.z, cellPosEnd.z do
for x = cellPosBegin.x, cellPosEnd.x do
local cellMaterial = GetCell(terrain, x, y, z)
if cellMaterial ~= emptyMaterial then
local cframePos = CellCenterToWorld(terrain, x, y, z)
local cellPos = Vector3int16.new(x,y,z)
local updated = false
for cellPosAdorn, adornTable in pairs(adornments) do
if cellPosAdorn == cellPos then
adornTable.KeepAlive = currentKeepAliveTag
if color then
adornTable.SelectionBox.Color = color
end
updated = true
break
end
end
if not updated then
local selectionPart, selectionBox = createAdornment(color)
selectionPart.Size = Vector3.new(4,4,4)
selectionPart.CFrame = CFrame.new(cframePos)
local adornTable = {SelectionPart = selectionPart, SelectionBox = selectionBox, KeepAlive = currentKeepAliveTag}
adornments[cellPos] = adornTable
end
end
end
end
end
cleanUpAdornments()
end
------------------------------------- setup code ------------------------------
lastRegion = regionToSelect
if selectEmptyCells then -- use one big selection to represent the area selected
local selectionPart, selectionBox = createAdornment(color)
selectionPart.Size = regionToSelect.Size
selectionPart.CFrame = regionToSelect.CFrame
adornments.SelectionPart = selectionPart
adornments.SelectionBox = selectionBox
updateSelection =
function (newRegion, color)
if newRegion and newRegion ~= lastRegion then
lastRegion = newRegion
selectionPart.Size = newRegion.Size
selectionPart.CFrame = newRegion.CFrame
end
if color then
selectionBox.Color = color
end
end
else -- use individual cell adorns to represent the area selected
adornFullCellsInRegion(regionToSelect, color)
updateSelection =
function (newRegion, color)
if newRegion and newRegion ~= lastRegion then
lastRegion = newRegion
adornFullCellsInRegion(newRegion, color)
end
end
end
local destroyFunc = function()
updateSelection = nil
if selectionContainer then selectionContainer:Destroy() end
adornments = nil
end
return updateSelection, destroyFunc
end
-----------------------------Terrain Utilities End-----------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------Signal class begin------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
--[[
A 'Signal' object identical to the internal RBXScriptSignal object in it's public API and semantics. This function
can be used to create "custom events" for user-made code.
API:
Method :connect( function handler )
Arguments: The function to connect to.
Returns: A new connection object which can be used to disconnect the connection
Description: Connects this signal to the function specified by |handler|. That is, when |fire( ... )| is called for
the signal the |handler| will be called with the arguments given to |fire( ... )|. Note, the functions
connected to a signal are called in NO PARTICULAR ORDER, so connecting one function after another does
NOT mean that the first will be called before the second as a result of a call to |fire|.
Method :disconnect()
Arguments: None
Returns: None
Description: Disconnects all of the functions connected to this signal.
Method :fire( ... )
Arguments: Any arguments are accepted
Returns: None
Description: Calls all of the currently connected functions with the given arguments.
Method :wait()
Arguments: None
Returns: The arguments given to fire
Description: This call blocks until
]]
function t.CreateSignal()
local this = {}
local mBindableEvent = Instance.new('BindableEvent')
local mAllCns = {} --all connection objects returned by mBindableEvent::connect
--main functions
function this:connect(func)
if self ~= this then error("connect must be called with `:`, not `.`", 2) end
if type(func) ~= 'function' then
error("Argument #1 of connect must be a function, got a "..type(func), 2)
end
local cn = mBindableEvent.Event:Connect(func)
mAllCns[cn] = true
local pubCn = {}
function pubCn:disconnect()
cn:Disconnect()
mAllCns[cn] = nil
end
pubCn.Disconnect = pubCn.disconnect
return pubCn
end
function this:disconnect()
if self ~= this then error("disconnect must be called with `:`, not `.`", 2) end
for cn, _ in pairs(mAllCns) do
cn:Disconnect()
mAllCns[cn] = nil
end
end
function this:wait()
if self ~= this then error("wait must be called with `:`, not `.`", 2) end
return mBindableEvent.Event:Wait()
end
function this:fire(...)
if self ~= this then error("fire must be called with `:`, not `.`", 2) end
mBindableEvent:Fire(...)
end
this.Connect = this.connect
this.Disconnect = this.disconnect
this.Wait = this.wait
this.Fire = this.fire
return this
end
------------------------------------------------- Sigal class End ------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------Create Function Begins---------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
--[[
A "Create" function for easy creation of Roblox instances. The function accepts a string which is the classname of
the object to be created. The function then returns another function which either accepts accepts no arguments, in
which case it simply creates an object of the given type, or a table argument that may contain several types of data,
in which case it mutates the object in varying ways depending on the nature of the aggregate data. These are the
type of data and what operation each will perform:
1) A string key mapping to some value:
Key-Value pairs in this form will be treated as properties of the object, and will be assigned in NO PARTICULAR
ORDER. If the order in which properties is assigned matter, then they must be assigned somewhere else than the
|Create| call's body.
2) An integral key mapping to another Instance:
Normal numeric keys mapping to Instances will be treated as children if the object being created, and will be
parented to it. This allows nice recursive calls to Create to create a whole hierarchy of objects without a
need for temporary variables to store references to those objects.
3) A key which is a value returned from Create.Event( eventname ), and a value which is a function function
The Create.E( string ) function provides a limited way to connect to signals inside of a Create hierarchy
for those who really want such a functionality. The name of the event whose name is passed to
Create.E( string )
4) A key which is the Create function itself, and a value which is a function
The function will be run with the argument of the object itself after all other initialization of the object is
done by create. This provides a way to do arbitrary things involving the object from withing the create
hierarchy.
Note: This function is called SYNCHRONOUSLY, that means that you should only so initialization in
it, not stuff which requires waiting, as the Create call will block until it returns. While waiting in the
constructor callback function is possible, it is probably not a good design choice.
Note: Since the constructor function is called after all other initialization, a Create block cannot have two
constructor functions, as it would not be possible to call both of them last, also, this would be unnecessary.
Some example usages:
A simple example which uses the Create function to create a model object and assign two of it's properties.
local model = Create'Model'{
Name = 'A New model',
Parent = game.Workspace,
}
An example where a larger hierarchy of object is made. After the call the hierarchy will look like this:
Model_Container
|-ObjectValue
| |
| `-BoolValueChild
`-IntValue
local model = Create'Model'{
Name = 'Model_Container',
Create'ObjectValue'{
Create'BoolValue'{
Name = 'BoolValueChild',
},
},
Create'IntValue'{},
}
An example using the event syntax:
local part = Create'Part'{
[Create.E'Touched'] = function(part)
print("I was touched by "..part.Name)
end,
}
An example using the general constructor syntax:
local model = Create'Part'{
[Create] = function(this)
print("Constructor running!")
this.Name = GetGlobalFoosAndBars(this)
end,
}
Note: It is also perfectly legal to save a reference to the function returned by a call Create, this will not cause
any unexpected behavior. EG:
local partCreatingFunction = Create'Part'
local part = partCreatingFunction()
]]
--the Create function need to be created as a functor, not a function, in order to support the Create.E syntax, so it
--will be created in several steps rather than as a single function declaration.
local function Create_PrivImpl(objectType)
if type(objectType) ~= 'string' then
error("Argument of Create must be a string", 2)
end
--return the proxy function that gives us the nice Create'string'{data} syntax
--The first function call is a function call using Lua's single-string-argument syntax
--The second function call is using Lua's single-table-argument syntax
--Both can be chained together for the nice effect.
return function(dat)
--default to nothing, to handle the no argument given case
dat = dat or {}
--make the object to mutate
local obj = Instance.new(objectType)
local parent = nil
--stored constructor function to be called after other initialization
local ctor = nil
for k, v in pairs(dat) do
--add property
if type(k) == 'string' then
if k == 'Parent' then
-- Parent should always be set last, setting the Parent of a new object
-- immediately makes performance worse for all subsequent property updates.
parent = v
else
obj[k] = v
end
--add child
elseif type(k) == 'number' then
if type(v) ~= 'userdata' then
error("Bad entry in Create body: Numeric keys must be paired with children, got a: "..type(v), 2)
end
v.Parent = obj
--event connect
elseif type(k) == 'table' and k.__eventname then
if type(v) ~= 'function' then
error("Bad entry in Create body: Key `[Create.E\'"..k.__eventname.."\']` must have a function value\
got: "..tostring(v), 2)
end
obj[k.__eventname]:connect(v)
--define constructor function
elseif k == t.Create then
if type(v) ~= 'function' then
error("Bad entry in Create body: Key `[Create]` should be paired with a constructor function, \
got: "..tostring(v), 2)
elseif ctor then
--ctor already exists, only one allowed
error("Bad entry in Create body: Only one constructor function is allowed", 2)
end
ctor = v
else
error("Bad entry ("..tostring(k).." => "..tostring(v)..") in Create body", 2)
end
end
--apply constructor function if it exists
if ctor then
ctor(obj)
end
if parent then
obj.Parent = parent
end
--return the completed object
return obj
end
end
--now, create the functor:
t.Create = setmetatable({}, {__call = function(tb, ...) return Create_PrivImpl(...) end})
--and create the "Event.E" syntax stub. Really it's just a stub to construct a table which our Create
--function can recognize as special.
t.Create.E = function(eventName)
return {__eventname = eventName}
end
-------------------------------------------------Create function End----------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------Documentation Begin-----------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
t.Help =
function(funcNameOrFunc)
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
if funcNameOrFunc == "DecodeJSON" or funcNameOrFunc == t.DecodeJSON then
return "Function DecodeJSON. " ..
"Arguments: (string). " ..
"Side effect: returns a table with all parsed JSON values"
end
if funcNameOrFunc == "EncodeJSON" or funcNameOrFunc == t.EncodeJSON then
return "Function EncodeJSON. " ..
"Arguments: (table). " ..
"Side effect: returns a string composed of argument table in JSON data format"
end
if funcNameOrFunc == "MakeWedge" or funcNameOrFunc == t.MakeWedge then
return "Function MakeWedge. " ..
"Arguments: (x, y, z, [default material]). " ..
"Description: Makes a wedge at location x, y, z. Sets cell x, y, z to default material if "..
"parameter is provided, if not sets cell x, y, z to be whatever material it previously was. "..
"Returns true if made a wedge, false if the cell remains a block "
end
if funcNameOrFunc == "SelectTerrainRegion" or funcNameOrFunc == t.SelectTerrainRegion then
return "Function SelectTerrainRegion. " ..
"Arguments: (regionToSelect, color, selectEmptyCells, selectionParent). " ..
"Description: Selects all terrain via a series of selection boxes within the regionToSelect " ..
"(this should be a region3 value). The selection box color is detemined by the color argument " ..
"(should be a brickcolor value). SelectionParent is the parent that the selection model gets placed to (optional)." ..
"SelectEmptyCells is bool, when true will select all cells in the " ..
"region, otherwise we only select non-empty cells. Returns a function that can update the selection," ..
"arguments to said function are a new region3 to select, and the adornment color (color arg is optional). " ..
"Also returns a second function that takes no arguments and destroys the selection"
end
if funcNameOrFunc == "CreateSignal" or funcNameOrFunc == t.CreateSignal then
return "Function CreateSignal. "..
"Arguments: None. "..
"Returns: The newly created Signal object. This object is identical to the RBXScriptSignal class "..
"used for events in Objects, but is a Lua-side object so it can be used to create custom events in"..
"Lua code. "..
"Methods of the Signal object: :connect, :wait, :fire, :disconnect. "..
"For more info you can pass the method name to the Help function, or view the wiki page "..
"for this library. EG: Help('Signal:connect')."
end
if funcNameOrFunc == "Signal:connect" then
return "Method Signal:connect. "..
"Arguments: (function handler). "..
"Return: A connection object which can be used to disconnect the connection to this handler. "..
"Description: Connectes a handler function to this Signal, so that when |fire| is called the "..
"handler function will be called with the arguments passed to |fire|."
end
if funcNameOrFunc == "Signal:wait" then
return "Method Signal:wait. "..
"Arguments: None. "..
"Returns: The arguments passed to the next call to |fire|. "..
"Description: This call does not return until the next call to |fire| is made, at which point it "..
"will return the values which were passed as arguments to that |fire| call."
end
if funcNameOrFunc == "Signal:fire" then
return "Method Signal:fire. "..
"Arguments: Any number of arguments of any type. "..
"Returns: None. "..
"Description: This call will invoke any connected handler functions, and notify any waiting code "..
"attached to this Signal to continue, with the arguments passed to this function. Note: The calls "..
"to handlers are made asynchronously, so this call will return immediately regardless of how long "..
"it takes the connected handler functions to complete."
end
if funcNameOrFunc == "Signal:disconnect" then
return "Method Signal:disconnect. "..
"Arguments: None. "..
"Returns: None. "..
"Description: This call disconnects all handlers attacched to this function, note however, it "..
"does NOT make waiting code continue, as is the behavior of normal Roblox events. This method "..
"can also be called on the connection object which is returned from Signal:connect to only "..
"disconnect a single handler, as opposed to this method, which will disconnect all handlers."
end
if funcNameOrFunc == "Create" then
return "Function Create. "..
"Arguments: A table containing information about how to construct a collection of objects. "..
"Returns: The constructed objects. "..
"Descrition: Create is a very powerfull function, whose description is too long to fit here, and "..
"is best described via example, please see the wiki page for a description of how to use it."
end
end
--------------------------------------------Documentation Ends----------------------------------------------------------
return t
]]>