· 5 years ago · Mar 26, 2020, 02:42 AM
1local t = {}
2
3local function ScopedConnect(parentInstance, instance, event, signalFunc, syncFunc, removeFunc)
4 local eventConnection = nil
5
6 --Connection on parentInstance is scoped by parentInstance (when destroyed, it goes away)
7 local tryConnect = function()
8 if game:IsAncestorOf(parentInstance) then
9 --Entering the world, make sure we are connected/synced
10 if not eventConnection then
11 eventConnection = instance[event]:connect(signalFunc)
12 if syncFunc then syncFunc() end
13 end
14 else
15 --Probably leaving the world, so disconnect for now
16 if eventConnection then
17 eventConnection:disconnect()
18 if removeFunc then removeFunc() end
19 end
20 end
21 end
22
23 --Hook it up to ancestryChanged signal
24 local connection = parentInstance.AncestryChanged:connect(tryConnect)
25
26 --Now connect us if we're already in the world
27 tryConnect()
28
29 return connection
30end
31
32local function getLayerCollectorAncestor(instance)
33 local localInstance = instance
34 while localInstance and not localInstance:IsA("LayerCollector") do
35 localInstance = localInstance.Parent
36 end
37 return localInstance
38end
39
40local function CreateButtons(frame, buttons, yPos, ySize)
41 local buttonNum = 1
42 local buttonObjs = {}
43 for i, obj in ipairs(buttons) do
44 local button = Instance.new("TextButton")
45 button.Name = "Button" .. buttonNum
46 button.Font = Enum.Font.Arial
47 button.FontSize = Enum.FontSize.Size18
48 button.AutoButtonColor = true
49 button.Modal = true
50 if obj["Style"] then
51 button.Style = obj.Style
52 else
53 button.Style = Enum.ButtonStyle.RobloxButton
54 end
55 if obj["ZIndex"] then
56 button.ZIndex = obj.ZIndex
57 end
58 button.Text = obj.Text
59 button.TextColor3 = Color3.new(1,1,1)
60 button.MouseButton1Click:connect(obj.Function)
61 button.Parent = frame
62 buttonObjs[buttonNum] = button
63
64 buttonNum = buttonNum + 1
65 end
66 local numButtons = buttonNum-1
67
68 if numButtons == 1 then
69 frame.Button1.Position = UDim2.new(0.35, 0, yPos.Scale, yPos.Offset)
70 frame.Button1.Size = UDim2.new(.4,0,ySize.Scale, ySize.Offset)
71 elseif numButtons == 2 then
72 frame.Button1.Position = UDim2.new(0.1, 0, yPos.Scale, yPos.Offset)
73 frame.Button1.Size = UDim2.new(.8/3,0, ySize.Scale, ySize.Offset)
74
75 frame.Button2.Position = UDim2.new(0.55, 0, yPos.Scale, yPos.Offset)
76 frame.Button2.Size = UDim2.new(.35,0, ySize.Scale, ySize.Offset)
77 elseif numButtons >= 3 then
78 local spacing = .1 / numButtons
79 local buttonSize = .9 / numButtons
80
81 buttonNum = 1
82 while buttonNum <= numButtons do
83 buttonObjs[buttonNum].Position = UDim2.new(spacing*buttonNum + (buttonNum-1) * buttonSize, 0, yPos.Scale, yPos.Offset)
84 buttonObjs[buttonNum].Size = UDim2.new(buttonSize, 0, ySize.Scale, ySize.Offset)
85 buttonNum = buttonNum + 1
86 end
87 end
88end
89
90local function setSliderPos(newAbsPosX,slider,sliderPosition,bar,steps)
91
92 local newStep = steps - 1 --otherwise we really get one more step than we want
93 local relativePosX = math.min(1, math.max(0, (newAbsPosX - bar.AbsolutePosition.X) / bar.AbsoluteSize.X ))
94 local wholeNum, remainder = math.modf(relativePosX * newStep)
95 if remainder > 0.5 then
96 wholeNum = wholeNum + 1
97 end
98 relativePosX = wholeNum/newStep
99
100 local result = math.ceil(relativePosX * newStep)
101 if sliderPosition.Value ~= (result + 1) then --only update if we moved a step
102 sliderPosition.Value = result + 1
103 slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
104 end
105
106end
107
108local function cancelSlide(areaSoak)
109 areaSoak.Visible = false
110end
111
112t.CreateStyledMessageDialog = function(title, message, style, buttons)
113 local frame = Instance.new("Frame")
114 frame.Size = UDim2.new(0.5, 0, 0, 165)
115 frame.Position = UDim2.new(0.25, 0, 0.5, -72.5)
116 frame.Name = "MessageDialog"
117 frame.Active = true
118 frame.Style = Enum.FrameStyle.RobloxRound
119
120 local styleImage = Instance.new("ImageLabel")
121 styleImage.Name = "StyleImage"
122 styleImage.BackgroundTransparency = 1
123 styleImage.Position = UDim2.new(0,5,0,15)
124 if style == "error" or style == "Error" then
125 styleImage.Size = UDim2.new(0, 71, 0, 71)
126 styleImage.Image = "https://www.roblox.com/asset/?id=42565285"
127 elseif style == "notify" or style == "Notify" then
128 styleImage.Size = UDim2.new(0, 71, 0, 71)
129 styleImage.Image = "https://www.roblox.com/asset/?id=42604978"
130 elseif style == "confirm" or style == "Confirm" then
131 styleImage.Size = UDim2.new(0, 74, 0, 76)
132 styleImage.Image = "https://www.roblox.com/asset/?id=42557901"
133 else
134 return t.CreateMessageDialog(title,message,buttons)
135 end
136 styleImage.Parent = frame
137
138 local titleLabel = Instance.new("TextLabel")
139 titleLabel.Name = "Title"
140 titleLabel.Text = title
141 titleLabel.TextStrokeTransparency = 0
142 titleLabel.BackgroundTransparency = 1
143 titleLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
144 titleLabel.Position = UDim2.new(0, 80, 0, 0)
145 titleLabel.Size = UDim2.new(1, -80, 0, 40)
146 titleLabel.Font = Enum.Font.ArialBold
147 titleLabel.FontSize = Enum.FontSize.Size36
148 titleLabel.TextXAlignment = Enum.TextXAlignment.Center
149 titleLabel.TextYAlignment = Enum.TextYAlignment.Center
150 titleLabel.Parent = frame
151
152 local messageLabel = Instance.new("TextLabel")
153 messageLabel.Name = "Message"
154 messageLabel.Text = message
155 messageLabel.TextStrokeTransparency = 0
156 messageLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
157 messageLabel.Position = UDim2.new(0.025, 80, 0, 45)
158 messageLabel.Size = UDim2.new(0.95, -80, 0, 55)
159 messageLabel.BackgroundTransparency = 1
160 messageLabel.Font = Enum.Font.Arial
161 messageLabel.FontSize = Enum.FontSize.Size18
162 messageLabel.TextWrap = true
163 messageLabel.TextXAlignment = Enum.TextXAlignment.Left
164 messageLabel.TextYAlignment = Enum.TextYAlignment.Top
165 messageLabel.Parent = frame
166
167 CreateButtons(frame, buttons, UDim.new(0, 105), UDim.new(0, 40) )
168
169 return frame
170end
171
172t.CreateMessageDialog = function(title, message, buttons)
173 local frame = Instance.new("Frame")
174 frame.Size = UDim2.new(0.5, 0, 0.5, 0)
175 frame.Position = UDim2.new(0.25, 0, 0.25, 0)
176 frame.Name = "MessageDialog"
177 frame.Active = true
178 frame.Style = Enum.FrameStyle.RobloxRound
179
180 local titleLabel = Instance.new("TextLabel")
181 titleLabel.Name = "Title"
182 titleLabel.Text = title
183 titleLabel.BackgroundTransparency = 1
184 titleLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
185 titleLabel.Position = UDim2.new(0, 0, 0, 0)
186 titleLabel.Size = UDim2.new(1, 0, 0.15, 0)
187 titleLabel.Font = Enum.Font.ArialBold
188 titleLabel.FontSize = Enum.FontSize.Size36
189 titleLabel.TextXAlignment = Enum.TextXAlignment.Center
190 titleLabel.TextYAlignment = Enum.TextYAlignment.Center
191 titleLabel.Parent = frame
192
193 local messageLabel = Instance.new("TextLabel")
194 messageLabel.Name = "Message"
195 messageLabel.Text = message
196 messageLabel.TextColor3 = Color3.new(221/255,221/255,221/255)
197 messageLabel.Position = UDim2.new(0.025, 0, 0.175, 0)
198 messageLabel.Size = UDim2.new(0.95, 0, .55, 0)
199 messageLabel.BackgroundTransparency = 1
200 messageLabel.Font = Enum.Font.Arial
201 messageLabel.FontSize = Enum.FontSize.Size18
202 messageLabel.TextWrap = true
203 messageLabel.TextXAlignment = Enum.TextXAlignment.Left
204 messageLabel.TextYAlignment = Enum.TextYAlignment.Top
205 messageLabel.Parent = frame
206
207 CreateButtons(frame, buttons, UDim.new(0.8,0), UDim.new(0.15, 0))
208
209 return frame
210end
211
212-- written by jmargh
213-- to be used for the new settings menu
214t.CreateScrollingDropDownMenu = function(onSelectedCallback, size, position, baseZ)
215 local maxVisibleList = 6
216 local baseZIndex = 0
217 if type(baseZ) == 'number' then
218 baseZIndex = baseZ
219 end
220
221 local dropDownMenu = {}
222 local currentList = nil
223
224 local updateFunc = nil
225 local frame = Instance.new('Frame')
226 frame.Name = "DropDownMenuFrame"
227 frame.Size = size
228 frame.Position = position
229 frame.BackgroundTransparency = 1
230 dropDownMenu.Frame = frame
231
232 local currentSelectionName = Instance.new('TextButton')
233 currentSelectionName.Name = "CurrentSelectionName"
234 currentSelectionName.Size = UDim2.new(1, 0, 1, 0)
235 currentSelectionName.BackgroundTransparency = 1
236 currentSelectionName.Font = Enum.Font.SourceSansBold
237 currentSelectionName.FontSize = Enum.FontSize.Size18
238 currentSelectionName.TextXAlignment = Enum.TextXAlignment.Left
239 currentSelectionName.TextYAlignment = Enum.TextYAlignment.Center
240 currentSelectionName.TextColor3 = Color3.new(0.5, 0.5, 0.5)
241 currentSelectionName.TextWrap = true
242 currentSelectionName.ZIndex = baseZIndex
243 currentSelectionName.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
244 currentSelectionName.Text = "Choose One"
245 currentSelectionName.Parent = frame
246 dropDownMenu.CurrentSelectionButton = currentSelectionName
247
248 local icon = Instance.new('ImageLabel')
249 icon.Name = "DropDownIcon"
250 icon.Size = UDim2.new(0, 16, 0, 12)
251 icon.Position = UDim2.new(1, -17, 0.5, -6)
252 icon.Image = 'rbxasset://textures/ui/dropdown_arrow.png'
253 icon.BackgroundTransparency = 1
254 icon.ZIndex = baseZIndex
255 icon.Parent = currentSelectionName
256
257 local listMenu = nil
258 local scrollingBackground = nil
259 local visibleCount = 0
260 local isOpen = false
261
262 local function onEntrySelected()
263 icon.Rotation = 0
264 scrollingBackground:TweenSize(UDim2.new(1, 0, 0, currentSelectionName.AbsoluteSize.y), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true)
265 --
266 listMenu.ScrollBarThickness = 0
267 listMenu:TweenSize(UDim2.new(1, -16, 0, 24), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true, function()
268 if not isOpen then
269 listMenu.Visible = false
270 scrollingBackground.Visible = false
271 end
272 end)
273 isOpen = false
274 end
275
276 currentSelectionName.MouseButton1Click:connect(function()
277 if not currentSelectionName.Active or #currentList == 0 then return end
278 if isOpen then
279 onEntrySelected()
280 return
281 end
282 --
283 isOpen = true
284 icon.Rotation = 180
285 if listMenu then listMenu.Visible = true end
286 if scrollingBackground then scrollingBackground.Visible = true end
287 --
288 if scrollingBackground then
289 scrollingBackground:TweenSize(UDim2.new(1, 0, 0, visibleCount * 24 + 8), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true)
290 end
291 if listMenu then
292 listMenu:TweenSize(UDim2.new(1, -16, 0, visibleCount * 24), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.15, true, function()
293 listMenu.ScrollBarThickness = 6
294 end)
295 end
296 end)
297
298 --[[ Public API ]]--
299 dropDownMenu.IsOpen = function()
300 return isOpen
301 end
302
303 dropDownMenu.Close = function()
304 onEntrySelected()
305 end
306
307 dropDownMenu.Reset = function()
308 isOpen = false
309 icon.Rotation = 0
310 listMenu.ScrollBarThickness = 0
311 listMenu.Size = UDim2.new(1, -16, 0, 24)
312 listMenu.Visible = false
313 scrollingBackground.Visible = false
314 end
315
316 dropDownMenu.SetVisible = function(isVisible)
317 if frame then
318 frame.Visible = isVisible
319 end
320 end
321
322 dropDownMenu.UpdateZIndex = function(newZIndexBase)
323 currentSelectionName.ZIndex = newZIndexBase
324 icon.ZIndex = newZIndexBase
325 if scrollingBackground then scrollingBackground.ZIndex = newZIndexBase + 1 end
326 if listMenu then
327 listMenu.ZIndex = newZIndexBase + 2
328 for _,child in pairs(listMenu:GetChildren()) do
329 child.ZIndex = newZIndexBase + 4
330 end
331 end
332 end
333
334 dropDownMenu.SetActive = function(isActive)
335 currentSelectionName.Active = isActive
336 end
337
338 dropDownMenu.SetSelectionText = function(text)
339 currentSelectionName.Text = text
340 end
341
342 dropDownMenu.CreateList = function(list)
343 currentSelectionName.Text = "Choose One"
344 if listMenu then listMenu:Destroy() end
345 if scrollingBackground then scrollingBackground:Destroy() end
346 --
347 currentList = list
348 local length = #list
349 visibleCount = math.min(maxVisibleList, length)
350 local listMenuOffset = visibleCount * 24
351
352 listMenu = Instance.new('ScrollingFrame')
353 listMenu.Name = "ListMenu"
354 listMenu.Size = UDim2.new(1, -16, 0, 24)
355 listMenu.Position = UDim2.new(0, 12, 0, 32)
356 listMenu.CanvasSize = UDim2.new(0, 0, 0, length * 24)
357 listMenu.BackgroundTransparency = 1
358 listMenu.BorderSizePixel = 0
359 listMenu.ZIndex = baseZIndex + 2
360 listMenu.Visible = false
361 listMenu.Active = true
362 listMenu.BottomImage = 'rbxasset://textures/ui/scroll-bottom.png'
363 listMenu.MidImage = 'rbxasset://textures/ui/scroll-middle.png'
364 listMenu.TopImage = 'rbxasset://textures/ui/scroll-top.png'
365 listMenu.ScrollBarThickness = 0
366 listMenu.Parent = frame
367
368 scrollingBackground = Instance.new('TextButton')
369 scrollingBackground.Name = "ScrollingBackground"
370 scrollingBackground.Size = UDim2.new(1, 0, 0, currentSelectionName.AbsoluteSize.y)
371 scrollingBackground.Position = UDim2.new(0, 0, 0, 28)
372 scrollingBackground.BackgroundColor3 = Color3.new(1, 1, 1)
373 scrollingBackground.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
374 scrollingBackground.ZIndex = baseZIndex + 1
375 scrollingBackground.Text = ""
376 scrollingBackground.Visible = false
377 scrollingBackground.AutoButtonColor = false
378 scrollingBackground.Parent = frame
379
380 for i = 1, length do
381 local entry = list[i]
382 local btn = Instance.new('TextButton')
383 btn.Name = entry
384 btn.Size = UDim2.new(1, 0, 0, 24)
385 btn.Position = UDim2.new(0, 0, 0, (i - 1) * 24)
386 btn.BackgroundTransparency = 0
387 btn.BackgroundColor3 = Color3.new(1, 1, 1)
388 btn.BorderSizePixel = 0
389 btn.Font = Enum.Font.SourceSans
390 btn.FontSize = Enum.FontSize.Size18
391 btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
392 btn.TextXAlignment = Enum.TextXAlignment.Left
393 btn.TextYAlignment = Enum.TextYAlignment.Center
394 btn.Text = entry
395 btn.ZIndex = baseZIndex + 4
396 btn.AutoButtonColor = false
397 btn.Parent = listMenu
398
399 btn.MouseButton1Click:connect(function()
400 currentSelectionName.Text = btn.Text
401 onEntrySelected()
402 btn.Font = Enum.Font.SourceSans
403 btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
404 btn.BackgroundColor3 = Color3.new(1, 1, 1)
405 onSelectedCallback(btn.Text)
406 end)
407
408 btn.MouseEnter:connect(function()
409 btn.TextColor3 = Color3.new(1, 1, 1)
410 btn.BackgroundColor3 = Color3.new(0.75, 0.75, 0.75)
411 end)
412 btn.MouseLeave:connect(function()
413 btn.TextColor3 = Color3.new(0.5, 0.5, 0.5)
414 btn.BackgroundColor3 = Color3.new(1, 1, 1)
415 end)
416 end
417 end
418
419 return dropDownMenu
420end
421
422t.CreateDropDownMenu = function(items, onSelect, forRoblox, whiteSkin, baseZ)
423 local baseZIndex = 0
424 if (type(baseZ) == "number") then
425 baseZIndex = baseZ
426 end
427 local width = UDim.new(0, 100)
428 local height = UDim.new(0, 32)
429
430 local xPos = 0.055
431 local frame = Instance.new("Frame")
432 local textColor = Color3.new(1,1,1)
433 if (whiteSkin) then
434 textColor = Color3.new(0.5, 0.5, 0.5)
435 end
436 frame.Name = "DropDownMenu"
437 frame.BackgroundTransparency = 1
438 frame.Size = UDim2.new(width, height)
439
440 local dropDownMenu = Instance.new("TextButton")
441 dropDownMenu.Name = "DropDownMenuButton"
442 dropDownMenu.TextWrap = true
443 dropDownMenu.TextColor3 = textColor
444 dropDownMenu.Text = "Choose One"
445 dropDownMenu.Font = Enum.Font.ArialBold
446 dropDownMenu.FontSize = Enum.FontSize.Size18
447 dropDownMenu.TextXAlignment = Enum.TextXAlignment.Left
448 dropDownMenu.TextYAlignment = Enum.TextYAlignment.Center
449 dropDownMenu.BackgroundTransparency = 1
450 dropDownMenu.AutoButtonColor = true
451 if (whiteSkin) then
452 dropDownMenu.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
453 else
454 dropDownMenu.Style = Enum.ButtonStyle.RobloxButton
455 end
456 dropDownMenu.Size = UDim2.new(1,0,1,0)
457 dropDownMenu.Parent = frame
458 dropDownMenu.ZIndex = 2 + baseZIndex
459
460 local dropDownIcon = Instance.new("ImageLabel")
461 dropDownIcon.Name = "Icon"
462 dropDownIcon.Active = false
463 if (whiteSkin) then
464 dropDownIcon.Image = "rbxasset://textures/ui/dropdown_arrow.png"
465 dropDownIcon.Size = UDim2.new(0,16,0,12)
466 dropDownIcon.Position = UDim2.new(1,-17,0.5, -6)
467 else
468 dropDownIcon.Image = "https://www.roblox.com/asset/?id=45732894"
469 dropDownIcon.Size = UDim2.new(0,11,0,6)
470 dropDownIcon.Position = UDim2.new(1,-11,0.5, -2)
471 end
472 dropDownIcon.BackgroundTransparency = 1
473 dropDownIcon.Parent = dropDownMenu
474 dropDownIcon.ZIndex = 2 + baseZIndex
475
476 local itemCount = #items
477 local dropDownItemCount = #items
478 local useScrollButtons = false
479 if dropDownItemCount > 6 then
480 useScrollButtons = true
481 dropDownItemCount = 6
482 end
483
484 local droppedDownMenu = Instance.new("TextButton")
485 droppedDownMenu.Name = "List"
486 droppedDownMenu.Text = ""
487 droppedDownMenu.BackgroundTransparency = 1
488 --droppedDownMenu.AutoButtonColor = true
489 if (whiteSkin) then
490 droppedDownMenu.Style = Enum.ButtonStyle.RobloxRoundDropdownButton
491 else
492 droppedDownMenu.Style = Enum.ButtonStyle.RobloxButton
493 end
494 droppedDownMenu.Visible = false
495 droppedDownMenu.Active = true --Blocks clicks
496 droppedDownMenu.Position = UDim2.new(0,0,0,0)
497 droppedDownMenu.Size = UDim2.new(1,0, (1 + dropDownItemCount)*.8, 0)
498 droppedDownMenu.Parent = frame
499 droppedDownMenu.ZIndex = 2 + baseZIndex
500
501 local choiceButton = Instance.new("TextButton")
502 choiceButton.Name = "ChoiceButton"
503 choiceButton.BackgroundTransparency = 1
504 choiceButton.BorderSizePixel = 0
505 choiceButton.Text = "ReplaceMe"
506 choiceButton.TextColor3 = textColor
507 choiceButton.TextXAlignment = Enum.TextXAlignment.Left
508 choiceButton.TextYAlignment = Enum.TextYAlignment.Center
509 choiceButton.BackgroundColor3 = Color3.new(1, 1, 1)
510 choiceButton.Font = Enum.Font.Arial
511 choiceButton.FontSize = Enum.FontSize.Size18
512 if useScrollButtons then
513 choiceButton.Size = UDim2.new(1,-13, .8/((dropDownItemCount + 1)*.8),0)
514 else
515 choiceButton.Size = UDim2.new(1, 0, .8/((dropDownItemCount + 1)*.8),0)
516 end
517 choiceButton.TextWrap = true
518 choiceButton.ZIndex = 2 + baseZIndex
519
520 local areaSoak = Instance.new("TextButton")
521 areaSoak.Name = "AreaSoak"
522 areaSoak.Text = ""
523 areaSoak.BackgroundTransparency = 1
524 areaSoak.Active = true
525 areaSoak.Size = UDim2.new(1,0,1,0)
526 areaSoak.Visible = false
527 areaSoak.ZIndex = 3 + baseZIndex
528
529 local dropDownSelected = false
530
531 local scrollUpButton
532 local scrollDownButton
533 local scrollMouseCount = 0
534
535 local setZIndex = function(baseZIndex)
536 droppedDownMenu.ZIndex = baseZIndex +1
537 if scrollUpButton then
538 scrollUpButton.ZIndex = baseZIndex + 3
539 end
540 if scrollDownButton then
541 scrollDownButton.ZIndex = baseZIndex + 3
542 end
543
544 local children = droppedDownMenu:GetChildren()
545 if children then
546 for i, child in ipairs(children) do
547 if child.Name == "ChoiceButton" then
548 child.ZIndex = baseZIndex + 2
549 elseif child.Name == "ClickCaptureButton" then
550 child.ZIndex = baseZIndex
551 end
552 end
553 end
554 end
555
556 local scrollBarPosition = 1
557 local updateScroll = function()
558 if scrollUpButton then
559 scrollUpButton.Active = scrollBarPosition > 1
560 end
561 if scrollDownButton then
562 scrollDownButton.Active = scrollBarPosition + dropDownItemCount <= itemCount
563 end
564
565 local children = droppedDownMenu:GetChildren()
566 if not children then return end
567
568 local childNum = 1
569 for i, obj in ipairs(children) do
570 if obj.Name == "ChoiceButton" then
571 if childNum < scrollBarPosition or childNum >= scrollBarPosition + dropDownItemCount then
572 obj.Visible = false
573 else
574 obj.Position = UDim2.new(0,0,((childNum-scrollBarPosition+1)*.8)/((dropDownItemCount+1)*.8),0)
575 obj.Visible = true
576 end
577 obj.TextColor3 = textColor
578 obj.BackgroundTransparency = 1
579
580 childNum = childNum + 1
581 end
582 end
583 end
584 local toggleVisibility = function()
585 dropDownSelected = not dropDownSelected
586
587 areaSoak.Visible = not areaSoak.Visible
588 dropDownMenu.Visible = not dropDownSelected
589 droppedDownMenu.Visible = dropDownSelected
590 if dropDownSelected then
591 setZIndex(4 + baseZIndex)
592 else
593 setZIndex(2 + baseZIndex)
594 end
595 if useScrollButtons then
596 updateScroll()
597 end
598 end
599 droppedDownMenu.MouseButton1Click:connect(toggleVisibility)
600
601 local updateSelection = function(text)
602 local foundItem = false
603 local children = droppedDownMenu:GetChildren()
604 local childNum = 1
605 if children then
606 for i, obj in ipairs(children) do
607 if obj.Name == "ChoiceButton" then
608 if obj.Text == text then
609 obj.Font = Enum.Font.ArialBold
610 foundItem = true
611 scrollBarPosition = childNum
612 if (whiteSkin) then
613 obj.TextColor3 = Color3.new(90/255,142/255,233/255)
614 end
615 else
616 obj.Font = Enum.Font.Arial
617 if (whiteSkin) then
618 obj.TextColor3 = textColor
619 end
620 end
621 childNum = childNum + 1
622 end
623 end
624 end
625 if not text then
626 dropDownMenu.Text = "Choose One"
627 scrollBarPosition = 1
628 else
629 if not foundItem then
630 error("Invalid Selection Update -- " .. text)
631 end
632
633 if scrollBarPosition + dropDownItemCount > itemCount + 1 then
634 scrollBarPosition = itemCount - dropDownItemCount + 1
635 end
636
637 dropDownMenu.Text = text
638 end
639 end
640
641 local function scrollDown()
642 if scrollBarPosition + dropDownItemCount <= itemCount then
643 scrollBarPosition = scrollBarPosition + 1
644 updateScroll()
645 return true
646 end
647 return false
648 end
649 local function scrollUp()
650 if scrollBarPosition > 1 then
651 scrollBarPosition = scrollBarPosition - 1
652 updateScroll()
653 return true
654 end
655 return false
656 end
657
658 if useScrollButtons then
659 --Make some scroll buttons
660 scrollUpButton = Instance.new("ImageButton")
661 scrollUpButton.Name = "ScrollUpButton"
662 scrollUpButton.BackgroundTransparency = 1
663 scrollUpButton.Image = "rbxasset://textures/ui/scrollbuttonUp.png"
664 scrollUpButton.Size = UDim2.new(0,17,0,17)
665 scrollUpButton.Position = UDim2.new(1,-11,(1*.8)/((dropDownItemCount+1)*.8),0)
666 scrollUpButton.MouseButton1Click:connect(
667 function()
668 scrollMouseCount = scrollMouseCount + 1
669 end)
670 scrollUpButton.MouseLeave:connect(
671 function()
672 scrollMouseCount = scrollMouseCount + 1
673 end)
674 scrollUpButton.MouseButton1Down:connect(
675 function()
676 scrollMouseCount = scrollMouseCount + 1
677
678 scrollUp()
679 local val = scrollMouseCount
680 wait(0.5)
681 while val == scrollMouseCount do
682 if scrollUp() == false then
683 break
684 end
685 wait(0.1)
686 end
687 end)
688
689 scrollUpButton.Parent = droppedDownMenu
690
691 scrollDownButton = Instance.new("ImageButton")
692 scrollDownButton.Name = "ScrollDownButton"
693 scrollDownButton.BackgroundTransparency = 1
694 scrollDownButton.Image = "rbxasset://textures/ui/scrollbuttonDown.png"
695 scrollDownButton.Size = UDim2.new(0,17,0,17)
696 scrollDownButton.Position = UDim2.new(1,-11,1,-11)
697 scrollDownButton.Parent = droppedDownMenu
698 scrollDownButton.MouseButton1Click:connect(
699 function()
700 scrollMouseCount = scrollMouseCount + 1
701 end)
702 scrollDownButton.MouseLeave:connect(
703 function()
704 scrollMouseCount = scrollMouseCount + 1
705 end)
706 scrollDownButton.MouseButton1Down:connect(
707 function()
708 scrollMouseCount = scrollMouseCount + 1
709
710 scrollDown()
711 local val = scrollMouseCount
712 wait(0.5)
713 while val == scrollMouseCount do
714 if scrollDown() == false then
715 break
716 end
717 wait(0.1)
718 end
719 end)
720
721 local scrollbar = Instance.new("ImageLabel")
722 scrollbar.Name = "ScrollBar"
723 scrollbar.Image = "rbxasset://textures/ui/scrollbar.png"
724 scrollbar.BackgroundTransparency = 1
725 scrollbar.Size = UDim2.new(0, 18, (dropDownItemCount*.8)/((dropDownItemCount+1)*.8), -(17) - 11 - 4)
726 scrollbar.Position = UDim2.new(1,-11,(1*.8)/((dropDownItemCount+1)*.8),17+2)
727 scrollbar.Parent = droppedDownMenu
728 end
729
730 for i,item in ipairs(items) do
731 -- needed to maintain local scope for items in event listeners below
732 local button = choiceButton:clone()
733 if forRoblox then
734 button.RobloxLocked = true
735 end
736 button.Text = item
737 button.Parent = droppedDownMenu
738 if (whiteSkin) then
739 button.TextColor3 = textColor
740 end
741
742 button.MouseButton1Click:connect(function()
743 --Remove Highlight
744 if (not whiteSkin) then
745 button.TextColor3 = Color3.new(1,1,1)
746 end
747 button.BackgroundTransparency = 1
748
749 updateSelection(item)
750 onSelect(item)
751
752 toggleVisibility()
753 end)
754 button.MouseEnter:connect(function()
755 --Add Highlight
756 if (not whiteSkin) then
757 button.TextColor3 = Color3.new(0,0,0)
758 end
759 button.BackgroundTransparency = 0
760 end)
761
762 button.MouseLeave:connect(function()
763 --Remove Highlight
764 if (not whiteSkin) then
765 button.TextColor3 = Color3.new(1,1,1)
766 end
767 button.BackgroundTransparency = 1
768 end)
769 end
770
771 --This does the initial layout of the buttons
772 updateScroll()
773
774 frame.AncestryChanged:connect(function(child,parent)
775 if parent == nil then
776 areaSoak.Parent = nil
777 else
778 areaSoak.Parent = getLayerCollectorAncestor(frame)
779 end
780 end)
781
782 dropDownMenu.MouseButton1Click:connect(toggleVisibility)
783 areaSoak.MouseButton1Click:connect(toggleVisibility)
784 return frame, updateSelection
785end
786
787t.CreatePropertyDropDownMenu = function(instance, property, enum)
788
789 local items = enum:GetEnumItems()
790 local names = {}
791 local nameToItem = {}
792 for i,obj in ipairs(items) do
793 names[i] = obj.Name
794 nameToItem[obj.Name] = obj
795 end
796
797 local frame
798 local updateSelection
799 frame, updateSelection = t.CreateDropDownMenu(names, function(text) instance[property] = nameToItem[text] end)
800
801 ScopedConnect(frame, instance, "Changed",
802 function(prop)
803 if prop == property then
804 updateSelection(instance[property].Name)
805 end
806 end,
807 function()
808 updateSelection(instance[property].Name)
809 end)
810
811 return frame
812end
813
814t.GetFontHeight = function(font, fontSize)
815 if font == nil or fontSize == nil then
816 error("Font and FontSize must be non-nil")
817 end
818
819 local fontSizeInt = tonumber(fontSize.Name:match("%d+")) -- Clever hack to extract the size from the enum itself.
820
821 if font == Enum.Font.Legacy then -- Legacy has a 50% bigger size.
822 return math.ceil(fontSizeInt*1.5)
823 else -- Size is literally just the fontSizeInt
824 return fontSizeInt
825 end
826end
827
828local function layoutGuiObjectsHelper(frame, guiObjects, settingsTable)
829 local totalPixels = frame.AbsoluteSize.Y
830 local pixelsRemaining = frame.AbsoluteSize.Y
831 for i, child in ipairs(guiObjects) do
832 if child:IsA("TextLabel") or child:IsA("TextButton") then
833 local isLabel = child:IsA("TextLabel")
834 if isLabel then
835 pixelsRemaining = pixelsRemaining - settingsTable["TextLabelPositionPadY"]
836 else
837 pixelsRemaining = pixelsRemaining - settingsTable["TextButtonPositionPadY"]
838 end
839 child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
840 child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, pixelsRemaining)
841
842 if child.TextFits and child.TextBounds.Y < pixelsRemaining then
843 child.Visible = true
844 if isLabel then
845 child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.TextBounds.Y + settingsTable["TextLabelSizePadY"])
846 else
847 child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.TextBounds.Y + settingsTable["TextButtonSizePadY"])
848 end
849
850 while not child.TextFits do
851 child.Size = UDim2.new(child.Size.X.Scale, child.Size.X.Offset, 0, child.AbsoluteSize.Y + 1)
852 end
853 pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
854
855 if isLabel then
856 pixelsRemaining = pixelsRemaining - settingsTable["TextLabelPositionPadY"]
857 else
858 pixelsRemaining = pixelsRemaining - settingsTable["TextButtonPositionPadY"]
859 end
860 else
861 child.Visible = false
862 pixelsRemaining = -1
863 end
864
865 else
866 --GuiObject
867 child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
868 pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
869 child.Visible = (pixelsRemaining >= 0)
870 end
871 end
872end
873
874t.LayoutGuiObjects = function(frame, guiObjects, settingsTable)
875 if not frame:IsA("GuiObject") then
876 error("Frame must be a GuiObject")
877 end
878 for i, child in ipairs(guiObjects) do
879 if not child:IsA("GuiObject") then
880 error("All elements that are layed out must be of type GuiObject")
881 end
882 end
883
884 if not settingsTable then
885 settingsTable = {}
886 end
887
888 if not settingsTable["TextLabelSizePadY"] then
889 settingsTable["TextLabelSizePadY"] = 0
890 end
891 if not settingsTable["TextLabelPositionPadY"] then
892 settingsTable["TextLabelPositionPadY"] = 0
893 end
894 if not settingsTable["TextButtonSizePadY"] then
895 settingsTable["TextButtonSizePadY"] = 12
896 end
897 if not settingsTable["TextButtonPositionPadY"] then
898 settingsTable["TextButtonPositionPadY"] = 2
899 end
900
901 --Wrapper frame takes care of styled objects
902 local wrapperFrame = Instance.new("Frame")
903 wrapperFrame.Name = "WrapperFrame"
904 wrapperFrame.BackgroundTransparency = 1
905 wrapperFrame.Size = UDim2.new(1,0,1,0)
906 wrapperFrame.Parent = frame
907
908 for i, child in ipairs(guiObjects) do
909 child.Parent = wrapperFrame
910 end
911
912 local recalculate = function()
913 wait()
914 layoutGuiObjectsHelper(wrapperFrame, guiObjects, settingsTable)
915 end
916
917 frame.Changed:connect(
918 function(prop)
919 if prop == "AbsoluteSize" then
920 --Wait a heartbeat for it to sync in
921 recalculate(nil)
922 end
923 end)
924 frame.AncestryChanged:connect(recalculate)
925
926 layoutGuiObjectsHelper(wrapperFrame, guiObjects, settingsTable)
927end
928
929
930t.CreateSlider = function(steps,width,position)
931 local sliderGui = Instance.new("Frame")
932 sliderGui.Size = UDim2.new(1,0,1,0)
933 sliderGui.BackgroundTransparency = 1
934 sliderGui.Name = "SliderGui"
935
936 local sliderSteps = Instance.new("IntValue")
937 sliderSteps.Name = "SliderSteps"
938 sliderSteps.Value = steps
939 sliderSteps.Parent = sliderGui
940
941 local areaSoak = Instance.new("TextButton")
942 areaSoak.Name = "AreaSoak"
943 areaSoak.Text = ""
944 areaSoak.BackgroundTransparency = 1
945 areaSoak.Active = false
946 areaSoak.Size = UDim2.new(1,0,1,0)
947 areaSoak.Visible = false
948 areaSoak.ZIndex = 4
949
950 sliderGui.AncestryChanged:connect(function(child,parent)
951 if parent == nil then
952 areaSoak.Parent = nil
953 else
954 areaSoak.Parent = getLayerCollectorAncestor(sliderGui)
955 end
956 end)
957
958 local sliderPosition = Instance.new("IntValue")
959 sliderPosition.Name = "SliderPosition"
960 sliderPosition.Value = 0
961 sliderPosition.Parent = sliderGui
962
963 local id = math.random(1,100)
964
965 local bar = Instance.new("TextButton")
966 bar.Text = ""
967 bar.AutoButtonColor = false
968 bar.Name = "Bar"
969 bar.BackgroundColor3 = Color3.new(0,0,0)
970 if type(width) == "number" then
971 bar.Size = UDim2.new(0,width,0,5)
972 else
973 bar.Size = UDim2.new(0,200,0,5)
974 end
975 bar.BorderColor3 = Color3.new(95/255,95/255,95/255)
976 bar.ZIndex = 2
977 bar.Parent = sliderGui
978
979 if position["X"] and position["X"]["Scale"] and position["X"]["Offset"] and position["Y"] and position["Y"]["Scale"] and position["Y"]["Offset"] then
980 bar.Position = position
981 end
982
983 local slider = Instance.new("ImageButton")
984 slider.Name = "Slider"
985 slider.BackgroundTransparency = 1
986 slider.Image = "rbxasset://textures/ui/Slider.png"
987 slider.Position = UDim2.new(0,0,0.5,-10)
988 slider.Size = UDim2.new(0,20,0,20)
989 slider.ZIndex = 3
990 slider.Parent = bar
991
992 local areaSoakMouseMoveCon = nil
993
994 areaSoak.MouseLeave:connect(function()
995 if areaSoak.Visible then
996 cancelSlide(areaSoak)
997 end
998 end)
999 areaSoak.MouseButton1Up:connect(function()
1000 if areaSoak.Visible then
1001 cancelSlide(areaSoak)
1002 end
1003 end)
1004
1005 slider.MouseButton1Down:connect(function()
1006 areaSoak.Visible = true
1007 if areaSoakMouseMoveCon then areaSoakMouseMoveCon:disconnect() end
1008 areaSoakMouseMoveCon = areaSoak.MouseMoved:connect(function(x,y)
1009 setSliderPos(x,slider,sliderPosition,bar,steps)
1010 end)
1011 end)
1012
1013 slider.MouseButton1Up:connect(function() cancelSlide(areaSoak) end)
1014
1015 sliderPosition.Changed:connect(function(prop)
1016 sliderPosition.Value = math.min(steps, math.max(1,sliderPosition.Value))
1017 local relativePosX = (sliderPosition.Value - 1) / (steps - 1)
1018 slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
1019 end)
1020
1021 bar.MouseButton1Down:connect(function(x,y)
1022 setSliderPos(x,slider,sliderPosition,bar,steps)
1023 end)
1024
1025 return sliderGui, sliderPosition, sliderSteps
1026
1027end
1028
1029
1030
1031t.CreateSliderNew = function(steps,width,position)
1032 local sliderGui = Instance.new("Frame")
1033 sliderGui.Size = UDim2.new(1,0,1,0)
1034 sliderGui.BackgroundTransparency = 1
1035 sliderGui.Name = "SliderGui"
1036
1037 local sliderSteps = Instance.new("IntValue")
1038 sliderSteps.Name = "SliderSteps"
1039 sliderSteps.Value = steps
1040 sliderSteps.Parent = sliderGui
1041
1042 local areaSoak = Instance.new("TextButton")
1043 areaSoak.Name = "AreaSoak"
1044 areaSoak.Text = ""
1045 areaSoak.BackgroundTransparency = 1
1046 areaSoak.Active = false
1047 areaSoak.Size = UDim2.new(1,0,1,0)
1048 areaSoak.Visible = false
1049 areaSoak.ZIndex = 6
1050
1051 sliderGui.AncestryChanged:connect(function(child,parent)
1052 if parent == nil then
1053 areaSoak.Parent = nil
1054 else
1055 areaSoak.Parent = getLayerCollectorAncestor(sliderGui)
1056 end
1057 end)
1058
1059 local sliderPosition = Instance.new("IntValue")
1060 sliderPosition.Name = "SliderPosition"
1061 sliderPosition.Value = 0
1062 sliderPosition.Parent = sliderGui
1063
1064 local id = math.random(1,100)
1065
1066 local sliderBarImgHeight = 7
1067 local sliderBarCapImgWidth = 4
1068
1069 local bar = Instance.new("ImageButton")
1070 bar.BackgroundTransparency = 1
1071 bar.Image = "rbxasset://textures/ui/Slider-BKG-Center.png"
1072 bar.Name = "Bar"
1073 local displayWidth = 200
1074 if type(width) == "number" then
1075 bar.Size = UDim2.new(0,width - (sliderBarCapImgWidth * 2),0,sliderBarImgHeight)
1076 displayWidth = width - (sliderBarCapImgWidth * 2)
1077 else
1078 bar.Size = UDim2.new(0,200,0,sliderBarImgHeight)
1079 end
1080 bar.ZIndex = 3
1081 bar.Parent = sliderGui
1082 if position["X"] and position["X"]["Scale"] and position["X"]["Offset"] and position["Y"] and position["Y"]["Scale"] and position["Y"]["Offset"] then
1083 bar.Position = position
1084 end
1085
1086 local barLeft = bar:clone()
1087 barLeft.Name = "BarLeft"
1088 barLeft.Image = "rbxasset://textures/ui/Slider-BKG-Left-Cap.png"
1089 barLeft.Size = UDim2.new(0, sliderBarCapImgWidth, 0, sliderBarImgHeight)
1090 barLeft.Position = UDim2.new(position.X.Scale, position.X.Offset - sliderBarCapImgWidth, position.Y.Scale, position.Y.Offset)
1091 barLeft.Parent = sliderGui
1092 barLeft.ZIndex = 3
1093
1094 local barRight = barLeft:clone()
1095 barRight.Name = "BarRight"
1096 barRight.Image = "rbxasset://textures/ui/Slider-BKG-Right-Cap.png"
1097 barRight.Position = UDim2.new(position.X.Scale, position.X.Offset + displayWidth, position.Y.Scale, position.Y.Offset)
1098 barRight.Parent = sliderGui
1099
1100 local fillLeft = barLeft:clone()
1101 fillLeft.Name = "FillLeft"
1102 fillLeft.Image = "rbxasset://textures/ui/Slider-Fill-Left-Cap.png"
1103 fillLeft.Parent = sliderGui
1104 fillLeft.ZIndex = 4
1105
1106 local fill = fillLeft:clone()
1107 fill.Name = "Fill"
1108 fill.Image = "rbxasset://textures/ui/Slider-Fill-Center.png"
1109 fill.Parent = bar
1110 fill.ZIndex = 4
1111 fill.Position = UDim2.new(0, 0, 0, 0)
1112 fill.Size = UDim2.new(0.5, 0, 1, 0)
1113
1114
1115-- bar.Visible = false
1116
1117 local slider = Instance.new("ImageButton")
1118 slider.Name = "Slider"
1119 slider.BackgroundTransparency = 1
1120 slider.Image = "rbxasset://textures/ui/slider_new_tab.png"
1121 slider.Position = UDim2.new(0,0,0.5,-14)
1122 slider.Size = UDim2.new(0,28,0,28)
1123 slider.ZIndex = 5
1124 slider.Parent = bar
1125
1126 local areaSoakMouseMoveCon = nil
1127
1128 areaSoak.MouseLeave:connect(function()
1129 if areaSoak.Visible then
1130 cancelSlide(areaSoak)
1131 end
1132 end)
1133 areaSoak.MouseButton1Up:connect(function()
1134 if areaSoak.Visible then
1135 cancelSlide(areaSoak)
1136 end
1137 end)
1138
1139 slider.MouseButton1Down:connect(function()
1140 areaSoak.Visible = true
1141 if areaSoakMouseMoveCon then areaSoakMouseMoveCon:disconnect() end
1142 areaSoakMouseMoveCon = areaSoak.MouseMoved:connect(function(x,y)
1143 setSliderPos(x,slider,sliderPosition,bar,steps)
1144 end)
1145 end)
1146
1147 slider.MouseButton1Up:connect(function() cancelSlide(areaSoak) end)
1148
1149 sliderPosition.Changed:connect(function(prop)
1150 sliderPosition.Value = math.min(steps, math.max(1,sliderPosition.Value))
1151 local relativePosX = (sliderPosition.Value - 1) / (steps - 1)
1152 slider.Position = UDim2.new(relativePosX,-slider.AbsoluteSize.X/2,slider.Position.Y.Scale,slider.Position.Y.Offset)
1153 fill.Size = UDim2.new(relativePosX, 0, 1, 0)
1154 end)
1155
1156 bar.MouseButton1Down:connect(function(x,y)
1157 setSliderPos(x,slider,sliderPosition,bar,steps)
1158 end)
1159
1160 fill.MouseButton1Down:connect(function(x,y)
1161 setSliderPos(x,slider,sliderPosition,bar,steps)
1162 end)
1163
1164 fillLeft.MouseButton1Down:connect(function(x,y)
1165 setSliderPos(x,slider,sliderPosition,bar,steps)
1166 end)
1167
1168
1169 return sliderGui, sliderPosition, sliderSteps
1170
1171end
1172
1173
1174
1175
1176
1177t.CreateTrueScrollingFrame = function()
1178 local lowY = nil
1179 local highY = nil
1180
1181 local dragCon = nil
1182 local upCon = nil
1183
1184 local internalChange = false
1185
1186 local descendantsChangeConMap = {}
1187
1188 local scrollingFrame = Instance.new("Frame")
1189 scrollingFrame.Name = "ScrollingFrame"
1190 scrollingFrame.Active = true
1191 scrollingFrame.Size = UDim2.new(1,0,1,0)
1192 scrollingFrame.ClipsDescendants = true
1193
1194 local controlFrame = Instance.new("Frame")
1195 controlFrame.Name = "ControlFrame"
1196 controlFrame.BackgroundTransparency = 1
1197 controlFrame.Size = UDim2.new(0,18,1,0)
1198 controlFrame.Position = UDim2.new(1,-20,0,0)
1199 controlFrame.Parent = scrollingFrame
1200
1201 local scrollBottom = Instance.new("BoolValue")
1202 scrollBottom.Value = false
1203 scrollBottom.Name = "ScrollBottom"
1204 scrollBottom.Parent = controlFrame
1205
1206 local scrollUp = Instance.new("BoolValue")
1207 scrollUp.Value = false
1208 scrollUp.Name = "scrollUp"
1209 scrollUp.Parent = controlFrame
1210
1211 local scrollUpButton = Instance.new("TextButton")
1212 scrollUpButton.Name = "ScrollUpButton"
1213 scrollUpButton.Text = ""
1214 scrollUpButton.AutoButtonColor = false
1215 scrollUpButton.BackgroundColor3 = Color3.new(0,0,0)
1216 scrollUpButton.BorderColor3 = Color3.new(1,1,1)
1217 scrollUpButton.BackgroundTransparency = 0.5
1218 scrollUpButton.Size = UDim2.new(0,18,0,18)
1219 scrollUpButton.ZIndex = 2
1220 scrollUpButton.Parent = controlFrame
1221 for i = 1, 6 do
1222 local triFrame = Instance.new("Frame")
1223 triFrame.BorderColor3 = Color3.new(1,1,1)
1224 triFrame.Name = "tri" .. tostring(i)
1225 triFrame.ZIndex = 3
1226 triFrame.BackgroundTransparency = 0.5
1227 triFrame.Size = UDim2.new(0,12 - ((i -1) * 2),0,0)
1228 triFrame.Position = UDim2.new(0,3 + (i -1),0.5,2 - (i -1))
1229 triFrame.Parent = scrollUpButton
1230 end
1231 scrollUpButton.MouseEnter:connect(function()
1232 scrollUpButton.BackgroundTransparency = 0.1
1233 local upChildren = scrollUpButton:GetChildren()
1234 for i = 1, #upChildren do
1235 upChildren[i].BackgroundTransparency = 0.1
1236 end
1237 end)
1238 scrollUpButton.MouseLeave:connect(function()
1239 scrollUpButton.BackgroundTransparency = 0.5
1240 local upChildren = scrollUpButton:GetChildren()
1241 for i = 1, #upChildren do
1242 upChildren[i].BackgroundTransparency = 0.5
1243 end
1244 end)
1245
1246 local scrollDownButton = scrollUpButton:clone()
1247 scrollDownButton.Name = "ScrollDownButton"
1248 scrollDownButton.Position = UDim2.new(0,0,1,-18)
1249 local downChildren = scrollDownButton:GetChildren()
1250 for i = 1, #downChildren do
1251 downChildren[i].Position = UDim2.new(0,3 + (i -1),0.5,-2 + (i - 1))
1252 end
1253 scrollDownButton.MouseEnter:connect(function()
1254 scrollDownButton.BackgroundTransparency = 0.1
1255 local downChildren = scrollDownButton:GetChildren()
1256 for i = 1, #downChildren do
1257 downChildren[i].BackgroundTransparency = 0.1
1258 end
1259 end)
1260 scrollDownButton.MouseLeave:connect(function()
1261 scrollDownButton.BackgroundTransparency = 0.5
1262 local downChildren = scrollDownButton:GetChildren()
1263 for i = 1, #downChildren do
1264 downChildren[i].BackgroundTransparency = 0.5
1265 end
1266 end)
1267 scrollDownButton.Parent = controlFrame
1268
1269 local scrollTrack = Instance.new("Frame")
1270 scrollTrack.Name = "ScrollTrack"
1271 scrollTrack.BackgroundTransparency = 1
1272 scrollTrack.Size = UDim2.new(0,18,1,-38)
1273 scrollTrack.Position = UDim2.new(0,0,0,19)
1274 scrollTrack.Parent = controlFrame
1275
1276 local scrollbar = Instance.new("TextButton")
1277 scrollbar.BackgroundColor3 = Color3.new(0,0,0)
1278 scrollbar.BorderColor3 = Color3.new(1,1,1)
1279 scrollbar.BackgroundTransparency = 0.5
1280 scrollbar.AutoButtonColor = false
1281 scrollbar.Text = ""
1282 scrollbar.Active = true
1283 scrollbar.Name = "ScrollBar"
1284 scrollbar.ZIndex = 2
1285 scrollbar.BackgroundTransparency = 0.5
1286 scrollbar.Size = UDim2.new(0, 18, 0.1, 0)
1287 scrollbar.Position = UDim2.new(0,0,0,0)
1288 scrollbar.Parent = scrollTrack
1289
1290 local scrollNub = Instance.new("Frame")
1291 scrollNub.Name = "ScrollNub"
1292 scrollNub.BorderColor3 = Color3.new(1,1,1)
1293 scrollNub.Size = UDim2.new(0,10,0,0)
1294 scrollNub.Position = UDim2.new(0.5,-5,0.5,0)
1295 scrollNub.ZIndex = 2
1296 scrollNub.BackgroundTransparency = 0.5
1297 scrollNub.Parent = scrollbar
1298
1299 local newNub = scrollNub:clone()
1300 newNub.Position = UDim2.new(0.5,-5,0.5,-2)
1301 newNub.Parent = scrollbar
1302
1303 local lastNub = scrollNub:clone()
1304 lastNub.Position = UDim2.new(0.5,-5,0.5,2)
1305 lastNub.Parent = scrollbar
1306
1307 scrollbar.MouseEnter:connect(function()
1308 scrollbar.BackgroundTransparency = 0.1
1309 scrollNub.BackgroundTransparency = 0.1
1310 newNub.BackgroundTransparency = 0.1
1311 lastNub.BackgroundTransparency = 0.1
1312 end)
1313 scrollbar.MouseLeave:connect(function()
1314 scrollbar.BackgroundTransparency = 0.5
1315 scrollNub.BackgroundTransparency = 0.5
1316 newNub.BackgroundTransparency = 0.5
1317 lastNub.BackgroundTransparency = 0.5
1318 end)
1319
1320 local mouseDrag = Instance.new("ImageButton")
1321 mouseDrag.Active = false
1322 mouseDrag.Size = UDim2.new(1.5, 0, 1.5, 0)
1323 mouseDrag.AutoButtonColor = false
1324 mouseDrag.BackgroundTransparency = 1
1325 mouseDrag.Name = "mouseDrag"
1326 mouseDrag.Position = UDim2.new(-0.25, 0, -0.25, 0)
1327 mouseDrag.ZIndex = 10
1328
1329 local function positionScrollBar(x,y,offset)
1330 local oldPos = scrollbar.Position
1331
1332 if y < scrollTrack.AbsolutePosition.y then
1333 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,0,0)
1334 return (oldPos ~= scrollbar.Position)
1335 end
1336
1337 local relativeSize = scrollbar.AbsoluteSize.Y/scrollTrack.AbsoluteSize.Y
1338
1339 if y > (scrollTrack.AbsolutePosition.y + scrollTrack.AbsoluteSize.y) then
1340 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,1 - relativeSize,0)
1341 return (oldPos ~= scrollbar.Position)
1342 end
1343 local newScaleYPos = (y - scrollTrack.AbsolutePosition.y - offset)/scrollTrack.AbsoluteSize.y
1344 if newScaleYPos + relativeSize > 1 then
1345 newScaleYPos = 1 - relativeSize
1346 scrollBottom.Value = true
1347 scrollUp.Value = false
1348 elseif newScaleYPos <= 0 then
1349 newScaleYPos = 0
1350 scrollUp.Value = true
1351 scrollBottom.Value = false
1352 else
1353 scrollUp.Value = false
1354 scrollBottom.Value = false
1355 end
1356 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,newScaleYPos,0)
1357
1358 return (oldPos ~= scrollbar.Position)
1359 end
1360
1361 local function drillDownSetHighLow(instance)
1362 if not instance or not instance:IsA("GuiObject") then return end
1363 if instance == controlFrame then return end
1364 if instance:IsDescendantOf(controlFrame) then return end
1365 if not instance.Visible then return end
1366
1367 if lowY and lowY > instance.AbsolutePosition.Y then
1368 lowY = instance.AbsolutePosition.Y
1369 elseif not lowY then
1370 lowY = instance.AbsolutePosition.Y
1371 end
1372 if highY and highY < (instance.AbsolutePosition.Y + instance.AbsoluteSize.Y) then
1373 highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
1374 elseif not highY then
1375 highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
1376 end
1377 local children = instance:GetChildren()
1378 for i = 1, #children do
1379 drillDownSetHighLow(children[i])
1380 end
1381 end
1382
1383 local function resetHighLow()
1384 local firstChildren = scrollingFrame:GetChildren()
1385
1386 for i = 1, #firstChildren do
1387 drillDownSetHighLow(firstChildren[i])
1388 end
1389 end
1390
1391 local function recalculate()
1392 internalChange = true
1393
1394 local percentFrame = 0
1395 if scrollbar.Position.Y.Scale > 0 then
1396 if scrollbar.Visible then
1397 percentFrame = scrollbar.Position.Y.Scale/((scrollTrack.AbsoluteSize.Y - scrollbar.AbsoluteSize.Y)/scrollTrack.AbsoluteSize.Y)
1398 else
1399 percentFrame = 0
1400 end
1401 end
1402 if percentFrame > 0.99 then percentFrame = 1 end
1403
1404 local hiddenYAmount = (scrollingFrame.AbsoluteSize.Y - (highY - lowY)) * percentFrame
1405
1406 local guiChildren = scrollingFrame:GetChildren()
1407 for i = 1, #guiChildren do
1408 if guiChildren[i] ~= controlFrame then
1409 guiChildren[i].Position = UDim2.new(guiChildren[i].Position.X.Scale,guiChildren[i].Position.X.Offset,
1410 0, math.ceil(guiChildren[i].AbsolutePosition.Y) - math.ceil(lowY) + hiddenYAmount)
1411 end
1412 end
1413
1414 lowY = nil
1415 highY = nil
1416 resetHighLow()
1417 internalChange = false
1418 end
1419
1420 local function setSliderSizeAndPosition()
1421 if not highY or not lowY then return end
1422
1423 local totalYSpan = math.abs(highY - lowY)
1424 if totalYSpan == 0 then
1425 scrollbar.Visible = false
1426 scrollDownButton.Visible = false
1427 scrollUpButton.Visible = false
1428
1429 if dragCon then dragCon:disconnect() dragCon = nil end
1430 if upCon then upCon:disconnect() upCon = nil end
1431 return
1432 end
1433
1434 local percentShown = scrollingFrame.AbsoluteSize.Y/totalYSpan
1435 if percentShown >= 1 then
1436 scrollbar.Visible = false
1437 scrollDownButton.Visible = false
1438 scrollUpButton.Visible = false
1439 recalculate()
1440 else
1441 scrollbar.Visible = true
1442 scrollDownButton.Visible = true
1443 scrollUpButton.Visible = true
1444
1445 scrollbar.Size = UDim2.new(scrollbar.Size.X.Scale,scrollbar.Size.X.Offset,percentShown,0)
1446 end
1447
1448 local percentPosition = (scrollingFrame.AbsolutePosition.Y - lowY)/totalYSpan
1449 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,percentPosition,-scrollbar.AbsoluteSize.X/2)
1450
1451 if scrollbar.AbsolutePosition.y < scrollTrack.AbsolutePosition.y then
1452 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,0,0)
1453 end
1454
1455 if (scrollbar.AbsolutePosition.y + scrollbar.AbsoluteSize.Y) > (scrollTrack.AbsolutePosition.y + scrollTrack.AbsoluteSize.y) then
1456 local relativeSize = scrollbar.AbsoluteSize.Y/scrollTrack.AbsoluteSize.Y
1457 scrollbar.Position = UDim2.new(scrollbar.Position.X.Scale,scrollbar.Position.X.Offset,1 - relativeSize,0)
1458 end
1459 end
1460
1461 local buttonScrollAmountPixels = 7
1462 local reentrancyGuardScrollUp = false
1463 local function doScrollUp()
1464 if reentrancyGuardScrollUp then return end
1465
1466 reentrancyGuardScrollUp = true
1467 if positionScrollBar(0,scrollbar.AbsolutePosition.Y - buttonScrollAmountPixels,0) then
1468 recalculate()
1469 end
1470 reentrancyGuardScrollUp = false
1471 end
1472
1473 local reentrancyGuardScrollDown = false
1474 local function doScrollDown()
1475 if reentrancyGuardScrollDown then return end
1476
1477 reentrancyGuardScrollDown = true
1478 if positionScrollBar(0,scrollbar.AbsolutePosition.Y + buttonScrollAmountPixels,0) then
1479 recalculate()
1480 end
1481 reentrancyGuardScrollDown = false
1482 end
1483
1484 local function scrollUp(mouseYPos)
1485 if scrollUpButton.Active then
1486 scrollStamp = tick()
1487 local current = scrollStamp
1488 local upCon
1489 upCon = mouseDrag.MouseButton1Up:connect(function()
1490 scrollStamp = tick()
1491 mouseDrag.Parent = nil
1492 upCon:disconnect()
1493 end)
1494 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
1495 doScrollUp()
1496 wait(0.2)
1497 local t = tick()
1498 local w = 0.1
1499 while scrollStamp == current do
1500 doScrollUp()
1501 if mouseYPos and mouseYPos > scrollbar.AbsolutePosition.y then
1502 break
1503 end
1504 if not scrollUpButton.Active then break end
1505 if tick()-t > 5 then
1506 w = 0
1507 elseif tick()-t > 2 then
1508 w = 0.06
1509 end
1510 wait(w)
1511 end
1512 end
1513 end
1514
1515 local function scrollDown(mouseYPos)
1516 if scrollDownButton.Active then
1517 scrollStamp = tick()
1518 local current = scrollStamp
1519 local downCon
1520 downCon = mouseDrag.MouseButton1Up:connect(function()
1521 scrollStamp = tick()
1522 mouseDrag.Parent = nil
1523 downCon:disconnect()
1524 end)
1525 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
1526 doScrollDown()
1527 wait(0.2)
1528 local t = tick()
1529 local w = 0.1
1530 while scrollStamp == current do
1531 doScrollDown()
1532 if mouseYPos and mouseYPos < (scrollbar.AbsolutePosition.y + scrollbar.AbsoluteSize.x) then
1533 break
1534 end
1535 if not scrollDownButton.Active then break end
1536 if tick()-t > 5 then
1537 w = 0
1538 elseif tick()-t > 2 then
1539 w = 0.06
1540 end
1541 wait(w)
1542 end
1543 end
1544 end
1545
1546 scrollbar.MouseButton1Down:connect(function(x,y)
1547 if scrollbar.Active then
1548 scrollStamp = tick()
1549 local mouseOffset = y - scrollbar.AbsolutePosition.y
1550 if dragCon then dragCon:disconnect() dragCon = nil end
1551 if upCon then upCon:disconnect() upCon = nil end
1552 local prevY = y
1553 local reentrancyGuardMouseScroll = false
1554 dragCon = mouseDrag.MouseMoved:connect(function(x,y)
1555 if reentrancyGuardMouseScroll then return end
1556
1557 reentrancyGuardMouseScroll = true
1558 if positionScrollBar(x,y,mouseOffset) then
1559 recalculate()
1560 end
1561 reentrancyGuardMouseScroll = false
1562
1563 end)
1564 upCon = mouseDrag.MouseButton1Up:connect(function()
1565 scrollStamp = tick()
1566 mouseDrag.Parent = nil
1567 dragCon:disconnect(); dragCon = nil
1568 upCon:disconnect(); drag = nil
1569 end)
1570 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
1571 end
1572 end)
1573
1574 local scrollMouseCount = 0
1575
1576 scrollUpButton.MouseButton1Down:connect(function()
1577 scrollUp()
1578 end)
1579 scrollUpButton.MouseButton1Up:connect(function()
1580 scrollStamp = tick()
1581 end)
1582
1583 scrollDownButton.MouseButton1Up:connect(function()
1584 scrollStamp = tick()
1585 end)
1586 scrollDownButton.MouseButton1Down:connect(function()
1587 scrollDown()
1588 end)
1589
1590 scrollbar.MouseButton1Up:connect(function()
1591 scrollStamp = tick()
1592 end)
1593
1594 local function heightCheck(instance)
1595 if highY and (instance.AbsolutePosition.Y + instance.AbsoluteSize.Y) > highY then
1596 highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
1597 elseif not highY then
1598 highY = instance.AbsolutePosition.Y + instance.AbsoluteSize.Y
1599 end
1600 setSliderSizeAndPosition()
1601 end
1602
1603 local function highLowRecheck()
1604 local oldLowY = lowY
1605 local oldHighY = highY
1606 lowY = nil
1607 highY = nil
1608 resetHighLow()
1609
1610 if (lowY ~= oldLowY) or (highY ~= oldHighY) then
1611 setSliderSizeAndPosition()
1612 end
1613 end
1614
1615 local function descendantChanged(this, prop)
1616 if internalChange then return end
1617 if not this.Visible then return end
1618
1619 if prop == "Size" or prop == "Position" then
1620 wait()
1621 highLowRecheck()
1622 end
1623 end
1624
1625 scrollingFrame.DescendantAdded:connect(function(instance)
1626 if not instance:IsA("GuiObject") then return end
1627
1628 if instance.Visible then
1629 wait() -- wait a heartbeat for sizes to reconfig
1630 highLowRecheck()
1631 end
1632
1633 descendantsChangeConMap[instance] = instance.Changed:connect(function(prop) descendantChanged(instance, prop) end)
1634 end)
1635
1636 scrollingFrame.DescendantRemoving:connect(function(instance)
1637 if not instance:IsA("GuiObject") then return end
1638 if descendantsChangeConMap[instance] then
1639 descendantsChangeConMap[instance]:disconnect()
1640 descendantsChangeConMap[instance] = nil
1641 end
1642 wait() -- wait a heartbeat for sizes to reconfig
1643 highLowRecheck()
1644 end)
1645
1646 scrollingFrame.Changed:connect(function(prop)
1647 if prop == "AbsoluteSize" then
1648 if not highY or not lowY then return end
1649
1650 highLowRecheck()
1651 setSliderSizeAndPosition()
1652 end
1653 end)
1654
1655 return scrollingFrame, controlFrame
1656end
1657
1658t.CreateScrollingFrame = function(orderList,scrollStyle)
1659 local frame = Instance.new("Frame")
1660 frame.Name = "ScrollingFrame"
1661 frame.BackgroundTransparency = 1
1662 frame.Size = UDim2.new(1,0,1,0)
1663
1664 local scrollUpButton = Instance.new("ImageButton")
1665 scrollUpButton.Name = "ScrollUpButton"
1666 scrollUpButton.BackgroundTransparency = 1
1667 scrollUpButton.Image = "rbxasset://textures/ui/scrollbuttonUp.png"
1668 scrollUpButton.Size = UDim2.new(0,17,0,17)
1669
1670
1671 local scrollDownButton = Instance.new("ImageButton")
1672 scrollDownButton.Name = "ScrollDownButton"
1673 scrollDownButton.BackgroundTransparency = 1
1674 scrollDownButton.Image = "rbxasset://textures/ui/scrollbuttonDown.png"
1675 scrollDownButton.Size = UDim2.new(0,17,0,17)
1676
1677 local scrollbar = Instance.new("ImageButton")
1678 scrollbar.Name = "ScrollBar"
1679 scrollbar.Image = "rbxasset://textures/ui/scrollbar.png"
1680 scrollbar.BackgroundTransparency = 1
1681 scrollbar.Size = UDim2.new(0, 18, 0, 150)
1682
1683 local scrollStamp = 0
1684
1685 local scrollDrag = Instance.new("ImageButton")
1686 scrollDrag.Image = "https://www.roblox.com/asset/?id=61367186"
1687 scrollDrag.Size = UDim2.new(1, 0, 0, 16)
1688 scrollDrag.BackgroundTransparency = 1
1689 scrollDrag.Name = "ScrollDrag"
1690 scrollDrag.Active = true
1691 scrollDrag.Parent = scrollbar
1692
1693 local mouseDrag = Instance.new("ImageButton")
1694 mouseDrag.Active = false
1695 mouseDrag.Size = UDim2.new(1.5, 0, 1.5, 0)
1696 mouseDrag.AutoButtonColor = false
1697 mouseDrag.BackgroundTransparency = 1
1698 mouseDrag.Name = "mouseDrag"
1699 mouseDrag.Position = UDim2.new(-0.25, 0, -0.25, 0)
1700 mouseDrag.ZIndex = 10
1701
1702 local style = "simple"
1703 if scrollStyle and tostring(scrollStyle) then
1704 style = scrollStyle
1705 end
1706
1707 local scrollPosition = 1
1708 local rowSize = 0
1709 local howManyDisplayed = 0
1710
1711 local layoutGridScrollBar = function()
1712 howManyDisplayed = 0
1713 local guiObjects = {}
1714 if orderList then
1715 for i, child in ipairs(orderList) do
1716 if child.Parent == frame then
1717 table.insert(guiObjects, child)
1718 end
1719 end
1720 else
1721 local children = frame:GetChildren()
1722 if children then
1723 for i, child in ipairs(children) do
1724 if child:IsA("GuiObject") then
1725 table.insert(guiObjects, child)
1726 end
1727 end
1728 end
1729 end
1730 if #guiObjects == 0 then
1731 scrollUpButton.Active = false
1732 scrollDownButton.Active = false
1733 scrollDrag.Active = false
1734 scrollPosition = 1
1735 return
1736 end
1737
1738 if scrollPosition > #guiObjects then
1739 scrollPosition = #guiObjects
1740 end
1741
1742 if scrollPosition < 1 then scrollPosition = 1 end
1743
1744 local totalPixelsY = frame.AbsoluteSize.Y
1745 local pixelsRemainingY = frame.AbsoluteSize.Y
1746
1747 local totalPixelsX = frame.AbsoluteSize.X
1748
1749 local xCounter = 0
1750 local rowSizeCounter = 0
1751 local setRowSize = true
1752
1753 local pixelsBelowScrollbar = 0
1754 local pos = #guiObjects
1755
1756 local currentRowY = 0
1757
1758 pos = scrollPosition
1759 --count up from current scroll position to fill out grid
1760 while pos <= #guiObjects and pixelsBelowScrollbar < totalPixelsY do
1761 xCounter = xCounter + guiObjects[pos].AbsoluteSize.X
1762 --previous pos was the end of a row
1763 if xCounter >= totalPixelsX then
1764 pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
1765 currentRowY = 0
1766 xCounter = guiObjects[pos].AbsoluteSize.X
1767 end
1768 if guiObjects[pos].AbsoluteSize.Y > currentRowY then
1769 currentRowY = guiObjects[pos].AbsoluteSize.Y
1770 end
1771 pos = pos + 1
1772 end
1773 --Count wherever current row left off
1774 pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
1775 currentRowY = 0
1776
1777 pos = scrollPosition - 1
1778 xCounter = 0
1779
1780 --objects with varying X,Y dimensions can rarely cause minor errors
1781 --rechecking every new scrollPosition is necessary to avoid 100% of errors
1782
1783 --count backwards from current scrollPosition to see if we can add more rows
1784 while pixelsBelowScrollbar + currentRowY < totalPixelsY and pos >= 1 do
1785 xCounter = xCounter + guiObjects[pos].AbsoluteSize.X
1786 rowSizeCounter = rowSizeCounter + 1
1787 if xCounter >= totalPixelsX then
1788 rowSize = rowSizeCounter - 1
1789 rowSizeCounter = 0
1790 xCounter = guiObjects[pos].AbsoluteSize.X
1791 if pixelsBelowScrollbar + currentRowY <= totalPixelsY then
1792 --It fits, so back up our scroll position
1793 pixelsBelowScrollbar = pixelsBelowScrollbar + currentRowY
1794 if scrollPosition <= rowSize then
1795 scrollPosition = 1
1796 break
1797 else
1798 scrollPosition = scrollPosition - rowSize
1799 end
1800 currentRowY = 0
1801 else
1802 break
1803 end
1804 end
1805
1806 if guiObjects[pos].AbsoluteSize.Y > currentRowY then
1807 currentRowY = guiObjects[pos].AbsoluteSize.Y
1808 end
1809
1810 pos = pos - 1
1811 end
1812
1813 --Do check last time if pos = 0
1814 if (pos == 0) and (pixelsBelowScrollbar + currentRowY <= totalPixelsY) then
1815 scrollPosition = 1
1816 end
1817
1818 xCounter = 0
1819 --pos = scrollPosition
1820 rowSizeCounter = 0
1821 setRowSize = true
1822 local lastChildSize = 0
1823
1824 local xOffset,yOffset = 0
1825 if guiObjects[1] then
1826 yOffset = math.ceil(math.floor(math.fmod(totalPixelsY,guiObjects[1].AbsoluteSize.X))/2)
1827 xOffset = math.ceil(math.floor(math.fmod(totalPixelsX,guiObjects[1].AbsoluteSize.Y))/2)
1828 end
1829
1830 for i, child in ipairs(guiObjects) do
1831 if i < scrollPosition then
1832 --print("Hiding " .. child.Name)
1833 child.Visible = false
1834 else
1835 if pixelsRemainingY < 0 then
1836 --print("Out of Space " .. child.Name)
1837 child.Visible = false
1838 else
1839 --print("Laying out " .. child.Name)
1840 --GuiObject
1841 if setRowSize then rowSizeCounter = rowSizeCounter + 1 end
1842 if xCounter + child.AbsoluteSize.X >= totalPixelsX then
1843 if setRowSize then
1844 rowSize = rowSizeCounter - 1
1845 setRowSize = false
1846 end
1847 xCounter = 0
1848 pixelsRemainingY = pixelsRemainingY - child.AbsoluteSize.Y
1849 end
1850 child.Position = UDim2.new(child.Position.X.Scale,xCounter + xOffset, 0, totalPixelsY - pixelsRemainingY + yOffset)
1851 xCounter = xCounter + child.AbsoluteSize.X
1852 child.Visible = ((pixelsRemainingY - child.AbsoluteSize.Y) >= 0)
1853 if child.Visible then
1854 howManyDisplayed = howManyDisplayed + 1
1855 end
1856 lastChildSize = child.AbsoluteSize
1857 end
1858 end
1859 end
1860
1861 scrollUpButton.Active = (scrollPosition > 1)
1862 if lastChildSize == 0 then
1863 scrollDownButton.Active = false
1864 else
1865 scrollDownButton.Active = ((pixelsRemainingY - lastChildSize.Y) < 0)
1866 end
1867 scrollDrag.Active = #guiObjects > howManyDisplayed
1868 scrollDrag.Visible = scrollDrag.Active
1869 end
1870
1871
1872
1873 local layoutSimpleScrollBar = function()
1874 local guiObjects = {}
1875 howManyDisplayed = 0
1876
1877 if orderList then
1878 for i, child in ipairs(orderList) do
1879 if child.Parent == frame then
1880 table.insert(guiObjects, child)
1881 end
1882 end
1883 else
1884 local children = frame:GetChildren()
1885 if children then
1886 for i, child in ipairs(children) do
1887 if child:IsA("GuiObject") then
1888 table.insert(guiObjects, child)
1889 end
1890 end
1891 end
1892 end
1893 if #guiObjects == 0 then
1894 scrollUpButton.Active = false
1895 scrollDownButton.Active = false
1896 scrollDrag.Active = false
1897 scrollPosition = 1
1898 return
1899 end
1900
1901 if scrollPosition > #guiObjects then
1902 scrollPosition = #guiObjects
1903 end
1904
1905 local totalPixels = frame.AbsoluteSize.Y
1906 local pixelsRemaining = frame.AbsoluteSize.Y
1907
1908 local pixelsBelowScrollbar = 0
1909 local pos = #guiObjects
1910 while pixelsBelowScrollbar < totalPixels and pos >= 1 do
1911 if pos >= scrollPosition then
1912 pixelsBelowScrollbar = pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y
1913 else
1914 if pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y <= totalPixels then
1915 --It fits, so back up our scroll position
1916 pixelsBelowScrollbar = pixelsBelowScrollbar + guiObjects[pos].AbsoluteSize.Y
1917 if scrollPosition <= 1 then
1918 scrollPosition = 1
1919 break
1920 else
1921 --local ("Backing up ScrollPosition from -- " ..scrollPosition)
1922 scrollPosition = scrollPosition - 1
1923 end
1924 else
1925 break
1926 end
1927 end
1928 pos = pos - 1
1929 end
1930
1931 pos = scrollPosition
1932 for i, child in ipairs(guiObjects) do
1933 if i < scrollPosition then
1934 --print("Hiding " .. child.Name)
1935 child.Visible = false
1936 else
1937 if pixelsRemaining < 0 then
1938 --print("Out of Space " .. child.Name)
1939 child.Visible = false
1940 else
1941 --print("Laying out " .. child.Name)
1942 --GuiObject
1943 child.Position = UDim2.new(child.Position.X.Scale, child.Position.X.Offset, 0, totalPixels - pixelsRemaining)
1944 pixelsRemaining = pixelsRemaining - child.AbsoluteSize.Y
1945 if (pixelsRemaining >= 0) then
1946 child.Visible = true
1947 howManyDisplayed = howManyDisplayed + 1
1948 else
1949 child.Visible = false
1950 end
1951 end
1952 end
1953 end
1954 scrollUpButton.Active = (scrollPosition > 1)
1955 scrollDownButton.Active = (pixelsRemaining < 0)
1956 scrollDrag.Active = #guiObjects > howManyDisplayed
1957 scrollDrag.Visible = scrollDrag.Active
1958 end
1959
1960
1961 local moveDragger = function()
1962 local guiObjects = 0
1963 local children = frame:GetChildren()
1964 if children then
1965 for i, child in ipairs(children) do
1966 if child:IsA("GuiObject") then
1967 guiObjects = guiObjects + 1
1968 end
1969 end
1970 end
1971
1972 if not scrollDrag.Parent then return end
1973
1974 local dragSizeY = scrollDrag.Parent.AbsoluteSize.y * (1/(guiObjects - howManyDisplayed + 1))
1975 if dragSizeY < 16 then dragSizeY = 16 end
1976 scrollDrag.Size = UDim2.new(scrollDrag.Size.X.Scale,scrollDrag.Size.X.Offset,scrollDrag.Size.Y.Scale,dragSizeY)
1977
1978 local relativeYPos = (scrollPosition - 1)/(guiObjects - (howManyDisplayed))
1979 if relativeYPos > 1 then relativeYPos = 1
1980 elseif relativeYPos < 0 then relativeYPos = 0 end
1981 local absYPos = 0
1982
1983 if relativeYPos ~= 0 then
1984 absYPos = (relativeYPos * scrollbar.AbsoluteSize.y) - (relativeYPos * scrollDrag.AbsoluteSize.y)
1985 end
1986
1987 scrollDrag.Position = UDim2.new(scrollDrag.Position.X.Scale,scrollDrag.Position.X.Offset,scrollDrag.Position.Y.Scale,absYPos)
1988 end
1989
1990 local reentrancyGuard = false
1991 local recalculate = function()
1992 if reentrancyGuard then
1993 return
1994 end
1995 reentrancyGuard = true
1996 wait()
1997 local success, err = nil
1998 if style == "grid" then
1999 success, err = pcall(function() layoutGridScrollBar() end)
2000 elseif style == "simple" then
2001 success, err = pcall(function() layoutSimpleScrollBar() end)
2002 end
2003 if not success then print(err) end
2004 moveDragger()
2005 reentrancyGuard = false
2006 end
2007
2008 local doScrollUp = function()
2009 scrollPosition = (scrollPosition) - rowSize
2010 if scrollPosition < 1 then scrollPosition = 1 end
2011 recalculate(nil)
2012 end
2013
2014 local doScrollDown = function()
2015 scrollPosition = (scrollPosition) + rowSize
2016 recalculate(nil)
2017 end
2018
2019 local scrollUp = function(mouseYPos)
2020 if scrollUpButton.Active then
2021 scrollStamp = tick()
2022 local current = scrollStamp
2023 local upCon
2024 upCon = mouseDrag.MouseButton1Up:connect(function()
2025 scrollStamp = tick()
2026 mouseDrag.Parent = nil
2027 upCon:disconnect()
2028 end)
2029 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
2030 doScrollUp()
2031 wait(0.2)
2032 local t = tick()
2033 local w = 0.1
2034 while scrollStamp == current do
2035 doScrollUp()
2036 if mouseYPos and mouseYPos > scrollDrag.AbsolutePosition.y then
2037 break
2038 end
2039 if not scrollUpButton.Active then break end
2040 if tick()-t > 5 then
2041 w = 0
2042 elseif tick()-t > 2 then
2043 w = 0.06
2044 end
2045 wait(w)
2046 end
2047 end
2048 end
2049
2050 local scrollDown = function(mouseYPos)
2051 if scrollDownButton.Active then
2052 scrollStamp = tick()
2053 local current = scrollStamp
2054 local downCon
2055 downCon = mouseDrag.MouseButton1Up:connect(function()
2056 scrollStamp = tick()
2057 mouseDrag.Parent = nil
2058 downCon:disconnect()
2059 end)
2060 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
2061 doScrollDown()
2062 wait(0.2)
2063 local t = tick()
2064 local w = 0.1
2065 while scrollStamp == current do
2066 doScrollDown()
2067 if mouseYPos and mouseYPos < (scrollDrag.AbsolutePosition.y + scrollDrag.AbsoluteSize.x) then
2068 break
2069 end
2070 if not scrollDownButton.Active then break end
2071 if tick()-t > 5 then
2072 w = 0
2073 elseif tick()-t > 2 then
2074 w = 0.06
2075 end
2076 wait(w)
2077 end
2078 end
2079 end
2080
2081 local y = 0
2082 scrollDrag.MouseButton1Down:connect(function(x,y)
2083 if scrollDrag.Active then
2084 scrollStamp = tick()
2085 local mouseOffset = y - scrollDrag.AbsolutePosition.y
2086 local dragCon
2087 local upCon
2088 dragCon = mouseDrag.MouseMoved:connect(function(x,y)
2089 local barAbsPos = scrollbar.AbsolutePosition.y
2090 local barAbsSize = scrollbar.AbsoluteSize.y
2091
2092 local dragAbsSize = scrollDrag.AbsoluteSize.y
2093 local barAbsOne = barAbsPos + barAbsSize - dragAbsSize
2094 y = y - mouseOffset
2095 y = y < barAbsPos and barAbsPos or y > barAbsOne and barAbsOne or y
2096 y = y - barAbsPos
2097
2098 local guiObjects = 0
2099 local children = frame:GetChildren()
2100 if children then
2101 for i, child in ipairs(children) do
2102 if child:IsA("GuiObject") then
2103 guiObjects = guiObjects + 1
2104 end
2105 end
2106 end
2107
2108 local doublePercent = y/(barAbsSize-dragAbsSize)
2109 local rowDiff = rowSize
2110 local totalScrollCount = guiObjects - (howManyDisplayed - 1)
2111 local newScrollPosition = math.floor((doublePercent * totalScrollCount) + 0.5) + rowDiff
2112 if newScrollPosition < scrollPosition then
2113 rowDiff = -rowDiff
2114 end
2115
2116 if newScrollPosition < 1 then
2117 newScrollPosition = 1
2118 end
2119
2120 scrollPosition = newScrollPosition
2121 recalculate(nil)
2122 end)
2123 upCon = mouseDrag.MouseButton1Up:connect(function()
2124 scrollStamp = tick()
2125 mouseDrag.Parent = nil
2126 dragCon:disconnect(); dragCon = nil
2127 upCon:disconnect(); drag = nil
2128 end)
2129 mouseDrag.Parent = getLayerCollectorAncestor(scrollbar)
2130 end
2131 end)
2132
2133 local scrollMouseCount = 0
2134
2135 scrollUpButton.MouseButton1Down:connect(
2136 function()
2137 scrollUp()
2138 end)
2139 scrollUpButton.MouseButton1Up:connect(function()
2140 scrollStamp = tick()
2141 end)
2142
2143
2144 scrollDownButton.MouseButton1Up:connect(function()
2145 scrollStamp = tick()
2146 end)
2147 scrollDownButton.MouseButton1Down:connect(
2148 function()
2149 scrollDown()
2150 end)
2151
2152 scrollbar.MouseButton1Up:connect(function()
2153 scrollStamp = tick()
2154 end)
2155 scrollbar.MouseButton1Down:connect(
2156 function(x,y)
2157 if y > (scrollDrag.AbsoluteSize.y + scrollDrag.AbsolutePosition.y) then
2158 scrollDown(y)
2159 elseif y < (scrollDrag.AbsolutePosition.y) then
2160 scrollUp(y)
2161 end
2162 end)
2163
2164
2165 frame.ChildAdded:connect(function()
2166 recalculate(nil)
2167 end)
2168
2169 frame.ChildRemoved:connect(function()
2170 recalculate(nil)
2171 end)
2172
2173 frame.Changed:connect(
2174 function(prop)
2175 if prop == "AbsoluteSize" then
2176 --Wait a heartbeat for it to sync in
2177 recalculate(nil)
2178 end
2179 end)
2180 frame.AncestryChanged:connect(function() recalculate(nil) end)
2181
2182 return frame, scrollUpButton, scrollDownButton, recalculate, scrollbar
2183end
2184local function binaryGrow(min, max, fits)
2185 if min > max then
2186 return min
2187 end
2188 local biggestLegal = min
2189
2190 while min <= max do
2191 local mid = min + math.floor((max - min) / 2)
2192 if fits(mid) and (biggestLegal == nil or biggestLegal < mid) then
2193 biggestLegal = mid
2194
2195 --Try growing
2196 min = mid + 1
2197 else
2198 --Doesn't fit, shrink
2199 max = mid - 1
2200 end
2201 end
2202 return biggestLegal
2203end
2204
2205
2206local function binaryShrink(min, max, fits)
2207 if min > max then
2208 return min
2209 end
2210 local smallestLegal = max
2211
2212 while min <= max do
2213 local mid = min + math.floor((max - min) / 2)
2214 if fits(mid) and (smallestLegal == nil or smallestLegal > mid) then
2215 smallestLegal = mid
2216
2217 --It fits, shrink
2218 max = mid - 1
2219 else
2220 --Doesn't fit, grow
2221 min = mid + 1
2222 end
2223 end
2224 return smallestLegal
2225end
2226
2227
2228local function getGuiOwner(instance)
2229 while instance ~= nil do
2230 if instance:IsA("ScreenGui") or instance:IsA("BillboardGui") then
2231 return instance
2232 end
2233 instance = instance.Parent
2234 end
2235 return nil
2236end
2237
2238t.AutoTruncateTextObject = function(textLabel)
2239 local text = textLabel.Text
2240
2241 local fullLabel = textLabel:Clone()
2242 fullLabel.Name = "Full" .. textLabel.Name
2243 fullLabel.BorderSizePixel = 0
2244 fullLabel.BackgroundTransparency = 0
2245 fullLabel.Text = text
2246 fullLabel.TextXAlignment = Enum.TextXAlignment.Center
2247 fullLabel.Position = UDim2.new(0,-3,0,0)
2248 fullLabel.Size = UDim2.new(0,100,1,0)
2249 fullLabel.Visible = false
2250 fullLabel.Parent = textLabel
2251
2252 local shortText = nil
2253 local mouseEnterConnection = nil
2254 local mouseLeaveConnection= nil
2255
2256 local checkForResize = function()
2257 if getGuiOwner(textLabel) == nil then
2258 return
2259 end
2260 textLabel.Text = text
2261 if textLabel.TextFits then
2262 --Tear down the rollover if it is active
2263 if mouseEnterConnection then
2264 mouseEnterConnection:disconnect()
2265 mouseEnterConnection = nil
2266 end
2267 if mouseLeaveConnection then
2268 mouseLeaveConnection:disconnect()
2269 mouseLeaveConnection = nil
2270 end
2271 else
2272 local len = string.len(text)
2273 textLabel.Text = text .. "~"
2274
2275 --Shrink the text
2276 local textSize = binaryGrow(0, len,
2277 function(pos)
2278 if pos == 0 then
2279 textLabel.Text = "~"
2280 else
2281 textLabel.Text = string.sub(text, 1, pos) .. "~"
2282 end
2283 return textLabel.TextFits
2284 end)
2285 shortText = string.sub(text, 1, textSize) .. "~"
2286 textLabel.Text = shortText
2287
2288 --Make sure the fullLabel fits
2289 if not fullLabel.TextFits then
2290 --Already too small, grow it really bit to start
2291 fullLabel.Size = UDim2.new(0, 10000, 1, 0)
2292 end
2293
2294 --Okay, now try to binary shrink it back down
2295 local fullLabelSize = binaryShrink(textLabel.AbsoluteSize.X,fullLabel.AbsoluteSize.X,
2296 function(size)
2297 fullLabel.Size = UDim2.new(0, size, 1, 0)
2298 return fullLabel.TextFits
2299 end)
2300 fullLabel.Size = UDim2.new(0,fullLabelSize+6,1,0)
2301
2302 --Now setup the rollover effects, if they are currently off
2303 if mouseEnterConnection == nil then
2304 mouseEnterConnection = textLabel.MouseEnter:connect(
2305 function()
2306 fullLabel.ZIndex = textLabel.ZIndex + 1
2307 fullLabel.Visible = true
2308 --textLabel.Text = ""
2309 end)
2310 end
2311 if mouseLeaveConnection == nil then
2312 mouseLeaveConnection = textLabel.MouseLeave:connect(
2313 function()
2314 fullLabel.Visible = false
2315 --textLabel.Text = shortText
2316 end)
2317 end
2318 end
2319 end
2320 textLabel.AncestryChanged:connect(checkForResize)
2321 textLabel.Changed:connect(
2322 function(prop)
2323 if prop == "AbsoluteSize" then
2324 checkForResize()
2325 end
2326 end)
2327
2328 checkForResize()
2329
2330 local function changeText(newText)
2331 text = newText
2332 fullLabel.Text = text
2333 checkForResize()
2334 end
2335
2336 return textLabel, changeText
2337end
2338
2339local function TransitionTutorialPages(fromPage, toPage, transitionFrame, currentPageValue)
2340 if fromPage then
2341 fromPage.Visible = false
2342 if transitionFrame.Visible == false then
2343 transitionFrame.Size = fromPage.Size
2344 transitionFrame.Position = fromPage.Position
2345 end
2346 else
2347 if transitionFrame.Visible == false then
2348 transitionFrame.Size = UDim2.new(0.0,50,0.0,50)
2349 transitionFrame.Position = UDim2.new(0.5,-25,0.5,-25)
2350 end
2351 end
2352 transitionFrame.Visible = true
2353 currentPageValue.Value = nil
2354
2355 local newSize, newPosition
2356 if toPage then
2357 --Make it visible so it resizes
2358 toPage.Visible = true
2359
2360 newSize = toPage.Size
2361 newPosition = toPage.Position
2362
2363 toPage.Visible = false
2364 else
2365 newSize = UDim2.new(0.0,50,0.0,50)
2366 newPosition = UDim2.new(0.5,-25,0.5,-25)
2367 end
2368 transitionFrame:TweenSizeAndPosition(newSize, newPosition, Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.3, true,
2369 function(state)
2370 if state == Enum.TweenStatus.Completed then
2371 transitionFrame.Visible = false
2372 if toPage then
2373 toPage.Visible = true
2374 currentPageValue.Value = toPage
2375 end
2376 end
2377 end)
2378end
2379
2380t.CreateTutorial = function(name, tutorialKey, createButtons)
2381 local frame = Instance.new("Frame")
2382 frame.Name = "Tutorial-" .. name
2383 frame.BackgroundTransparency = 1
2384 frame.Size = UDim2.new(0.6, 0, 0.6, 0)
2385 frame.Position = UDim2.new(0.2, 0, 0.2, 0)
2386
2387 local transitionFrame = Instance.new("Frame")
2388 transitionFrame.Name = "TransitionFrame"
2389 transitionFrame.Style = Enum.FrameStyle.RobloxRound
2390 transitionFrame.Size = UDim2.new(0.6, 0, 0.6, 0)
2391 transitionFrame.Position = UDim2.new(0.2, 0, 0.2, 0)
2392 transitionFrame.Visible = false
2393 transitionFrame.Parent = frame
2394
2395 local currentPageValue = Instance.new("ObjectValue")
2396 currentPageValue.Name = "CurrentTutorialPage"
2397 currentPageValue.Value = nil
2398 currentPageValue.Parent = frame
2399
2400 local boolValue = Instance.new("BoolValue")
2401 boolValue.Name = "Buttons"
2402 boolValue.Value = createButtons
2403 boolValue.Parent = frame
2404
2405 local pages = Instance.new("Frame")
2406 pages.Name = "Pages"
2407 pages.BackgroundTransparency = 1
2408 pages.Size = UDim2.new(1,0,1,0)
2409 pages.Parent = frame
2410
2411 local function getVisiblePageAndHideOthers()
2412 local visiblePage = nil
2413 local children = pages:GetChildren()
2414 if children then
2415 for i,child in ipairs(children) do
2416 if child.Visible then
2417 if visiblePage then
2418 child.Visible = false
2419 else
2420 visiblePage = child
2421 end
2422 end
2423 end
2424 end
2425 return visiblePage
2426 end
2427
2428 local showTutorial = function(alwaysShow)
2429 if alwaysShow or UserSettings().GameSettings:GetTutorialState(tutorialKey) == false then
2430 print("Showing tutorial-",tutorialKey)
2431 local currentTutorialPage = getVisiblePageAndHideOthers()
2432
2433 local firstPage = pages:FindFirstChild("TutorialPage1")
2434 if firstPage then
2435 TransitionTutorialPages(currentTutorialPage, firstPage, transitionFrame, currentPageValue)
2436 else
2437 error("Could not find TutorialPage1")
2438 end
2439 end
2440 end
2441
2442 local dismissTutorial = function()
2443 local currentTutorialPage = getVisiblePageAndHideOthers()
2444
2445 if currentTutorialPage then
2446 TransitionTutorialPages(currentTutorialPage, nil, transitionFrame, currentPageValue)
2447 end
2448
2449 UserSettings().GameSettings:SetTutorialState(tutorialKey, true)
2450 end
2451
2452 local gotoPage = function(pageNum)
2453 local page = pages:FindFirstChild("TutorialPage" .. pageNum)
2454 local currentTutorialPage = getVisiblePageAndHideOthers()
2455 TransitionTutorialPages(currentTutorialPage, page, transitionFrame, currentPageValue)
2456 end
2457
2458 return frame, showTutorial, dismissTutorial, gotoPage
2459end
2460
2461local function CreateBasicTutorialPage(name, handleResize, skipTutorial, giveDoneButton)
2462 local frame = Instance.new("Frame")
2463 frame.Name = "TutorialPage"
2464 frame.Style = Enum.FrameStyle.RobloxRound
2465 frame.Size = UDim2.new(0.6, 0, 0.6, 0)
2466 frame.Position = UDim2.new(0.2, 0, 0.2, 0)
2467 frame.Visible = false
2468
2469 local frameHeader = Instance.new("TextLabel")
2470 frameHeader.Name = "Header"
2471 frameHeader.Text = name
2472 frameHeader.BackgroundTransparency = 1
2473 frameHeader.FontSize = Enum.FontSize.Size24
2474 frameHeader.Font = Enum.Font.ArialBold
2475 frameHeader.TextColor3 = Color3.new(1,1,1)
2476 frameHeader.TextXAlignment = Enum.TextXAlignment.Center
2477 frameHeader.TextWrap = true
2478 frameHeader.Size = UDim2.new(1,-55, 0, 22)
2479 frameHeader.Position = UDim2.new(0,0,0,0)
2480 frameHeader.Parent = frame
2481
2482 local skipButton = Instance.new("ImageButton")
2483 skipButton.Name = "SkipButton"
2484 skipButton.AutoButtonColor = false
2485 skipButton.BackgroundTransparency = 1
2486 skipButton.Image = "rbxasset://textures/ui/closeButton.png"
2487 skipButton.MouseButton1Click:connect(function()
2488 skipTutorial()
2489 end)
2490 skipButton.MouseEnter:connect(function()
2491 skipButton.Image = "rbxasset://textures/ui/closeButton_dn.png"
2492 end)
2493 skipButton.MouseLeave:connect(function()
2494 skipButton.Image = "rbxasset://textures/ui/closeButton.png"
2495 end)
2496 skipButton.Size = UDim2.new(0, 25, 0, 25)
2497 skipButton.Position = UDim2.new(1, -25, 0, 0)
2498 skipButton.Parent = frame
2499
2500
2501 if giveDoneButton then
2502 local doneButton = Instance.new("TextButton")
2503 doneButton.Name = "DoneButton"
2504 doneButton.Style = Enum.ButtonStyle.RobloxButtonDefault
2505 doneButton.Text = "Done"
2506 doneButton.TextColor3 = Color3.new(1,1,1)
2507 doneButton.Font = Enum.Font.ArialBold
2508 doneButton.FontSize = Enum.FontSize.Size18
2509 doneButton.Size = UDim2.new(0,100,0,50)
2510 doneButton.Position = UDim2.new(0.5,-50,1,-50)
2511
2512 if skipTutorial then
2513 doneButton.MouseButton1Click:connect(function() skipTutorial() end)
2514 end
2515
2516 doneButton.Parent = frame
2517 end
2518
2519 local innerFrame = Instance.new("Frame")
2520 innerFrame.Name = "ContentFrame"
2521 innerFrame.BackgroundTransparency = 1
2522 innerFrame.Position = UDim2.new(0,0,0,25)
2523 innerFrame.Parent = frame
2524
2525 local nextButton = Instance.new("TextButton")
2526 nextButton.Name = "NextButton"
2527 nextButton.Text = "Next"
2528 nextButton.TextColor3 = Color3.new(1,1,1)
2529 nextButton.Font = Enum.Font.Arial
2530 nextButton.FontSize = Enum.FontSize.Size18
2531 nextButton.Style = Enum.ButtonStyle.RobloxButtonDefault
2532 nextButton.Size = UDim2.new(0,80, 0, 32)
2533 nextButton.Position = UDim2.new(0.5, 5, 1, -32)
2534 nextButton.Active = false
2535 nextButton.Visible = false
2536 nextButton.Parent = frame
2537
2538 local prevButton = Instance.new("TextButton")
2539 prevButton.Name = "PrevButton"
2540 prevButton.Text = "Previous"
2541 prevButton.TextColor3 = Color3.new(1,1,1)
2542 prevButton.Font = Enum.Font.Arial
2543 prevButton.FontSize = Enum.FontSize.Size18
2544 prevButton.Style = Enum.ButtonStyle.RobloxButton
2545 prevButton.Size = UDim2.new(0,80, 0, 32)
2546 prevButton.Position = UDim2.new(0.5, -85, 1, -32)
2547 prevButton.Active = false
2548 prevButton.Visible = false
2549 prevButton.Parent = frame
2550
2551 if giveDoneButton then
2552 innerFrame.Size = UDim2.new(1,0,1,-75)
2553 else
2554 innerFrame.Size = UDim2.new(1,0,1,-22)
2555 end
2556
2557 local parentConnection = nil
2558
2559 local function basicHandleResize()
2560 if frame.Visible and frame.Parent then
2561 local maxSize = math.min(frame.Parent.AbsoluteSize.X, frame.Parent.AbsoluteSize.Y)
2562 handleResize(200,maxSize)
2563 end
2564 end
2565
2566 frame.Changed:connect(
2567 function(prop)
2568 if prop == "Parent" then
2569 if parentConnection ~= nil then
2570 parentConnection:disconnect()
2571 parentConnection = nil
2572 end
2573 if frame.Parent and frame.Parent:IsA("GuiObject") then
2574 parentConnection = frame.Parent.Changed:connect(
2575 function(parentProp)
2576 if parentProp == "AbsoluteSize" then
2577 wait()
2578 basicHandleResize()
2579 end
2580 end)
2581 basicHandleResize()
2582 end
2583 end
2584
2585 if prop == "Visible" then
2586 basicHandleResize()
2587 end
2588 end)
2589
2590 return frame, innerFrame
2591end
2592
2593t.CreateTextTutorialPage = function(name, text, skipTutorialFunc)
2594 local frame = nil
2595 local contentFrame = nil
2596
2597 local textLabel = Instance.new("TextLabel")
2598 textLabel.BackgroundTransparency = 1
2599 textLabel.TextColor3 = Color3.new(1,1,1)
2600 textLabel.Text = text
2601 textLabel.TextWrap = true
2602 textLabel.TextXAlignment = Enum.TextXAlignment.Left
2603 textLabel.TextYAlignment = Enum.TextYAlignment.Center
2604 textLabel.Font = Enum.Font.Arial
2605 textLabel.FontSize = Enum.FontSize.Size14
2606 textLabel.Size = UDim2.new(1,0,1,0)
2607
2608 local function handleResize(minSize, maxSize)
2609 size = binaryShrink(minSize, maxSize,
2610 function(size)
2611 frame.Size = UDim2.new(0, size, 0, size)
2612 return textLabel.TextFits
2613 end)
2614 frame.Size = UDim2.new(0, size, 0, size)
2615 frame.Position = UDim2.new(0.5, -size/2, 0.5, -size/2)
2616 end
2617
2618 frame, contentFrame = CreateBasicTutorialPage(name, handleResize, skipTutorialFunc)
2619 textLabel.Parent = contentFrame
2620
2621 return frame
2622end
2623
2624t.CreateImageTutorialPage = function(name, imageAsset, x, y, skipTutorialFunc, giveDoneButton)
2625 local frame = nil
2626 local contentFrame = nil
2627
2628 local imageLabel = Instance.new("ImageLabel")
2629 imageLabel.BackgroundTransparency = 1
2630 imageLabel.Image = imageAsset
2631 imageLabel.Size = UDim2.new(0,x,0,y)
2632 imageLabel.Position = UDim2.new(0.5,-x/2,0.5,-y/2)
2633
2634 local function handleResize(minSize, maxSize)
2635 size = binaryShrink(minSize, maxSize,
2636 function(size)
2637 return size >= x and size >= y
2638 end)
2639 if size >= x and size >= y then
2640 imageLabel.Size = UDim2.new(0,x, 0,y)
2641 imageLabel.Position = UDim2.new(0.5,-x/2, 0.5, -y/2)
2642 else
2643 if x > y then
2644 --X is limiter, so
2645 imageLabel.Size = UDim2.new(1,0,y/x,0)
2646 imageLabel.Position = UDim2.new(0,0, 0.5 - (y/x)/2, 0)
2647 else
2648 --Y is limiter
2649 imageLabel.Size = UDim2.new(x/y,0,1, 0)
2650 imageLabel.Position = UDim2.new(0.5-(x/y)/2, 0, 0, 0)
2651 end
2652 end
2653 size = size + 50
2654 frame.Size = UDim2.new(0, size, 0, size)
2655 frame.Position = UDim2.new(0.5, -size/2, 0.5, -size/2)
2656 end
2657
2658 frame, contentFrame = CreateBasicTutorialPage(name, handleResize, skipTutorialFunc, giveDoneButton)
2659 imageLabel.Parent = contentFrame
2660
2661 return frame
2662end
2663
2664t.AddTutorialPage = function(tutorial, tutorialPage)
2665 local transitionFrame = tutorial.TransitionFrame
2666 local currentPageValue = tutorial.CurrentTutorialPage
2667
2668 if not tutorial.Buttons.Value then
2669 tutorialPage.NextButton.Parent = nil
2670 tutorialPage.PrevButton.Parent = nil
2671 end
2672
2673 local children = tutorial.Pages:GetChildren()
2674 if children and #children > 0 then
2675 tutorialPage.Name = "TutorialPage" .. (#children+1)
2676 local previousPage = children[#children]
2677 if not previousPage:IsA("GuiObject") then
2678 error("All elements under Pages must be GuiObjects")
2679 end
2680
2681 if tutorial.Buttons.Value then
2682 if previousPage.NextButton.Active then
2683 error("NextButton already Active on previousPage, please only add pages with RbxGui.AddTutorialPage function")
2684 end
2685 previousPage.NextButton.MouseButton1Click:connect(
2686 function()
2687 TransitionTutorialPages(previousPage, tutorialPage, transitionFrame, currentPageValue)
2688 end)
2689 previousPage.NextButton.Active = true
2690 previousPage.NextButton.Visible = true
2691
2692 if tutorialPage.PrevButton.Active then
2693 error("PrevButton already Active on tutorialPage, please only add pages with RbxGui.AddTutorialPage function")
2694 end
2695 tutorialPage.PrevButton.MouseButton1Click:connect(
2696 function()
2697 TransitionTutorialPages(tutorialPage, previousPage, transitionFrame, currentPageValue)
2698 end)
2699 tutorialPage.PrevButton.Active = true
2700 tutorialPage.PrevButton.Visible = true
2701 end
2702
2703 tutorialPage.Parent = tutorial.Pages
2704 else
2705 --First child
2706 tutorialPage.Name = "TutorialPage1"
2707 tutorialPage.Parent = tutorial.Pages
2708 end
2709end
2710
2711t.CreateSetPanel = function(userIdsForSets, objectSelected, dialogClosed, size, position, showAdminCategories, useAssetVersionId)
2712
2713 if not userIdsForSets then
2714 error("CreateSetPanel: userIdsForSets (first arg) is nil, should be a table of number ids")
2715 end
2716 if type(userIdsForSets) ~= "table" and type(userIdsForSets) ~= "userdata" then
2717 error("CreateSetPanel: userIdsForSets (first arg) is of type " ..type(userIdsForSets) .. ", should be of type table or userdata")
2718 end
2719 if not objectSelected then
2720 error("CreateSetPanel: objectSelected (second arg) is nil, should be a callback function!")
2721 end
2722 if type(objectSelected) ~= "function" then
2723 error("CreateSetPanel: objectSelected (second arg) is of type " .. type(objectSelected) .. ", should be of type function!")
2724 end
2725 if dialogClosed and type(dialogClosed) ~= "function" then
2726 error("CreateSetPanel: dialogClosed (third arg) is of type " .. type(dialogClosed) .. ", should be of type function!")
2727 end
2728
2729 if showAdminCategories == nil then -- by default, don't show beta sets
2730 showAdminCategories = false
2731 end
2732
2733 local arrayPosition = 1
2734 local insertButtons = {}
2735 local insertButtonCons = {}
2736 local contents = nil
2737 local setGui = nil
2738
2739 -- used for water selections
2740 local waterForceDirection = "NegX"
2741 local waterForce = "None"
2742 local waterGui, waterTypeChangedEvent = nil
2743
2744 local Data = {}
2745 Data.CurrentCategory = nil
2746 Data.Category = {}
2747 local SetCache = {}
2748
2749 local userCategoryButtons = nil
2750
2751 local buttonWidth = 64
2752 local buttonHeight = buttonWidth
2753
2754 local SmallThumbnailUrl = nil
2755 local LargeThumbnailUrl = nil
2756 local BaseUrl = game:GetService("ContentProvider").BaseUrl:lower()
2757 local AssetGameUrl = string.gsub(BaseUrl, "www", "assetgame")
2758
2759 if useAssetVersionId then
2760 LargeThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&assetversionid="
2761 SmallThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&assetversionid="
2762 else
2763 LargeThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=420&ht=420&aid="
2764 SmallThumbnailUrl = AssetGameUrl .. "Game/Tools/ThumbnailAsset.ashx?fmt=png&wd=75&ht=75&aid="
2765 end
2766
2767 local function drillDownSetZIndex(parent, index)
2768 local children = parent:GetChildren()
2769 for i = 1, #children do
2770 if children[i]:IsA("GuiObject") then
2771 children[i].ZIndex = index
2772 end
2773 drillDownSetZIndex(children[i], index)
2774 end
2775 end
2776
2777 -- for terrain stamping
2778 local currTerrainDropDownFrame = nil
2779 local terrainShapes = {"Block","Vertical Ramp","Corner Wedge","Inverse Corner Wedge","Horizontal Ramp","Auto-Wedge"}
2780 local terrainShapeMap = {}
2781 for i = 1, #terrainShapes do
2782 terrainShapeMap[terrainShapes[i]] = i - 1
2783 end
2784 terrainShapeMap[terrainShapes[#terrainShapes]] = 6
2785
2786 local function createWaterGui()
2787 local waterForceDirections = {"NegX","X","NegY","Y","NegZ","Z"}
2788 local waterForces = {"None", "Small", "Medium", "Strong", "Max"}
2789
2790 local waterFrame = Instance.new("Frame")
2791 waterFrame.Name = "WaterFrame"
2792 waterFrame.Style = Enum.FrameStyle.RobloxSquare
2793 waterFrame.Size = UDim2.new(0,150,0,110)
2794 waterFrame.Visible = false
2795
2796 local waterForceLabel = Instance.new("TextLabel")
2797 waterForceLabel.Name = "WaterForceLabel"
2798 waterForceLabel.BackgroundTransparency = 1
2799 waterForceLabel.Size = UDim2.new(1,0,0,12)
2800 waterForceLabel.Font = Enum.Font.ArialBold
2801 waterForceLabel.FontSize = Enum.FontSize.Size12
2802 waterForceLabel.TextColor3 = Color3.new(1,1,1)
2803 waterForceLabel.TextXAlignment = Enum.TextXAlignment.Left
2804 waterForceLabel.Text = "Water Force"
2805 waterForceLabel.Parent = waterFrame
2806
2807 local waterForceDirLabel = waterForceLabel:Clone()
2808 waterForceDirLabel.Name = "WaterForceDirectionLabel"
2809 waterForceDirLabel.Text = "Water Force Direction"
2810 waterForceDirLabel.Position = UDim2.new(0,0,0,50)
2811 waterForceDirLabel.Parent = waterFrame
2812
2813 local waterTypeChangedEvent = Instance.new("BindableEvent",waterFrame)
2814 waterTypeChangedEvent.Name = "WaterTypeChangedEvent"
2815
2816 local waterForceDirectionSelectedFunc = function(newForceDirection)
2817 waterForceDirection = newForceDirection
2818 waterTypeChangedEvent:Fire({waterForce, waterForceDirection})
2819 end
2820 local waterForceSelectedFunc = function(newForce)
2821 waterForce = newForce
2822 waterTypeChangedEvent:Fire({waterForce, waterForceDirection})
2823 end
2824
2825 local waterForceDirectionDropDown, forceWaterDirectionSelection = t.CreateDropDownMenu(waterForceDirections, waterForceDirectionSelectedFunc)
2826 waterForceDirectionDropDown.Size = UDim2.new(1,0,0,25)
2827 waterForceDirectionDropDown.Position = UDim2.new(0,0,1,3)
2828 forceWaterDirectionSelection("NegX")
2829 waterForceDirectionDropDown.Parent = waterForceDirLabel
2830
2831 local waterForceDropDown, forceWaterForceSelection = t.CreateDropDownMenu(waterForces, waterForceSelectedFunc)
2832 forceWaterForceSelection("None")
2833 waterForceDropDown.Size = UDim2.new(1,0,0,25)
2834 waterForceDropDown.Position = UDim2.new(0,0,1,3)
2835 waterForceDropDown.Parent = waterForceLabel
2836
2837 return waterFrame, waterTypeChangedEvent
2838 end
2839
2840 -- Helper Function that contructs gui elements
2841 local function createSetGui()
2842
2843 local setGui = Instance.new("ScreenGui")
2844 setGui.Name = "SetGui"
2845
2846 local setPanel = Instance.new("Frame")
2847 setPanel.Name = "SetPanel"
2848 setPanel.Active = true
2849 setPanel.BackgroundTransparency = 1
2850 if position then
2851 setPanel.Position = position
2852 else
2853 setPanel.Position = UDim2.new(0.2, 29, 0.1, 24)
2854 end
2855 if size then
2856 setPanel.Size = size
2857 else
2858 setPanel.Size = UDim2.new(0.6, -58, 0.64, 0)
2859 end
2860 setPanel.Style = Enum.FrameStyle.RobloxRound
2861 setPanel.ZIndex = 6
2862 setPanel.Parent = setGui
2863
2864 -- Children of SetPanel
2865 local itemPreview = Instance.new("Frame")
2866 itemPreview.Name = "ItemPreview"
2867 itemPreview.BackgroundTransparency = 1
2868 itemPreview.Position = UDim2.new(0.8,5,0.085,0)
2869 itemPreview.Size = UDim2.new(0.21,0,0.9,0)
2870 itemPreview.ZIndex = 6
2871 itemPreview.Parent = setPanel
2872
2873 -- Children of ItemPreview
2874 local textPanel = Instance.new("Frame")
2875 textPanel.Name = "TextPanel"
2876 textPanel.BackgroundTransparency = 1
2877 textPanel.Position = UDim2.new(0,0,0.45,0)
2878 textPanel.Size = UDim2.new(1,0,0.55,0)
2879 textPanel.ZIndex = 6
2880 textPanel.Parent = itemPreview
2881
2882 -- Children of TextPanel
2883 local rolloverText = Instance.new("TextLabel")
2884 rolloverText.Name = "RolloverText"
2885 rolloverText.BackgroundTransparency = 1
2886 rolloverText.Size = UDim2.new(1,0,0,48)
2887 rolloverText.ZIndex = 6
2888 rolloverText.Font = Enum.Font.ArialBold
2889 rolloverText.FontSize = Enum.FontSize.Size24
2890 rolloverText.Text = ""
2891 rolloverText.TextColor3 = Color3.new(1,1,1)
2892 rolloverText.TextWrap = true
2893 rolloverText.TextXAlignment = Enum.TextXAlignment.Left
2894 rolloverText.TextYAlignment = Enum.TextYAlignment.Top
2895 rolloverText.Parent = textPanel
2896
2897 local largePreview = Instance.new("ImageLabel")
2898 largePreview.Name = "LargePreview"
2899 largePreview.BackgroundTransparency = 1
2900 largePreview.Image = ""
2901 largePreview.Size = UDim2.new(1,0,0,170)
2902 largePreview.ZIndex = 6
2903 largePreview.Parent = itemPreview
2904
2905 local sets = Instance.new("Frame")
2906 sets.Name = "Sets"
2907 sets.BackgroundTransparency = 1
2908 sets.Position = UDim2.new(0,0,0,5)
2909 sets.Size = UDim2.new(0.23,0,1,-5)
2910 sets.ZIndex = 6
2911 sets.Parent = setPanel
2912
2913 -- Children of Sets
2914 local line = Instance.new("Frame")
2915 line.Name = "Line"
2916 line.BackgroundColor3 = Color3.new(1,1,1)
2917 line.BackgroundTransparency = 0.7
2918 line.BorderSizePixel = 0
2919 line.Position = UDim2.new(1,-3,0.06,0)
2920 line.Size = UDim2.new(0,3,0.9,0)
2921 line.ZIndex = 6
2922 line.Parent = sets
2923
2924 local setsLists, controlFrame = t.CreateTrueScrollingFrame()
2925 setsLists.Size = UDim2.new(1,-6,0.94,0)
2926 setsLists.Position = UDim2.new(0,0,0.06,0)
2927 setsLists.BackgroundTransparency = 1
2928 setsLists.Name = "SetsLists"
2929 setsLists.ZIndex = 6
2930 setsLists.Parent = sets
2931 drillDownSetZIndex(controlFrame, 7)
2932
2933 local setsHeader = Instance.new("TextLabel")
2934 setsHeader.Name = "SetsHeader"
2935 setsHeader.BackgroundTransparency = 1
2936 setsHeader.Size = UDim2.new(0,47,0,24)
2937 setsHeader.ZIndex = 6
2938 setsHeader.Font = Enum.Font.ArialBold
2939 setsHeader.FontSize = Enum.FontSize.Size24
2940 setsHeader.Text = "Sets"
2941 setsHeader.TextColor3 = Color3.new(1,1,1)
2942 setsHeader.TextXAlignment = Enum.TextXAlignment.Left
2943 setsHeader.TextYAlignment = Enum.TextYAlignment.Top
2944 setsHeader.Parent = sets
2945
2946 local cancelButton = Instance.new("TextButton")
2947 cancelButton.Name = "CancelButton"
2948 cancelButton.Position = UDim2.new(1,-32,0,-2)
2949 cancelButton.Size = UDim2.new(0,34,0,34)
2950 cancelButton.Style = Enum.ButtonStyle.RobloxButtonDefault
2951 cancelButton.ZIndex = 6
2952 cancelButton.Text = ""
2953 cancelButton.Modal = true
2954 cancelButton.Parent = setPanel
2955
2956 -- Children of Cancel Button
2957 local cancelImage = Instance.new("ImageLabel")
2958 cancelImage.Name = "CancelImage"
2959 cancelImage.BackgroundTransparency = 1
2960 cancelImage.Image = "https://www.roblox.com/asset/?id=54135717"
2961 cancelImage.Position = UDim2.new(0,-2,0,-2)
2962 cancelImage.Size = UDim2.new(0,16,0,16)
2963 cancelImage.ZIndex = 6
2964 cancelImage.Parent = cancelButton
2965
2966 return setGui
2967 end
2968
2969 local function createSetButton(text)
2970 local setButton = Instance.new("TextButton")
2971
2972 if text then setButton.Text = text
2973 else setButton.Text = "" end
2974
2975 setButton.AutoButtonColor = false
2976 setButton.BackgroundTransparency = 1
2977 setButton.BackgroundColor3 = Color3.new(1,1,1)
2978 setButton.BorderSizePixel = 0
2979 setButton.Size = UDim2.new(1,-5,0,18)
2980 setButton.ZIndex = 6
2981 setButton.Visible = false
2982 setButton.Font = Enum.Font.Arial
2983 setButton.FontSize = Enum.FontSize.Size18
2984 setButton.TextColor3 = Color3.new(1,1,1)
2985 setButton.TextXAlignment = Enum.TextXAlignment.Left
2986
2987 return setButton
2988 end
2989
2990 local function buildSetButton(name, setId, setImageId, i, count)
2991 local button = createSetButton(name)
2992 button.Text = name
2993 button.Name = "SetButton"
2994 button.Visible = true
2995
2996 local setValue = Instance.new("IntValue")
2997 setValue.Name = "SetId"
2998 setValue.Value = setId
2999 setValue.Parent = button
3000
3001 local setName = Instance.new("StringValue")
3002 setName.Name = "SetName"
3003 setName.Value = name
3004 setName.Parent = button
3005
3006 return button
3007 end
3008
3009 local function processCategory(sets)
3010 local setButtons = {}
3011 local numSkipped = 0
3012 for i = 1, #sets do
3013 if not showAdminCategories and sets[i].Name == "Beta" then
3014 numSkipped = numSkipped + 1
3015 else
3016 setButtons[i - numSkipped] = buildSetButton(sets[i].Name, sets[i].CategoryId, sets[i].ImageAssetId, i - numSkipped, #sets)
3017 end
3018 end
3019 return setButtons
3020 end
3021
3022 local function handleResize()
3023 wait() -- neccessary to insure heartbeat happened
3024
3025 local itemPreview = setGui.SetPanel.ItemPreview
3026
3027 itemPreview.LargePreview.Size = UDim2.new(1,0,0,itemPreview.AbsoluteSize.X)
3028 itemPreview.LargePreview.Position = UDim2.new(0.5,-itemPreview.LargePreview.AbsoluteSize.X/2,0,0)
3029 itemPreview.TextPanel.Position = UDim2.new(0,0,0,itemPreview.LargePreview.AbsoluteSize.Y)
3030 itemPreview.TextPanel.Size = UDim2.new(1,0,0,itemPreview.AbsoluteSize.Y - itemPreview.LargePreview.AbsoluteSize.Y)
3031 end
3032
3033 local function makeInsertAssetButton()
3034 local insertAssetButtonExample = Instance.new("Frame")
3035 insertAssetButtonExample.Name = "InsertAssetButtonExample"
3036 insertAssetButtonExample.Position = UDim2.new(0,128,0,64)
3037 insertAssetButtonExample.Size = UDim2.new(0,64,0,64)
3038 insertAssetButtonExample.BackgroundTransparency = 1
3039 insertAssetButtonExample.ZIndex = 6
3040 insertAssetButtonExample.Visible = false
3041
3042 local assetId = Instance.new("IntValue")
3043 assetId.Name = "AssetId"
3044 assetId.Value = 0
3045 assetId.Parent = insertAssetButtonExample
3046
3047 local assetName = Instance.new("StringValue")
3048 assetName.Name = "AssetName"
3049 assetName.Value = ""
3050 assetName.Parent = insertAssetButtonExample
3051
3052 local button = Instance.new("TextButton")
3053 button.Name = "Button"
3054 button.Text = ""
3055 button.Style = Enum.ButtonStyle.RobloxButton
3056 button.Position = UDim2.new(0.025,0,0.025,0)
3057 button.Size = UDim2.new(0.95,0,0.95,0)
3058 button.ZIndex = 6
3059 button.Parent = insertAssetButtonExample
3060
3061 local buttonImage = Instance.new("ImageLabel")
3062 buttonImage.Name = "ButtonImage"
3063 buttonImage.Image = ""
3064 buttonImage.Position = UDim2.new(0,-7,0,-7)
3065 buttonImage.Size = UDim2.new(1,14,1,14)
3066 buttonImage.BackgroundTransparency = 1
3067 buttonImage.ZIndex = 7
3068 buttonImage.Parent = button
3069
3070 local configIcon = buttonImage:clone()
3071 configIcon.Name = "ConfigIcon"
3072 configIcon.Visible = false
3073 configIcon.Position = UDim2.new(1,-23,1,-24)
3074 configIcon.Size = UDim2.new(0,16,0,16)
3075 configIcon.Image = ""
3076 configIcon.ZIndex = 6
3077 configIcon.Parent = insertAssetButtonExample
3078
3079 return insertAssetButtonExample
3080 end
3081
3082 local function showLargePreview(insertButton)
3083 if insertButton:FindFirstChild("AssetId") then
3084 delay(0,function()
3085 game:GetService("ContentProvider"):Preload(LargeThumbnailUrl .. tostring(insertButton.AssetId.Value))
3086 setGui.SetPanel.ItemPreview.LargePreview.Image = LargeThumbnailUrl .. tostring(insertButton.AssetId.Value)
3087 end)
3088 end
3089 if insertButton:FindFirstChild("AssetName") then
3090 setGui.SetPanel.ItemPreview.TextPanel.RolloverText.Text = insertButton.AssetName.Value
3091 end
3092 end
3093
3094 local function selectTerrainShape(shape)
3095 if currTerrainDropDownFrame then
3096 objectSelected(tostring(currTerrainDropDownFrame.AssetName.Value), tonumber(currTerrainDropDownFrame.AssetId.Value), shape)
3097 end
3098 end
3099
3100 local function createTerrainTypeButton(name, parent)
3101 local dropDownTextButton = Instance.new("TextButton")
3102 dropDownTextButton.Name = name .. "Button"
3103 dropDownTextButton.Font = Enum.Font.ArialBold
3104 dropDownTextButton.FontSize = Enum.FontSize.Size14
3105 dropDownTextButton.BorderSizePixel = 0
3106 dropDownTextButton.TextColor3 = Color3.new(1,1,1)
3107 dropDownTextButton.Text = name
3108 dropDownTextButton.TextXAlignment = Enum.TextXAlignment.Left
3109 dropDownTextButton.BackgroundTransparency = 1
3110 dropDownTextButton.ZIndex = parent.ZIndex + 1
3111 dropDownTextButton.Size = UDim2.new(0,parent.Size.X.Offset - 2,0,16)
3112 dropDownTextButton.Position = UDim2.new(0,1,0,0)
3113
3114 dropDownTextButton.MouseEnter:connect(function()
3115 dropDownTextButton.BackgroundTransparency = 0
3116 dropDownTextButton.TextColor3 = Color3.new(0,0,0)
3117 end)
3118
3119 dropDownTextButton.MouseLeave:connect(function()
3120 dropDownTextButton.BackgroundTransparency = 1
3121 dropDownTextButton.TextColor3 = Color3.new(1,1,1)
3122 end)
3123
3124 dropDownTextButton.MouseButton1Click:connect(function()
3125 dropDownTextButton.BackgroundTransparency = 1
3126 dropDownTextButton.TextColor3 = Color3.new(1,1,1)
3127 if dropDownTextButton.Parent and dropDownTextButton.Parent:IsA("GuiObject") then
3128 dropDownTextButton.Parent.Visible = false
3129 end
3130 selectTerrainShape(terrainShapeMap[dropDownTextButton.Text])
3131 end)
3132
3133 return dropDownTextButton
3134 end
3135
3136 local function createTerrainDropDownMenu(zIndex)
3137 local dropDown = Instance.new("Frame")
3138 dropDown.Name = "TerrainDropDown"
3139 dropDown.BackgroundColor3 = Color3.new(0,0,0)
3140 dropDown.BorderColor3 = Color3.new(1,0,0)
3141 dropDown.Size = UDim2.new(0,200,0,0)
3142 dropDown.Visible = false
3143 dropDown.ZIndex = zIndex
3144 dropDown.Parent = setGui
3145
3146 for i = 1, #terrainShapes do
3147 local shapeButton = createTerrainTypeButton(terrainShapes[i],dropDown)
3148 shapeButton.Position = UDim2.new(0,1,0,(i - 1) * (shapeButton.Size.Y.Offset))
3149 shapeButton.Parent = dropDown
3150 dropDown.Size = UDim2.new(0,200,0,dropDown.Size.Y.Offset + (shapeButton.Size.Y.Offset))
3151 end
3152
3153 dropDown.MouseLeave:connect(function()
3154 dropDown.Visible = false
3155 end)
3156 end
3157
3158
3159 local function createDropDownMenuButton(parent)
3160 local dropDownButton = Instance.new("ImageButton")
3161 dropDownButton.Name = "DropDownButton"
3162 dropDownButton.Image = "https://www.roblox.com/asset/?id=67581509"
3163 dropDownButton.BackgroundTransparency = 1
3164 dropDownButton.Size = UDim2.new(0,16,0,16)
3165 dropDownButton.Position = UDim2.new(1,-24,0,6)
3166 dropDownButton.ZIndex = parent.ZIndex + 2
3167 dropDownButton.Parent = parent
3168
3169 if not setGui:FindFirstChild("TerrainDropDown") then
3170 createTerrainDropDownMenu(8)
3171 end
3172
3173 dropDownButton.MouseButton1Click:connect(function()
3174 setGui.TerrainDropDown.Visible = true
3175 setGui.TerrainDropDown.Position = UDim2.new(0,parent.AbsolutePosition.X,0,parent.AbsolutePosition.Y)
3176 currTerrainDropDownFrame = parent
3177 end)
3178 end
3179
3180 local function buildInsertButton()
3181 local insertButton = makeInsertAssetButton()
3182 insertButton.Name = "InsertAssetButton"
3183 insertButton.Visible = true
3184
3185 if Data.Category[Data.CurrentCategory].SetName == "High Scalability" then
3186 createDropDownMenuButton(insertButton)
3187 end
3188
3189 local lastEnter = nil
3190 local mouseEnterCon = insertButton.MouseEnter:connect(function()
3191 lastEnter = insertButton
3192 delay(0.1,function()
3193 if lastEnter == insertButton then
3194 showLargePreview(insertButton)
3195 end
3196 end)
3197 end)
3198 return insertButton, mouseEnterCon
3199 end
3200
3201 local function realignButtonGrid(columns)
3202 local x = 0
3203 local y = 0
3204 for i = 1, #insertButtons do
3205 insertButtons[i].Position = UDim2.new(0, buttonWidth * x, 0, buttonHeight * y)
3206 x = x + 1
3207 if x >= columns then
3208 x = 0
3209 y = y + 1
3210 end
3211 end
3212 end
3213
3214 local function setInsertButtonImageBehavior(insertFrame, visible, name, assetId)
3215 if visible then
3216 insertFrame.AssetName.Value = name
3217 insertFrame.AssetId.Value = assetId
3218 local newImageUrl = SmallThumbnailUrl .. assetId
3219 if newImageUrl ~= insertFrame.Button.ButtonImage.Image then
3220 delay(0,function()
3221 game:GetService("ContentProvider"):Preload(SmallThumbnailUrl .. assetId)
3222 if insertFrame:findFirstChild("Button") then
3223 insertFrame.Button.ButtonImage.Image = SmallThumbnailUrl .. assetId
3224 end
3225 end)
3226 end
3227 table.insert(insertButtonCons,
3228 insertFrame.Button.MouseButton1Click:connect(function()
3229 -- special case for water, show water selection gui
3230 local isWaterSelected = (name == "Water") and (Data.Category[Data.CurrentCategory].SetName == "High Scalability")
3231 waterGui.Visible = isWaterSelected
3232 if isWaterSelected then
3233 objectSelected(name, tonumber(assetId), nil)
3234 else
3235 objectSelected(name, tonumber(assetId))
3236 end
3237 end)
3238 )
3239 insertFrame.Visible = true
3240 else
3241 insertFrame.Visible = false
3242 end
3243 end
3244
3245 local function loadSectionOfItems(setGui, rows, columns)
3246 local pageSize = rows * columns
3247
3248 if arrayPosition > #contents then return end
3249
3250 local origArrayPos = arrayPosition
3251
3252 local yCopy = 0
3253 for i = 1, pageSize + 1 do
3254 if arrayPosition >= #contents + 1 then
3255 break
3256 end
3257
3258 local buttonCon
3259 insertButtons[arrayPosition], buttonCon = buildInsertButton()
3260 table.insert(insertButtonCons,buttonCon)
3261 insertButtons[arrayPosition].Parent = setGui.SetPanel.ItemsFrame
3262 arrayPosition = arrayPosition + 1
3263 end
3264 realignButtonGrid(columns)
3265
3266 local indexCopy = origArrayPos
3267 for index = origArrayPos, arrayPosition do
3268 if insertButtons[index] then
3269 if contents[index] then
3270
3271 -- we don't want water to have a drop down button
3272 if contents[index].Name == "Water" then
3273 if Data.Category[Data.CurrentCategory].SetName == "High Scalability" then
3274 insertButtons[index]:FindFirstChild("DropDownButton",true):Destroy()
3275 end
3276 end
3277
3278 local assetId
3279 if useAssetVersionId then
3280 assetId = contents[index].AssetVersionId
3281 else
3282 assetId = contents[index].AssetId
3283 end
3284 setInsertButtonImageBehavior(insertButtons[index], true, contents[index].Name, assetId)
3285 else
3286 break
3287 end
3288 else
3289 break
3290 end
3291 indexCopy = index
3292 end
3293 end
3294
3295 local function setSetIndex()
3296 Data.Category[Data.CurrentCategory].Index = 0
3297
3298 rows = 7
3299 columns = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.X/buttonWidth)
3300
3301 contents = Data.Category[Data.CurrentCategory].Contents
3302 if contents then
3303 -- remove our buttons and their connections
3304 for i = 1, #insertButtons do
3305 insertButtons[i]:remove()
3306 end
3307 for i = 1, #insertButtonCons do
3308 if insertButtonCons[i] then insertButtonCons[i]:disconnect() end
3309 end
3310 insertButtonCons = {}
3311 insertButtons = {}
3312
3313 arrayPosition = 1
3314 loadSectionOfItems(setGui, rows, columns)
3315 end
3316 end
3317
3318 local function selectSet(button, setName, setId, setIndex)
3319 if button and Data.Category[Data.CurrentCategory] ~= nil then
3320 if button ~= Data.Category[Data.CurrentCategory].Button then
3321 Data.Category[Data.CurrentCategory].Button = button
3322
3323 if SetCache[setId] == nil then
3324 SetCache[setId] = game:GetService("InsertService"):GetCollection(setId)
3325 end
3326 Data.Category[Data.CurrentCategory].Contents = SetCache[setId]
3327
3328 Data.Category[Data.CurrentCategory].SetName = setName
3329 Data.Category[Data.CurrentCategory].SetId = setId
3330 end
3331 setSetIndex()
3332 end
3333 end
3334
3335 local function selectCategoryPage(buttons, page)
3336 if buttons ~= Data.CurrentCategory then
3337 if Data.CurrentCategory then
3338 for key, button in pairs(Data.CurrentCategory) do
3339 button.Visible = false
3340 end
3341 end
3342
3343 Data.CurrentCategory = buttons
3344 if Data.Category[Data.CurrentCategory] == nil then
3345 Data.Category[Data.CurrentCategory] = {}
3346 if #buttons > 0 then
3347 selectSet(buttons[1], buttons[1].SetName.Value, buttons[1].SetId.Value, 0)
3348 end
3349 else
3350 Data.Category[Data.CurrentCategory].Button = nil
3351 selectSet(Data.Category[Data.CurrentCategory].ButtonFrame, Data.Category[Data.CurrentCategory].SetName, Data.Category[Data.CurrentCategory].SetId, Data.Category[Data.CurrentCategory].Index)
3352 end
3353 end
3354 end
3355
3356 local function selectCategory(category)
3357 selectCategoryPage(category, 0)
3358 end
3359
3360 local function resetAllSetButtonSelection()
3361 local setButtons = setGui.SetPanel.Sets.SetsLists:GetChildren()
3362 for i = 1, #setButtons do
3363 if setButtons[i]:IsA("TextButton") then
3364 setButtons[i].Selected = false
3365 setButtons[i].BackgroundTransparency = 1
3366 setButtons[i].TextColor3 = Color3.new(1,1,1)
3367 setButtons[i].BackgroundColor3 = Color3.new(1,1,1)
3368 end
3369 end
3370 end
3371
3372 local function populateSetsFrame()
3373 local currRow = 0
3374 for i = 1, #userCategoryButtons do
3375 local button = userCategoryButtons[i]
3376 button.Visible = true
3377 button.Position = UDim2.new(0,5,0,currRow * button.Size.Y.Offset)
3378 button.Parent = setGui.SetPanel.Sets.SetsLists
3379
3380 if i == 1 then -- we will have this selected by default, so show it
3381 button.Selected = true
3382 button.BackgroundColor3 = Color3.new(0,204/255,0)
3383 button.TextColor3 = Color3.new(0,0,0)
3384 button.BackgroundTransparency = 0
3385 end
3386
3387 button.MouseEnter:connect(function()
3388 if not button.Selected then
3389 button.BackgroundTransparency = 0
3390 button.TextColor3 = Color3.new(0,0,0)
3391 end
3392 end)
3393 button.MouseLeave:connect(function()
3394 if not button.Selected then
3395 button.BackgroundTransparency = 1
3396 button.TextColor3 = Color3.new(1,1,1)
3397 end
3398 end)
3399 button.MouseButton1Click:connect(function()
3400 resetAllSetButtonSelection()
3401 button.Selected = not button.Selected
3402 button.BackgroundColor3 = Color3.new(0,204/255,0)
3403 button.TextColor3 = Color3.new(0,0,0)
3404 button.BackgroundTransparency = 0
3405 selectSet(button, button.Text, userCategoryButtons[i].SetId.Value, 0)
3406 end)
3407
3408 currRow = currRow + 1
3409 end
3410
3411 local buttons = setGui.SetPanel.Sets.SetsLists:GetChildren()
3412
3413 -- set first category as loaded for default
3414 if buttons then
3415 for i = 1, #buttons do
3416 if buttons[i]:IsA("TextButton") then
3417 selectSet(buttons[i], buttons[i].Text, userCategoryButtons[i].SetId.Value, 0)
3418 selectCategory(userCategoryButtons)
3419 break
3420 end
3421 end
3422 end
3423 end
3424
3425 setGui = createSetGui()
3426 waterGui, waterTypeChangedEvent = createWaterGui()
3427 waterGui.Position = UDim2.new(0,55,0,0)
3428 waterGui.Parent = setGui
3429 setGui.Changed:connect(function(prop) -- this resizes the preview image to always be the right size
3430 if prop == "AbsoluteSize" then
3431 handleResize()
3432 setSetIndex()
3433 end
3434 end)
3435
3436 local scrollFrame, controlFrame = t.CreateTrueScrollingFrame()
3437 scrollFrame.Size = UDim2.new(0.54,0,0.85,0)
3438 scrollFrame.Position = UDim2.new(0.24,0,0.085,0)
3439 scrollFrame.Name = "ItemsFrame"
3440 scrollFrame.ZIndex = 6
3441 scrollFrame.Parent = setGui.SetPanel
3442 scrollFrame.BackgroundTransparency = 1
3443
3444 drillDownSetZIndex(controlFrame,7)
3445
3446 controlFrame.Parent = setGui.SetPanel
3447 controlFrame.Position = UDim2.new(0.76, 5, 0, 0)
3448
3449 local debounce = false
3450 controlFrame.ScrollBottom.Changed:connect(function(prop)
3451 if controlFrame.ScrollBottom.Value == true then
3452 if debounce then return end
3453 debounce = true
3454 loadSectionOfItems(setGui, rows, columns)
3455 debounce = false
3456 end
3457 end)
3458
3459 local userData = {}
3460 for id = 1, #userIdsForSets do
3461 local newUserData = game:GetService("InsertService"):GetUserSets(userIdsForSets[id])
3462 if newUserData and #newUserData > 2 then
3463 -- start at #3 to skip over My Decals and My Models for each account
3464 for category = 3, #newUserData do
3465 if newUserData[category].Name == "High Scalability" then -- we want high scalability parts to show first
3466 table.insert(userData,1,newUserData[category])
3467 else
3468 table.insert(userData, newUserData[category])
3469 end
3470 end
3471 end
3472
3473 end
3474 if userData then
3475 userCategoryButtons = processCategory(userData)
3476 end
3477
3478 rows = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.Y/buttonHeight)
3479 columns = math.floor(setGui.SetPanel.ItemsFrame.AbsoluteSize.X/buttonWidth)
3480
3481 populateSetsFrame()
3482
3483 setGui.SetPanel.CancelButton.MouseButton1Click:connect(function()
3484 setGui.SetPanel.Visible = false
3485 if dialogClosed then dialogClosed() end
3486 end)
3487
3488 local setVisibilityFunction = function(visible)
3489 if visible then
3490 setGui.SetPanel.Visible = true
3491 else
3492 setGui.SetPanel.Visible = false
3493 end
3494 end
3495
3496 local getVisibilityFunction = function()
3497 if setGui then
3498 if setGui:FindFirstChild("SetPanel") then
3499 return setGui.SetPanel.Visible
3500 end
3501 end
3502
3503 return false
3504 end
3505
3506 return setGui, setVisibilityFunction, getVisibilityFunction, waterTypeChangedEvent
3507end
3508
3509t.CreateTerrainMaterialSelector = function(size,position)
3510 local terrainMaterialSelectionChanged = Instance.new("BindableEvent")
3511 terrainMaterialSelectionChanged.Name = "TerrainMaterialSelectionChanged"
3512
3513 local selectedButton = nil
3514
3515 local frame = Instance.new("Frame")
3516 frame.Name = "TerrainMaterialSelector"
3517 if size then
3518 frame.Size = size
3519 else
3520 frame.Size = UDim2.new(0, 245, 0, 230)
3521 end
3522 if position then
3523 frame.Position = position
3524 end
3525 frame.BorderSizePixel = 0
3526 frame.BackgroundColor3 = Color3.new(0,0,0)
3527 frame.Active = true
3528
3529 terrainMaterialSelectionChanged.Parent = frame
3530
3531 local waterEnabled = true -- todo: turn this on when water is ready
3532
3533 local materialToImageMap = {}
3534 local materialNames = {"Grass", "Sand", "Brick", "Granite", "Asphalt", "Iron", "Aluminum", "Gold", "Plank", "Log", "Gravel", "Cinder Block", "Stone Wall", "Concrete", "Plastic (red)", "Plastic (blue)"}
3535 if waterEnabled then
3536 table.insert(materialNames,"Water")
3537 end
3538 local currentMaterial = 1
3539
3540 function getEnumFromName(choice)
3541 if choice == "Grass" then return 1 end
3542 if choice == "Sand" then return 2 end
3543 if choice == "Erase" then return 0 end
3544 if choice == "Brick" then return 3 end
3545 if choice == "Granite" then return 4 end
3546 if choice == "Asphalt" then return 5 end
3547 if choice == "Iron" then return 6 end
3548 if choice == "Aluminum" then return 7 end
3549 if choice == "Gold" then return 8 end
3550 if choice == "Plank" then return 9 end
3551 if choice == "Log" then return 10 end
3552 if choice == "Gravel" then return 11 end
3553 if choice == "Cinder Block" then return 12 end
3554 if choice == "Stone Wall" then return 13 end
3555 if choice == "Concrete" then return 14 end
3556 if choice == "Plastic (red)" then return 15 end
3557 if choice == "Plastic (blue)" then return 16 end
3558 if choice == "Water" then return 17 end
3559 end
3560
3561 function getNameFromEnum(choice)
3562 if choice == Enum.CellMaterial.Grass or choice == 1 then return "Grass"end
3563 if choice == Enum.CellMaterial.Sand or choice == 2 then return "Sand" end
3564 if choice == Enum.CellMaterial.Empty or choice == 0 then return "Erase" end
3565 if choice == Enum.CellMaterial.Brick or choice == 3 then return "Brick" end
3566 if choice == Enum.CellMaterial.Granite or choice == 4 then return "Granite" end
3567 if choice == Enum.CellMaterial.Asphalt or choice == 5 then return "Asphalt" end
3568 if choice == Enum.CellMaterial.Iron or choice == 6 then return "Iron" end
3569 if choice == Enum.CellMaterial.Aluminum or choice == 7 then return "Aluminum" end
3570 if choice == Enum.CellMaterial.Gold or choice == 8 then return "Gold" end
3571 if choice == Enum.CellMaterial.WoodPlank or choice == 9 then return "Plank" end
3572 if choice == Enum.CellMaterial.WoodLog or choice == 10 then return "Log" end
3573 if choice == Enum.CellMaterial.Gravel or choice == 11 then return "Gravel" end
3574 if choice == Enum.CellMaterial.CinderBlock or choice == 12 then return "Cinder Block" end
3575 if choice == Enum.CellMaterial.MossyStone or choice == 13 then return "Stone Wall" end
3576 if choice == Enum.CellMaterial.Cement or choice == 14 then return "Concrete" end
3577 if choice == Enum.CellMaterial.RedPlastic or choice == 15 then return "Plastic (red)" end
3578 if choice == Enum.CellMaterial.BluePlastic or choice == 16 then return "Plastic (blue)" end
3579
3580 if waterEnabled then
3581 if choice == Enum.CellMaterial.Water or choice == 17 then return "Water" end
3582 end
3583 end
3584
3585
3586 local function updateMaterialChoice(choice)
3587 currentMaterial = getEnumFromName(choice)
3588 terrainMaterialSelectionChanged:Fire(currentMaterial)
3589 end
3590
3591 -- we so need a better way to do this
3592 for i,v in pairs(materialNames) do
3593 materialToImageMap[v] = {}
3594 if v == "Grass" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=56563112"
3595 elseif v == "Sand" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=62356652"
3596 elseif v == "Brick" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=65961537"
3597 elseif v == "Granite" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532153"
3598 elseif v == "Asphalt" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532038"
3599 elseif v == "Iron" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532093"
3600 elseif v == "Aluminum" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531995"
3601 elseif v == "Gold" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532118"
3602 elseif v == "Plastic (red)" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531848"
3603 elseif v == "Plastic (blue)" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531924"
3604 elseif v == "Plank" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532015"
3605 elseif v == "Log" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532051"
3606 elseif v == "Gravel" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532206"
3607 elseif v == "Cinder Block" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532103"
3608 elseif v == "Stone Wall" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67531804"
3609 elseif v == "Concrete" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=67532059"
3610 elseif v == "Water" then materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=81407474"
3611 else materialToImageMap[v].Regular = "https://www.roblox.com/asset/?id=66887593" -- fill in the rest here!!
3612 end
3613 end
3614
3615 local scrollFrame, scrollUp, scrollDown, recalculateScroll = t.CreateScrollingFrame(nil,"grid")
3616 scrollFrame.Size = UDim2.new(0.85,0,1,0)
3617 scrollFrame.Position = UDim2.new(0,0,0,0)
3618 scrollFrame.Parent = frame
3619
3620 scrollUp.Parent = frame
3621 scrollUp.Visible = true
3622 scrollUp.Position = UDim2.new(1,-19,0,0)
3623
3624 scrollDown.Parent = frame
3625 scrollDown.Visible = true
3626 scrollDown.Position = UDim2.new(1,-19,1,-17)
3627
3628 local function goToNewMaterial(buttonWrap, materialName)
3629 updateMaterialChoice(materialName)
3630 buttonWrap.BackgroundTransparency = 0
3631 selectedButton.BackgroundTransparency = 1
3632 selectedButton = buttonWrap
3633 end
3634
3635 local function createMaterialButton(name)
3636 local buttonWrap = Instance.new("TextButton")
3637 buttonWrap.Text = ""
3638 buttonWrap.Size = UDim2.new(0,32,0,32)
3639 buttonWrap.BackgroundColor3 = Color3.new(1,1,1)
3640 buttonWrap.BorderSizePixel = 0
3641 buttonWrap.BackgroundTransparency = 1
3642 buttonWrap.AutoButtonColor = false
3643 buttonWrap.Name = tostring(name)
3644
3645 local imageButton = Instance.new("ImageButton")
3646 imageButton.AutoButtonColor = false
3647 imageButton.BackgroundTransparency = 1
3648 imageButton.Size = UDim2.new(0,30,0,30)
3649 imageButton.Position = UDim2.new(0,1,0,1)
3650 imageButton.Name = tostring(name)
3651 imageButton.Parent = buttonWrap
3652 imageButton.Image = materialToImageMap[name].Regular
3653
3654 local enumType = Instance.new("NumberValue")
3655 enumType.Name = "EnumType"
3656 enumType.Parent = buttonWrap
3657 enumType.Value = 0
3658
3659 imageButton.MouseEnter:connect(function()
3660 buttonWrap.BackgroundTransparency = 0
3661 end)
3662 imageButton.MouseLeave:connect(function()
3663 if selectedButton ~= buttonWrap then
3664 buttonWrap.BackgroundTransparency = 1
3665 end
3666 end)
3667 imageButton.MouseButton1Click:connect(function()
3668 if selectedButton ~= buttonWrap then
3669 goToNewMaterial(buttonWrap, tostring(name))
3670 end
3671 end)
3672
3673 return buttonWrap
3674 end
3675
3676 for i = 1, #materialNames do
3677 local imageButton = createMaterialButton(materialNames[i])
3678
3679 if materialNames[i] == "Grass" then -- always start with grass as the default
3680 selectedButton = imageButton
3681 imageButton.BackgroundTransparency = 0
3682 end
3683
3684 imageButton.Parent = scrollFrame
3685 end
3686
3687 local forceTerrainMaterialSelection = function(newMaterialType)
3688 if not newMaterialType then return end
3689 if currentMaterial == newMaterialType then return end
3690
3691 local matName = getNameFromEnum(newMaterialType)
3692 local buttons = scrollFrame:GetChildren()
3693 for i = 1, #buttons do
3694 if buttons[i].Name == "Plastic (blue)" and matName == "Plastic (blue)" then goToNewMaterial(buttons[i],matName) return end
3695 if buttons[i].Name == "Plastic (red)" and matName == "Plastic (red)" then goToNewMaterial(buttons[i],matName) return end
3696 if string.find(buttons[i].Name, matName) then
3697 goToNewMaterial(buttons[i],matName)
3698 return
3699 end
3700 end
3701 end
3702
3703 frame.Changed:connect(function ( prop )
3704 if prop == "AbsoluteSize" then
3705 recalculateScroll()
3706 end
3707 end)
3708
3709 recalculateScroll()
3710 return frame, terrainMaterialSelectionChanged, forceTerrainMaterialSelection
3711end
3712
3713t.CreateLoadingFrame = function(name,size,position)
3714 game:GetService("ContentProvider"):Preload("https://www.roblox.com/asset/?id=35238053")
3715
3716 local loadingFrame = Instance.new("Frame")
3717 loadingFrame.Name = "LoadingFrame"
3718 loadingFrame.Style = Enum.FrameStyle.RobloxRound
3719
3720 if size then loadingFrame.Size = size
3721 else loadingFrame.Size = UDim2.new(0,300,0,160) end
3722 if position then loadingFrame.Position = position
3723 else loadingFrame.Position = UDim2.new(0.5, -150, 0.5,-80) end
3724
3725 local loadingBar = Instance.new("Frame")
3726 loadingBar.Name = "LoadingBar"
3727 loadingBar.BackgroundColor3 = Color3.new(0,0,0)
3728 loadingBar.BorderColor3 = Color3.new(79/255,79/255,79/255)
3729 loadingBar.Position = UDim2.new(0,0,0,41)
3730 loadingBar.Size = UDim2.new(1,0,0,30)
3731 loadingBar.Parent = loadingFrame
3732
3733 local loadingGreenBar = Instance.new("ImageLabel")
3734 loadingGreenBar.Name = "LoadingGreenBar"
3735 loadingGreenBar.Image = "https://www.roblox.com/asset/?id=35238053"
3736 loadingGreenBar.Position = UDim2.new(0,0,0,0)
3737 loadingGreenBar.Size = UDim2.new(0,0,1,0)
3738 loadingGreenBar.Visible = false
3739 loadingGreenBar.Parent = loadingBar
3740
3741 local loadingPercent = Instance.new("TextLabel")
3742 loadingPercent.Name = "LoadingPercent"
3743 loadingPercent.BackgroundTransparency = 1
3744 loadingPercent.Position = UDim2.new(0,0,1,0)
3745 loadingPercent.Size = UDim2.new(1,0,0,14)
3746 loadingPercent.Font = Enum.Font.Arial
3747 loadingPercent.Text = "0%"
3748 loadingPercent.FontSize = Enum.FontSize.Size14
3749 loadingPercent.TextColor3 = Color3.new(1,1,1)
3750 loadingPercent.Parent = loadingBar
3751
3752 local cancelButton = Instance.new("TextButton")
3753 cancelButton.Name = "CancelButton"
3754 cancelButton.Position = UDim2.new(0.5,-60,1,-40)
3755 cancelButton.Size = UDim2.new(0,120,0,40)
3756 cancelButton.Font = Enum.Font.Arial
3757 cancelButton.FontSize = Enum.FontSize.Size18
3758 cancelButton.TextColor3 = Color3.new(1,1,1)
3759 cancelButton.Text = "Cancel"
3760 cancelButton.Style = Enum.ButtonStyle.RobloxButton
3761 cancelButton.Parent = loadingFrame
3762
3763 local loadingName = Instance.new("TextLabel")
3764 loadingName.Name = "loadingName"
3765 loadingName.BackgroundTransparency = 1
3766 loadingName.Size = UDim2.new(1,0,0,18)
3767 loadingName.Position = UDim2.new(0,0,0,2)
3768 loadingName.Font = Enum.Font.Arial
3769 loadingName.Text = name
3770 loadingName.TextColor3 = Color3.new(1,1,1)
3771 loadingName.TextStrokeTransparency = 1
3772 loadingName.FontSize = Enum.FontSize.Size18
3773 loadingName.Parent = loadingFrame
3774
3775 local cancelButtonClicked = Instance.new("BindableEvent")
3776 cancelButtonClicked.Name = "CancelButtonClicked"
3777 cancelButtonClicked.Parent = cancelButton
3778 cancelButton.MouseButton1Click:connect(function()
3779 cancelButtonClicked:Fire()
3780 end)
3781
3782 local updateLoadingGuiPercent = function(percent, tweenAction, tweenLength)
3783 if percent and type(percent) ~= "number" then
3784 error("updateLoadingGuiPercent expects number as argument, got",type(percent),"instead")
3785 end
3786
3787 local newSize = nil
3788 if percent < 0 then
3789 newSize = UDim2.new(0,0,1,0)
3790 elseif percent > 1 then
3791 newSize = UDim2.new(1,0,1,0)
3792 else
3793 newSize = UDim2.new(percent,0,1,0)
3794 end
3795
3796 if tweenAction then
3797 if not tweenLength then
3798 error("updateLoadingGuiPercent is set to tween new percentage, but got no tween time length! Please pass this in as third argument")
3799 end
3800
3801 if (newSize.X.Scale > 0) then
3802 loadingGreenBar.Visible = true
3803 loadingGreenBar:TweenSize( newSize,
3804 Enum.EasingDirection.Out,
3805 Enum.EasingStyle.Quad,
3806 tweenLength,
3807 true)
3808 else
3809 loadingGreenBar:TweenSize( newSize,
3810 Enum.EasingDirection.Out,
3811 Enum.EasingStyle.Quad,
3812 tweenLength,
3813 true,
3814 function()
3815 if (newSize.X.Scale < 0) then
3816 loadingGreenBar.Visible = false
3817 end
3818 end)
3819 end
3820
3821 else
3822 loadingGreenBar.Size = newSize
3823 loadingGreenBar.Visible = (newSize.X.Scale > 0)
3824 end
3825 end
3826
3827 loadingGreenBar.Changed:connect(function(prop)
3828 if prop == "Size" then
3829 loadingPercent.Text = tostring( math.ceil(loadingGreenBar.Size.X.Scale * 100) ) .. "%"
3830 end
3831 end)
3832
3833 return loadingFrame, updateLoadingGuiPercent, cancelButtonClicked
3834end
3835
3836t.CreatePluginFrame = function (name,size,position,scrollable,parent)
3837 local function createMenuButton(size,position,text,fontsize,name,parent)
3838 local button = Instance.new("TextButton",parent)
3839 button.AutoButtonColor = false
3840 button.Name = name
3841 button.BackgroundTransparency = 1
3842 button.Position = position
3843 button.Size = size
3844 button.Font = Enum.Font.ArialBold
3845 button.FontSize = fontsize
3846 button.Text = text
3847 button.TextColor3 = Color3.new(1,1,1)
3848 button.BorderSizePixel = 0
3849 button.BackgroundColor3 = Color3.new(20/255,20/255,20/255)
3850
3851 button.MouseEnter:connect(function ( )
3852 if button.Selected then return end
3853 button.BackgroundTransparency = 0
3854 end)
3855 button.MouseLeave:connect(function ( )
3856 if button.Selected then return end
3857 button.BackgroundTransparency = 1
3858 end)
3859
3860 return button
3861
3862 end
3863
3864 local dragBar = Instance.new("Frame",parent)
3865 dragBar.Name = tostring(name) .. "DragBar"
3866 dragBar.BackgroundColor3 = Color3.new(39/255,39/255,39/255)
3867 dragBar.BorderColor3 = Color3.new(0,0,0)
3868 if size then
3869 dragBar.Size = UDim2.new(size.X.Scale,size.X.Offset,0,20) + UDim2.new(0,20,0,0)
3870 else
3871 dragBar.Size = UDim2.new(0,183,0,20)
3872 end
3873 if position then
3874 dragBar.Position = position
3875 end
3876 dragBar.Active = true
3877 dragBar.Draggable = true
3878 --dragBar.Visible = false
3879 dragBar.MouseEnter:connect(function ( )
3880 dragBar.BackgroundColor3 = Color3.new(49/255,49/255,49/255)
3881 end)
3882 dragBar.MouseLeave:connect(function ( )
3883 dragBar.BackgroundColor3 = Color3.new(39/255,39/255,39/255)
3884 end)
3885
3886 -- plugin name label
3887 local pluginNameLabel = Instance.new("TextLabel",dragBar)
3888 pluginNameLabel.Name = "BarNameLabel"
3889 pluginNameLabel.Text = " " .. tostring(name)
3890 pluginNameLabel.TextColor3 = Color3.new(1,1,1)
3891 pluginNameLabel.TextStrokeTransparency = 0
3892 pluginNameLabel.Size = UDim2.new(1,0,1,0)
3893 pluginNameLabel.Font = Enum.Font.ArialBold
3894 pluginNameLabel.FontSize = Enum.FontSize.Size18
3895 pluginNameLabel.TextXAlignment = Enum.TextXAlignment.Left
3896 pluginNameLabel.BackgroundTransparency = 1
3897
3898 -- close button
3899 local closeButton = createMenuButton(UDim2.new(0,15,0,17),UDim2.new(1,-16,0.5,-8),"X",Enum.FontSize.Size14,"CloseButton",dragBar)
3900 local closeEvent = Instance.new("BindableEvent")
3901 closeEvent.Name = "CloseEvent"
3902 closeEvent.Parent = closeButton
3903 closeButton.MouseButton1Click:connect(function ()
3904 closeEvent:Fire()
3905 closeButton.BackgroundTransparency = 1
3906 end)
3907
3908 -- help button
3909 local helpButton = createMenuButton(UDim2.new(0,15,0,17),UDim2.new(1,-51,0.5,-8),"?",Enum.FontSize.Size14,"HelpButton",dragBar)
3910 local helpFrame = Instance.new("Frame",dragBar)
3911 helpFrame.Name = "HelpFrame"
3912 helpFrame.BackgroundColor3 = Color3.new(0,0,0)
3913 helpFrame.Size = UDim2.new(0,300,0,552)
3914 helpFrame.Position = UDim2.new(1,5,0,0)
3915 helpFrame.Active = true
3916 helpFrame.BorderSizePixel = 0
3917 helpFrame.Visible = false
3918
3919 helpButton.MouseButton1Click:connect(function( )
3920 helpFrame.Visible = not helpFrame.Visible
3921 if helpFrame.Visible then
3922 helpButton.Selected = true
3923 helpButton.BackgroundTransparency = 0
3924 local screenGui = getLayerCollectorAncestor(helpFrame)
3925 if screenGui then
3926 if helpFrame.AbsolutePosition.X + helpFrame.AbsoluteSize.X > screenGui.AbsoluteSize.X then --position on left hand side
3927 helpFrame.Position = UDim2.new(0,-5 - helpFrame.AbsoluteSize.X,0,0)
3928 else -- position on right hand side
3929 helpFrame.Position = UDim2.new(1,5,0,0)
3930 end
3931 else
3932 helpFrame.Position = UDim2.new(1,5,0,0)
3933 end
3934 else
3935 helpButton.Selected = false
3936 helpButton.BackgroundTransparency = 1
3937 end
3938 end)
3939
3940 local minimizeButton = createMenuButton(UDim2.new(0,16,0,17),UDim2.new(1,-34,0.5,-8),"-",Enum.FontSize.Size14,"MinimizeButton",dragBar)
3941 minimizeButton.TextYAlignment = Enum.TextYAlignment.Top
3942
3943 local minimizeFrame = Instance.new("Frame",dragBar)
3944 minimizeFrame.Name = "MinimizeFrame"
3945 minimizeFrame.BackgroundColor3 = Color3.new(73/255,73/255,73/255)
3946 minimizeFrame.BorderColor3 = Color3.new(0,0,0)
3947 minimizeFrame.Position = UDim2.new(0,0,1,0)
3948 if size then
3949 minimizeFrame.Size = UDim2.new(size.X.Scale,size.X.Offset,0,50) + UDim2.new(0,20,0,0)
3950 else
3951 minimizeFrame.Size = UDim2.new(0,183,0,50)
3952 end
3953 minimizeFrame.Visible = false
3954
3955 local minimizeBigButton = Instance.new("TextButton",minimizeFrame)
3956 minimizeBigButton.Position = UDim2.new(0.5,-50,0.5,-20)
3957 minimizeBigButton.Name = "MinimizeButton"
3958 minimizeBigButton.Size = UDim2.new(0,100,0,40)
3959 minimizeBigButton.Style = Enum.ButtonStyle.RobloxButton
3960 minimizeBigButton.Font = Enum.Font.ArialBold
3961 minimizeBigButton.FontSize = Enum.FontSize.Size18
3962 minimizeBigButton.TextColor3 = Color3.new(1,1,1)
3963 minimizeBigButton.Text = "Show"
3964
3965 local separatingLine = Instance.new("Frame",dragBar)
3966 separatingLine.Name = "SeparatingLine"
3967 separatingLine.BackgroundColor3 = Color3.new(115/255,115/255,115/255)
3968 separatingLine.BorderSizePixel = 0
3969 separatingLine.Position = UDim2.new(1,-18,0.5,-7)
3970 separatingLine.Size = UDim2.new(0,1,0,14)
3971
3972 local otherSeparatingLine = separatingLine:clone()
3973 otherSeparatingLine.Position = UDim2.new(1,-35,0.5,-7)
3974 otherSeparatingLine.Parent = dragBar
3975
3976 local widgetContainer = Instance.new("Frame",dragBar)
3977 widgetContainer.Name = "WidgetContainer"
3978 widgetContainer.BackgroundTransparency = 1
3979 widgetContainer.Position = UDim2.new(0,0,1,0)
3980 widgetContainer.BorderColor3 = Color3.new(0,0,0)
3981 if not scrollable then
3982 widgetContainer.BackgroundTransparency = 0
3983 widgetContainer.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
3984 end
3985
3986 if size then
3987 if scrollable then
3988 widgetContainer.Size = size
3989 else
3990 widgetContainer.Size = UDim2.new(0,dragBar.AbsoluteSize.X,size.Y.Scale,size.Y.Offset)
3991 end
3992 else
3993 if scrollable then
3994 widgetContainer.Size = UDim2.new(0,163,0,400)
3995 else
3996 widgetContainer.Size = UDim2.new(0,dragBar.AbsoluteSize.X,0,400)
3997 end
3998 end
3999 if position then
4000 widgetContainer.Position = position + UDim2.new(0,0,0,20)
4001 end
4002
4003 local frame,control,verticalDragger = nil
4004 if scrollable then
4005 --frame for widgets
4006 frame,control = t.CreateTrueScrollingFrame()
4007 frame.Size = UDim2.new(1, 0, 1, 0)
4008 frame.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
4009 frame.BorderColor3 = Color3.new(0,0,0)
4010 frame.Active = true
4011 frame.Parent = widgetContainer
4012 control.Parent = dragBar
4013 control.BackgroundColor3 = Color3.new(72/255,72/255,72/255)
4014 control.BorderSizePixel = 0
4015 control.BackgroundTransparency = 0
4016 control.Position = UDim2.new(1,-21,1,1)
4017 if size then
4018 control.Size = UDim2.new(0,21,size.Y.Scale,size.Y.Offset)
4019 else
4020 control.Size = UDim2.new(0,21,0,400)
4021 end
4022 control:FindFirstChild("ScrollDownButton").Position = UDim2.new(0,0,1,-20)
4023
4024 local fakeLine = Instance.new("Frame",control)
4025 fakeLine.Name = "FakeLine"
4026 fakeLine.BorderSizePixel = 0
4027 fakeLine.BackgroundColor3 = Color3.new(0,0,0)
4028 fakeLine.Size = UDim2.new(0,1,1,1)
4029 fakeLine.Position = UDim2.new(1,0,0,0)
4030
4031 verticalDragger = Instance.new("TextButton",widgetContainer)
4032 verticalDragger.ZIndex = 2
4033 verticalDragger.AutoButtonColor = false
4034 verticalDragger.Name = "VerticalDragger"
4035 verticalDragger.BackgroundColor3 = Color3.new(50/255,50/255,50/255)
4036 verticalDragger.BorderColor3 = Color3.new(0,0,0)
4037 verticalDragger.Size = UDim2.new(1,20,0,20)
4038 verticalDragger.Position = UDim2.new(0,0,1,0)
4039 verticalDragger.Active = true
4040 verticalDragger.Text = ""
4041
4042 local scrubFrame = Instance.new("Frame",verticalDragger)
4043 scrubFrame.Name = "ScrubFrame"
4044 scrubFrame.BackgroundColor3 = Color3.new(1,1,1)
4045 scrubFrame.BorderSizePixel = 0
4046 scrubFrame.Position = UDim2.new(0.5,-5,0.5,0)
4047 scrubFrame.Size = UDim2.new(0,10,0,1)
4048 scrubFrame.ZIndex = 5
4049 local scrubTwo = scrubFrame:clone()
4050 scrubTwo.Position = UDim2.new(0.5,-5,0.5,-2)
4051 scrubTwo.Parent = verticalDragger
4052 local scrubThree = scrubFrame:clone()
4053 scrubThree.Position = UDim2.new(0.5,-5,0.5,2)
4054 scrubThree.Parent = verticalDragger
4055
4056 local areaSoak = Instance.new("TextButton",getLayerCollectorAncestor(parent))
4057 areaSoak.Name = "AreaSoak"
4058 areaSoak.Size = UDim2.new(1,0,1,0)
4059 areaSoak.BackgroundTransparency = 1
4060 areaSoak.BorderSizePixel = 0
4061 areaSoak.Text = ""
4062 areaSoak.ZIndex = 10
4063 areaSoak.Visible = false
4064 areaSoak.Active = true
4065
4066 local draggingVertical = false
4067 local startYPos = nil
4068 verticalDragger.MouseEnter:connect(function ()
4069 verticalDragger.BackgroundColor3 = Color3.new(60/255,60/255,60/255)
4070 end)
4071 verticalDragger.MouseLeave:connect(function ()
4072 verticalDragger.BackgroundColor3 = Color3.new(50/255,50/255,50/255)
4073 end)
4074 verticalDragger.MouseButton1Down:connect(function(x,y)
4075 draggingVertical = true
4076 areaSoak.Visible = true
4077 startYPos = y
4078 end)
4079 areaSoak.MouseButton1Up:connect(function ( )
4080 draggingVertical = false
4081 areaSoak.Visible = false
4082 end)
4083 areaSoak.MouseMoved:connect(function(x,y)
4084 if not draggingVertical then return end
4085
4086 local yDelta = y - startYPos
4087 if not control.ScrollDownButton.Visible and yDelta > 0 then
4088 return
4089 end
4090
4091 if (widgetContainer.Size.Y.Offset + yDelta) < 150 then
4092 widgetContainer.Size = UDim2.new(widgetContainer.Size.X.Scale, widgetContainer.Size.X.Offset,widgetContainer.Size.Y.Scale,150)
4093 control.Size = UDim2.new (0,21,0,150)
4094 return
4095 end
4096
4097 startYPos = y
4098
4099 if widgetContainer.Size.Y.Offset + yDelta >= 0 then
4100 widgetContainer.Size = UDim2.new(widgetContainer.Size.X.Scale, widgetContainer.Size.X.Offset,widgetContainer.Size.Y.Scale,widgetContainer.Size.Y.Offset + yDelta)
4101 control.Size = UDim2.new(0,21,0,control.Size.Y.Offset + yDelta )
4102 end
4103 end)
4104 end
4105
4106 local function switchMinimize()
4107 minimizeFrame.Visible = not minimizeFrame.Visible
4108 if scrollable then
4109 frame.Visible = not frame.Visible
4110 verticalDragger.Visible = not verticalDragger.Visible
4111 control.Visible = not control.Visible
4112 else
4113 widgetContainer.Visible = not widgetContainer.Visible
4114 end
4115
4116 if minimizeFrame.Visible then
4117 minimizeButton.Text = "+"
4118 else
4119 minimizeButton.Text = "-"
4120 end
4121 end
4122
4123 minimizeBigButton.MouseButton1Click:connect(function ( )
4124 switchMinimize()
4125 end)
4126
4127 minimizeButton.MouseButton1Click:connect(function( )
4128 switchMinimize()
4129 end)
4130
4131 if scrollable then
4132 return dragBar, frame, helpFrame, closeEvent
4133 else
4134 return dragBar, widgetContainer, helpFrame, closeEvent
4135 end
4136end
4137
4138t.Help =
4139 function(funcNameOrFunc)
4140 --input argument can be a string or a function. Should return a description (of arguments and expected side effects)
4141 if funcNameOrFunc == "CreatePropertyDropDownMenu" or funcNameOrFunc == t.CreatePropertyDropDownMenu then
4142 return "Function CreatePropertyDropDownMenu. " ..
4143 "Arguments: (instance, propertyName, enumType). " ..
4144 "Side effect: returns a container with a drop-down-box that is linked to the 'property' field of 'instance' which is of type 'enumType'"
4145 end
4146 if funcNameOrFunc == "CreateDropDownMenu" or funcNameOrFunc == t.CreateDropDownMenu then
4147 return "Function CreateDropDownMenu. " ..
4148 "Arguments: (items, onItemSelected). " ..
4149 "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"
4150 end
4151 if funcNameOrFunc == "CreateMessageDialog" or funcNameOrFunc == t.CreateMessageDialog then
4152 return "Function CreateMessageDialog. " ..
4153 "Arguments: (title, message, buttons). " ..
4154 "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"
4155 end
4156 if funcNameOrFunc == "CreateStyledMessageDialog" or funcNameOrFunc == t.CreateStyledMessageDialog then
4157 return "Function CreateStyledMessageDialog. " ..
4158 "Arguments: (title, message, style, buttons). " ..
4159 "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"
4160 end
4161 if funcNameOrFunc == "GetFontHeight" or funcNameOrFunc == t.GetFontHeight then
4162 return "Function GetFontHeight. " ..
4163 "Arguments: (font, fontSize). " ..
4164 "Side effect: returns the size in pixels of the given font + fontSize"
4165 end
4166 if funcNameOrFunc == "LayoutGuiObjects" or funcNameOrFunc == t.LayoutGuiObjects then
4167
4168 end
4169 if funcNameOrFunc == "CreateScrollingFrame" or funcNameOrFunc == t.CreateScrollingFrame then
4170 return "Function CreateScrollingFrame. " ..
4171 "Arguments: (orderList, style) " ..
4172 "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)"
4173 end
4174 if funcNameOrFunc == "CreateTrueScrollingFrame" or funcNameOrFunc == t.CreateTrueScrollingFrame then
4175 return "Function CreateTrueScrollingFrame. " ..
4176 "Arguments: (nil) " ..
4177 "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."
4178 end
4179 if funcNameOrFunc == "AutoTruncateTextObject" or funcNameOrFunc == t.AutoTruncateTextObject then
4180 return "Function AutoTruncateTextObject. " ..
4181 "Arguments: (textLabel) " ..
4182 "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"
4183 end
4184 if funcNameOrFunc == "CreateSlider" or funcNameOrFunc == t.CreateSlider then
4185 return "Function CreateSlider. " ..
4186 "Arguments: (steps, width, position) " ..
4187 "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."
4188 end
4189 if funcNameOrFunc == "CreateSliderNew" or funcNameOrFunc == t.CreateSliderNew then
4190 return "Function CreateSliderNew. " ..
4191 "Arguments: (steps, width, position) " ..
4192 "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."
4193 end
4194 if funcNameOrFunc == "CreateLoadingFrame" or funcNameOrFunc == t.CreateLoadingFrame then
4195 return "Function CreateLoadingFrame. " ..
4196 "Arguments: (name, size, position) " ..
4197 "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."
4198 end
4199 if funcNameOrFunc == "CreateTerrainMaterialSelector" or funcNameOrFunc == t.CreateTerrainMaterialSelector then
4200 return "Function CreateTerrainMaterialSelector. " ..
4201 "Arguments: (size, position) " ..
4202 "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."
4203 end
4204 end
4205
4206
4207
4208-- Do a line/plane intersection. The line starts at the camera. The plane is at y == 0, normal(0, 1, 0)
4209--
4210-- vectorPos - End point of the line.
4211--
4212-- Return:
4213-- cellPos - The terrain cell intersection point if there is one, vectorPos if there isn't.
4214-- hit - Whether there was a plane intersection. Value is true if there was, false if not.
4215function PlaneIntersection(vectorPos)
4216 local hit = false
4217 local currCamera = game:GetService("Workspace").CurrentCamera
4218 local startPos = Vector3.new(currCamera.CoordinateFrame.p.X, currCamera.CoordinateFrame.p.Y, currCamera.CoordinateFrame.p.Z)
4219 local endPos = Vector3.new(vectorPos.X, vectorPos.Y, vectorPos.Z)
4220 local normal = Vector3.new(0, 1, 0)
4221 local p3 = Vector3.new(0, 0, 0)
4222 local startEndDot = normal:Dot(endPos - startPos)
4223 local cellPos = vectorPos
4224 if startEndDot ~= 0 then
4225 local t = normal:Dot(p3 - startPos) / startEndDot
4226 if(t >=0 and t <=1) then
4227 local intersection = ((endPos - startPos) * t) + startPos
4228 cellPos = game:GetService("Workspace").Terrain:WorldToCell(intersection)
4229 hit = true
4230 end
4231 end
4232
4233 return cellPos, hit
4234end
4235
4236
4237-- Purpose:
4238-- Checks for terrain touched by the mouse hit.
4239-- Will do a plane intersection if no terrain is touched.
4240--
4241-- mouse - Mouse to check the .hit for.
4242--
4243-- Return:
4244-- cellPos - Cell position hit. Nil if none.
4245function GetTerrainForMouse(mouse)
4246 -- There was no target, so all it could be is a plane intersection.
4247 -- Check for a plane intersection. If there isn't one then nothing will get hit.
4248 local cell = game:GetService("Workspace").Terrain:WorldToCellPreferSolid(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
4249 local planeLoc = nil
4250 local hit = nil
4251 -- If nothing was hit, do the plane intersection.
4252 if 0 == game:GetService("Workspace").Terrain:GetCell(cell.X, cell.Y, cell.Z).Value then
4253 cell = nil
4254 planeLoc, hit = PlaneIntersection(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
4255 if hit then
4256 cell = planeLoc
4257 end
4258 end
4259 return cell
4260end
4261
4262-- setup helper functions
4263local insertBoundingBoxOverlapVector = Vector3.new(.3, .3, .3) -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units
4264
4265-- rotates a model by yAngle radians about the global y-axis
4266local function rotatePartAndChildren(part, rotCF, offsetFromOrigin)
4267 -- rotate this thing, if it's a part
4268 if part:IsA("BasePart") then
4269 part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin
4270 end
4271
4272 -- recursively do the same to all children
4273 local partChildren = part:GetChildren()
4274 for c = 1, #partChildren do rotatePartAndChildren(partChildren[c], rotCF, offsetFromOrigin) end
4275end
4276
4277local function modelRotate(model, yAngle)
4278 local rotCF = CFrame.Angles(0, yAngle, 0)
4279 local offsetFromOrigin = model:GetModelCFrame().p
4280
4281 rotatePartAndChildren(model, rotCF, offsetFromOrigin)
4282end
4283
4284
4285local function collectParts(object, baseParts, scripts, decals)
4286 if object:IsA("BasePart") then
4287 baseParts[#baseParts+1] = object
4288 elseif object:IsA("Script") then
4289 scripts[#scripts+1] = object
4290 elseif object:IsA("Decal") then
4291 decals[#decals+1] = object
4292 end
4293
4294 for index,child in pairs(object:GetChildren()) do
4295 collectParts(child, baseParts, scripts, decals)
4296 end
4297end
4298
4299local function clusterPartsInRegion(startVector, endVector)
4300 local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
4301
4302 local startCell = cluster:WorldToCell(startVector)
4303 local endCell = cluster:WorldToCell(endVector)
4304
4305 local startX = startCell.X
4306 local startY = startCell.Y
4307 local startZ = startCell.Z
4308
4309 local endX = endCell.X
4310 local endY = endCell.Y
4311 local endZ = endCell.Z
4312
4313 if startX < cluster.MaxExtents.Min.X then startX = cluster.MaxExtents.Min.X end
4314 if startY < cluster.MaxExtents.Min.Y then startY = cluster.MaxExtents.Min.Y end
4315 if startZ < cluster.MaxExtents.Min.Z then startZ = cluster.MaxExtents.Min.Z end
4316
4317 if endX > cluster.MaxExtents.Max.X then endX = cluster.MaxExtents.Max.X end
4318 if endY > cluster.MaxExtents.Max.Y then endY = cluster.MaxExtents.Max.Y end
4319 if endZ > cluster.MaxExtents.Max.Z then endZ = cluster.MaxExtents.Max.Z end
4320
4321 for x = startX, endX do
4322 for y = startY, endY do
4323 for z = startZ, endZ do
4324 if (cluster:GetCell(x, y, z).Value) > 0 then return true end
4325 end
4326 end
4327 end
4328
4329 return false
4330end
4331
4332local function findSeatsInModel(parent, seatTable)
4333 if not parent then return end
4334
4335 if parent.className == "Seat" or parent.className == "VehicleSeat" then
4336 table.insert(seatTable, parent)
4337 end
4338 local myChildren = parent:GetChildren()
4339 for j = 1, #myChildren do
4340 findSeatsInModel(myChildren[j], seatTable)
4341 end
4342end
4343
4344local function setSeatEnabledStatus(model, isEnabled)
4345 local seatList = {}
4346 findSeatsInModel(model, seatList)
4347
4348 if isEnabled then
4349 -- remove any welds called "SeatWeld" in seats
4350 for i = 1, #seatList do
4351 local nextSeat = seatList[i]:FindFirstChild("SeatWeld")
4352 while nextSeat do nextSeat:Remove() nextSeat = seatList[i]:FindFirstChild("SeatWeld") end
4353 end
4354 else
4355 -- put a weld called "SeatWeld" in every seat
4356 -- this tricks it into thinking there's already someone sitting there, and it won't make you sit XD
4357 for i = 1, #seatList do
4358 local fakeWeld = Instance.new("Weld")
4359 fakeWeld.Name = "SeatWeld"
4360 fakeWeld.Parent = seatList[i]
4361 end
4362 end
4363end
4364
4365local function autoAlignToFace(parts)
4366 local aatf = parts:FindFirstChild("AutoAlignToFace")
4367 if aatf then return aatf.Value else return false end
4368end
4369
4370local function getClosestAlignedWorldDirection(aVector3InWorld)
4371 local xDir = Vector3.new(1,0,0)
4372 local yDir = Vector3.new(0,1,0)
4373 local zDir = Vector3.new(0,0,1)
4374 local xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z
4375 local yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z
4376 local zDot = aVector3InWorld.x * zDir.x + aVector3InWorld.y * zDir.y + aVector3InWorld.z * zDir.z
4377
4378 if math.abs(xDot) > math.abs(yDot) and math.abs(xDot) > math.abs(zDot) then
4379 if xDot > 0 then
4380 return 0
4381 else
4382 return 3
4383 end
4384 elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs(zDot) then
4385 if yDot > 0 then
4386 return 1
4387 else
4388 return 4
4389 end
4390 else
4391 if zDot > 0 then
4392 return 2
4393 else
4394 return 5
4395 end
4396 end
4397end
4398
4399local function positionPartsAtCFrame3(aCFrame, currentParts)
4400 local insertCFrame = nil
4401 if not currentParts then return currentParts end
4402 if currentParts and (currentParts:IsA("Model") or currentParts:IsA("Tool")) then
4403 insertCFrame = currentParts:GetModelCFrame()
4404 currentParts:TranslateBy(aCFrame.p - insertCFrame.p)
4405 else
4406 currentParts.CFrame = aCFrame
4407 end
4408 return currentParts
4409end
4410
4411local function calcRayHitTime(rayStart, raySlope, intersectionPlane)
4412 if math.abs(raySlope) < .01 then return 0 end -- 0 slope --> we just say intersection time is 0, and sidestep this dimension
4413 return (intersectionPlane - rayStart) / raySlope
4414end
4415
4416local function modelTargetSurface(partOrModel, rayStart, rayEnd)
4417 if not partOrModel then
4418 return 0
4419 end
4420
4421 local modelCFrame = nil
4422 local modelSize = nil
4423 if partOrModel:IsA("Model") then
4424 modelCFrame = partOrModel:GetModelCFrame()
4425 modelSize = partOrModel:GetModelSize()
4426 else
4427 modelCFrame = partOrModel.CFrame
4428 modelSize = partOrModel.Size
4429 end
4430
4431 local mouseRayStart = modelCFrame:pointToObjectSpace(rayStart)
4432 local mouseRayEnd = modelCFrame:pointToObjectSpace(rayEnd)
4433 local mouseSlope = mouseRayEnd - mouseRayStart
4434
4435 local xPositive = 1
4436 local yPositive = 1
4437 local zPositive = 1
4438 if mouseSlope.X > 0 then xPositive = -1 end
4439 if mouseSlope.Y > 0 then yPositive = -1 end
4440 if mouseSlope.Z > 0 then zPositive = -1 end
4441
4442 -- find which surface the transformed mouse ray hits (using modelSize):
4443 local xHitTime = calcRayHitTime(mouseRayStart.X, mouseSlope.X, modelSize.X/2 * xPositive)
4444 local yHitTime = calcRayHitTime(mouseRayStart.Y, mouseSlope.Y, modelSize.Y/2 * yPositive)
4445 local zHitTime = calcRayHitTime(mouseRayStart.Z, mouseSlope.Z, modelSize.Z/2 * zPositive)
4446
4447 local hitFace = 0
4448
4449 --if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 then
4450 if xHitTime > yHitTime then
4451 if xHitTime > zHitTime then
4452 -- xFace is hit
4453 hitFace = 1*xPositive
4454 else
4455 -- zFace is hit
4456 hitFace = 3*zPositive
4457 end
4458 else
4459 if yHitTime > zHitTime then
4460 -- yFace is hit
4461 hitFace = 2*yPositive
4462 else
4463 -- zFace is hit
4464 hitFace = 3*zPositive
4465 end
4466 end
4467
4468 return hitFace
4469end
4470
4471local function getBoundingBox2(partOrModel)
4472
4473 -- for models, the bounding box is defined as the minimum and maximum individual part bounding boxes
4474 -- relative to the first part's coordinate frame.
4475 local minVec = Vector3.new(math.huge, math.huge, math.huge)
4476 local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
4477
4478 if partOrModel:IsA("Terrain") then
4479 minVec = Vector3.new(-2, -2, -2)
4480 maxVec = Vector3.new(2, 2, 2)
4481 elseif partOrModel:IsA("BasePart") then
4482 minVec = -0.5 * partOrModel.Size
4483 maxVec = -minVec
4484 else
4485 maxVec = partOrModel:GetModelSize()*0.5
4486 minVec = -maxVec
4487 end
4488
4489 -- Adjust bounding box to reflect what the model or part author wants in terms of justification
4490 local justifyValue = partOrModel:FindFirstChild("Justification")
4491 if justifyValue ~= nil then
4492 -- find the multiple of 4 that contains the model
4493 local justify = justifyValue.Value
4494 local two = Vector3.new(2, 2, 2)
4495 local actualBox = maxVec - minVec - Vector3.new(0.01, 0.01, 0.01)
4496 local containingGridBox = Vector3.new(4 * math.ceil(actualBox.x/4), 4 * math.ceil(actualBox.y/4), 4 * math.ceil(actualBox.z/4))
4497 local adjustment = containingGridBox - actualBox
4498 minVec = minVec - 0.5 * adjustment * justify
4499 maxVec = maxVec + 0.5 * adjustment * (two - justify)
4500 end
4501
4502 return minVec, maxVec
4503end
4504
4505local function getBoundingBoxInWorldCoordinates(partOrModel)
4506 local minVec = Vector3.new(math.huge, math.huge, math.huge)
4507 local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
4508
4509 if partOrModel:IsA("BasePart") and not partOrModel:IsA("Terrain") then
4510 local vec1 = partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
4511 local vec2 = partOrModel.CFrame:pointToWorldSpace(0.5 * partOrModel.Size)
4512 minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
4513 maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
4514 elseif partOrModel:IsA("Terrain") then
4515 -- we shouldn't have to deal with this case
4516 --minVec = Vector3.new(-2, -2, -2)
4517 --maxVec = Vector3.new(2, 2, 2)
4518 else
4519 local vec1 = partOrModel:GetModelCFrame():pointToWorldSpace(-0.5 * partOrModel:GetModelSize())
4520 local vec2 = partOrModel:GetModelCFrame():pointToWorldSpace(0.5 * partOrModel:GetModelSize())
4521 minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
4522 maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
4523 end
4524
4525 return minVec, maxVec
4526end
4527
4528local function getTargetPartBoundingBox(targetPart)
4529 if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
4530 return getBoundingBox2(targetPart.Parent)
4531 else
4532 return getBoundingBox2(targetPart)
4533 end
4534end
4535
4536local function getMouseTargetCFrame(targetPart)
4537 if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
4538 if targetPart.Parent:IsA("Tool") then return targetPart.Parent.Handle.CFrame
4539 else return targetPart.Parent:GetModelCFrame() end
4540 else
4541 return targetPart.CFrame
4542 end
4543end
4544
4545local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this part
4546 if not part then return false end
4547 if not part.Parent then return false end
4548 if part:FindFirstChild("Humanoid") then return false end
4549 if part:FindFirstChild("RobloxStamper") or part:FindFirstChild("RobloxModel") then return true end
4550 if part:IsA("Part") and not part.CanCollide then return false end
4551 if part == game:GetService("Lighting") then return false end
4552 return isBlocker(part.Parent)
4553end
4554
4555-- helper function to determine if a character can be pushed upwards by a certain amount
4556-- 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
4557local function spaceAboveCharacter(charTorso, newTorsoY, stampData)
4558 local partsAboveChar = game:GetService("Workspace"):FindPartsInRegion3(
4559 Region3.new(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75),
4560 Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)),
4561 charTorso.Parent,
4562 100)
4563
4564 for j = 1, #partsAboveChar do
4565 if partsAboveChar[j].CanCollide and not partsAboveChar[j]:IsDescendantOf(stampData.CurrentParts) then return false end
4566 end
4567
4568 if clusterPartsInRegion(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75),
4569 Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)) then
4570 return false
4571 end
4572
4573 return true
4574end
4575
4576
4577local function findConfigAtMouseTarget(Mouse, stampData)
4578 -- *Critical Assumption* :
4579 -- This function assumes the target CF axes are orthogonal with the target bounding box faces
4580 -- And, it assumes the insert CF axes are orthongonal with the insert bounding box faces
4581 -- Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor
4582 -- will it work for parts in a model that are not orthogonally aligned with the model's CF.
4583
4584 if not Mouse then return nil end -- This can happen sometimes, return if so
4585 if not stampData then error("findConfigAtMouseTarget: stampData is nil") return nil end
4586 if not stampData["CurrentParts"] then return nil end
4587
4588 local grid = 4.0
4589 local admissibleConfig = false
4590 local targetConfig = CFrame.new(0,0,0)
4591
4592 local minBB, maxBB = getBoundingBox2(stampData.CurrentParts)
4593 local diagBB = maxBB - minBB
4594
4595 local insertCFrame
4596 if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
4597 insertCFrame = stampData.CurrentParts:GetModelCFrame()
4598 else
4599 insertCFrame = stampData.CurrentParts.CFrame
4600 end
4601
4602 if Mouse then
4603 if stampData.CurrentParts:IsA("Tool") then
4604 Mouse.TargetFilter = stampData.CurrentParts.Handle
4605 else
4606 Mouse.TargetFilter = stampData.CurrentParts
4607 end
4608 end
4609
4610 local hitPlane = false
4611 local targetPart = nil
4612 local success = pcall(function() targetPart = Mouse.Target end)
4613
4614 if not success then-- or targetPart == nil then
4615 return admissibleConfig, targetConfig
4616 end
4617
4618 local mouseHitInWorld = Vector3.new(0, 0, 0)
4619 if Mouse then
4620 mouseHitInWorld = Vector3.new(Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z)
4621 end
4622
4623 local cellPos = nil
4624
4625 -- Nothing was hit, so check for the default plane.
4626 if nil == targetPart then
4627 cellPos = GetTerrainForMouse(Mouse)
4628 if nil == cellPos then
4629 hitPlane = false
4630 return admissibleConfig, targetConfig
4631 else
4632 targetPart = game:GetService("Workspace").Terrain
4633 hitPlane = true
4634 -- Take into account error that will occur.
4635 cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z)
4636 mouseHitInWorld = game:GetService("Workspace").Terrain:CellCenterToWorld(cellPos.x, cellPos.y, cellPos.z)
4637 end
4638 end
4639
4640 -- test mouse hit location
4641 local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart)
4642 local diagBBTarget = maxBBTarget - minBBTarget
4643 local targetCFrame = getMouseTargetCFrame(targetPart)
4644
4645 if targetPart:IsA("Terrain") then
4646 local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")
4647 local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld)
4648 if hitPlane then
4649 cellID = cellPos
4650 end
4651
4652 targetCFrame = CFrame.new(game:GetService("Workspace").Terrain:CellCenterToWorld(cellID.x, cellID.y, cellID.z))
4653 end
4654
4655 local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld)
4656 local targetVectorInWorld = Vector3.new(0,0,0)
4657 if Mouse then
4658 -- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME! (.TargetSurface is in terms of the part CFrame, so this would break, right? [HotThoth])
4659 -- (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse)
4660 --targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))
4661 targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface)) -- better, but model cframe would be best
4662 --[[if targetPart.Parent:IsA("Model") then
4663 local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right
4664 local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
4665 if hitFace > 0 then
4666 targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace])
4667 elseif hitFace < 0 then
4668 targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace])
4669 end
4670 end]]
4671 end
4672
4673 local targetRefPointInTarget
4674 local clampToSurface
4675 local insertRefPointInInsert
4676
4677 if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 then
4678 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
4679 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
4680 clampToSurface = Vector3.new(0,1,1)
4681 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 3 then
4682 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
4683 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
4684 clampToSurface = Vector3.new(0,1,1)
4685 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 1 then
4686 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
4687 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
4688 clampToSurface = Vector3.new(1,0,1)
4689 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 4 then
4690 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
4691 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
4692 clampToSurface = Vector3.new(1,0,1)
4693 elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 2 then
4694 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
4695 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
4696 clampToSurface = Vector3.new(1,1,0)
4697 else
4698 targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
4699 insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
4700 clampToSurface = Vector3.new(1,1,0)
4701 end
4702
4703 targetRefPointInTarget = targetRefPointInTarget * (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget)
4704 insertRefPointInInsert = insertRefPointInInsert * (0.5 * diagBB) + 0.5 * (maxBB + minBB)
4705
4706 -- To Do: For cases that are not aligned to the world grid, account for the minimal rotation
4707 -- needed to bring the Insert part(s) into alignment with the Target Part
4708 -- Apply the rotation here
4709
4710 local delta = mouseHitInTarget - targetRefPointInTarget
4711 local deltaClamped = Vector3.new(grid * math.modf(delta.x/grid), grid * math.modf(delta.y/grid), grid * math.modf(delta.z/grid))
4712 deltaClamped = deltaClamped * clampToSurface
4713 local targetTouchInTarget = deltaClamped + targetRefPointInTarget
4714
4715 local TargetTouchRelToWorld = targetCFrame:pointToWorldSpace(targetTouchInTarget)
4716 local InsertTouchInWorld = insertCFrame:vectorToWorldSpace(insertRefPointInInsert)
4717 local posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld
4718
4719 local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = insertCFrame:components()
4720 targetConfig = CFrame.new(posInsertOriginInWorld.x, posInsertOriginInWorld.y, posInsertOriginInWorld.z, R00, R01, R02, R10, R11, R12, R20, R21, R22)
4721 admissibleConfig = true
4722
4723 return admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld)
4724end
4725
4726local function truncateToCircleEighth(bigValue, littleValue)
4727 local big = math.abs(bigValue)
4728 local little = math.abs(littleValue)
4729 local hypotenuse = math.sqrt(big*big + little*little)
4730 local frac = little / hypotenuse
4731
4732 local bigSign = 1
4733 local littleSign = 1
4734 if bigValue < 0 then bigSign = -1 end
4735 if littleValue < 0 then littleSign = -1 end
4736
4737 if frac > .382683432 then
4738 -- between 22.5 and 45 degrees, so truncate to 45-degree tilt
4739 return .707106781 * hypotenuse * bigSign, .707106781 * hypotenuse * littleSign
4740 else
4741 -- between 0 and 22.5 degrees, so truncate to 0-degree tilt
4742 return hypotenuse * bigSign, 0
4743 end
4744end
4745
4746
4747local function saveTheWelds(object, manualWeldTable, manualWeldParentTable)
4748 if object:IsA("ManualWeld") or object:IsA("Rotate") then
4749 table.insert(manualWeldTable, object)
4750 table.insert(manualWeldParentTable, object.Parent)
4751 else
4752 local children = object:GetChildren()
4753 for i = 1, #children do
4754 saveTheWelds(children[i], manualWeldTable, manualWeldParentTable)
4755 end
4756 end
4757end
4758
4759local function restoreTheWelds(manualWeldTable, manualWeldParentTable)
4760 for i = 1, #manualWeldTable do
4761 manualWeldTable[i].Parent = manualWeldParentTable[i]
4762 end
4763end
4764
4765t.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model and stamper metadata
4766 if not EditRegion then return true, false end
4767
4768 local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel)
4769
4770 if minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X/2 or
4771 minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y/2 or
4772 minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z/2 then
4773 return false, false
4774 end
4775
4776 if maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X/2 or
4777 maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y/2 or
4778 maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z/2 then
4779 return false, false
4780 end
4781
4782 return true, false
4783end
4784
4785t.GetStampModel = function(assetId, terrainShape, useAssetVersionId)
4786 if assetId == 0 then
4787 return nil, "No Asset"
4788 end
4789 if assetId < 0 then
4790 return nil, "Negative Asset"
4791 end
4792
4793 local function UnlockInstances(object)
4794 if object:IsA("BasePart") then
4795 object.Locked = false
4796 end
4797 for index,child in pairs(object:GetChildren()) do
4798 UnlockInstances(child)
4799 end
4800 end
4801
4802 local function getClosestColorToTerrainMaterial(terrainValue)
4803 if terrainValue == 1 then
4804 return BrickColor.new("Bright green")
4805 elseif terrainValue == 2 then
4806 return BrickColor.new("Bright yellow")
4807 elseif terrainValue == 3 then
4808 return BrickColor.new("Bright red")
4809 elseif terrainValue == 4 then
4810 return BrickColor.new("Sand red")
4811 elseif terrainValue == 5 then
4812 return BrickColor.new("Black")
4813 elseif terrainValue == 6 then
4814 return BrickColor.new("Dark stone grey")
4815 elseif terrainValue == 7 then
4816 return BrickColor.new("Sand blue")
4817 elseif terrainValue == 8 then
4818 return BrickColor.new("Deep orange")
4819 elseif terrainValue == 9 then
4820 return BrickColor.new("Dark orange")
4821 elseif terrainValue == 10 then
4822 return BrickColor.new("Reddish brown")
4823 elseif terrainValue == 11 then
4824 return BrickColor.new("Light orange")
4825 elseif terrainValue == 12 then
4826 return BrickColor.new("Light stone grey")
4827 elseif terrainValue == 13 then
4828 return BrickColor.new("Sand green")
4829 elseif terrainValue == 14 then
4830 return BrickColor.new("Medium stone grey")
4831 elseif terrainValue == 15 then
4832 return BrickColor.new("Really red")
4833 elseif terrainValue == 16 then
4834 return BrickColor.new("Really blue")
4835 elseif terrainValue == 17 then
4836 return BrickColor.new("Bright blue")
4837 else
4838 return BrickColor.new("Bright green")
4839 end
4840 end
4841
4842 local function setupFakeTerrainPart(cellMat, cellType, cellOrient)
4843 local newTerrainPiece = nil
4844 if (cellType == 1 or cellType == 4) then newTerrainPiece = Instance.new("WedgePart")
4845 elseif (cellType == 2) then newTerrainPiece = Instance.new("CornerWedgePart")
4846 else newTerrainPiece = Instance.new("Part") end
4847 newTerrainPiece.Name = "MegaClusterCube"
4848 newTerrainPiece.Size = Vector3.new(4, 4, 4)
4849 newTerrainPiece.BottomSurface = "Smooth"
4850 newTerrainPiece.TopSurface = "Smooth"
4851
4852 -- can add decals or textures here if feeling particularly adventurous... for now, can make a table of look-up colors
4853 newTerrainPiece.BrickColor = getClosestColorToTerrainMaterial(cellMat)
4854
4855 local sideways = 0
4856 local flipped = math.pi
4857 if cellType == 4 then sideways = -math.pi/2 end
4858 if cellType == 2 or cellType == 3 then flipped = 0 end
4859 newTerrainPiece.CFrame = CFrame.Angles(0, math.pi/2*cellOrient + flipped, sideways)
4860
4861 if cellType == 3 then
4862 local inverseCornerWedgeMesh = Instance.new("SpecialMesh")
4863 inverseCornerWedgeMesh.MeshType = "FileMesh"
4864 inverseCornerWedgeMesh.MeshId = "https://www.roblox.com/asset/?id=66832495"
4865 inverseCornerWedgeMesh.Scale = Vector3.new(2, 2, 2)
4866 inverseCornerWedgeMesh.Parent = newTerrainPiece
4867 end
4868
4869 local materialTag = Instance.new("Vector3Value")
4870 materialTag.Value = Vector3.new(cellMat, cellType, cellOrient)
4871 materialTag.Name = "ClusterMaterial"
4872 materialTag.Parent = newTerrainPiece
4873
4874 return newTerrainPiece
4875 end
4876
4877 -- This call will cause a "wait" until the data comes back
4878 -- below we wait a max of 8 seconds before deciding to bail out on loading
4879 local root
4880 local loader
4881 loading = true
4882 if useAssetVersionId then
4883 loader = coroutine.create(function()
4884 root = game:GetService("InsertService"):LoadAssetVersion(assetId)
4885 loading = false
4886 end)
4887 coroutine.resume(loader)
4888 else
4889 loader = coroutine.create(function()
4890 root = game:GetService("InsertService"):LoadAsset(assetId)
4891 loading = false
4892 end)
4893 coroutine.resume(loader)
4894 end
4895
4896 local lastGameTime = 0
4897 local totalTime = 0
4898 local maxWait = 8
4899 while loading and totalTime < maxWait do
4900 lastGameTime = tick()
4901 wait(1)
4902 totalTime = totalTime + tick() - lastGameTime
4903 end
4904 loading = false
4905
4906 if totalTime >= maxWait then
4907 return nil, "Load Time Fail"
4908 end
4909
4910
4911 if root == nil then
4912 return nil, "Load Asset Fail"
4913 end
4914
4915 if not root:IsA("Model") then
4916 return nil, "Load Type Fail"
4917 end
4918
4919 local instances = root:GetChildren()
4920 if #instances == 0 then
4921 return nil, "Empty Model Fail"
4922 end
4923
4924 --Unlock all parts that are inserted, to make sure they are editable
4925 UnlockInstances(root)
4926
4927 --Continue the insert process
4928 root = root:GetChildren()[1]
4929
4930 --Examine the contents and decide what it looks like
4931 for pos, instance in pairs(instances) do
4932 if instance:IsA("Team") then
4933 instance.Parent = game:GetService("Teams")
4934 elseif instance:IsA("Sky") then
4935 local lightingService = game:GetService("Lighting")
4936 for index,child in pairs(lightingService:GetChildren()) do
4937 if child:IsA("Sky") then
4938 child:Remove();
4939 end
4940 end
4941 instance.Parent = lightingService
4942 return
4943 end
4944 end
4945
4946 -- ...and tag all inserted models for subsequent origin identification
4947 -- if no RobloxModel tag already exists, then add it.
4948 if root:FindFirstChild("RobloxModel") == nil then
4949 local stringTag = Instance.new("BoolValue", root)
4950 stringTag.Name = "RobloxModel"
4951
4952 if root:FindFirstChild("RobloxStamper") == nil then
4953 local stringTag2 = Instance.new("BoolValue", root)
4954 stringTag2.Name = "RobloxStamper"
4955 end
4956 end
4957
4958 if terrainShape then
4959 if root.Name == "MegaClusterCube" then
4960 if (terrainShape == 6) then -- insert an autowedging tag
4961 local autowedgeTag = Instance.new("BoolValue")
4962 autowedgeTag.Name = "AutoWedge"
4963 autowedgeTag.Parent = root
4964 else
4965 local clusterTag = root:FindFirstChild("ClusterMaterial")
4966 if clusterTag then
4967 if clusterTag:IsA("Vector3Value") then
4968 root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z)
4969 else
4970 root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0)
4971 end
4972 else
4973 root = setupFakeTerrainPart(1, terrainShape, 0)
4974 end
4975 end
4976 end
4977 end
4978
4979 return root
4980end
4981
4982
4983
4984t.SetupStamperDragger = function(modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc)
4985 if not modelToStamp then
4986 error("SetupStamperDragger: modelToStamp (first arg) is nil! Should be a stamper model")
4987 return nil
4988 end
4989 if not modelToStamp:IsA("Model") and not modelToStamp:IsA("BasePart") then
4990 error("SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!")
4991 return nil
4992 end
4993 if not Mouse then
4994 error("SetupStamperDragger: Mouse (second arg) is nil! Should be a mouse object")
4995 return nil
4996 end
4997 if not Mouse:IsA("Mouse") then
4998 error("SetupStamperDragger: Mouse (second arg) is not of type Mouse!")
4999 return nil
5000 end
5001
5002 local stampInModel = nil
5003 local allowedStampRegion = nil
5004 local stampFailedFunc = nil
5005 if StampInModel then
5006 if not StampInModel:IsA("Model") then
5007 error("SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'")
5008 return nil
5009 end
5010 if not AllowedStampRegion then
5011 error("SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined")
5012 return nil
5013 end
5014 stampFailedFunc = StampFailedFunc
5015 stampInModel = StampInModel
5016 allowedStampRegion = AllowedStampRegion
5017 end
5018
5019 -- Init all state variables
5020 local gInitial90DegreeRotations = 0
5021 local stampData = nil
5022 local mouseTarget = nil
5023
5024 local errorBox = Instance.new("SelectionBox")
5025 errorBox.Color = BrickColor.new("Bright red")
5026 errorBox.Transparency = 0
5027 errorBox.Archivable = false
5028
5029 -- for megacluster MEGA STAMPING
5030 local adornPart = Instance.new("Part")
5031 adornPart.Parent = nil
5032 adornPart.Size = Vector3.new(4, 4, 4)
5033 adornPart.CFrame = CFrame.new()
5034 adornPart.Archivable = false
5035
5036 local adorn = Instance.new("SelectionBox")
5037 adorn.Color = BrickColor.new("Toothpaste")
5038 adorn.Adornee = adornPart
5039 adorn.Visible = true
5040 adorn.Transparency = 0
5041 adorn.Name = "HighScalabilityStamperLine"
5042 adorn.Archivable = false
5043
5044 local HighScalabilityLine = {}
5045 HighScalabilityLine.Start = nil
5046 HighScalabilityLine.End = nil
5047 HighScalabilityLine.Adorn = adorn
5048 HighScalabilityLine.AdornPart = adornPart
5049 HighScalabilityLine.InternalLine = nil
5050 HighScalabilityLine.NewHint = true
5051
5052 HighScalabilityLine.MorePoints = {nil, nil}
5053 HighScalabilityLine.MoreLines = {nil, nil}
5054 HighScalabilityLine.Dimensions = 1
5055
5056 local control = {}
5057 local movingLock = false
5058 local stampUpLock = false
5059 local unstampableSurface = false
5060 local mouseCons = {}
5061 local keyCon = nil
5062
5063 local stamped = Instance.new("BoolValue")
5064 stamped.Archivable = false
5065 stamped.Value = false
5066
5067 local lastTarget = {}
5068 lastTarget.TerrainOrientation = 0
5069 lastTarget.CFrame = 0
5070
5071 local cellInfo = {}
5072 cellInfo.Material = 1
5073 cellInfo.clusterType = 0
5074 cellInfo.clusterOrientation = 0
5075
5076 local function isMegaClusterPart()
5077 if not stampData then return false end
5078 if not stampData.CurrentParts then return false end
5079
5080 return ( stampData.CurrentParts:FindFirstChild("ClusterMaterial",true) or (stampData.CurrentParts.Name == "MegaClusterCube") )
5081 end
5082
5083 local function DoHighScalabilityRegionSelect()
5084 local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube")
5085 if not megaCube then
5086 if not stampData.CurrentParts.Name == "MegaClusterCube" then
5087 return
5088 else
5089 megaCube = stampData.CurrentParts
5090 end
5091 end
5092
5093 HighScalabilityLine.End = megaCube.CFrame.p
5094 local line = nil
5095 local line2 = Vector3.new(0, 0, 0)
5096 local line3 = Vector3.new(0, 0, 0)
5097
5098 if HighScalabilityLine.Dimensions == 1 then
5099 -- extract the line from these positions and limit to a 2D plane made from 2 of the world axes
5100 -- then use dominating axis to limit line to be at 45-degree intervals
5101 -- will use this internal representation of the line for the actual stamping
5102 line = (HighScalabilityLine.End - HighScalabilityLine.Start)
5103
5104 if math.abs(line.X) < math.abs(line.Y) then
5105 if math.abs(line.X) < math.abs(line.Z) then
5106 -- limit to Y/Z plane, domination unknown
5107 local newY, newZ
5108 if (math.abs(line.Y) > math.abs(line.Z)) then
5109 newY, newZ = truncateToCircleEighth(line.Y, line.Z)
5110 else
5111 newZ, newY = truncateToCircleEighth(line.Z, line.Y)
5112 end
5113 line = Vector3.new(0, newY, newZ)
5114 else
5115 -- limit to X/Y plane, with Y dominating
5116 local newY, newX = truncateToCircleEighth(line.Y, line.X)
5117 line = Vector3.new(newX, newY, 0)
5118 end
5119 else
5120 if math.abs(line.Y) < math.abs(line.Z) then
5121 -- limit to X/Z plane, domination unknown
5122 local newX, newZ
5123 if math.abs(line.X) > math.abs(line.Z) then
5124 newX, newZ = truncateToCircleEighth(line.X, line.Z)
5125 else
5126 newZ, newX = truncateToCircleEighth(line.Z, line.X)
5127 end
5128 line = Vector3.new(newX, 0, newZ)
5129 else
5130 -- limit to X/Y plane, with X dominating
5131 local newX, newY = truncateToCircleEighth(line.X, line.Y)
5132 line = Vector3.new(newX, newY, 0)
5133 end
5134 end
5135 HighScalabilityLine.InternalLine = line
5136
5137 elseif HighScalabilityLine.Dimensions == 2 then
5138 line = HighScalabilityLine.MoreLines[1]
5139 line2 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[1]
5140
5141 -- take out any component of line2 along line1, so you get perpendicular to line1 component
5142 line2 = line2 - line.unit*line.unit:Dot(line2)
5143
5144 local tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
5145
5146 -- then zero out whichever is the smaller component
5147 local yAxis = tempCFrame:vectorToWorldSpace(Vector3.new(0, 1, 0))
5148 local xAxis = tempCFrame:vectorToWorldSpace(Vector3.new(1, 0, 0))
5149
5150 local xComp = xAxis:Dot(line2)
5151 local yComp = yAxis:Dot(line2)
5152
5153 if math.abs(yComp) > math.abs(xComp) then
5154 line2 = line2 - xAxis * xComp
5155 else
5156 line2 = line2 - yAxis * yComp
5157 end
5158
5159 HighScalabilityLine.InternalLine = line2
5160
5161 elseif HighScalabilityLine.Dimensions == 3 then
5162 line = HighScalabilityLine.MoreLines[1]
5163 line2 = HighScalabilityLine.MoreLines[2]
5164 line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2]
5165
5166 -- zero out all components of previous lines
5167 line3 = line3 - line.unit * line.unit:Dot(line3)
5168 line3 = line3 - line2.unit * line2.unit:Dot(line3)
5169
5170 HighScalabilityLine.InternalLine = line3
5171 end
5172
5173 -- resize the "line" graphic to be the correct size and orientation
5174 local tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
5175
5176 if HighScalabilityLine.Dimensions == 1 then -- faster calculation for line
5177 HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, line.magnitude + 4)
5178 HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(Vector3.new(2, 2, 2) - HighScalabilityLine.AdornPart.Size/2)
5179 else
5180 local boxSize = tempCFrame:vectorToObjectSpace(line + line2 + line3)
5181 HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, 4) + Vector3.new(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z))
5182 HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(boxSize/2)
5183 end
5184
5185 -- make player able to see this ish
5186
5187 local gui = nil
5188 if game:GetService("Players")["LocalPlayer"] then
5189 gui = game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui")
5190 if gui and gui:IsA("PlayerGui") then
5191 if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
5192 HighScalabilityLine.Adorn.Parent = gui
5193 elseif HighScalabilityLine.Dimensions > 1 then
5194 HighScalabilityLine.Adorn.Parent = gui
5195 end
5196 end
5197 end
5198
5199 if gui == nil then -- we are in studio
5200 gui = game:GetService("CoreGui")
5201 if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
5202 HighScalabilityLine.Adorn.Parent = gui
5203 elseif HighScalabilityLine.Dimensions > 1 then
5204 HighScalabilityLine.Adorn.Parent = gui
5205 end
5206 end
5207 end
5208
5209
5210 local function DoStamperMouseMove(Mouse)
5211 if not Mouse then
5212 error("Error: RbxStamper.DoStamperMouseMove: Mouse is nil")
5213 return
5214 end
5215 if not Mouse:IsA("Mouse") then
5216 error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className,"should be of type Mouse")
5217 return
5218 end
5219
5220 -- There wasn't a target (no part or terrain), so check for plane intersection.
5221 if not Mouse.Target then
5222 local cellPos = GetTerrainForMouse(Mouse)
5223 if nil == cellPos then
5224 return
5225 end
5226 end
5227
5228 if not stampData then
5229 return
5230 end
5231
5232 -- don't move with dragger - will move in one step on mouse down
5233 -- draw ghost at acceptable positions
5234 local configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Mouse, stampData)
5235 if not configFound then
5236 error("RbxStamper.DoStamperMouseMove No configFound, returning")
5237 return
5238 end
5239
5240 local numRotations = 0 -- update this according to how many rotations you need to get it to target surface
5241 if autoAlignToFace(stampData.CurrentParts) and targetSurface ~= 1 and targetSurface ~= 4 then -- pre-rotate the flag or portrait so it's aligned correctly
5242 if targetSurface == 3 then numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
5243 elseif targetSurface == 0 then numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
5244 elseif targetSurface == 5 then numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
5245 elseif targetSurface == 2 then numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
5246 end
5247 end
5248
5249 local ry = math.pi/2
5250 gInitial90DegreeRotations = gInitial90DegreeRotations + numRotations
5251 if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
5252 --stampData.CurrentParts:Rotate(0, ry*numRotations, 0)
5253 modelRotate(stampData.CurrentParts, ry*numRotations)
5254 else
5255 stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry*numRotations, 0) * stampData.CurrentParts.CFrame
5256 end
5257
5258 -- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE
5259 local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
5260
5261 -- need to offset by distance to be dragged
5262 local currModelCFrame = nil
5263 if stampData.CurrentParts:IsA("Model") then
5264 currModelCFrame = stampData.CurrentParts:GetModelCFrame()
5265 else
5266 currModelCFrame = stampData.CurrentParts.CFrame
5267 end
5268
5269 minBB = minBB + targetCFrame.p - currModelCFrame.p
5270 maxBB = maxBB + targetCFrame.p - currModelCFrame.p
5271
5272 -- don't drag into terrain
5273 if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
5274 if lastTarget.CFrame then
5275 if (stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)) then
5276 local theClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5277 if theClusterMaterial:IsA("Vector3Value") then
5278 local stampClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5279 if stampClusterMaterial then
5280 stampClusterMaterial = theClusterMaterial
5281 end
5282 end
5283 end
5284 end
5285 return
5286 end
5287
5288 -- 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
5289 if isMegaClusterPart() then
5290 local cellToStamp = game:GetService("Workspace").Terrain:WorldToCell(targetCFrame.p)
5291 local newCFramePosition = game:GetService("Workspace").Terrain:CellCenterToWorld(cellToStamp.X, cellToStamp.Y, cellToStamp.Z)
5292 local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = targetCFrame:components()
5293 targetCFrame = CFrame.new(newCFramePosition.X,newCFramePosition.Y,newCFramePosition.Z,R00, R01, R02, R10, R11, R12, R20, R21, R22)
5294 end
5295
5296 positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
5297 lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe
5298 if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
5299 local clusterMat = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5300 if clusterMat:IsA("Vector3Value") then
5301 lastTarget.TerrainOrientation = clusterMat.Value.Z
5302 end
5303 end
5304
5305
5306 -- auto break joints code
5307 if Mouse and Mouse.Target and Mouse.Target.Parent then
5308 local modelInfo = Mouse.Target:FindFirstChild("RobloxModel")
5309 if not modelInfo then modelInfo = Mouse.Target.Parent:FindFirstChild("RobloxModel") end
5310
5311 local myModelInfo = stampData.CurrentParts:FindFirstChild("UnstampableFaces")
5312
5313 --if (modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then -- need better targetSurface calcs
5314 if (true) then
5315 local breakingFaces = ""
5316 local myBreakingFaces = ""
5317 if modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces") then breakingFaces = modelInfo.Parent.UnstampableFaces.Value end
5318 if myModelInfo then myBreakingFaces = myModelInfo.Value end
5319 local hitFace = 0
5320
5321 if modelInfo then hitFace = modelTargetSurface(modelInfo.Parent, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p, Mouse.Hit.p) end
5322
5323 -- are we stamping TO an unstampable surface?
5324 for bf in string.gmatch(breakingFaces, "[^,]+") do
5325 if hitFace == tonumber(bf) then
5326 -- return before we hit the JointsService code below!
5327 unstampableSurface = true
5328 game:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
5329 return
5330 end
5331 end
5332
5333 -- now we have to cast the ray back in the other direction to find the surface we're stamping FROM
5334 hitFace = modelTargetSurface(stampData.CurrentParts, Mouse.Hit.p, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p)
5335
5336 -- are we stamping WITH an unstampable surface?
5337 for bf in string.gmatch(myBreakingFaces, "[^,]+") do
5338 if hitFace == tonumber(bf) then
5339 unstampableSurface = true
5340 game:GetService("JointsService"):ClearJoinAfterMoveJoints() -- clear the JointsService cache
5341 return
5342 end
5343 end
5344
5345 -- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
5346 -- targetSurface: 1 is top, 4 is bottom,
5347 end
5348 end
5349
5350 -- to show joints during the mouse move
5351 unstampableSurface = false
5352 game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
5353
5354 -- most common mouse inactive error occurs here, so check mouse active one more time in a pcall
5355 if not pcall(function()
5356 if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
5357 return
5358 else
5359 return
5360 end
5361 end)
5362 then
5363 error("Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check")
5364 game:GetService("JointsService"):ClearJoinAfterMoveJoints()
5365 Mouse = nil
5366 return
5367 end
5368
5369 if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
5370 game:GetService("JointsService"):SetJoinAfterMoveTarget(Mouse.Target)
5371 else
5372 game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
5373 end
5374 game:GetService("JointsService"):ShowPermissibleJoints()
5375
5376 -- here we allow for a line of high-scalability parts
5377 if isMegaClusterPart() and HighScalabilityLine and HighScalabilityLine.Start then
5378 DoHighScalabilityRegionSelect()
5379 end
5380 end
5381
5382 local function setupKeyListener(key, Mouse)
5383 if control and control["Paused"] then return end -- don't do this if we have no stamp
5384
5385 key = string.lower(key)
5386 if key == 'r' and not autoAlignToFace(stampData.CurrentParts) then -- rotate the model
5387 gInitial90DegreeRotations = gInitial90DegreeRotations + 1
5388
5389 -- Update orientation value if this is a fake terrain part
5390 local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5391 if clusterValues and clusterValues:IsA("Vector3Value") then
5392 clusterValues.Value = Vector3.new(clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z + 1) % 4)
5393 end
5394
5395 -- Rotate the parts or all the parts in the model
5396 local ry = math.pi/2
5397 if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
5398 --stampData.CurrentParts:Rotate(0, ry, 0)
5399 modelRotate(stampData.CurrentParts, ry)
5400 else
5401 stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
5402 end
5403
5404 -- After rotating, update the position
5405 configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
5406 if configFound then
5407 positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
5408
5409 -- update everything else in MouseMove
5410 DoStamperMouseMove(Mouse)
5411 end
5412 elseif key == 'c' then -- try to expand our high scalability dragger dimension
5413 if HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 and HighScalabilityLine.Dimensions < 3 then
5414 HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End
5415 HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine
5416 HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1
5417 HighScalabilityLine.NewHint = true
5418 end
5419 end
5420 end
5421
5422 keyCon = Mouse.KeyDown:connect(function(key) -- init key connection (keeping code close to func)
5423 setupKeyListener(key, Mouse)
5424 end)
5425
5426 local function resetHighScalabilityLine()
5427 if HighScalabilityLine then
5428 HighScalabilityLine.Start = nil
5429 HighScalabilityLine.End = nil
5430 HighScalabilityLine.InternalLine = nil
5431 HighScalabilityLine.NewHint = true
5432 end
5433 end
5434
5435 local function flashRedBox()
5436 local gui = game:GetService("CoreGui")
5437 if game:GetService("Players") then
5438 if game:GetService("Players")["LocalPlayer"] then
5439 if game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui") then
5440 gui = game:GetService("Players").LocalPlayer.PlayerGui
5441 end
5442 end
5443 end
5444 if not stampData["ErrorBox"] then return end
5445
5446 stampData.ErrorBox.Parent = gui
5447 if stampData.CurrentParts:IsA("Tool") then
5448 stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
5449 else
5450 stampData.ErrorBox.Adornee = stampData.CurrentParts
5451 end
5452
5453 delay(0,function()
5454 for i = 1, 3 do
5455 if stampData["ErrorBox"] then stampData.ErrorBox.Visible = true end
5456 wait(0.13)
5457 if stampData["ErrorBox"] then stampData.ErrorBox.Visible = false end
5458 wait(0.13)
5459 end
5460 if stampData["ErrorBox"] then
5461 stampData.ErrorBox.Adornee = nil
5462 stampData.ErrorBox.Parent = nil
5463 end
5464 end)
5465 end
5466
5467 local function DoStamperMouseDown(Mouse)
5468 if not Mouse then
5469 error("Error: RbxStamper.DoStamperMouseDown: Mouse is nil")
5470 return
5471 end
5472 if not Mouse:IsA("Mouse") then
5473 error("Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className,"should be of type Mouse")
5474 return
5475 end
5476 if not stampData then
5477 return
5478 end
5479
5480 if isMegaClusterPart() then
5481 if Mouse and HighScalabilityLine then
5482 local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube", true)
5483 local terrain = game:GetService("Workspace").Terrain
5484 if megaCube then
5485 HighScalabilityLine.Dimensions = 1
5486 local tempCell = terrain:WorldToCell(megaCube.CFrame.p)
5487 HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
5488 return
5489 else
5490 HighScalabilityLine.Dimensions = 1
5491 local tempCell = terrain:WorldToCell(stampData.CurrentParts.CFrame.p)
5492 HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
5493 return
5494 end
5495 end
5496 end
5497 end
5498
5499 local function loadSurfaceTypes(part, surfaces)
5500 part.TopSurface = surfaces[1]
5501 part.BottomSurface = surfaces[2]
5502 part.LeftSurface = surfaces[3]
5503 part.RightSurface = surfaces[4]
5504 part.FrontSurface = surfaces[5]
5505 part.BackSurface = surfaces[6]
5506 end
5507
5508 local function saveSurfaceTypes(part, myTable)
5509 local tempTable = {}
5510 tempTable[1] = part.TopSurface
5511 tempTable[2] = part.BottomSurface
5512 tempTable[3] = part.LeftSurface
5513 tempTable[4] = part.RightSurface
5514 tempTable[5] = part.FrontSurface
5515 tempTable[6] = part.BackSurface
5516
5517 myTable[part] = tempTable
5518 end
5519
5520 local function makeSurfaceUnjoinable(part, surface)
5521 -- TODO: FILL OUT!
5522 end
5523
5524 local function prepareModel(model)
5525 if not model then return nil end
5526
5527 local gDesiredTrans = 0.7
5528 local gStaticTrans = 1
5529
5530 local clone = model:Clone()
5531 local scripts = {}
5532 local parts = {}
5533 local decals = {}
5534
5535 stampData = {}
5536 stampData.DisabledScripts = {}
5537 stampData.TransparencyTable = {}
5538 stampData.MaterialTable = {}
5539 stampData.CanCollideTable = {}
5540 stampData.AnchoredTable = {}
5541 stampData.ArchivableTable = {}
5542 stampData.DecalTransparencyTable = {}
5543 stampData.SurfaceTypeTable = {}
5544
5545 collectParts(clone, parts, scripts, decals)
5546
5547 if #parts <= 0 then return nil, "no parts found in modelToStamp" end
5548
5549 for index,script in pairs(scripts) do
5550 if not(script.Disabled) then
5551 script.Disabled = true
5552 stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script
5553 end
5554 end
5555 for index, part in pairs(parts) do
5556 stampData.TransparencyTable[part] = part.Transparency
5557 part.Transparency = gStaticTrans + (1 - gStaticTrans) * part.Transparency
5558 stampData.MaterialTable[part] = part.Material
5559 part.Material = Enum.Material.Plastic
5560 stampData.CanCollideTable[part] = part.CanCollide
5561 part.CanCollide = false
5562 stampData.AnchoredTable[part] = part.Anchored
5563 part.Anchored = true
5564 stampData.ArchivableTable[part] = part.Archivable
5565 part.Archivable = false
5566
5567 saveSurfaceTypes(part, stampData.SurfaceTypeTable)
5568
5569 local fadeInDelayTime = 0.5
5570 local transFadeInTime = 0.5
5571 delay(0,function()
5572 wait(fadeInDelayTime) -- give it some time to be completely transparent
5573
5574 local begTime = tick()
5575 local currTime = begTime
5576 while (currTime - begTime) < transFadeInTime and part and part:IsA("BasePart") and part.Transparency > gDesiredTrans do
5577 local newTrans = 1 - (((currTime - begTime)/transFadeInTime) * (gStaticTrans - gDesiredTrans))
5578 if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
5579 part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part]
5580 end
5581 wait(0.03)
5582 currTime = tick()
5583 end
5584 if part and part:IsA("BasePart") then
5585 if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
5586 part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part]
5587 end
5588 end
5589 end)
5590 end
5591
5592 for index, decal in pairs(decals) do
5593 stampData.DecalTransparencyTable[decal] = decal.Transparency
5594 decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency
5595 end
5596
5597 -- disable all seats
5598 setSeatEnabledStatus(clone, true)
5599 setSeatEnabledStatus(clone, false)
5600
5601 stampData.CurrentParts = clone
5602
5603 -- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame"
5604 if autoAlignToFace(clone) then
5605 stampData.CurrentParts:ResetOrientationToIdentity()
5606 gInitial90DegreeRotations = 0
5607 else -- pre-rotate if necessary
5608 local ry = gInitial90DegreeRotations * math.pi/2
5609 if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
5610 --stampData.CurrentParts:Rotate(0, ry, 0)
5611 modelRotate(stampData.CurrentParts, ry)
5612 else
5613 stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
5614 end
5615 end
5616
5617 -- 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
5618 -- 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
5619 -- issue (fingers crossed) [HotThoth]
5620
5621 local clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5622 if clusterMaterial and clusterMaterial:IsA("Vector3Value") then
5623 clusterMaterial.Value = Vector3.new(clusterMaterial.Value.X, clusterMaterial.Value.Y, (clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4)
5624 end
5625
5626 -- After rotating, update the position
5627 local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
5628 if configFound then
5629 stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
5630 end
5631
5632 -- to show joints during the mouse move
5633 game:GetService("JointsService"):SetJoinAfterMoveInstance(stampData.CurrentParts)
5634
5635 return clone, parts
5636 end
5637
5638 local function checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
5639 local cellCenterToWorld = game:GetService("Workspace").Terrain.CellCenterToWorld
5640 local cellCenter = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
5641 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)
5642
5643 local skipThisCell = false
5644
5645 for b = 1, #cellBlockingParts do
5646 if isBlocker(cellBlockingParts[b]) then skipThisCell = true break end
5647 end
5648
5649 if not skipThisCell then
5650 -- pop players up above any set cells
5651 local alreadyPushedUp = {}
5652 -- if no blocking model below, then see if stamping on top of a character
5653 for b = 1, #cellBlockingParts do
5654 if cellBlockingParts[b].Parent and
5655 not alreadyPushedUp[cellBlockingParts[b].Parent] and
5656 cellBlockingParts[b].Parent:FindFirstChild("Humanoid") and
5657 cellBlockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
5658 -----------------------------------------------------------------------------------
5659 local blockingPersonTorso = cellBlockingParts[b].Parent:FindFirstChild("Torso")
5660 alreadyPushedUp[cellBlockingParts[b].Parent] = true
5661
5662 if blockingPersonTorso then
5663 -- 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)
5664 local newY = cellCenter.Y + 5
5665 if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
5666 blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
5667 else
5668 -- if no space, we just skip this one
5669 skipThisCell = true
5670 break
5671 end
5672 end
5673 -----------------------------------------------------------------------------------
5674 end
5675 end
5676 end
5677
5678 if not skipThisCell then -- if we STILL aren't skipping... then we're good to go!
5679 local canSetCell = true
5680
5681 if checkHighScalabilityStamp then -- check to see if cell is in region, if not we'll skip set
5682 if allowedStampRegion then
5683 local cellPos = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
5684 if cellPos.X + 2 > allowedStampRegion.CFrame.p.X + allowedStampRegion.Size.X/2 then
5685 canSetCell = false
5686 elseif cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X/2 then
5687 canSetCell = false
5688 elseif cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y/2 then
5689 canSetCell = false
5690 elseif cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y/2 then
5691 canSetCell = false
5692 elseif cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z/2 then
5693 canSetCell = false
5694 elseif cellPos.Z - 2 < allowedStampRegion.CFrame.p.Z - allowedStampRegion.Size.Z/2 then
5695 canSetCell = false
5696 end
5697 end
5698 end
5699
5700 return canSetCell
5701 end
5702 return false
5703 end
5704
5705
5706 local function ResolveMegaClusterStamp(checkHighScalabilityStamp)
5707 local cellSet = false
5708
5709 local cluser = game:GetService("Workspace").Terrain
5710
5711 local line = HighScalabilityLine.InternalLine
5712 local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
5713 local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
5714
5715 local clusterMaterial = 1 -- default is grass
5716 local clusterType = 0 -- default is brick
5717 local clusterOrientation = 0 -- default is 0 rotation
5718
5719 local autoWedgeClusterParts = false
5720 if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
5721
5722 if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
5723 clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
5724 if clusterMaterial:IsA("Vector3Value") then
5725 clusterType = clusterMaterial.Value.Y
5726 clusterOrientation = clusterMaterial.Value.Z
5727 clusterMaterial = clusterMaterial.Value.X
5728 elseif clusterMaterial:IsA("IntValue") then
5729 clusterMaterial = clusterMaterial.Value
5730 end
5731 end
5732
5733 if HighScalabilityLine.Adorn.Parent and HighScalabilityLine.Start and ((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0)) then
5734 local startCell = game:GetService("Workspace").Terrain:WorldToCell(HighScalabilityLine.Start)
5735 local xInc = {0,0,0}
5736 local yInc = {0,0,0}
5737 local zInc = {0,0,0}
5738
5739 local cluster = game:GetService("Workspace").Terrain
5740
5741 local incrementVect = {nil, nil, nil}
5742 local stepVect = {Vector3.new(0, 0, 0), Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)}
5743
5744 local worldAxes = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
5745
5746 local lines = {}
5747 if HighScalabilityLine.Dimensions > 1 then table.insert(lines, HighScalabilityLine.MoreLines[1]) end
5748 if line and line.magnitude > 0 then table.insert(lines, line) end
5749 if HighScalabilityLine.Dimensions > 2 then table.insert(lines, HighScalabilityLine.MoreLines[2]) end
5750
5751 for i = 1, #lines do
5752 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
5753
5754 if lines[i].X > 0 then xInc[i] = 1 elseif lines[i].X < 0 then xInc[i] = -1 end
5755 if lines[i].Y > 0 then yInc[i] = 1 elseif lines[i].Y < 0 then yInc[i] = -1 end
5756 if lines[i].Z > 0 then zInc[i] = 1 elseif lines[i].Z < 0 then zInc[i] = -1 end
5757
5758 incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i])
5759 if incrementVect[i].magnitude < .9 then incrementVect[i] = nil end
5760 end
5761
5762
5763 if not lines[2] then lines[2] = Vector3.new(0, 0, 0) end
5764 if not lines[3] then lines[3] = Vector3.new(0, 0, 0) end
5765
5766 local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
5767 local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
5768
5769 while (stepVect[3].magnitude*4 <= lines[3].magnitude) do
5770 local outerStepVectIndex = 1
5771 while outerStepVectIndex < 4 do
5772 stepVect[2] = Vector3.new(0, 0, 0)
5773 while (stepVect[2].magnitude*4 <= lines[2].magnitude) do
5774 local innerStepVectIndex = 1
5775 while innerStepVectIndex < 4 do
5776 stepVect[1] = Vector3.new(0, 0, 0)
5777 while (stepVect[1].magnitude*4 <= lines[1].magnitude) do
5778 local stepVectSum = stepVect[1] + stepVect[2] + stepVect[3]
5779 local cellPos = Vector3int16.new(startCell.X + stepVectSum.X, startCell.Y + stepVectSum.Y, startCell.Z + stepVectSum.Z)
5780 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
5781 -- check if overlaps player or part
5782 local okToStampTerrainBlock = checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
5783
5784 if okToStampTerrainBlock then
5785 if waterForceTag then
5786 cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
5787 else
5788 cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation)
5789 end
5790 cellSet = true
5791
5792 -- auto-wedge it?
5793 if (autoWedgeClusterParts) then
5794 game:GetService("Workspace").Terrain:AutowedgeCells(Region3int16.new(Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
5795 Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)))
5796 end
5797 end
5798 end
5799 stepVect[1] = stepVect[1] + incrementVect[1]
5800 end
5801 if incrementVect[2] then
5802 while innerStepVectIndex < 4 and worldAxes[innerStepVectIndex]:Dot(incrementVect[2]) == 0 do
5803 innerStepVectIndex = innerStepVectIndex + 1
5804 end
5805 if innerStepVectIndex < 4 then
5806 stepVect[2] = stepVect[2] + worldAxes[innerStepVectIndex] * worldAxes[innerStepVectIndex]:Dot(incrementVect[2])
5807 end
5808 innerStepVectIndex = innerStepVectIndex + 1
5809 else
5810 stepVect[2] = Vector3.new(1, 0, 0)
5811 innerStepVectIndex = 4 -- skip all remaining loops
5812 end
5813 if (stepVect[2].magnitude*4 > lines[2].magnitude) then innerStepVectIndex = 4 end
5814 end
5815 end
5816 if incrementVect[3] then
5817 while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) == 0 do
5818 outerStepVectIndex = outerStepVectIndex + 1
5819 end
5820 if outerStepVectIndex < 4 then
5821 stepVect[3] = stepVect[3] + worldAxes[outerStepVectIndex] * worldAxes[outerStepVectIndex]:Dot(incrementVect[3])
5822 end
5823 outerStepVectIndex = outerStepVectIndex + 1
5824 else -- skip all remaining loops
5825 stepVect[3] = Vector3.new(1, 0, 0) outerStepVectIndex = 4
5826 end
5827 if (stepVect[3].magnitude*4 > lines[3].magnitude) then outerStepVectIndex = 4 end
5828 end
5829 end
5830 end
5831
5832 -- and also get rid of any HighScalabilityLine stuff if it's there
5833 HighScalabilityLine.Start = nil
5834 HighScalabilityLine.Adorn.Parent = nil
5835
5836 -- Mark for undo.
5837 if cellSet then
5838 stampData.CurrentParts.Parent = nil
5839 pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StamperMulti") end)
5840 end
5841
5842 return cellSet
5843 end
5844
5845 local function DoStamperMouseUp(Mouse)
5846 if not Mouse then
5847 error("Error: RbxStamper.DoStamperMouseUp: Mouse is nil")
5848 return false
5849 end
5850 if not Mouse:IsA("Mouse") then
5851 error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className,"should be of type Mouse")
5852 return false
5853 end
5854
5855 if not stampData.Dragger then
5856 error("Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil")
5857 return false
5858 end
5859
5860 if not HighScalabilityLine then
5861 return false
5862 end
5863
5864 local checkHighScalabilityStamp = nil
5865 if stampInModel then
5866 local canStamp = nil
5867 local isHSLPart = isMegaClusterPart()
5868
5869 if isHSLPart and
5870 HighScalabilityLine and
5871 HighScalabilityLine.Start and
5872 HighScalabilityLine.InternalLine and
5873 HighScalabilityLine.InternalLine.magnitude > 0 then -- we have an HSL line, test later
5874 canStamp = true
5875 checkHighScalabilityStamp = true
5876 else
5877 canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
5878 end
5879
5880 if not canStamp then
5881 if stampFailedFunc then
5882 stampFailedFunc()
5883 end
5884 return false
5885 end
5886 end
5887
5888 -- if unstampable face, then don't let us stamp there!
5889 if unstampableSurface then
5890 flashRedBox()
5891 return false
5892 end
5893
5894 -- recheck if we can stamp, as we just moved part
5895 local canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
5896 if not canStamp then
5897 if stampFailedFunc then
5898 stampFailedFunc()
5899 end
5900 return false
5901 end
5902
5903 -- Prevent part from being stamped on top of a player
5904
5905 local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
5906
5907 -- HotThoth's note: Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the
5908 -- value of configFound from the previous onStamperMouseMove call which moved the CurrentParts
5909 -- Shouldn't this be true when lastTargetCFrame has been set and false otherwise?
5910 configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
5911
5912 if configFound and not HighScalabilityLine.Adorn.Parent then
5913 if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
5914 flashRedBox()
5915 return false
5916 end
5917
5918 local blockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(minBB + insertBoundingBoxOverlapVector,
5919 maxBB - insertBoundingBoxOverlapVector),
5920 stampData.CurrentParts,
5921 100)
5922
5923
5924 for b = 1, #blockingParts do
5925 if isBlocker(blockingParts[b]) then
5926 flashRedBox()
5927 return false
5928 end
5929 end
5930
5931 local alreadyPushedUp = {}
5932 -- if no blocking model below, then see if stamping on top of a character
5933 for b = 1, #blockingParts do
5934 if blockingParts[b].Parent and
5935 not alreadyPushedUp[blockingParts[b].Parent] and
5936 blockingParts[b].Parent:FindFirstChild("Humanoid") and
5937 blockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
5938 ---------------------------------------------------------------------------
5939 local blockingPersonTorso = blockingParts[b].Parent:FindFirstChild("Torso")
5940 alreadyPushedUp[blockingParts[b].Parent] = true
5941
5942 if blockingPersonTorso then
5943 -- 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)
5944 local newY = maxBB.Y + 3
5945 if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
5946 blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
5947 else
5948 -- if no space, we just error
5949 flashRedBox()
5950 return false
5951 end
5952 end
5953 ---------------------------------------------------------------------------
5954 end
5955 end
5956
5957 elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) then -- if no config then only stamp if it's a real HSL!
5958 resetHighScalabilityLine()
5959 return false
5960 end
5961
5962 -- something will be stamped! so set the "StampedSomething" toggle to true
5963 if game:GetService("Players")["LocalPlayer"] then
5964 if game:GetService("Players").LocalPlayer["Character"] then
5965 local localChar = game:GetService("Players").LocalPlayer.Character
5966 local stampTracker = localChar:FindFirstChild("StampTracker")
5967 if stampTracker and not stampTracker.Value then
5968 stampTracker.Value = true
5969 end
5970 end
5971 end
5972
5973 -- if we drew a line of mega parts, stamp them out
5974 if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart() then
5975 if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp then
5976 -- kill the ghost part
5977 stampData.CurrentParts.Parent = nil
5978 return true
5979 end
5980 end
5981
5982 -- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
5983 HighScalabilityLine.Start = nil
5984 HighScalabilityLine.Adorn.Parent = nil
5985
5986 local cluster = game:GetService("Workspace").Terrain
5987
5988 -- if target point is in cluster, just use cluster:SetCell
5989 if isMegaClusterPart() then
5990 -- if targetCFrame is inside cluster, just set that cell to 1 and return
5991 --local cellPos = cluster:WorldToCell(targetCFrame.p)
5992
5993 local cellPos
5994 if stampData.CurrentParts:IsA("Model") then cellPos = cluster:WorldToCell(stampData.CurrentParts:GetModelCFrame().p)
5995 else cellPos = cluster:WorldToCell(stampData.CurrentParts.CFrame.p) end
5996
5997 local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
5998 local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
5999
6000 if checkTerrainBlockCollisions(cellPos, false) then
6001
6002 local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
6003 local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
6004 local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
6005
6006 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
6007
6008 if waterForceTag then
6009 cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
6010 elseif not clusterValues then
6011 cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, cellInfo.Material, cellInfo.clusterType, gInitial90DegreeRotations % 4)
6012 elseif clusterValues:IsA("Vector3Value") then
6013 cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value.X, clusterValues.Value.Y, clusterValues.Value.Z)
6014 else
6015 cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value, 0, 0)
6016 end
6017
6018 local autoWedgeClusterParts = false
6019 if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
6020
6021 -- auto-wedge it
6022 if (autoWedgeClusterParts) then
6023 game:GetService("Workspace").Terrain:AutowedgeCells(
6024 Region3int16.new(
6025 Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
6026 Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)
6027 )
6028 )
6029 end
6030
6031 -- kill the ghost part
6032 stampData.CurrentParts.Parent = nil
6033
6034 -- Mark for undo. It has to happen here or the selection display will come back also.
6035 pcall(function() game:GetService("ChangeHistoryService"):SetWaypoint("StamperSingle") end)
6036 return true
6037 end
6038 else
6039 -- you tried to stamp a HSL-single part where one does not belong!
6040 flashRedBox()
6041 return false
6042 end
6043 end
6044
6045 local function getPlayer()
6046 if game:GetService("Players")["LocalPlayer"] then
6047 return game:GetService("Players").LocalPlayer
6048 end
6049 return nil
6050 end
6051
6052
6053 -- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints
6054 if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
6055 if stampData.CurrentParts:IsA("Model") then
6056 -- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X<
6057 local manualWeldTable = {}
6058 local manualWeldParentTable = {}
6059 saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable)
6060 stampData.CurrentParts:BreakJoints()
6061 stampData.CurrentParts:MakeJoints()
6062 restoreTheWelds(manualWeldTable, manualWeldParentTable)
6063 end
6064
6065 -- 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)
6066 local playerIdTag = stampData.CurrentParts:FindFirstChild("PlayerIdTag")
6067 local playerNameTag = stampData.CurrentParts:FindFirstChild("PlayerNameTag")
6068 if playerIdTag ~= nil then
6069 local tempPlayerValue = getPlayer()
6070 if tempPlayerValue ~= nil then playerIdTag.Value = tempPlayerValue.UserId end
6071 end
6072 if playerNameTag ~= nil then
6073 if game:GetService("Players")["LocalPlayer"] then
6074 local tempPlayerValue = game:GetService("Players").LocalPlayer
6075 if tempPlayerValue ~= nil then playerNameTag.Value = tempPlayerValue.Name end
6076 end
6077 end
6078 -- ...and tag all inserted models for subsequent origin identification
6079 -- if no RobloxModel tag already exists, then add it.
6080 if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
6081 local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
6082 stringTag.Name = "RobloxModel"
6083
6084 if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
6085 local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
6086 stringTag2.Name = "RobloxStamper"
6087 end
6088 end
6089
6090 else
6091 stampData.CurrentParts:BreakJoints()
6092 if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
6093 local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
6094 stringTag2.Name = "RobloxStamper"
6095 end
6096 end
6097
6098 -- make sure all the joints are activated before restoring anchor states
6099 game:GetService("JointsService"):CreateJoinAfterMoveJoints()
6100
6101 -- Restore the original properties for all parts being stamped
6102 for part, transparency in pairs(stampData.TransparencyTable) do
6103 part.Transparency = transparency
6104 end
6105 for part, archivable in pairs(stampData.ArchivableTable) do
6106 part.Archivable = archivable
6107 end
6108 for part, material in pairs(stampData.MaterialTable) do
6109 part.Material = material
6110 end
6111 for part, collide in pairs(stampData.CanCollideTable) do
6112 part.CanCollide = collide
6113 end
6114 for part, anchored in pairs(stampData.AnchoredTable) do
6115 part.Anchored = anchored
6116 end
6117 for decal, transparency in pairs(stampData.DecalTransparencyTable) do
6118 decal.Transparency = transparency
6119 end
6120
6121 for part, surfaces in pairs(stampData.SurfaceTypeTable) do
6122 loadSurfaceTypes(part, surfaces)
6123 end
6124
6125 if isMegaClusterPart() then
6126 stampData.CurrentParts.Transparency = 0
6127 end
6128
6129 -- re-enable all seats
6130 setSeatEnabledStatus(stampData.CurrentParts, true)
6131
6132 stampData.TransparencyTable = nil
6133 stampData.ArchivableTable = nil
6134 stampData.MaterialTable = nil
6135 stampData.CanCollideTable = nil
6136 stampData.AnchoredTable = nil
6137 stampData.SurfaceTypeTable = nil
6138
6139 -- ...and tag all inserted models for subsequent origin identification
6140 -- if no RobloxModel tag already exists, then add it.
6141 if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
6142 local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
6143 stringTag.Name = "RobloxModel"
6144 end
6145
6146 --Re-enable the scripts
6147 for index,script in pairs(stampData.DisabledScripts) do
6148 script.Disabled = false
6149 end
6150
6151 --Now that they are all marked enabled, reinsert them into the world so they start running
6152 for index,script in pairs(stampData.DisabledScripts) do
6153 local oldParent = script.Parent
6154 script.Parent = nil
6155 script:Clone().Parent = oldParent
6156 end
6157
6158 -- clear out more data
6159 stampData.DisabledScripts = nil
6160 stampData.Dragger = nil
6161 stampData.CurrentParts = nil
6162
6163 pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StampedObject") end)
6164 return true
6165 end
6166
6167 local function pauseStamper()
6168 for i = 1, #mouseCons do -- stop the mouse from doing anything
6169 mouseCons[i]:disconnect()
6170 mouseCons[i] = nil
6171 end
6172 mouseCons = {}
6173
6174 if stampData and stampData.CurrentParts then -- remove our ghost part
6175 stampData.CurrentParts.Parent = nil
6176 stampData.CurrentParts:Remove()
6177 end
6178
6179 resetHighScalabilityLine()
6180
6181 game:GetService("JointsService"):ClearJoinAfterMoveJoints()
6182 end
6183
6184
6185 local function prepareUnjoinableSurfaces(modelCFrame, parts, whichSurface)
6186 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!
6187 local isPositive = 1
6188 if whichSurface < 0 then isPositive = isPositive * -1 whichSurface = whichSurface*-1 end
6189 local surfaceNormal = isPositive * modelCFrame:vectorToWorldSpace(AXIS_VECTORS[whichSurface])
6190
6191 for i = 1, #parts do
6192 local currPart = parts[i]
6193
6194 -- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
6195 local surfaceNormalInLocalCoords = currPart.CFrame:vectorToObjectSpace(surfaceNormal)
6196 if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Y) then
6197 if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Z) then
6198 if surfaceNormalInLocalCoords.X > 0 then currPart.RightSurface = "Unjoinable" else currPart.LeftSurface = "Unjoinable" end
6199 else
6200 if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
6201 end
6202 else
6203 if math.abs(surfaceNormalInLocalCoords.Y) > math.abs(surfaceNormalInLocalCoords.Z) then
6204 if surfaceNormalInLocalCoords.Y > 0 then currPart.TopSurface = "Unjoinable" else currPart.BottomSurface = "Unjoinable" end
6205 else
6206 if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
6207 end
6208 end
6209 end
6210 end
6211
6212 local function resumeStamper()
6213 local clone, parts = prepareModel(modelToStamp)
6214
6215 if not clone or not parts then
6216 return
6217 end
6218
6219 -- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
6220 local unjoinableTag = clone:FindFirstChild("UnjoinableFaces", true)
6221 if unjoinableTag then
6222 for unjoinableSurface in string.gmatch(unjoinableTag.Value, "[^,]*") do
6223 if tonumber(unjoinableSurface) then
6224 if clone:IsA("Model") then
6225 prepareUnjoinableSurfaces(clone:GetModelCFrame(), parts, tonumber(unjoinableSurface))
6226 else
6227 prepareUnjoinableSurfaces(clone.CFrame, parts, tonumber(unjoinableSurface))
6228 end
6229 end
6230 end
6231 end
6232
6233 stampData.ErrorBox = errorBox
6234 if stampInModel then
6235 clone.Parent = stampInModel
6236 else
6237 clone.Parent = game:GetService("Workspace")
6238 end
6239
6240 if clone:FindFirstChild("ClusterMaterial", true) then -- extract all info from vector
6241 local clusterMaterial = clone:FindFirstChild("ClusterMaterial", true)
6242 if (clusterMaterial:IsA("Vector3Value")) then
6243 cellInfo.Material = clusterMaterial.Value.X
6244 cellInfo.clusterType = clusterMaterial.Value.Y
6245 cellInfo.clusterOrientation = clusterMaterial.Value.Z
6246 elseif clusterMaterial:IsA("IntValue") then
6247 cellInfo.Material = clusterMaterial.Value
6248 end
6249 end
6250
6251 pcall(function() mouseTarget = Mouse.Target end)
6252
6253 if mouseTarget and mouseTarget.Parent:FindFirstChild("RobloxModel") == nil then
6254 game:GetService("JointsService"):SetJoinAfterMoveTarget(mouseTarget)
6255 else
6256 game:GetService("JointsService"):SetJoinAfterMoveTarget(nil)
6257 end
6258 game:GetService("JointsService"):ShowPermissibleJoints()
6259
6260 for index, object in pairs(stampData.DisabledScripts) do
6261 if object.Name == "GhostRemovalScript" then
6262 object.Parent = stampData.CurrentParts
6263 end
6264 end
6265
6266 stampData.Dragger = Instance.new("Dragger")
6267
6268 --Begin a movement by faking a MouseDown signal
6269 stampData.Dragger:MouseDown(parts[1], Vector3.new(0,0,0), parts)
6270 stampData.Dragger:MouseUp()
6271
6272 DoStamperMouseMove(Mouse)
6273
6274 table.insert(mouseCons,Mouse.Move:connect(function()
6275 if movingLock or stampUpLock then return end
6276 movingLock = true
6277 DoStamperMouseMove(Mouse)
6278 movingLock = false
6279 end))
6280
6281 table.insert(mouseCons,Mouse.Button1Down:connect(function()
6282 DoStamperMouseDown(Mouse)
6283 end))
6284
6285 table.insert(mouseCons,Mouse.Button1Up:connect(function()
6286 stampUpLock = true
6287 while movingLock do wait() end
6288 stamped.Value = DoStamperMouseUp(Mouse)
6289 resetHighScalabilityLine()
6290 stampUpLock = false
6291 end))
6292
6293 stamped.Value = false
6294 end
6295
6296 local function resetStamperState(newModelToStamp)
6297
6298 -- if we have a new model, swap it out
6299 if newModelToStamp then
6300 if not newModelToStamp:IsA("Model") and not newModelToStamp:IsA("BasePart") then
6301 error("resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!")
6302 end
6303 modelToStamp = newModelToStamp
6304 end
6305
6306 -- first clear our state
6307 pauseStamper()
6308 -- now lets load in the new model
6309 resumeStamper()
6310
6311 end
6312
6313 -- load the model initially
6314 resetStamperState()
6315
6316
6317 -- setup the control table we pass back to the user
6318 control.Stamped = stamped -- BoolValue that fires when user stamps
6319 control.Paused = false
6320
6321 control.LoadNewModel = function(newStampModel) -- allows us to specify a new stamper model to be used with this stamper
6322 if newStampModel and not newStampModel:IsA("Model") and not newStampModel:IsA("BasePart") then
6323 error("Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!")
6324 return nil
6325 end
6326 resetStamperState(newStampModel)
6327 end
6328
6329 control.ReloadModel = function() -- will automatically set stamper to get a new model of current model and start stamping with new model
6330 resetStamperState()
6331 end
6332
6333 control.Pause = function() -- temporarily stops stamping, use resume to start up again
6334 if not control.Paused then
6335 pauseStamper()
6336 control.Paused = true
6337 else
6338 print("RbxStamper Warning: Tried to call Control.Pause() when already paused")
6339 end
6340 end
6341
6342 control.Resume = function() -- resumes stamping, if currently paused
6343 if control.Paused then
6344 resumeStamper()
6345 control.Paused = false
6346 else
6347 print("RbxStamper Warning: Tried to call Control.Resume() without Pausing First")
6348 end
6349 end
6350
6351 control.ResetRotation = function() -- resets the model rotation so new models are at default orientation
6352 -- gInitial90DegreeRotations = 0
6353 -- 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
6354 -- High-Scalability and with the new model orientation setting methods (model:ResetOrientationToIdentity()) [HotThoth]
6355 end
6356
6357 control.Destroy = function() -- Stops current Stamp operation and destroys control construct
6358 for i = 1, #mouseCons do
6359 mouseCons[i]:disconnect()
6360 mouseCons[i] = nil
6361 end
6362
6363 if keyCon then
6364 keyCon:disconnect()
6365 end
6366
6367 game:GetService("JointsService"):ClearJoinAfterMoveJoints()
6368
6369 if adorn then adorn:Destroy() end
6370 if adornPart then adornPart:Destroy() end
6371 if errorBox then errorBox:Destroy() end
6372 if stampData then
6373 if stampData["Dragger"] then
6374 stampData.Dragger:Destroy()
6375 end
6376 if stampData.CurrentParts then
6377 stampData.CurrentParts:Destroy()
6378 end
6379 end
6380 if control and control["Stamped"] then
6381 control.Stamped:Destroy()
6382 end
6383 control = nil
6384 end
6385
6386 return control
6387end
6388
6389t.Help =
6390 function(funcNameOrFunc)
6391 --input argument can be a string or a function. Should return a description (of arguments and expected side effects)
6392 if funcNameOrFunc == "GetStampModel" or funcNameOrFunc == t.GetStampModel then
6393 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"
6394 end
6395 if funcNameOrFunc == "SetupStamperDragger" or funcNameOrFunc == t.SetupStamperDragger then
6396 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."
6397 end
6398 end
6399
6400------------------------------------------------------------------------------------------------------------------------
6401------------------------------------------------------------------------------------------------------------------------
6402------------------------------------------------------------------------------------------------------------------------
6403------------------------------------------------JSON Functions Begin----------------------------------------------------
6404------------------------------------------------------------------------------------------------------------------------
6405------------------------------------------------------------------------------------------------------------------------
6406------------------------------------------------------------------------------------------------------------------------
6407
6408 --JSON Encoder and Parser for Lua 5.1
6409 --
6410 --Copyright 2007 Shaun Brown (http://www.chipmunkav.com)
6411 --All Rights Reserved.
6412
6413 --Permission is hereby granted, free of charge, to any person
6414 --obtaining a copy of this software to deal in the Software without
6415 --restriction, including without limitation the rights to use,
6416 --copy, modify, merge, publish, distribute, sublicense, and/or
6417 --sell copies of the Software, and to permit persons to whom the
6418 --Software is furnished to do so, subject to the following conditions:
6419
6420 --The above copyright notice and this permission notice shall be
6421 --included in all copies or substantial portions of the Software.
6422 --If you find this software useful please give www.chipmunkav.com a mention.
6423
6424 --THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
6425 --EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
6426 --OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
6427 --IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
6428 --ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
6429 --CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
6430 --CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6431
6432local string = string
6433local math = math
6434local table = table
6435local error = error
6436local tonumber = tonumber
6437local tostring = tostring
6438local type = type
6439local setmetatable = setmetatable
6440local pairs = pairs
6441local ipairs = ipairs
6442local assert = assert
6443
6444
6445local StringBuilder = {
6446 buffer = {}
6447}
6448
6449function StringBuilder:New()
6450 local o = {}
6451 setmetatable(o, self)
6452 self.__index = self
6453 o.buffer = {}
6454 return o
6455end
6456
6457function StringBuilder:Append(s)
6458 self.buffer[#self.buffer+1] = s
6459end
6460
6461function StringBuilder:ToString()
6462 return table.concat(self.buffer)
6463end
6464
6465local JsonWriter = {
6466 backslashes = {
6467 ['\b'] = "\\b",
6468 ['\t'] = "\\t",
6469 ['\n'] = "\\n",
6470 ['\f'] = "\\f",
6471 ['\r'] = "\\r",
6472 ['"'] = "\\\"",
6473 ['\\'] = "\\\\",
6474 ['/'] = "\\/"
6475 }
6476}
6477
6478function JsonWriter:New()
6479 local o = {}
6480 o.writer = StringBuilder:New()
6481 setmetatable(o, self)
6482 self.__index = self
6483 return o
6484end
6485
6486function JsonWriter:Append(s)
6487 self.writer:Append(s)
6488end
6489
6490function JsonWriter:ToString()
6491 return self.writer:ToString()
6492end
6493
6494function JsonWriter:Write(o)
6495 local t = type(o)
6496 if t == "nil" then
6497 self:WriteNil()
6498 elseif t == "boolean" then
6499 self:WriteString(o)
6500 elseif t == "number" then
6501 self:WriteString(o)
6502 elseif t == "string" then
6503 self:ParseString(o)
6504 elseif t == "table" then
6505 self:WriteTable(o)
6506 elseif t == "function" then
6507 self:WriteFunction(o)
6508 elseif t == "thread" then
6509 self:WriteError(o)
6510 elseif t == "userdata" then
6511 self:WriteError(o)
6512 end
6513end
6514
6515function JsonWriter:WriteNil()
6516 self:Append("null")
6517end
6518
6519function JsonWriter:WriteString(o)
6520 self:Append(tostring(o))
6521end
6522
6523function JsonWriter:ParseString(s)
6524 self:Append('"')
6525 self:Append(string.gsub(s, "[%z%c\\\"/]", function(n)
6526 local c = self.backslashes[n]
6527 if c then return c end
6528 return string.format("\\u%.4X", string.byte(n))
6529 end))
6530 self:Append('"')
6531end
6532
6533function JsonWriter:IsArray(t)
6534 local count = 0
6535 local isindex = function(k)
6536 if type(k) == "number" and k > 0 then
6537 if math.floor(k) == k then
6538 return true
6539 end
6540 end
6541 return false
6542 end
6543 for k,v in pairs(t) do
6544 if not isindex(k) then
6545 return false, '{', '}'
6546 else
6547 count = math.max(count, k)
6548 end
6549 end
6550 return true, '[', ']', count
6551end
6552
6553function JsonWriter:WriteTable(t)
6554 local ba, st, et, n = self:IsArray(t)
6555 self:Append(st)
6556 if ba then
6557 for i = 1, n do
6558 self:Write(t[i])
6559 if i < n then
6560 self:Append(',')
6561 end
6562 end
6563 else
6564 local first = true;
6565 for k, v in pairs(t) do
6566 if not first then
6567 self:Append(',')
6568 end
6569 first = false;
6570 self:ParseString(k)
6571 self:Append(':')
6572 self:Write(v)
6573 end
6574 end
6575 self:Append(et)
6576end
6577
6578function JsonWriter:WriteError(o)
6579 error(string.format(
6580 "Encoding of %s unsupported",
6581 tostring(o)))
6582end
6583
6584function JsonWriter:WriteFunction(o)
6585 if o == Null then
6586 self:WriteNil()
6587 else
6588 self:WriteError(o)
6589 end
6590end
6591
6592local StringReader = {
6593 s = "",
6594 i = 0
6595}
6596
6597function StringReader:New(s)
6598 local o = {}
6599 setmetatable(o, self)
6600 self.__index = self
6601 o.s = s or o.s
6602 return o
6603end
6604
6605function StringReader:Peek()
6606 local i = self.i + 1
6607 if i <= #self.s then
6608 return string.sub(self.s, i, i)
6609 end
6610 return nil
6611end
6612
6613function StringReader:Next()
6614 self.i = self.i+1
6615 if self.i <= #self.s then
6616 return string.sub(self.s, self.i, self.i)
6617 end
6618 return nil
6619end
6620
6621function StringReader:All()
6622 return self.s
6623end
6624
6625local JsonReader = {
6626 escapes = {
6627 ['t'] = '\t',
6628 ['n'] = '\n',
6629 ['f'] = '\f',
6630 ['r'] = '\r',
6631 ['b'] = '\b',
6632 }
6633}
6634
6635function JsonReader:New(s)
6636 local o = {}
6637 o.reader = StringReader:New(s)
6638 setmetatable(o, self)
6639 self.__index = self
6640 return o;
6641end
6642
6643function JsonReader:Read()
6644 self:SkipWhiteSpace()
6645 local peek = self:Peek()
6646 if peek == nil then
6647 error(string.format(
6648 "Nil string: '%s'",
6649 self:All()))
6650 elseif peek == '{' then
6651 return self:ReadObject()
6652 elseif peek == '[' then
6653 return self:ReadArray()
6654 elseif peek == '"' then
6655 return self:ReadString()
6656 elseif string.find(peek, "[%+%-%d]") then
6657 return self:ReadNumber()
6658 elseif peek == 't' then
6659 return self:ReadTrue()
6660 elseif peek == 'f' then
6661 return self:ReadFalse()
6662 elseif peek == 'n' then
6663 return self:ReadNull()
6664 elseif peek == '/' then
6665 self:ReadComment()
6666 return self:Read()
6667 else
6668 return nil
6669 end
6670end
6671
6672function JsonReader:ReadTrue()
6673 self:TestReservedWord{'t','r','u','e'}
6674 return true
6675end
6676
6677function JsonReader:ReadFalse()
6678 self:TestReservedWord{'f','a','l','s','e'}
6679 return false
6680end
6681
6682function JsonReader:ReadNull()
6683 self:TestReservedWord{'n','u','l','l'}
6684 return nil
6685end
6686
6687function JsonReader:TestReservedWord(t)
6688 for i, v in ipairs(t) do
6689 if self:Next() ~= v then
6690 error(string.format(
6691 "Error reading '%s': %s",
6692 table.concat(t),
6693 self:All()))
6694 end
6695 end
6696end
6697
6698function JsonReader:ReadNumber()
6699 local result = self:Next()
6700 local peek = self:Peek()
6701 while peek ~= nil and string.find(
6702 peek,
6703 "[%+%-%d%.eE]") do
6704 result = result .. self:Next()
6705 peek = self:Peek()
6706 end
6707 result = tonumber(result)
6708 if result == nil then
6709 error(string.format(
6710 "Invalid number: '%s'",
6711 result))
6712 else
6713 return result
6714 end
6715end
6716
6717function JsonReader:ReadString()
6718 local result = ""
6719 assert(self:Next() == '"')
6720 while self:Peek() ~= '"' do
6721 local ch = self:Next()
6722 if ch == '\\' then
6723 ch = self:Next()
6724 if self.escapes[ch] then
6725 ch = self.escapes[ch]
6726 end
6727 end
6728 result = result .. ch
6729 end
6730 assert(self:Next() == '"')
6731 local fromunicode = function(m)
6732 return string.char(tonumber(m, 16))
6733 end
6734 return string.gsub(
6735 result,
6736 "u%x%x(%x%x)",
6737 fromunicode)
6738end
6739
6740function JsonReader:ReadComment()
6741 assert(self:Next() == '/')
6742 local second = self:Next()
6743 if second == '/' then
6744 self:ReadSingleLineComment()
6745 elseif second == '*' then
6746 self:ReadBlockComment()
6747 else
6748 error(string.format(
6749 "Invalid comment: %s",
6750 self:All()))
6751 end
6752end
6753
6754function JsonReader:ReadBlockComment()
6755 local done = false
6756 while not done do
6757 local ch = self:Next()
6758 if ch == '*' and self:Peek() == '/' then
6759 done = true
6760 end
6761 if not done and
6762 ch == '/' and
6763 self:Peek() == "*" then
6764 error(string.format(
6765 "Invalid comment: %s, '/*' illegal.",
6766 self:All()))
6767 end
6768 end
6769 self:Next()
6770end
6771
6772function JsonReader:ReadSingleLineComment()
6773 local ch = self:Next()
6774 while ch ~= '\r' and ch ~= '\n' do
6775 ch = self:Next()
6776 end
6777end
6778
6779function JsonReader:ReadArray()
6780 local result = {}
6781 assert(self:Next() == '[')
6782 local done = false
6783 if self:Peek() == ']' then
6784 done = true;
6785 end
6786 while not done do
6787 local item = self:Read()
6788 result[#result+1] = item
6789 self:SkipWhiteSpace()
6790 if self:Peek() == ']' then
6791 done = true
6792 end
6793 if not done then
6794 local ch = self:Next()
6795 if ch ~= ',' then
6796 error(string.format(
6797 "Invalid array: '%s' due to: '%s'",
6798 self:All(), ch))
6799 end
6800 end
6801 end
6802 assert(']' == self:Next())
6803 return result
6804end
6805
6806function JsonReader:ReadObject()
6807 local result = {}
6808 assert(self:Next() == '{')
6809 local done = false
6810 if self:Peek() == '}' then
6811 done = true
6812 end
6813 while not done do
6814 local key = self:Read()
6815 if type(key) ~= "string" then
6816 error(string.format(
6817 "Invalid non-string object key: %s",
6818 key))
6819 end
6820 self:SkipWhiteSpace()
6821 local ch = self:Next()
6822 if ch ~= ':' then
6823 error(string.format(
6824 "Invalid object: '%s' due to: '%s'",
6825 self:All(),
6826 ch))
6827 end
6828 self:SkipWhiteSpace()
6829 local val = self:Read()
6830 result[key] = val
6831 self:SkipWhiteSpace()
6832 if self:Peek() == '}' then
6833 done = true
6834 end
6835 if not done then
6836 ch = self:Next()
6837 if ch ~= ',' then
6838 error(string.format(
6839 "Invalid array: '%s' near: '%s'",
6840 self:All(),
6841 ch))
6842 end
6843 end
6844 end
6845 assert(self:Next() == "}")
6846 return result
6847end
6848
6849function JsonReader:SkipWhiteSpace()
6850 local p = self:Peek()
6851 while p ~= nil and string.find(p, "[%s/]") do
6852 if p == '/' then
6853 self:ReadComment()
6854 else
6855 self:Next()
6856 end
6857 p = self:Peek()
6858 end
6859end
6860
6861function JsonReader:Peek()
6862 return self.reader:Peek()
6863end
6864
6865function JsonReader:Next()
6866 return self.reader:Next()
6867end
6868
6869function JsonReader:All()
6870 return self.reader:All()
6871end
6872
6873function Encode(o)
6874 local writer = JsonWriter:New()
6875 writer:Write(o)
6876 return writer:ToString()
6877end
6878
6879function Decode(s)
6880 local reader = JsonReader:New(s)
6881 return reader:Read()
6882end
6883
6884function Null()
6885 return Null
6886end
6887-------------------- End JSON Parser ------------------------
6888
6889t.DecodeJSON = function(jsonString)
6890 pcall(function() warn("RbxUtility.DecodeJSON is deprecated, please use Game:GetService('HttpService'):JSONDecode() instead.") end)
6891
6892 if type(jsonString) == "string" then
6893 return Decode(jsonString)
6894 end
6895 print("RbxUtil.DecodeJSON expects string argument!")
6896 return nil
6897end
6898
6899t.EncodeJSON = function(jsonTable)
6900 pcall(function() warn("RbxUtility.EncodeJSON is deprecated, please use Game:GetService('HttpService'):JSONEncode() instead.") end)
6901 return Encode(jsonTable)
6902end
6903
6904
6905
6906
6907
6908
6909
6910
6911------------------------------------------------------------------------------------------------------------------------
6912------------------------------------------------------------------------------------------------------------------------
6913------------------------------------------------------------------------------------------------------------------------
6914--------------------------------------------Terrain Utilities Begin-----------------------------------------------------
6915------------------------------------------------------------------------------------------------------------------------
6916------------------------------------------------------------------------------------------------------------------------
6917------------------------------------------------------------------------------------------------------------------------
6918--makes a wedge at location x, y, z
6919--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
6920--returns true if made a wedge, false if the cell remains a block
6921t.MakeWedge = function(x, y, z, defaultmaterial)
6922 return game:GetService("Terrain"):AutoWedgeCell(x,y,z)
6923end
6924
6925t.SelectTerrainRegion = function(regionToSelect, color, selectEmptyCells, selectionParent)
6926 local terrain = game:GetService("Workspace"):FindFirstChild("Terrain")
6927 if not terrain then return end
6928
6929 assert(regionToSelect)
6930 assert(color)
6931
6932 if not type(regionToSelect) == "Region3" then
6933 error("regionToSelect (first arg), should be of type Region3, but is type",type(regionToSelect))
6934 end
6935 if not type(color) == "BrickColor" then
6936 error("color (second arg), should be of type BrickColor, but is type",type(color))
6937 end
6938
6939 -- frequently used terrain calls (speeds up call, no lookup necessary)
6940 local GetCell = terrain.GetCell
6941 local WorldToCellPreferSolid = terrain.WorldToCellPreferSolid
6942 local CellCenterToWorld = terrain.CellCenterToWorld
6943 local emptyMaterial = Enum.CellMaterial.Empty
6944
6945 -- container for all adornments, passed back to user
6946 local selectionContainer = Instance.new("Model")
6947 selectionContainer.Name = "SelectionContainer"
6948 selectionContainer.Archivable = false
6949 if selectionParent then
6950 selectionContainer.Parent = selectionParent
6951 else
6952 selectionContainer.Parent = game:GetService("Workspace")
6953 end
6954
6955 local updateSelection = nil -- function we return to allow user to update selection
6956 local currentKeepAliveTag = nil -- a tag that determines whether adorns should be destroyed
6957 local aliveCounter = 0 -- helper for currentKeepAliveTag
6958 local lastRegion = nil -- used to stop updates that do nothing
6959 local adornments = {} -- contains all adornments
6960 local reusableAdorns = {}
6961
6962 local selectionPart = Instance.new("Part")
6963 selectionPart.Name = "SelectionPart"
6964 selectionPart.Transparency = 1
6965 selectionPart.Anchored = true
6966 selectionPart.Locked = true
6967 selectionPart.CanCollide = false
6968 selectionPart.Size = Vector3.new(4.2,4.2,4.2)
6969
6970 local selectionBox = Instance.new("SelectionBox")
6971
6972 -- srs translation from region3 to region3int16
6973 local function Region3ToRegion3int16(region3)
6974 local theLowVec = region3.CFrame.p - (region3.Size/2) + Vector3.new(2,2,2)
6975 local lowCell = WorldToCellPreferSolid(terrain,theLowVec)
6976
6977 local theHighVec = region3.CFrame.p + (region3.Size/2) - Vector3.new(2,2,2)
6978 local highCell = WorldToCellPreferSolid(terrain, theHighVec)
6979
6980 local highIntVec = Vector3int16.new(highCell.x,highCell.y,highCell.z)
6981 local lowIntVec = Vector3int16.new(lowCell.x,lowCell.y,lowCell.z)
6982
6983 return Region3int16.new(lowIntVec,highIntVec)
6984 end
6985
6986 -- helper function that creates the basis for a selection box
6987 function createAdornment(theColor)
6988 local selectionPartClone = nil
6989 local selectionBoxClone = nil
6990
6991 if #reusableAdorns > 0 then
6992 selectionPartClone = reusableAdorns[1]["part"]
6993 selectionBoxClone = reusableAdorns[1]["box"]
6994 table.remove(reusableAdorns,1)
6995
6996 selectionBoxClone.Visible = true
6997 else
6998 selectionPartClone = selectionPart:Clone()
6999 selectionPartClone.Archivable = false
7000
7001 selectionBoxClone = selectionBox:Clone()
7002 selectionBoxClone.Archivable = false
7003
7004 selectionBoxClone.Adornee = selectionPartClone
7005 selectionBoxClone.Parent = selectionContainer
7006
7007 selectionBoxClone.Adornee = selectionPartClone
7008
7009 selectionBoxClone.Parent = selectionContainer
7010 end
7011
7012 if theColor then
7013 selectionBoxClone.Color = theColor
7014 end
7015
7016 return selectionPartClone, selectionBoxClone
7017 end
7018
7019 -- iterates through all current adornments and deletes any that don't have latest tag
7020 function cleanUpAdornments()
7021 for cellPos, adornTable in pairs(adornments) do
7022
7023 if adornTable.KeepAlive ~= currentKeepAliveTag then -- old news, we should get rid of this
7024 adornTable.SelectionBox.Visible = false
7025 table.insert(reusableAdorns,{part = adornTable.SelectionPart, box = adornTable.SelectionBox})
7026 adornments[cellPos] = nil
7027 end
7028 end
7029 end
7030
7031 -- helper function to update tag
7032 function incrementAliveCounter()
7033 aliveCounter = aliveCounter + 1
7034 if aliveCounter > 1000000 then
7035 aliveCounter = 0
7036 end
7037 return aliveCounter
7038 end
7039
7040 -- finds full cells in region and adorns each cell with a box, with the argument color
7041 function adornFullCellsInRegion(region, color)
7042 local regionBegin = region.CFrame.p - (region.Size/2) + Vector3.new(2,2,2)
7043 local regionEnd = region.CFrame.p + (region.Size/2) - Vector3.new(2,2,2)
7044
7045 local cellPosBegin = WorldToCellPreferSolid(terrain, regionBegin)
7046 local cellPosEnd = WorldToCellPreferSolid(terrain, regionEnd)
7047
7048 currentKeepAliveTag = incrementAliveCounter()
7049 for y = cellPosBegin.y, cellPosEnd.y do
7050 for z = cellPosBegin.z, cellPosEnd.z do
7051 for x = cellPosBegin.x, cellPosEnd.x do
7052 local cellMaterial = GetCell(terrain, x, y, z)
7053
7054 if cellMaterial ~= emptyMaterial then
7055 local cframePos = CellCenterToWorld(terrain, x, y, z)
7056 local cellPos = Vector3int16.new(x,y,z)
7057
7058 local updated = false
7059 for cellPosAdorn, adornTable in pairs(adornments) do
7060 if cellPosAdorn == cellPos then
7061 adornTable.KeepAlive = currentKeepAliveTag
7062 if color then
7063 adornTable.SelectionBox.Color = color
7064 end
7065 updated = true
7066 break
7067 end
7068 end
7069
7070 if not updated then
7071 local selectionPart, selectionBox = createAdornment(color)
7072 selectionPart.Size = Vector3.new(4,4,4)
7073 selectionPart.CFrame = CFrame.new(cframePos)
7074 local adornTable = {SelectionPart = selectionPart, SelectionBox = selectionBox, KeepAlive = currentKeepAliveTag}
7075 adornments[cellPos] = adornTable
7076 end
7077 end
7078 end
7079 end
7080 end
7081 cleanUpAdornments()
7082 end
7083
7084
7085 ------------------------------------- setup code ------------------------------
7086 lastRegion = regionToSelect
7087
7088 if selectEmptyCells then -- use one big selection to represent the area selected
7089 local selectionPart, selectionBox = createAdornment(color)
7090
7091 selectionPart.Size = regionToSelect.Size
7092 selectionPart.CFrame = regionToSelect.CFrame
7093
7094 adornments.SelectionPart = selectionPart
7095 adornments.SelectionBox = selectionBox
7096
7097 updateSelection =
7098 function (newRegion, color)
7099 if newRegion and newRegion ~= lastRegion then
7100 lastRegion = newRegion
7101 selectionPart.Size = newRegion.Size
7102 selectionPart.CFrame = newRegion.CFrame
7103 end
7104 if color then
7105 selectionBox.Color = color
7106 end
7107 end
7108 else -- use individual cell adorns to represent the area selected
7109 adornFullCellsInRegion(regionToSelect, color)
7110 updateSelection =
7111 function (newRegion, color)
7112 if newRegion and newRegion ~= lastRegion then
7113 lastRegion = newRegion
7114 adornFullCellsInRegion(newRegion, color)
7115 end
7116 end
7117
7118 end
7119
7120 local destroyFunc = function()
7121 updateSelection = nil
7122 if selectionContainer then selectionContainer:Destroy() end
7123 adornments = nil
7124 end
7125
7126 return updateSelection, destroyFunc
7127end
7128
7129-----------------------------Terrain Utilities End-----------------------------
7130
7131
7132
7133
7134
7135
7136
7137------------------------------------------------------------------------------------------------------------------------
7138------------------------------------------------------------------------------------------------------------------------
7139------------------------------------------------------------------------------------------------------------------------
7140------------------------------------------------Signal class begin------------------------------------------------------
7141------------------------------------------------------------------------------------------------------------------------
7142------------------------------------------------------------------------------------------------------------------------
7143------------------------------------------------------------------------------------------------------------------------
7144--[[
7145A 'Signal' object identical to the internal RBXScriptSignal object in it's public API and semantics. This function
7146can be used to create "custom events" for user-made code.
7147API:
7148Method :connect( function handler )
7149 Arguments: The function to connect to.
7150 Returns: A new connection object which can be used to disconnect the connection
7151 Description: Connects this signal to the function specified by |handler|. That is, when |fire( ... )| is called for
7152 the signal the |handler| will be called with the arguments given to |fire( ... )|. Note, the functions
7153 connected to a signal are called in NO PARTICULAR ORDER, so connecting one function after another does
7154 NOT mean that the first will be called before the second as a result of a call to |fire|.
7155
7156Method :disconnect()
7157 Arguments: None
7158 Returns: None
7159 Description: Disconnects all of the functions connected to this signal.
7160
7161Method :fire( ... )
7162 Arguments: Any arguments are accepted
7163 Returns: None
7164 Description: Calls all of the currently connected functions with the given arguments.
7165
7166Method :wait()
7167 Arguments: None
7168 Returns: The arguments given to fire
7169 Description: This call blocks until
7170]]
7171
7172function t.CreateSignal()
7173 local this = {}
7174
7175 local mBindableEvent = Instance.new('BindableEvent')
7176 local mAllCns = {} --all connection objects returned by mBindableEvent::connect
7177
7178 --main functions
7179 function this:connect(func)
7180 if self ~= this then error("connect must be called with `:`, not `.`", 2) end
7181 if type(func) ~= 'function' then
7182 error("Argument #1 of connect must be a function, got a "..type(func), 2)
7183 end
7184 local cn = mBindableEvent.Event:Connect(func)
7185 mAllCns[cn] = true
7186 local pubCn = {}
7187 function pubCn:disconnect()
7188 cn:Disconnect()
7189 mAllCns[cn] = nil
7190 end
7191 pubCn.Disconnect = pubCn.disconnect
7192
7193 return pubCn
7194 end
7195
7196 function this:disconnect()
7197 if self ~= this then error("disconnect must be called with `:`, not `.`", 2) end
7198 for cn, _ in pairs(mAllCns) do
7199 cn:Disconnect()
7200 mAllCns[cn] = nil
7201 end
7202 end
7203
7204 function this:wait()
7205 if self ~= this then error("wait must be called with `:`, not `.`", 2) end
7206 return mBindableEvent.Event:Wait()
7207 end
7208
7209 function this:fire(...)
7210 if self ~= this then error("fire must be called with `:`, not `.`", 2) end
7211 mBindableEvent:Fire(...)
7212 end
7213
7214 this.Connect = this.connect
7215 this.Disconnect = this.disconnect
7216 this.Wait = this.wait
7217 this.Fire = this.fire
7218
7219 return this
7220end
7221
7222------------------------------------------------- Sigal class End ------------------------------------------------------
7223
7224
7225
7226
7227------------------------------------------------------------------------------------------------------------------------
7228------------------------------------------------------------------------------------------------------------------------
7229------------------------------------------------------------------------------------------------------------------------
7230-----------------------------------------------Create Function Begins---------------------------------------------------
7231------------------------------------------------------------------------------------------------------------------------
7232------------------------------------------------------------------------------------------------------------------------
7233------------------------------------------------------------------------------------------------------------------------
7234--[[
7235A "Create" function for easy creation of Roblox instances. The function accepts a string which is the classname of
7236the object to be created. The function then returns another function which either accepts accepts no arguments, in
7237which case it simply creates an object of the given type, or a table argument that may contain several types of data,
7238in which case it mutates the object in varying ways depending on the nature of the aggregate data. These are the
7239type of data and what operation each will perform:
72401) A string key mapping to some value:
7241 Key-Value pairs in this form will be treated as properties of the object, and will be assigned in NO PARTICULAR
7242 ORDER. If the order in which properties is assigned matter, then they must be assigned somewhere else than the
7243 |Create| call's body.
7244
72452) An integral key mapping to another Instance:
7246 Normal numeric keys mapping to Instances will be treated as children if the object being created, and will be
7247 parented to it. This allows nice recursive calls to Create to create a whole hierarchy of objects without a
7248 need for temporary variables to store references to those objects.
7249
72503) A key which is a value returned from Create.Event( eventname ), and a value which is a function function
7251 The Create.E( string ) function provides a limited way to connect to signals inside of a Create hierarchy
7252 for those who really want such a functionality. The name of the event whose name is passed to
7253 Create.E( string )
7254
72554) A key which is the Create function itself, and a value which is a function
7256 The function will be run with the argument of the object itself after all other initialization of the object is
7257 done by create. This provides a way to do arbitrary things involving the object from withing the create
7258 hierarchy.
7259 Note: This function is called SYNCHRONOUSLY, that means that you should only so initialization in
7260 it, not stuff which requires waiting, as the Create call will block until it returns. While waiting in the
7261 constructor callback function is possible, it is probably not a good design choice.
7262 Note: Since the constructor function is called after all other initialization, a Create block cannot have two
7263 constructor functions, as it would not be possible to call both of them last, also, this would be unnecessary.
7264
7265
7266Some example usages:
7267
7268A simple example which uses the Create function to create a model object and assign two of it's properties.
7269local model = Create'Model'{
7270 Name = 'A New model',
7271 Parent = game.Workspace,
7272}
7273
7274
7275An example where a larger hierarchy of object is made. After the call the hierarchy will look like this:
7276Model_Container
7277 |-ObjectValue
7278 | |
7279 | `-BoolValueChild
7280 `-IntValue
7281
7282local model = Create'Model'{
7283 Name = 'Model_Container',
7284 Create'ObjectValue'{
7285 Create'BoolValue'{
7286 Name = 'BoolValueChild',
7287 },
7288 },
7289 Create'IntValue'{},
7290}
7291
7292
7293An example using the event syntax:
7294
7295local part = Create'Part'{
7296 [Create.E'Touched'] = function(part)
7297 print("I was touched by "..part.Name)
7298 end,
7299}
7300
7301
7302An example using the general constructor syntax:
7303
7304local model = Create'Part'{
7305 [Create] = function(this)
7306 print("Constructor running!")
7307 this.Name = GetGlobalFoosAndBars(this)
7308 end,
7309}
7310
7311
7312Note: It is also perfectly legal to save a reference to the function returned by a call Create, this will not cause
7313 any unexpected behavior. EG:
7314 local partCreatingFunction = Create'Part'
7315 local part = partCreatingFunction()
7316]]
7317
7318--the Create function need to be created as a functor, not a function, in order to support the Create.E syntax, so it
7319--will be created in several steps rather than as a single function declaration.
7320local function Create_PrivImpl(objectType)
7321 if type(objectType) ~= 'string' then
7322 error("Argument of Create must be a string", 2)
7323 end
7324 --return the proxy function that gives us the nice Create'string'{data} syntax
7325 --The first function call is a function call using Lua's single-string-argument syntax
7326 --The second function call is using Lua's single-table-argument syntax
7327 --Both can be chained together for the nice effect.
7328 return function(dat)
7329 --default to nothing, to handle the no argument given case
7330 dat = dat or {}
7331
7332 --make the object to mutate
7333 local obj = Instance.new(objectType)
7334 local parent = nil
7335
7336 --stored constructor function to be called after other initialization
7337 local ctor = nil
7338
7339 for k, v in pairs(dat) do
7340 --add property
7341 if type(k) == 'string' then
7342 if k == 'Parent' then
7343 -- Parent should always be set last, setting the Parent of a new object
7344 -- immediately makes performance worse for all subsequent property updates.
7345 parent = v
7346 else
7347 obj[k] = v
7348 end
7349
7350
7351 --add child
7352 elseif type(k) == 'number' then
7353 if type(v) ~= 'userdata' then
7354 error("Bad entry in Create body: Numeric keys must be paired with children, got a: "..type(v), 2)
7355 end
7356 v.Parent = obj
7357
7358
7359 --event connect
7360 elseif type(k) == 'table' and k.__eventname then
7361 if type(v) ~= 'function' then
7362 error("Bad entry in Create body: Key `[Create.E\'"..k.__eventname.."\']` must have a function value\
7363 got: "..tostring(v), 2)
7364 end
7365 obj[k.__eventname]:connect(v)
7366
7367
7368 --define constructor function
7369 elseif k == t.Create then
7370 if type(v) ~= 'function' then
7371 error("Bad entry in Create body: Key `[Create]` should be paired with a constructor function, \
7372 got: "..tostring(v), 2)
7373 elseif ctor then
7374 --ctor already exists, only one allowed
7375 error("Bad entry in Create body: Only one constructor function is allowed", 2)
7376 end
7377 ctor = v
7378
7379
7380 else
7381 error("Bad entry ("..tostring(k).." => "..tostring(v)..") in Create body", 2)
7382 end
7383 end
7384
7385 --apply constructor function if it exists
7386 if ctor then
7387 ctor(obj)
7388 end
7389
7390 if parent then
7391 obj.Parent = parent
7392 end
7393
7394 --return the completed object
7395 return obj
7396 end
7397end
7398
7399--now, create the functor:
7400t.Create = setmetatable({}, {__call = function(tb, ...) return Create_PrivImpl(...) end})
7401
7402--and create the "Event.E" syntax stub. Really it's just a stub to construct a table which our Create
7403--function can recognize as special.
7404t.Create.E = function(eventName)
7405 return {__eventname = eventName}
7406end
7407
7408-------------------------------------------------Create function End----------------------------------------------------
7409
7410
7411
7412
7413------------------------------------------------------------------------------------------------------------------------
7414------------------------------------------------------------------------------------------------------------------------
7415------------------------------------------------------------------------------------------------------------------------
7416------------------------------------------------Documentation Begin-----------------------------------------------------
7417------------------------------------------------------------------------------------------------------------------------
7418------------------------------------------------------------------------------------------------------------------------
7419------------------------------------------------------------------------------------------------------------------------
7420
7421t.Help =
7422 function(funcNameOrFunc)
7423 --input argument can be a string or a function. Should return a description (of arguments and expected side effects)
7424 if funcNameOrFunc == "DecodeJSON" or funcNameOrFunc == t.DecodeJSON then
7425 return "Function DecodeJSON. " ..
7426 "Arguments: (string). " ..
7427 "Side effect: returns a table with all parsed JSON values"
7428 end
7429 if funcNameOrFunc == "EncodeJSON" or funcNameOrFunc == t.EncodeJSON then
7430 return "Function EncodeJSON. " ..
7431 "Arguments: (table). " ..
7432 "Side effect: returns a string composed of argument table in JSON data format"
7433 end
7434 if funcNameOrFunc == "MakeWedge" or funcNameOrFunc == t.MakeWedge then
7435 return "Function MakeWedge. " ..
7436 "Arguments: (x, y, z, [default material]). " ..
7437 "Description: Makes a wedge at location x, y, z. Sets cell x, y, z to default material if "..
7438 "parameter is provided, if not sets cell x, y, z to be whatever material it previously was. "..
7439 "Returns true if made a wedge, false if the cell remains a block "
7440 end
7441 if funcNameOrFunc == "SelectTerrainRegion" or funcNameOrFunc == t.SelectTerrainRegion then
7442 return "Function SelectTerrainRegion. " ..
7443 "Arguments: (regionToSelect, color, selectEmptyCells, selectionParent). " ..
7444 "Description: Selects all terrain via a series of selection boxes within the regionToSelect " ..
7445 "(this should be a region3 value). The selection box color is detemined by the color argument " ..
7446 "(should be a brickcolor value). SelectionParent is the parent that the selection model gets placed to (optional)." ..
7447 "SelectEmptyCells is bool, when true will select all cells in the " ..
7448 "region, otherwise we only select non-empty cells. Returns a function that can update the selection," ..
7449 "arguments to said function are a new region3 to select, and the adornment color (color arg is optional). " ..
7450 "Also returns a second function that takes no arguments and destroys the selection"
7451 end
7452 if funcNameOrFunc == "CreateSignal" or funcNameOrFunc == t.CreateSignal then
7453 return "Function CreateSignal. "..
7454 "Arguments: None. "..
7455 "Returns: The newly created Signal object. This object is identical to the RBXScriptSignal class "..
7456 "used for events in Objects, but is a Lua-side object so it can be used to create custom events in"..
7457 "Lua code. "..
7458 "Methods of the Signal object: :connect, :wait, :fire, :disconnect. "..
7459 "For more info you can pass the method name to the Help function, or view the wiki page "..
7460 "for this library. EG: Help('Signal:connect')."
7461 end
7462 if funcNameOrFunc == "Signal:connect" then
7463 return "Method Signal:connect. "..
7464 "Arguments: (function handler). "..
7465 "Return: A connection object which can be used to disconnect the connection to this handler. "..
7466 "Description: Connectes a handler function to this Signal, so that when |fire| is called the "..
7467 "handler function will be called with the arguments passed to |fire|."
7468 end
7469 if funcNameOrFunc == "Signal:wait" then
7470 return "Method Signal:wait. "..
7471 "Arguments: None. "..
7472 "Returns: The arguments passed to the next call to |fire|. "..
7473 "Description: This call does not return until the next call to |fire| is made, at which point it "..
7474 "will return the values which were passed as arguments to that |fire| call."
7475 end
7476 if funcNameOrFunc == "Signal:fire" then
7477 return "Method Signal:fire. "..
7478 "Arguments: Any number of arguments of any type. "..
7479 "Returns: None. "..
7480 "Description: This call will invoke any connected handler functions, and notify any waiting code "..
7481 "attached to this Signal to continue, with the arguments passed to this function. Note: The calls "..
7482 "to handlers are made asynchronously, so this call will return immediately regardless of how long "..
7483 "it takes the connected handler functions to complete."
7484 end
7485 if funcNameOrFunc == "Signal:disconnect" then
7486 return "Method Signal:disconnect. "..
7487 "Arguments: None. "..
7488 "Returns: None. "..
7489 "Description: This call disconnects all handlers attacched to this function, note however, it "..
7490 "does NOT make waiting code continue, as is the behavior of normal Roblox events. This method "..
7491 "can also be called on the connection object which is returned from Signal:connect to only "..
7492 "disconnect a single handler, as opposed to this method, which will disconnect all handlers."
7493 endxdf df
7494 if funcNameOrFunc == "Create" then
7495 return "Function Create. "..
7496 "Arguments: A table containing information about how to construct a collection of objects. "..
7497 "Returns: The constructed objects. "..
7498 "Descrition: Create is a very powerfull function, whose description is too long to fit here, and "..
7499 "is best described via example, please see the wiki page for a description of how to use it."
7500 end
7501 end
7502
7503--------------------------------------------Documentation Ends----------------------------------------------------------
7504
7505return t