· 9 years ago · Dec 28, 2016, 10:40 PM
1-- Configuration variables
2config = {
3 button_font = Enum.Font.Legacy;
4 button_font_size = Enum.FontSize.Size10;
5 button_size_x = 80;
6 button_size_y = 20;
7 control_size = 16;
8 desc_padding = 40;
9 desc_width_max = 300;
10 menu_auto_collapse = true;
11 menu_indent = 16;
12 plugin_safe_mode = true;
13 shortcut_keys_enabled = true;
14 tool_menu_length = 8;
15 tool_sounds_enabled = true;
16 tween_panel_enabled = true;
17 tween_speed = 0.25;
18}
19
20-- Plugin locations
21plugins = {
22 -- add asset IDs or script locations here;
23-- 56563025; -- Circles by Anaminus
24}
25
26-- Key remapping
27shortcuts = {
28 ["Move.Axis"] = "r";
29 ["Move.AxisSnap"] = "";
30 ["Move.First"] = "t";
31 ["Move.FirstSnap"] = "";
32 ["Move.Object"] = "y";
33 ["Rotate.Object"] = "f";
34 ["Rotate.ObjectSnap"] = "";
35 ["Rotate.Pivot"] = "g";
36 ["Rotate.PivotSnap"] = "";
37 ["Rotate.Group"] = "h";
38 ["Resize.Object"] = "v";
39 ["Resize.ObjectSnap"] = "";
40 ["Resize.Center"] = "b";
41 ["Weld.Join"] = "";
42 ["Weld.Break"] = "";
43 ["Scale.Scale"] = "";
44 ["Other.Delete"] = "-";
45 ["Other.Slope"] = "";
46 ["Other.Midpoint"] = "";
47 ["Control.Expand"] = "q";
48 ["Control.Help"] = "?";
49 ["Control.Close"] = "";
50}
51
52--------------------------------------------------------------------------------------------------------------------------------
53--------------------------------------------------------------------------------------------------------------------------------
54--------------------------------------------------------------------------------------------------------------------------------
55--------------------------------------------------------------------------------------------------------------------------------
56--------------------------------------------------------------------------------------------------------------------------------
57--------------------------------------------------------------------------------------------------------------------------------
58--------------------------------------------------------------------------------------------------------------------------------
59--------------------------------------------------------------------------------------------------------------------------------
60--------------------------------------------------------------------------------------------------------------------------------
61--------------------------------------------------------------------------------------------------------------------------------
62--------------------------------------------------------------------------------------------------------------------------------
63--------------------------------------------------------------------------------------------------------------------------------
64--------------------------------------------------------------------------------------------------------------------------------
65--------------------------------------------------------------------------------------------------------------------------------
66--------------------------------------------------------------------------------------------------------------------------------
67--------------------------------------------------------------------------------------------------------------------------------
68-- Begin CmdUtl ----
69
70-- check for valid lua version
71assert(
72 function()
73 return _VERSION == "Lua 5.1"
74 end,
75 "CmdUtl cannot run in ".._VERSION
76)
77
78-- check for valid security context
79assert(
80 pcall(function()
81 return game:GetService("Selection"):Get()
82 and game:GetService("CoreGui"):GetChildren()
83 end),
84 "CmdUtl cannot run in the current security context! See the Documentation for a proper setup"
85)
86
87-- Close other CmdUtls
88if type(_G.CloseCmdUtl) == "function" then
89 pcall(_G.CloseCmdUtl)
90end
91
92-- remove any remaining panels
93for i,v in pairs(game:GetService("CoreGui"):GetChildren()) do
94 if v.Name == "CmdUtl" then
95 v:Remove()
96 end
97end
98
99-- management for resource disposal
100local Disposal = {
101 normal = {}; -- items here will be recursively removed and handled
102 limited = {}; -- items here will simply be unreferenced with no recursion or handles
103}
104
105-- Add Disposal Reference: adds item to disposal management
106function ADR(item,limit)
107 if limit then
108 table.insert(Disposal.limited,item)
109 else
110 table.insert(Disposal.normal,item)
111 end
112end
113
114-- Remove Disposal Reference: removes item from disposal management
115function RDR(item,limit)
116 if limit then
117 for i,v in pairs(Disposal.limited) do
118 if v == item then
119 table.remove(Disposal.limited,i)
120 break
121 end
122 end
123 else
124 for i,v in pairs(Disposal.normal) do
125 if v == item then
126 table.remove(Disposal.normal,i)
127 break
128 end
129 end
130 end
131end
132
133-- notes: any globally set variables are available to built-in plugins
134
135-- create panel
136Screen = Instance.new("ScreenGui"); ADR(Screen)
137Screen.Name = "CmdUtl"
138Screen.Parent = game:GetService("CoreGui")
139Panel = Instance.new("Frame"); ADR(Panel)
140Panel.BackgroundTransparency = 1
141Panel.Position = UDim2.new(0, 0, 0.05, 0)
142Panel.Name = "Panel"
143Panel.Parent = Screen
144Div = Instance.new("Frame"); ADR(Div)
145Div.Position = UDim2.new(0, 0, 0, config.control_size)
146Div.Style = Enum.FrameStyle.RobloxRound
147Div.Name = "Items"
148Div.Parent = Panel
149
150Resource = {
151 control_color = Color3.new(0,0,0);
152 control_selected_color = Color3.new(0.5,0.5,0.5);
153 output_color = Color3.new(1,1,1);
154 warning_color = Color3.new(1,0.8,0);
155 error_color = Color3.new(0.8,0,0);
156}
157ADR(Resource)
158
159ID = {}; ADR(ID) -- identity table for panel elements
160Control = {}; ADR(Control) -- identity table for panel controls
161ControlData = {}; ADR(ControlData) -- holds control data
162Commands = {} -- holds command line functions
163Shortcut = {}; ADR(Shortcut) -- holds key/tool shortcut associations
164
165ToolSelectListener = {}; ADR(ToolSelectListener) -- ondown
166ToolDeselectListener = {}; ADR(ToolDeselectListener) -- onup
167ButtonDescription = {}; ADR(ButtonDescription)
168ToolWarnings = {}; ADR(ToolWarnings)
169ToolSafeMode = {}; ADR(ToolSafeMode)
170
171ToolState = {}; ADR(ToolState)
172ValueState = {}; ADR(ValueState)
173MenuState = {}; ADR(MenuState)
174
175PluginDataFromName = {}; ADR(PluginDataFromName)
176PluginDataFromButton = {}; ADR(PluginDataFromButton)
177PluginResources = {}; ADR(PluginResources)
178ToolsFromMenu = {}; ADR(ToolsFromMenu)
179MenuFromTool = {}; ADR(MenuFromTool)
180
181Mode = {
182 Enabled = true;
183 PanelExpanded = true;
184 DivTweenEnabled = true;
185 HelpModeEnabled = false;
186}
187ADR(Mode)
188
189version = "3.0.0"
190
191local floor = math.floor
192local cframe = CFrame.new
193local Selection = game:GetService("Selection")
194local ContentProvider = game:GetService("ContentProvider")
195local SoundService = game:GetService("SoundService")
196local GuiService = game:GetService("GuiService")
197
198local restrict_mt = {
199 __newindex = function(t,k,v)
200 if getfenv(2) == getfenv() then -- only this env is allowed to set new values
201 rawset(t,k,v)
202 else
203 error("Cannot set value \""..tostring(k).."\"",2)
204 end
205 end;
206}
207ADR(restrict_mt)
208
209-- holds sound objects referenced by sound id
210local SoundRef = {}; ADR(SoundRef)
211
212-- create default overlay objects
213local DefaultOverlay = Instance.new("Part"); ADR(DefaultOverlay)
214DefaultOverlay.Name = "SelectionOverlay"
215DefaultOverlay.Anchored = true
216DefaultOverlay.CanCollide = false
217DefaultOverlay.Locked = true
218DefaultOverlay.formFactor = "Custom"
219DefaultOverlay.TopSurface = 0
220DefaultOverlay.BottomSurface = 0
221DefaultOverlay.Transparency = 1
222local OverlayAdornments = {}; ADR(OverlayAdornments)
223OverlayAdornments.Handles = Instance.new("Handles"); ADR(OverlayAdornments.Handles)
224OverlayAdornments.Handles.Adornee = DefaultOverlay
225OverlayAdornments.Handles.Visible = false
226OverlayAdornments.ArcHandles = Instance.new("ArcHandles"); ADR(OverlayAdornments.ArcHandles)
227OverlayAdornments.ArcHandles.Adornee = DefaultOverlay
228OverlayAdornments.ArcHandles.Visible = false
229OverlayAdornments.SelectionBox = Instance.new("SelectionBox"); ADR(OverlayAdornments.SelectionBox)
230OverlayAdornments.SelectionBox.Adornee = DefaultOverlay
231OverlayAdornments.SelectionBox.Visible = false
232OverlayAdornments.SurfaceSelection = Instance.new("SurfaceSelection"); ADR(OverlayAdornments.SurfaceSelection)
233OverlayAdornments.SurfaceSelection.Adornee = DefaultOverlay
234OverlayAdornments.SurfaceSelection.Visible = false
235
236-- go-to for outputting info
237function Log(...)
238 local out = ""
239 local inp = {...}
240 local n = #inp
241 for i,msg in pairs(inp) do
242 out = out .. tostring(msg)
243 end
244 ----------------
245 print("LOG:",out)
246end
247
248function LogWarning(...)
249 local out = ""
250 local inp = {...}
251 local n = #inp
252 for i,msg in pairs(inp) do
253 out = out .. tostring(msg)
254 end
255 ----------------
256 print("LOG_WARNING:",out)
257end
258
259function LogError(...)
260 local out = ""
261 local inp = {...}
262 local n = #inp
263 for i,msg in pairs(inp) do
264 out = out .. tostring(msg)
265 end
266 ----------------
267 print("LOG_ERROR:",out)
268end
269
270-- checks if the value is a positive integer
271function IsPositiveInteger(n)
272 return type(n) == "number" and n > 0 and math.floor(n) == n
273end
274
275-- checks if the table contains a sequence of keys
276function IsSequential(array, m)
277 for i=1,m do
278 if array[i] == nil then return false end
279 end
280 return true
281end
282
283-- checks if a table is an array
284function IsArray(array)
285 local m = 0
286 for k, _ in pairs(array) do
287 if not IsPositiveInteger(k) then return false end
288 if k > m then m = k end
289 end
290 return IsSequential(array, m)
291end
292
293-- checks if the string contains only letters, numbers, and underscores, with the first character not being a number
294function IsVarName(name)
295 return name:match("^[%a_][%w_]-$") == name
296end
297
298local valid_protocols = {
299 ["http"] = true;
300 ["https"] = true;
301 ["rbxhttp"] = true;
302 ["rbxasset"] = true;
303 ["rbxassetid"] = true;
304}
305ADR(valid_protocols)
306
307-- checks if the value is a Content string
308function IsContent(link)
309 if type(link) == "string" then
310 local protocol = link:match("^(.+)://(.+)$")
311 return valid_protocols[protocol] or false
312 else
313 return false
314 end
315end
316
317-- recursive for GetFilteredSelection
318local function RecurseSelectionFilter(object,class,out)
319 if object:IsA(class) then
320 table.insert(out,object)
321 end
322 for _,child in pairs(object:GetChildren()) do
323 RecurseSelectionFilter(child,class,out)
324 end
325end
326
327local points = {
328 Vector3.new(-1,-1,-1);
329 Vector3.new( 1,-1,-1);
330 Vector3.new(-1, 1,-1);
331 Vector3.new( 1, 1,-1);
332 Vector3.new(-1,-1, 1);
333 Vector3.new( 1,-1, 1);
334 Vector3.new(-1, 1, 1);
335 Vector3.new( 1, 1, 1);
336}
337ADR(points)
338
339-- recursive for GetBoundingBox
340local function RecurseGetBoundingBox(object,sides,out)
341 if object:IsA"BasePart" then
342 local mod = object.Size/2
343 local rot = object.CFrame
344 for _,mult in pairs(points) do
345 local point = rot*cframe(mod*mult).p
346 if point.x > sides[1] then sides[1] = point.x end
347 if point.x < sides[2] then sides[2] = point.x end
348 if point.y > sides[3] then sides[3] = point.y end
349 if point.y < sides[4] then sides[4] = point.y end
350 if point.z > sides[5] then sides[5] = point.z end
351 if point.z < sides[6] then sides[6] = point.z end
352 end
353 table.insert(out,object)
354 end
355 for _,child in pairs(object:GetChildren()) do
356 RecurseGetBoundingBox(child,sides,out)
357 end
358end
359
360function GetBoundingBox(objects)
361 local sides = {-math.huge;math.huge;-math.huge;math.huge;-math.huge;math.huge}
362 local out = {}
363 for _,object in pairs(objects) do
364 RecurseGetBoundingBox(object,sides,out)
365 end
366 return
367 Vector3.new(sides[1]-sides[2],sides[3]-sides[4],sides[5]-sides[6]),
368 Vector3.new((sides[1]+sides[2])/2,(sides[3]+sides[4])/2,(sides[5]+sides[6])/2),
369 out
370end
371
372local ToolEnvMetadata = {}; ADR(ToolEnvMetadata)
373local ToolButtonMetadata = {}; ADR(ToolButtonMetadata)
374
375-- gets metadata from a button or the env calling the function calling this
376function GetToolMetadata(button)
377 local md
378 if button then
379 md = ToolButtonMetadata[button]
380 else
381 md = ToolEnvMetadata[getfenv(3)]
382 end
383 if not md then error("Invalid call",3) end
384 return md
385end
386
387local CommandEnvMetadata = {}; ADR(CommandEnvMetadata)
388
389-- gets metadata from the env calling the function calling this
390function GetCommandMetadata()
391 local md = CommandEnvMetadata[getfenv(3)]
392 if not md then error("Invalid call",3) end
393 return md
394end
395
396function SetDescription(button,visible)
397 local desc = ButtonDescription[button]
398 if desc then
399 if visible then
400 local y = button.AbsolutePosition.y
401 local m,s = y + desc.AbsoluteSize.y,Screen.AbsoluteSize.y-4
402 if m > s then y = y-(m-s) end
403 desc.Position = UDim2.new(0,0,0,y-DescriptionFrame.AbsolutePosition.y)
404 desc.Visible = true
405 elseif not state then
406 desc.Visible = false
407 end
408 end
409end
410
411-- selects a tool using its button
412function SelectTool(button,stop_prev)
413 local prev
414 if not stop_prev then
415 for button,b in pairs(ToolState) do
416 if b then
417 DeselectTool(button)
418 prev = button
419 end
420 end
421 end
422 ToolState[button] = true
423 button.Selected = true
424 local listener = ToolSelectListener[button]
425 local md = GetToolMetadata(button)
426 local overlay = md.Overlay
427 local env = md.Env
428 for i,v in pairs(overlay) do
429 v:Remove()
430 overlay[i] = nil
431 env["Overlay"..i] = nil
432 end
433 overlay.Part = DefaultOverlay:Clone()
434 overlay.Part.archivable = false
435 env["OverlayPart"] = overlay.Part
436 for i,v in pairs(OverlayAdornments) do
437 local c = v:Clone()
438 c.Adornee = overlay.Part
439 c.archivable = false
440 overlay[i] = c
441 c.Parent = Screen.Parent
442 env["Overlay"..i] = c
443 end
444 md.PreviousTool = prev
445 local e,o = pcall(listener)
446 if not e then
447 LogError("Tool:",button.Name,": ",o)
448 end
449end
450
451-- deselects a tool using its button
452function DeselectTool(button)
453 ToolState[button] = false
454 button.Selected = false
455 local listener = ToolDeselectListener[button]
456 if listener then
457 local e,o = pcall(listener)
458 if not e then
459 LogError("Tool:",button.Name,": ",o)
460 end
461 end
462 local md = GetToolMetadata(button)
463 local overlay = md.Overlay
464 local env = md.Env
465 for i,v in pairs(overlay) do
466 v:Remove()
467 overlay[i] = nil
468 env["Overlay"..i] = nil
469 end
470 local connections = md.Connections
471 for i,v in pairs(connections) do
472 v:disconnect()
473 connections[i] = nil
474 end
475end
476
477-- toggles the visibility of a menu with its menu button; optional force true or false
478function ToggleMenu(button,force)
479 local state = MenuState[button]
480 if Mode.Enabled and Mode.PanelExpanded then
481 if force == nil then
482 state[1] = not state[1]
483 else
484 state[1] = not not force
485 end
486 if not state[1] then -- if menu is collapsing
487 for i,tool in pairs(ToolsFromMenu[state[2]]) do -- deselect tools of that menu
488 if ToolState[tool] then
489 DeselectTool(tool)
490 end
491 end
492 end
493 button.Selected = state[1]
494 state[2].Visible = state[1]
495 end
496end
497
498-- holds various environments that will be set or copied
499Environment = {
500 Source = { -- plugin source
501 Safe = {
502 Axes = Axes; BrickColor = BrickColor; CFrame = CFrame; Color3 = Color3; Faces = Faces; Instance = Instance; Ray = Ray; Region3 = Region3; UDim = UDim; UDim2 = UDim2; Vector2 = Vector2; Vector3 = Vector3;
503 math = math; string = string; table = table;
504 Enum = Enum;
505 };
506 Unsafe = {
507 _VERSION = _VERSION;
508 ipairs = ipairs; next = next; pairs = pairs; pcall = pcall; print = print; select = select; tonumber = tonumber; tostring = tostring; type = type; unpack = unpack; xpcall = xpcall;
509 coroutine = coroutine; math = math; string = string; table = table;
510 Delay = Delay; delay = delay; LoadLibrary = LoadLibrary; LoadRobloxLibrary = LoadRobloxLibrary; printidentity = printidentity; Spawn = Spawn; tick = tick; time = time; Version = Version; version = version; Wait = Wait; wait = wait;
511 game = game; Game = Game; workspace = workspace; Workspace = Workspace;
512 assert = assert; collectgarbage = collectgarbage; dofile = dofile; error = error; gcinfo = gcinfo; getfenv = getfenv; getmetatable = getmetatable; load = load; loadfile = loadfile; loadstring = loadstring; newproxy = newproxy; rawequal = rawequal; rawget = rawget; rawset = rawset; setfenv = setfenv; setmetatable = setmetatable;
513 _G = _G;
514 shared = shared;
515 crash__ = crash__; settings = settings; Stats = Stats; stats = stats; UserSettings = UserSettings;
516 };
517 };
518 Listener = { -- tool listeners
519 Global = {
520 Safe = {
521 _VERSION = _VERSION;
522 ipairs = ipairs; next = next; pairs = pairs; pcall = pcall; print = print; select = select; tonumber = tonumber; tostring = tostring; type = type; unpack = unpack; xpcall = xpcall;
523 coroutine = coroutine; math = math; string = string; table = table;
524 Delay = Delay; delay = delay; LoadLibrary = LoadLibrary; LoadRobloxLibrary = LoadRobloxLibrary; printidentity = printidentity; Spawn = Spawn; tick = tick; time = time; Version = Version; version = version; Wait = Wait; wait = wait;
525 Axes = Axes; BrickColor = BrickColor; CFrame = CFrame; Color3 = Color3; Faces = Faces; Instance = Instance; Ray = Ray; Region3 = Region3; UDim = UDim; UDim2 = UDim2; Vector2 = Vector2; Vector3 = Vector3;
526 Enum = Enum; game = game; Game = Game; workspace = workspace; Workspace = Workspace;
527 };
528 Unsafe = {
529 assert = assert; collectgarbage = collectgarbage; dofile = dofile; error = error; gcinfo = gcinfo; getfenv = getfenv; getmetatable = getmetatable; load = load; loadfile = loadfile; loadstring = loadstring; newproxy = newproxy; rawequal = rawequal; rawget = rawget; rawset = rawset; setfenv = setfenv; setmetatable = setmetatable;
530 _G = _G;
531 shared = shared;
532 crash__ = crash__; settings = settings; Stats = Stats; stats = stats; UserSettings = UserSettings;
533 };
534 };
535 API = {
536 Safe = {
537 WrapOverlay = function(object,isbb)
538 local md = GetToolMetadata()
539 local overlay = md.Overlay.Part
540 if type(object) == "table" then
541 local size,pos = GetBoundingBox(object)
542 overlay.Size = size
543 overlay.CFrame = CFrame.new(pos)
544 overlay.Parent = workspace
545 elseif object:IsA"BasePart" then
546 if isbb then
547 local size,pos = GetBoundingBox{object}
548 overlay.Size = size
549 overlay.CFrame = CFrame.new(pos)
550 else
551 overlay.Size = object.Size
552 overlay.CFrame = object.CFrame
553 end
554 overlay.Parent = workspace
555 end
556 end;
557 GetOverlaySize = function()
558 local md = GetToolMetadata()
559 return md.Overlay.Part.Size
560 end;
561 GetOverlayCFrame = function()
562 local md = GetToolMetadata()
563 return md.Overlay.Part.CFrame
564 end;
565 SetOverlaySize = function(v)
566 local md = GetToolMetadata()
567 local overlay = md.Overlay.Part
568 local cf = overlay.CFrame
569 overlay.Size = v
570 overlay.CFrame = cf
571 end;
572 SetOverlayCFrame = function(cf)
573 local md = GetToolMetadata()
574 md.Overlay.Part.CFrame = cf
575 end;
576 SetOverlay = function(v,cf)
577 local md = GetToolMetadata()
578 local overlay = md.Overlay.Part
579 overlay.Size = v
580 overlay.CFrame = cf
581 end;
582 Round = function(number,by)
583 if by == 0 then
584 return number
585 else
586 return floor(number/by+0.5)*by
587 end
588 end;
589 Resource = function(key)
590 local md = GetToolMetadata()
591 local resource = md.Resource[key]
592 if resource then
593 return resource
594 else
595 error("\""..key.."\" is not a valid resource key",2)
596 end
597 end;
598 Config = function(key)
599 return config[key]
600 end;
601 GetSelection = function()
602 return Selection:Get()
603 end;
604 SetSelection = function(set)
605 Selection:Set(set)
606 end;
607 GetFilteredSelection = function(class)
608 local out = {}
609 for _,object in pairs(Selection:Get()) do
610 RecurseSelectionFilter(object,class,out)
611 end
612 return out
613 end;
614 GetFiltered = function(class,objects)
615 local out = {}
616 for _,object in pairs(objects) do
617 RecurseSelectionFilter(object,class,out)
618 end
619 return out
620 end;
621 GetBoundingBox = GetBoundingBox;
622 GetSelectionBoundingBox = function()
623 local size,pos,out = GetBoundingBox(Selection:Get())
624 return out,size,pos
625 end;
626 GetMidpoint = function(set)
627 local mid = Vector3.new()
628 for i,v in pairs(set) do
629 mid = mid+v.Position
630 end
631 return mid/#set
632 end;
633 GetButtonValue = function(id)
634 local md = GetToolMetadata()
635 local vbutton = md.ButtonFromId[id]
636 if vbutton then
637 local vstate = ValueState[vbutton]
638 if vstate then
639 return vstate[1]
640 else
641 error("cannot get value of button \""..id.."\"",2)
642 end
643 else
644 error("\""..id.."\" is not a defined button",2)
645 end
646 end;
647 SetButtonValue = function(id,value)
648 local md = GetToolMetadata()
649 local vbutton = md.ButtonFromId[id]
650 if vbutton then
651 local vstate = ValueState[vbutton]
652 if vstate then
653 vstate[2](value)
654 else
655 error("cannot get value of button \""..id.."\"",2)
656 end
657 else
658 error("\""..id.."\" is not a defined button",2)
659 end
660 end;
661 Deselect = function()
662 local md = GetToolMetadata()
663 DeselectTool(md.Button)
664 end;
665 SetWarning = function(index)
666 local md = GetToolMetadata()
667 DeselectTool(md.Button)
668 local warnings = md.Warnings
669 if warnings then
670 local msg = warnings[index or 1]
671 LogWarning("Tool \"",md.ID,"\": ",msg)
672 end
673 end;
674 Connect = function(event,listener)
675 local md = GetToolMetadata()
676 local connections = md.Connections
677 table.insert(connections,event:connect(listener))
678 end;
679 SelectPreviousTool = function()
680 local md = GetToolMetadata()
681 DeselectTool(md.Button)
682 local prev = md.PreviousTool
683 if prev then
684 SelectTool(prev)
685 end
686 end;
687 PlaySound = function(key)
688 if config.tool_sounds_enabled then
689 local md = GetToolMetadata()
690 local resource = md.Resource[key]
691 if resource then
692 if IsContent(resource) then
693 local sound = SoundRef[resource]
694 if not sound then
695 sound = Instance.new("StockSound")
696 sound.Name = "CmdUtl:"..key
697 sound.SoundId = resource
698 sound.archivable = false
699 sound.Parent = SoundService
700 SoundRef[resource] = sound
701 end
702 sound:Play()
703 end
704 end
705 end
706 end;
707 };
708 Unsafe = {};
709 };
710 };
711 Command = {
712 Global = {
713 Safe = {
714 _VERSION = _VERSION;
715 assert = assert; error = error; ipairs = ipairs; next = next; pairs = pairs; pcall = pcall; print = print; select = select; tonumber = tonumber; tostring = tostring; type = type; unpack = unpack; xpcall = xpcall;
716 coroutine = coroutine; math = math; string = string; table = table;
717 Delay = Delay; delay = delay; LoadLibrary = LoadLibrary; LoadRobloxLibrary = LoadRobloxLibrary; printidentity = printidentity; Spawn = Spawn; tick = tick; time = time; Version = Version; version = version; Wait = Wait; wait = wait;
718 Axes = Axes; BrickColor = BrickColor; CFrame = CFrame; Color3 = Color3; Faces = Faces; Instance = Instance; Ray = Ray; Region3 = Region3; UDim = UDim; UDim2 = UDim2; Vector2 = Vector2; Vector3 = Vector3;
719 Enum = Enum; game = game; Game = Game; workspace = workspace; Workspace = Workspace;
720 };
721 Unsafe = {
722 collectgarbage = collectgarbage; dofile = dofile; gcinfo = gcinfo; getfenv = getfenv; getmetatable = getmetatable; load = load; loadfile = loadfile; loadstring = loadstring; newproxy = newproxy; rawequal = rawequal; rawget = rawget; rawset = rawset; setfenv = setfenv; setmetatable = setmetatable;
723 _G = _G;
724 shared = shared;
725 crash__ = crash__; settings = settings; Stats = Stats; stats = stats; UserSettings = UserSettings;
726 };
727 };
728 API = {
729 Safe = {
730 Round = function(number,by)
731 if by == 0 then
732 return number
733 else
734 return floor(number/by+0.5)*by
735 end
736 end;
737 Resource = function(key)
738 local md = GetCommandMetadata()
739 local resource = md.Resource[key]
740 if resource then
741 return resource
742 else
743 error("\""..key.."\" is not a valid resource key",2)
744 end
745 end;
746 Config = function(key)
747 return config[key]
748 end;
749 GetSelection = function()
750 return Selection:Get()
751 end;
752 SetSelection = function(set)
753 Selection:Set(set)
754 end;
755 GetFilteredSelection = function(class)
756 local out = {}
757 for _,object in pairs(Selection:Get()) do
758 RecurseSelectionFilter(object,class,out)
759 end
760 return out
761 end;
762 GetFiltered = function(class,objects)
763 local out = {}
764 for _,object in pairs(objects) do
765 RecurseSelectionFilter(object,class,out)
766 end
767 return out
768 end;
769 GetBoundingBox = GetBoundingBox;
770 GetSelectionBoundingBox = function()
771 local size,pos,out = GetBoundingBox(Selection:Get())
772 return out,size,pos
773 end;
774 GetMidpoint = function(set)
775 local mid = Vector3.new()
776 for i,v in pairs(set) do
777 mid = mid+v.Position
778 end
779 return mid/#set
780 end;
781 };
782 Unsafe = {};
783 };
784 };
785 BuiltIn = getfenv();
786}
787ADR(Environment.Listener.API)
788ADR(Environment.Command.API)
789ADR(Environment,true)
790ADR(Environment.Source,true)
791ADR(Environment.Source.Safe,true)
792ADR(Environment.Source.Unsafe,true)
793ADR(Environment.Listener,true)
794ADR(Environment.Listener.Global,true)
795ADR(Environment.Listener.Global.Safe,true)
796ADR(Environment.Listener.Global.Unsafe,true)
797ADR(Environment.Command,true)
798ADR(Environment.Command.Global,true)
799ADR(Environment.Command.Global.Safe,true)
800ADR(Environment.Command.Global.Unsafe,true)
801
802-- notes: icon theme:
803-- all-white over transparent
804-- no curves
805-- 1/8 padding (32/256)
806-- 5/32 weight (40/256)
807
808-- most of the style is controlled here
809
810local frame_width = 8 -- RobloxRound
811
812-- tags that change text color
813local ColorTags = {
814 ["h"] = Color3.new(0.6,0.6,1);
815}
816
817MakeGuiObject = {
818 ["tool"] = function(name,text)
819 local button = Instance.new("TextButton"); ADR(button)
820 button.Name = name or button.Name
821 button.Text = text or button.Text
822 button.Font = config.button_font
823 button.FontSize = config.button_font_size
824 button.BackgroundColor3 = Color3.new(1, 1, 1)
825 button.Size = UDim2.new(1, 0, 1, 0)
826 button.Style = Enum.ButtonStyle.RobloxButton
827 button.TextColor3 = Color3.new(1, 1, 1)
828 button.BorderColor3 = Color3.new(0, 0, 0)
829 return button
830 end;
831 ["field"] = function(name,text)
832 local button = Instance.new("TextBox"); ADR(button)
833 button.Name = name or button.Name
834 button.Text = text or button.Text
835 button.Font = config.button_font
836 button.FontSize = config.button_font_size
837 button.BackgroundColor3 = Color3.new(0, 0, 0)
838 button.Size = UDim2.new(1, 0, 1, 0)
839 button.TextColor3 = Color3.new(1, 1, 1)
840 button.BorderColor3 = Color3.new(1, 1, 1)
841 button.BackgroundTransparency = 0.5
842 return button
843 end;
844 ["label"] = function(name,text)
845 local button = Instance.new("TextLabel"); ADR(button)
846 button.Name = name or button.Name
847 button.Text = text or button.Text
848 button.Font = config.button_font
849 button.FontSize = config.button_font_size
850 button.Size = UDim2.new(1, 0, 1, 0)
851 button.TextColor3 = Color3.new(1, 1, 1)
852 button.BorderColor3 = Color3.new(1, 1, 1)
853 button.BackgroundColor3 = Color3.new(0, 0, 0)
854 button.Position = UDim2.new(1, 0, 3, 0)
855 button.BackgroundTransparency = 0.5
856 return button
857 end;
858 ["toggle"] = function(name,value,text)
859 local button = Instance.new("TextButton"); ADR(button)
860 button.Name = name or button.Name
861 button.Text = text or name or button.Name
862 button.Font = config.button_font
863 button.FontSize = config.button_font_size
864 button.BackgroundColor3 = Color3.new(1, 1, 1)
865 button.Selected = value or false
866 button.Size = UDim2.new(1, 0, 1, 0)
867 button.Style = Enum.ButtonStyle.RobloxButton
868 button.TextColor3 = Color3.new(1, 1, 1)
869 button.BorderColor3 = Color3.new(0, 0, 0)
870 return button
871 end;
872 ["container"] = function(name)
873 local button = Instance.new("Frame"); ADR(button)
874 button.Name = name or button.Name
875 button.BorderSizePixel = 0
876 button.Size = UDim2.new(1, 0, 1, 0)
877 button.BorderColor3 = Color3.new(0, 0, 0)
878 button.BackgroundTransparency = 1
879 button.BackgroundColor3 = Color3.new(0, 0, 0)
880 return button
881 end;
882 ["title"] = function()
883 local title = Instance.new("TextLabel"); ADR(button)
884 title.Name = "Title"
885 title.BackgroundColor3 = Color3.new(0,0,0)
886 title.BackgroundTransparency = 0.3
887 title.BorderSizePixel = 0
888 title.Font = config.button_font
889 title.FontSize = config.button_font_size
890 title.TextColor3 = Color3.new(1,1,1)
891 title.Size = UDim2.new(1,0,0,config.control_size)
892 title.Position = UDim2.new(0,0,0,-config.control_size-frame_width)
893 return title
894 end;
895 ["descframe"] = function()
896 local frame = Instance.new("Frame"); ADR(frame)
897 frame.Name = "Descriptions"
898 frame.BackgroundTransparency = 1
899 frame.Position = UDim2.new(1,frame_width+config.control_size+config.desc_padding,0,0)
900 return frame
901 end;
902
903 ["description"] = function()
904 local desc = Instance.new("TextLabel"); ADR(desc)
905 desc.Name = "Description"
906 desc.Font = config.button_font
907 desc.FontSize = config.button_font_size
908 desc.TextColor3 = Color3.new(1, 1, 1)
909 desc.BorderColor3 = Color3.new(1, 1, 1)
910 desc.BackgroundColor3 = Color3.new(0, 0, 0)
911 desc.TextTransparency = 1
912 desc.ZIndex = 2
913 return desc
914 end;
915 ["paragraph"] = function()
916 local pad = Instance.new("Frame"); ADR(pad)
917 pad.Name = "Padding"
918 pad.BackgroundTransparency = 1
919 local para = Instance.new("TextLabel"); ADR(para)
920 para.Name = "Paragraph"
921 para.BackgroundTransparency = 1
922 para.Font = config.button_font
923 para.FontSize = config.button_font_size
924 para.TextXAlignment = "Left"
925 para.TextColor3 = Color3.new(1, 1, 1)
926 para.TextWrap = true
927 para.Position = UDim2.new(0,4,0,0)
928 para.Size = UDim2.new(1,-4,1,0)
929 para.ZIndex = 2
930 para.Parent = pad
931 return pad,para
932 end;
933 ["controlframe"] = function()
934 local frame = Instance.new("Frame"); ADR(frame)
935 frame.Name = "Controls"
936 frame.BackgroundTransparency = 1
937 frame.Position = UDim2.new(1,frame_width,0,0)
938 frame.Size = UDim2.new(0,config.control_size,0,config.control_size)
939 return frame
940 end;
941 ["controlbutton"] = function(name,image)
942 local button = Instance.new("ImageButton"); ADR(button)
943 button.BackgroundColor3 = Color3.new(0,0,0)
944 button.BackgroundTransparency = 0.3
945 button.BorderSizePixel = 0
946 button.Name = name or button.Name
947 button.Image = image or ""
948 button.Size = UDim2.new(1,0,1,0)
949 return button
950 end;
951 ["menubutton"] = function(name,text)
952 local button = Instance.new("TextButton"); ADR(button)
953 button.Name = name or button.Name
954 button.Text = text or button.Text
955 button.Font = config.button_font
956 button.FontSize = config.button_font_size
957 button.BackgroundColor3 = Color3.new(1, 1, 1)
958 button.Size = UDim2.new(0, config.button_size_x, 0, config.button_size_y)
959 button.Style = Enum.ButtonStyle.RobloxButton
960 button.TextColor3 = Color3.new(1, 1, 1)
961 button.BorderColor3 = Color3.new(0, 0, 0)
962 local tag = Instance.new("StringValue"); ADR(tag)
963 tag.Name = "ElementType"
964 tag.Value = "MenuButton"
965 tag.Parent = button
966 return button
967 end;
968 ["menu"] = function(name,x,y)
969 local menu = Instance.new("Frame"); ADR(menu)
970 menu.Size = UDim2.new(0, x, 0, y)
971 menu.BackgroundTransparency = 1
972 menu.Position = UDim2.new(0, config.menu_indent, 0, 20)
973 menu.Name = name or menu.Name
974 local item = Instance.new("Frame"); ADR(item)
975 item.Size = UDim2.new(0, config.button_size_x, 0, config.button_size_y)
976 item.BorderColor3 = Color3.new(0, 0, 0)
977 item.BackgroundTransparency = 1
978 item.Name = "Items"
979 item.BackgroundColor3 = Color3.new(1, 1, 1)
980 item.Parent = menu
981 local tag = Instance.new("StringValue"); ADR(tag)
982 tag.Name = "ElementType"
983 tag.Value = "Menu"
984 tag.Parent = menu
985 return menu
986 end;
987 ["seperator"] = function(y)
988 local sep = Instance.new("Frame"); ADR(sep)
989 sep.BorderSizePixel = 0
990 sep.Size = UDim2.new(1, 0, 0, 7)
991 sep.BorderColor3 = Color3.new(0, 0, 0)
992 sep.BackgroundTransparency = 1
993 sep.Position = UDim2.new(0, 0, 0, 0)
994 sep.Name = "Seperator"
995 sep.BackgroundColor3 = Color3.new(0, 0, 0)
996 local line = Instance.new("Frame"); ADR(line)
997 line.BackgroundTransparency = 0.5
998 line.Size = UDim2.new(1, 8, 0, 1)
999 line.BorderSizePixel = 0
1000 line.Position = UDim2.new(0, -4, 0.5, 0)
1001 line.Name = "Line"
1002 line.BackgroundColor3 = Color3.new(1, 1, 1)
1003 line.Parent = sep
1004 local tag = Instance.new("StringValue"); ADR(tag)
1005 tag.Name = "ElementType"
1006 tag.Value = "Seperator"
1007 tag.Parent = sep
1008 return sep
1009 end;
1010}
1011ADR(MakeGuiObject)
1012-- handles specific element types found in div
1013local HandleElementType = {
1014 [true] = { -- tween
1015 ["MenuButton"] = function(element,length)
1016 if element.Visible then
1017 local abs = element.AbsoluteSize
1018 local x = abs.x + element.AbsolutePosition.x
1019 element:TweenPosition(UDim2.new(0,0,0,length),"Out","Quad",config.tween_speed,true)
1020 return abs.y,x
1021 end
1022 end;
1023 ["Menu"] = function(element,length,object)
1024 if element.Visible then
1025 if element == object then
1026 element.Items.Visible = false
1027 end
1028 local abs = element.AbsoluteSize
1029 local x = abs.x + element.AbsolutePosition.x
1030 element:TweenPosition(UDim2.new(0,config.menu_indent,0,length),"Out","Quad",config.tween_speed,true,function()
1031 element.Items.Visible = true
1032 end)
1033 return abs.y,x
1034 end
1035 end;
1036 ["Seperator"] = function(element,length)
1037 if element.Visible then
1038 local abs = element.AbsoluteSize
1039 local next = element.Position
1040 element:TweenPosition(UDim2.new(0,0,0,length),"Out","Quad",config.tween_speed,true)
1041 return abs.y,0
1042 end
1043 end;
1044 };
1045 [false] = { -- no tween
1046 ["MenuButton"] = function(element,length)
1047 if element.Visible then
1048 element.Position = UDim2.new(0,0,0,length)
1049 local abs = element.AbsoluteSize
1050 local x = abs.x + element.AbsolutePosition.x
1051 return abs.y,x
1052 end
1053 end;
1054 ["Menu"] = function(element,length)
1055 if element.Visible then
1056 element.Position = UDim2.new(0,config.menu_indent,0,length)
1057 local abs = element.AbsoluteSize
1058 local x = abs.x + element.AbsolutePosition.x
1059 return abs.y,x
1060 end
1061 end;
1062 ["Seperator"] = function(element,length)
1063 if element.Visible then
1064 element.Position = UDim2.new(0,0,0,length)
1065 local abs = element.AbsoluteSize
1066 return abs.y,0
1067 end
1068 end;
1069 };
1070}
1071ADR(HandleElementType)
1072
1073-- makes the frame arrange its contents so that they stack
1074-- Notes on menu arrangement:
1075-- Content is ordered by child order
1076-- So, buttons and menus should be paired up when parented (1=button1, 2=menu1, 3=button2, 4=menu2, etc)
1077local function MakeDiv(frame)
1078 local children = {}
1079 local types = {}
1080 local connections = {}
1081 local in_con = {}
1082
1083 local function recalculate(object) -- recalculates panel's size
1084 if Mode.Enabled then
1085 Mode.Enabled = false
1086 local width = 0
1087 local length = 0
1088
1089 local tweening = config.tween_panel_enabled and Mode.DivTweenEnabled
1090 local handles = HandleElementType[tweening]
1091 for i,child in pairs(children) do
1092 local l,w = handles[types[child]](child,length,object)
1093 if l then
1094 width = w > width and w or width
1095 length = length + l
1096 end
1097 end
1098 if tweening then
1099 if #children > 0 then
1100 frame:TweenSize(UDim2.new(0,width - frame.AbsolutePosition.x+frame_width,0,length+frame_width*2),"Out","Quad",config.tween_speed,false,function() Mode.Enabled = true end)
1101 else
1102 frame:TweenSize(UDim2.new(0,0,0,length),"Out","Quad",config.tween_speed,false,function() Mode.Enabled = true end)
1103 end
1104 else
1105 if #children > 0 then
1106 frame.Size = UDim2.new(0,width - frame.AbsolutePosition.x+frame_width,0,length+frame_width*2)
1107 else
1108 frame.Size = UDim2.new(0,0,0,length)
1109 end
1110 Mode.Enabled = true
1111 end
1112 end
1113 end
1114
1115 local function add(object)
1116 local type_tag = object:FindFirstChild("ElementType")
1117 if type_tag and type_tag.className == "StringValue" then
1118 if HandleElementType[config.tween_panel_enabled][type_tag.Value] then
1119 table.insert(children,object)
1120 types[object] = type_tag.Value
1121 connections[object] = object.Changed:connect(function(p)
1122 if not Mode.Enabled and p == "AbsoluteSize" or p == "Visible" then
1123 recalculate(object)
1124 end
1125 end)
1126 recalculate(object)
1127 end
1128 end
1129 end
1130
1131 in_con.add = frame.ChildAdded:connect(add)
1132 in_con.remove = frame.ChildRemoved:connect(function(child)
1133 if types[object] then
1134 types[object] = nil
1135 if connections[child] then
1136 connections[child]:disconnect()
1137 connections[child] = nil
1138 end
1139 for i,v in pairs(children) do
1140 if v == child then
1141 table.remove(children,i)
1142 break
1143 end
1144 end
1145 recalculate()
1146 end
1147 end)
1148
1149 for _,child in pairs(frame:GetChildren()) do
1150 add(child)
1151 end
1152 recalculate()
1153
1154 local function dispose() -- undos everything
1155 for i,con in pairs(in_con) do
1156 con:disconnect()
1157 in_con[i] = nil
1158 end
1159 for i,v in pairs(children) do
1160 if connections[v] then
1161 connections[v]:disconnect()
1162 connections[v] = nil
1163 end
1164 types[v] = nil
1165 children[i] = nil
1166 end
1167 for i,con in pairs(connections) do
1168 con:disconnect()
1169 connections[i] = nil
1170 end
1171 children = nil
1172 types = nil
1173 connections = nil
1174 in_con = nil
1175 recalculate = nil
1176 add = nil
1177 dispose = nil
1178 end
1179
1180 return dispose
1181end
1182
1183local function MakeStackingList(frame)
1184 local children = {}
1185 local connections = {}
1186 local in_con = {}
1187
1188 local function recalculate(object) -- recalculates panel's size
1189 local width = 0
1190 local length = 0
1191 for i,child in pairs(children) do
1192 if child.Visible then
1193 child.Position = UDim2.new(0,0,0,length)
1194 local abs = child.AbsoluteSize
1195 local x = abs.x + child.AbsolutePosition.x
1196 width = x > width and x or width
1197 length = length + abs.y
1198 end
1199 end
1200 if #children > 0 then
1201 frame.Size = UDim2.new(0,width - frame.AbsolutePosition.x,0,length)
1202 else
1203 frame.Size = UDim2.new(0,0,0,length)
1204 end
1205 end
1206
1207 local function add(object)
1208 if object:IsA"GuiObject" then
1209 table.insert(children,object)
1210 connections[object] = object.Changed:connect(function(p)
1211 if p == "AbsoluteSize" or p == "Visible" then
1212 recalculate(object)
1213 end
1214 end)
1215 recalculate(object)
1216 end
1217 end
1218
1219 in_con.add = frame.ChildAdded:connect(add)
1220 in_con.remove = frame.ChildRemoved:connect(function(child)
1221 if connections[child] then
1222 connections[child]:disconnect()
1223 connections[child] = nil
1224 end
1225 for i,v in pairs(children) do
1226 if v == child then
1227 table.remove(children,i)
1228 break
1229 end
1230 end
1231 recalculate()
1232 end)
1233
1234 for _,child in pairs(frame:GetChildren()) do
1235 add(child)
1236 end
1237 recalculate()
1238
1239 local function dispose() -- undos everything
1240 for i,con in pairs(in_con) do
1241 con:disconnect()
1242 in_con[i] = nil
1243 end
1244 for i,v in pairs(children) do
1245 if connections[v] then
1246 connections[v]:disconnect()
1247 connections[v] = nil
1248 end
1249 children[i] = nil
1250 end
1251 for i,con in pairs(connections) do
1252 con:disconnect()
1253 connections[i] = nil
1254 end
1255 children = nil
1256 connections = nil
1257 in_con = nil
1258 recalculate = nil
1259 add = nil
1260 dispose = nil
1261 end
1262
1263 return dispose
1264end
1265
1266local InitData = {
1267 Main = {
1268 Tools = {};
1269 Menus = {};
1270 Controls = {};
1271 Commands = {};
1272 };
1273 Plugins = {
1274 Tools = {};
1275 Menus = {};
1276 Commands = {};
1277 };
1278}
1279ADR(InitData)
1280
1281local ColorTags = {
1282 ["h"] = Color3.new(0.6,0.6,1);
1283}
1284
1285-- creates a description label for an object
1286local function SetupDescription(button,text)
1287 if text and #text > 0 then
1288 local desc = MakeGuiObject["description"]()
1289 desc.Parent = Screen
1290 for line in text:gmatch("[^\r\n]+") do
1291 local pad,para = MakeGuiObject["paragraph"]()
1292 pad.Parent = desc
1293 para.Size = UDim2.new(0,0,0,0)
1294 local tag,text = line:match("^{(.+)}(.-)$")
1295 if tag then
1296 local c = ColorTags[tag:lower()]
1297 if c then para.TextColor3 = c end
1298 para.Text = text
1299 else
1300 para.Text = line
1301 end
1302 local bounds = para.TextBounds
1303 local x,y = bounds.x,bounds.y
1304 if x > config.desc_width_max then
1305 x = config.desc_width_max
1306 y = 100*y
1307 end
1308 x,y = math.ceil(x),math.ceil(y)
1309 para.Position = UDim2.new(0,4,0,2)
1310 para.Size = UDim2.new(0,x,0,y)
1311 local tb = para.TextBounds
1312 tb = Vector2.new(math.ceil(tb.x),math.ceil(tb.y))
1313 para.Size = UDim2.new(0,tb.x,0,tb.y)
1314 pad.Size = UDim2.new(0,tb.x+8,0,tb.y+4)
1315 end
1316 desc.Visible = false
1317 desc.Name = button.Name.."Description"
1318 desc.Parent = DescriptionFrame
1319 ADR(MakeStackingList(desc))
1320 ButtonDescription[button] = desc
1321 ADR(button.MouseEnter:connect(function()
1322 if Mode.HelpModeEnabled then
1323 SetDescription(button,true)
1324 end
1325 end))
1326 ADR(button.MouseLeave:connect(function()
1327 if Mode.HelpModeEnabled then
1328 SetDescription(button,false)
1329 end
1330 end))
1331 end
1332end
1333
1334local function SetupToolState(button)
1335 ADR(button.MouseButton1Click:connect(function()
1336 if ToolState[button] then
1337 DeselectTool(button)
1338 else
1339 SelectTool(button)
1340 end
1341 end))
1342end
1343
1344local function SetupMenuState(menubutton,state)
1345 ADR(menubutton.MouseButton1Click:connect(function()
1346 ToggleMenu(menubutton)
1347 end))
1348end
1349
1350local SetupValueState = {
1351 ["field"] = function(button,state,safe)
1352 local stype = type(state)
1353 local function update(input)
1354 button.Text = tostring(input)
1355 end
1356 if stype == "string" then
1357 local vstate = {state,update}
1358 ValueState[button] = vstate
1359 ADR(button.Changed:connect(function(p)
1360 if p == "Text" then
1361 vstate[1] = button.Text
1362 end
1363 end))
1364 button.Text = vstate[1]
1365 elseif stype == "number" then
1366 local vstate = {state,update}
1367 ValueState[button] = vstate
1368 ADR(button.Changed:connect(function(p)
1369 if p == "Text" then
1370 local check = tonumber(button.Text)
1371 if check then
1372 vstate[1] = check
1373 else
1374 button.Text = vstate[1]
1375 end
1376 end
1377 end))
1378 button.Text = vstate[1]
1379 elseif stype == "boolean" then
1380 local vstate = {state,update}
1381 ValueState[button] = vstate
1382 ADR(button.Changed:connect(function(p)
1383 if p == "Text" then
1384 local check = button.Text:lower()
1385 if check == "false" or check == "0" then
1386 vstate[1] = false
1387 elseif check == "true" or check == "1" then
1388 vstate[1] = true
1389 else
1390 button.Text = vstate[1] and "true" or "false"
1391 end
1392 end
1393 end))
1394 button.Text = state[1] and "true" or "false"
1395 elseif stype == "table" then
1396 local vstate = {state[1],update}
1397 ValueState[button] = vstate
1398 local func = state[2]
1399 local env = {}; ADR(env,true)
1400 for i,v in pairs(Environment.Listener.Global.Safe) do
1401 env[i] = v
1402 end
1403 if not safe then
1404 for i,v in pairs(Environment.Listener.Global.Unsafe) do
1405 env[i] = v
1406 end
1407 end
1408 if safe then
1409 setmetatable(env,{__newindex = function(t,k) error("Cannot set value \""..tostring(k).."\"",2) end})
1410 end
1411 setfenv(func,env)
1412 local con = button.Changed:connect(function(p)
1413 if p == "Text" then
1414 local e,s,v = pcall(func,button.Text)
1415 if e then
1416 if s then
1417 vstate[1] = v
1418 else
1419 button.Text = tostring(vstate[1])
1420 end
1421 else
1422 con:disconnect(); RDR(con)
1423 vstate[2] = function()end
1424 LogError("Field \"",button.Name,"\" listener: ",s)
1425 LogWarning("Disconnected listener from field \"",button.Name,"\"")
1426 end
1427 end
1428 end)
1429 ADR(con)
1430 button.Text = tostring(state[1])
1431 end
1432 end;
1433 ["label"] = function(button,state)
1434 local vstate = {state,function(input)
1435 button.Text = tostring(input)
1436 end}
1437 ValueState[button] = vstate
1438 ADR(button.Changed:connect(function(p)
1439 if p == "Text" then
1440 vstate[1] = button.Text
1441 end
1442 end))
1443 button.Text = state
1444 end;
1445 ["toggle"] = function(button,state)
1446 local vstate = {state,function(input)
1447 if type(input) == "boolean" then
1448 vstate[1] = input
1449 button.Selected = input
1450 end
1451 end}
1452 ValueState[button] = vstate
1453 ADR(button.MouseButton1Click:connect(function()
1454 local state = not vstate[1]
1455 vstate[1] = state
1456 button.Selected = state
1457 end))
1458 button.Selected = state
1459 end;
1460}
1461
1462local MakeButton
1463MakeButton = {
1464 ["tool"] = function(info)
1465 return MakeGuiObject["tool"](info[1],info[3])
1466 end;
1467 ["field"] = function(info)
1468 if type(info[3]) == "table" then
1469 return MakeGuiObject["field"](info[1],info[3][1])
1470 else
1471 return MakeGuiObject["field"](info[1],info[3])
1472 end
1473 end;
1474 ["label"] = function(info)
1475 return MakeGuiObject["label"](info[1],info[3])
1476 end;
1477 ["toggle"] = function(info)
1478 return MakeGuiObject["toggle"](info[1],info[3],info[4] and tostring(info[4]) or info[1])
1479 end;
1480 ["container"] = function(info,data,id)
1481 local container = MakeGuiObject["container"](info[1])
1482 local n = #info[3]
1483 for i,sub in pairs(info[3]) do
1484 local button = MakeButton[sub[2]](sub,data,id)
1485 SetupDescription(button,data.ButtonDescription[sub[1]])
1486 button.Size = UDim2.new(1/n,0,1,0)
1487 button.Position = UDim2.new((i-1)/n,0,0,0)
1488 button.Parent = container
1489 if SetupValueState[sub[2]] then
1490 SetupValueState[sub[2]](button,sub[3],data.SafeMode)
1491 end
1492 id[sub[1]] = button
1493 end
1494 return container
1495 end;
1496}
1497
1498local function PositionButtonsAsGrid(tools,l)
1499 local x,y = 0,0
1500 local sx,sy = 0,0
1501 for i=1,#tools do
1502 tools[i].Position = UDim2.new(x,0,y,0)
1503 sx = x > sx-1 and x+1 or sx
1504 sy = y > sy-1 and y+1 or sy
1505 if (i-1)%l+1 == l then
1506 x = x + 1
1507 y = 0
1508 else
1509 y = y + 1
1510 end
1511 end
1512 return sx,sy
1513end
1514
1515local InitDataType
1516InitDataType = {
1517 ["tool"] = function(data,ref)
1518 for i,v in pairs(data.Resources) do
1519 if IsContent(v) then
1520 ContentProvider:Preload(v)
1521 end
1522 end
1523 local button = MakeGuiObject["tool"](data.Name,data.Text)
1524 ToolState[button] = false
1525 ToolWarnings[button] = data.Warnings
1526 ToolSafeMode[button] = data.SafeMode
1527 SetupDescription(button,data.Description)
1528 ToolSelectListener[button] = data.SelectListener
1529 local env = {}; ADR(env,true)
1530 local metadata = {
1531 Button = button;
1532 ID = data.Name;
1533 Resource = data.Resources;
1534 Warnings = data.Warnings;
1535 Connections = {};
1536 Overlay = {};
1537 Env = env;
1538 PreviousTool = nil;
1539 }
1540 ADR(metatdata)
1541 ToolEnvMetadata[env] = metadata
1542 ToolButtonMetadata[button] = metadata
1543 if data.BuiltIn then
1544 for i,v in pairs(Environment.BuiltIn) do
1545 env[i] = v
1546 end
1547 end
1548 for i,v in pairs(Environment.Listener.Global.Safe) do
1549 env[i] = v
1550 end
1551 for i,v in pairs(Environment.Listener.API.Safe) do
1552 env[i] = v
1553 end
1554 if not data.SafeMode then
1555 for i,v in pairs(Environment.Listener.Global.Unsafe) do
1556 env[i] = v
1557 end
1558 for i,v in pairs(Environment.Listener.API.Unsafe) do
1559 env[i] = v
1560 end
1561 end
1562 if data.SafeMode then
1563 setmetatable(env,restrict_mt)
1564 end
1565 setfenv(data.SelectListener,env)
1566 if data.DeselectListener then
1567 ToolDeselectListener[button] = data.DeselectListener
1568 setfenv(data.DeselectListener,env)
1569 end
1570 SetupToolState(button)
1571 local key = data.ShortcutKey
1572 if key then
1573 if Shortcut[key] then
1574 LogWarning("Tool \""..data.Name.."\": shortcut key \""..key.."\" was already bound")
1575 else
1576 Shortcut[key] = button
1577 end
1578 end
1579 ref[data.Name] = button
1580 return button,metadata
1581 end;
1582 ["menu"] = function(data,ref)
1583 for i,v in pairs(data.Resources) do
1584 if IsContent(v) then
1585 ContentProvider:Preload(v)
1586 end
1587 end
1588 local id = {}
1589 ref[data.Name] = id
1590 local menubutton = MakeGuiObject["menubutton"](data.Name.."MenuButton",data.MenuText)
1591 local menu = MakeGuiObject["menu"](data.Name.."Menu",0,0)
1592 SetupDescription(menubutton,data.MenuDescription)
1593 menu.Visible = false
1594 id.MenuButton = menubutton
1595 id.Menu = menu
1596 local tools = {}; ADR(tools)
1597 ToolsFromMenu[menu] = tools
1598 local mds = {}
1599 local X,Y = 0,#data.MenuLayout
1600 for y,row in pairs(data.MenuLayout) do
1601 for x,info in pairs(row) do
1602 local button
1603 if info[2] == "tool" then
1604 local tdata = {
1605 Name = info[1];
1606 Type = "tool";
1607 SafeMode = data.SafeMode;
1608 BuiltIn = data.BuiltIn;
1609 Resources = data.Resources;
1610 Text = info[3];
1611 SelectListener = data.SelectListener[info[1]];
1612 DeselectListener = data.DeselectListener[info[1]];
1613 Description = data.ButtonDescription[info[1]];
1614 Warnings = data.ToolWarnings[info[1]];
1615 ShortcutKey = data.ShortcutKey[info[1]];
1616 }
1617 ADR(tdata)
1618 button,md = InitDataType["tool"](tdata,id)
1619 table.insert(tools,button)
1620 MenuFromTool[button] = menubutton
1621 table.insert(mds,md)
1622 else
1623 button = MakeButton[info[2]](info,data,id)
1624 if SetupValueState[info[2]] then
1625 SetupValueState[info[2]](button,info[3],data.SafeMode)
1626 end
1627 SetupDescription(button,data.ButtonDescription[info[1]])
1628 id[info[1]] = button
1629 end
1630 button.Position = UDim2.new(x-1,0,y-1,0)
1631 button.Parent = menu.Items
1632 X = x > X and x or X
1633 end
1634 end
1635 -- add the list of ids to buttons to each tool's metadata
1636 for _,md in pairs(mds) do
1637 md.ButtonFromId = id
1638 end
1639 menu.Size = UDim2.new(0,X*config.button_size_x,0,Y*config.button_size_y)
1640 local state = {false;menu}; ADR(state)
1641 MenuState[menubutton] = state
1642 SetupMenuState(menubutton)
1643 menubutton.Parent = Div
1644 menu.Parent = Div
1645 end;
1646 ["command"] = function(data,ref,doc,help)
1647 for i,v in pairs(data.Resources) do
1648 if IsContent(v) then
1649 ContentProvider:Preload(v)
1650 end
1651 end
1652 if ref[data.CommandName] then
1653 LogError("Command \"",data.CommandName,"\" already exists")
1654 else
1655 local env = {}; ADR(env,true)
1656 local metadata = {
1657 Resource = data.Resources;
1658 }
1659 ADR(metadata)
1660 CommandEnvMetadata[env] = metadata
1661 if data.BuiltIn then
1662 for i,v in pairs(Environment.BuiltIn) do
1663 env[i] = v
1664 end
1665 end
1666 for i,v in pairs(Environment.Command.Global.Safe) do
1667 env[i] = v
1668 end
1669 for i,v in pairs(Environment.Command.API.Safe) do
1670 env[i] = v
1671 end
1672 if not data.SafeMode then
1673 for i,v in pairs(Environment.Command.Global.Unsafe) do
1674 env[i] = v
1675 end
1676 for i,v in pairs(Environment.Command.API.Unsafe) do
1677 env[i] = v
1678 end
1679 end
1680 setfenv(data.CommandFunction,env)
1681 local h = {name = data.Name}
1682 if data.ArgDoc then
1683 h.args = data.CommandName..data.ArgDoc
1684 table.insert(doc,data.CommandName..data.ArgDoc)
1685 else
1686 h.args = data.CommandName.."( )"
1687 table.insert(doc,data.CommandName.."( )")
1688 end
1689 local d = {}
1690 if data.Description then
1691 for line in data.Description:gmatch("[^\r\n]+") do
1692 table.insert(d,line)
1693 end
1694 end
1695 h.desc = d
1696 help[data.CommandName] = h
1697 help[data.CommandFunction] = h
1698 ref[data.CommandName] = data.CommandFunction
1699 end
1700 end;
1701 ["control"] = function(data,ref)
1702 for i,v in pairs(data.Resources) do
1703 if IsContent(v) then
1704 ContentProvider:Preload(v)
1705 end
1706 end
1707 ContentProvider:Preload(data.ControlIcon)
1708 local control = MakeGuiObject["controlbutton"](data.ControlName,data.ControlIcon)
1709 for i,v in pairs(data.Resources) do
1710 Resource[i] = v
1711 end
1712 ref[data.Name] = control
1713 local listener = data.ControlListener
1714 if listener then
1715 setfenv(listener,Environment.BuiltIn)
1716 control.MouseButton1Click:connect(listener)
1717 end
1718 SetupDescription(control,data.Description)
1719 ControlData[control] = data
1720 local key = data.ShortcutKey
1721 if key then
1722 if Shortcut[key] then
1723 LogWarning("Control \""..data.Name.."\": shortcut key \""..key.."\" was already bound")
1724 else
1725 Shortcut[key] = control
1726 end
1727 end
1728 return control
1729 end;
1730}
1731
1732-- uses element data to generate the panel's meat
1733local function InitializePanel()
1734 wait(1) -- give gui time to initialize abs size/pos
1735 -- create title
1736 Title = MakeGuiObject["title"]()
1737 Title.Text = "CmdUtl"
1738 Title.Parent = Div
1739 -- Change title text based on size; neat!
1740 ADR(Title.Changed:connect(function(p)
1741 if p == "AbsoluteSize" then
1742 Title.Text = "Command Utility"
1743 if not Title.TextFits then
1744 Title.Text = "CmdUtl"
1745 end
1746 end
1747 end))
1748 -- create frame for description
1749 DescriptionFrame = MakeGuiObject["descframe"]()
1750 DescriptionFrame.Parent = Div
1751 -- create control frame
1752 if #InitData.Main.Controls > 0 then
1753 local controlframe = MakeGuiObject["controlframe"]()
1754 for i,data in pairs(InitData.Main.Controls) do
1755 local control = InitDataType["control"](data,Control)
1756 control.Position = UDim2.new(0,0,i-1,0)
1757 control.Parent = controlframe
1758 end
1759 controlframe.Parent = Div
1760 end
1761 -- create menus for main tools
1762 for i,data in pairs(InitData.Main.Menus) do
1763 InitDataType["menu"](data,ID)
1764 end
1765 -- create menu for other tools
1766 if #InitData.Main.Tools > 0 then
1767 local tools = {}; ADR(tools)
1768 local id = {}; ADR(id)
1769 ID.Other = id
1770 for i,data in pairs(InitData.Main.Tools) do
1771 local button = InitDataType["tool"](data,id)
1772 table.insert(tools,button)
1773 end
1774 local sx,sy = PositionButtonsAsGrid(tools,config.tool_menu_length)
1775 local menubutton = MakeGuiObject["menubutton"]("OtherMenuButton","Other")
1776 local menu = MakeGuiObject["menu"]("OtherMenu",sx*config.button_size_x,sy*config.button_size_y)
1777 SetupDescription(menubutton,"{h}Other Menu\nContains miscellaneous tools.")
1778 menu.Visible = false
1779 id.MenuButton = menubutton
1780 id.Menu = menu
1781 for i,tool in pairs(tools) do
1782 tool.Parent = menu.Items
1783 MenuFromTool[tool] = menubutton
1784 end
1785 ToolsFromMenu[menu] = tools
1786 local state = {false;menu}; ADR(state)
1787 MenuState[menubutton] = state
1788 SetupMenuState(menubutton,state)
1789 menubutton.Parent = Div
1790 menu.Parent = Div
1791 end
1792 -- create menu for plugin tools
1793 if #InitData.Plugins.Tools > 0 then
1794 local tools = {}; ADR(tools)
1795 local id = {}; ADR(id)
1796 ID.PluginTools = id
1797 for i,data in pairs(InitData.Plugins.Tools) do
1798 local button = InitDataType["tool"](data,id)
1799 table.insert(tools,button)
1800 end
1801 local sx,sy = PositionButtonsAsGrid(tools,config.tool_menu_length)
1802 local menubutton = MakeGuiObject["menubutton"]("PluginToolsMenuButton","Plugins")
1803 local menu = MakeGuiObject["menu"]("PluginToolsMenu",sx*config.button_size_x,sy*config.button_size_y)
1804 SetupDescription(menubutton,"{h}Plugin Menu\nContains tools generated by plugins.")
1805 menu.Visible = false
1806 id.MenuButton = menubutton
1807 id.Menu = menu
1808 for i,tool in pairs(tools) do
1809 tool.Parent = menu.Items
1810 MenuFromTool[tool] = menubutton
1811 end
1812 ToolsFromMenu[menu] = tools
1813 local state = {false;menu}; ADR(state)
1814 MenuState[menubutton] = state
1815 SetupMenuState(menubutton,state)
1816 menubutton.Parent = Div
1817 menu.Parent = Div
1818 end
1819 -- make plugin menus
1820 if #InitData.Plugins.Menus > 0 then
1821 -- add a seperator
1822 local sep = MakeGuiObject["seperator"]()
1823 sep.Parent = Div
1824 ID.Plugins = {}; ADR(ID.Plugins)
1825 -- add the menus
1826 for i,data in pairs(InitData.Plugins.Menus) do
1827 InitDataType["menu"](data,ID.Plugins)
1828 end
1829 end
1830 -- start div
1831 Mode.DivTweenEnabled = false
1832 ADR(MakeDiv(Div))
1833 Mode.DivTweenEnabled = true
1834 -- start up shortcut keys
1835 if config.shortcut_keys_enabled then
1836 local go = false
1837 for key in pairs(Shortcut) do
1838 go = true
1839 GuiService:AddKey(key)
1840 end
1841 if go then
1842 ADR(GuiService.KeyPressed:connect(function(key)
1843 local button = Shortcut[key]
1844 if button then
1845 if ToolState[button] ~= nil then
1846 if Mode.PanelExpanded then
1847 if ToolState[button] then
1848 DeselectTool(button)
1849 if config.menu_auto_collapse then
1850 ToggleMenu(MenuFromTool[button],false)
1851 end
1852 else
1853 SelectTool(button)
1854 ToggleMenu(MenuFromTool[button],ToolState[button])
1855 end
1856 end
1857 elseif ControlData[button] then
1858 ControlData[button].ControlListener()
1859 end
1860 end
1861 end))
1862 end
1863 end
1864end
1865
1866local CommandShortcuts = {
1867 G = game;
1868 W = game:GetService("Workspace");
1869 P = game:GetService("Players");
1870 L = game:GetService("Lighting");
1871 S = game:GetService("Selection");
1872 IS = game:GetService("InsertService");
1873 BS = game:GetService("BadgeService");
1874 CS = game:GetService("CollectionService");
1875 SC = game:GetService("ScriptContext");
1876 CP = game:GetService("ContentProvider");
1877 CG = game:GetService("CoreGui");
1878 JS = game:FindFirstChild("JointsService");
1879 D = game:GetService("Debris");
1880 SP = game:GetService("StarterPack");
1881 SG = game:GetService("StarterGui");
1882 SS = game:GetService("SoundService");
1883 RS = game:GetService("RunService");
1884}
1885ADR(CommandShortcuts,true)
1886
1887local function InitializeCommands()
1888 local Doc = {}; ADR(Doc)
1889 local Help = {}; ADR(Help)
1890 Commands["list"] = function()
1891 for _,line in pairs(Doc) do
1892 print(line)
1893 end
1894 end;
1895 Help["list"] = {
1896 name = "ListCommands";
1897 args = "list( )";
1898 desc = {"Shows a list of commands with their possible arguments, along with any shortcut variables."};
1899 }
1900 Help[Commands["list"]] = Help["list"]
1901 Commands["help"] = function(f)
1902 ft = type(f)
1903 if ft == "nil" then
1904 local ordered = {}
1905 for i in pairs(Help) do
1906 if type(i) == "string" then
1907 table.insert(ordered,i)
1908 end
1909 end
1910 table.sort(ordered)
1911 print("---- Type \"help(command)\" for help on that specific command.")
1912 for i,v in pairs(ordered) do
1913 local line = Help[v].desc[1]
1914 if line then
1915 print(v .. " : " .. line)
1916 else
1917 print(v)
1918 end
1919 end
1920 else
1921 local h = Help[f]
1922 if h then
1923 if #h.desc > 0 then
1924 print("---- Command \""..h.name.."\" ----------------")
1925 if h.args then print("> "..h.args) end
1926 for i,v in pairs(h.desc) do
1927 print(v)
1928 end
1929 else
1930 print("No help information was found for \""..f.."\".")
1931 end
1932 else
1933 print("\""..tostring(f).."\" is not a valid command.")
1934 end
1935 end
1936 end;
1937 Help["help"] = {
1938 name = "Help";
1939 args = "help( * command = nil )";
1940 desc = {"Shows help information for a command.";"'command' may be a string (the command's name), or a function (the command function itself).";"If 'command' is not specified, then a list of possible commands will be displayed."};
1941 }
1942 Help[Commands["help"]] = Help["help"]
1943 Commands["close"] = function()
1944 DisposeResources()
1945 end;
1946 Help["close"] = {
1947 name = "CloseCmdUtl";
1948 args = "close( )";
1949 desc = {"Closes CmdUtl.";"nMost resources taken up by CmdUtl are released and collected."};
1950 }
1951 Help[Commands["help"]] = Help["help"]
1952 table.insert(Doc,[[---- Commands ----------------]])
1953 table.insert(Doc,[[list( )]])
1954 table.insert(Doc,[[help( string command = nil )]])
1955 table.insert(Doc,[[close( )]])
1956 for i,v in pairs(CommandShortcuts) do
1957 Commands[i] = v
1958 end
1959 for i,data in pairs(InitData.Main.Commands) do
1960 InitDataType["command"](data,Commands,Doc,Help)
1961 end
1962 for i,data in pairs(InitData.Plugins.Commands) do
1963 InitDataType["command"](data,Commands,Doc,Help)
1964 end
1965 table.insert(Doc,[[---- Shortcut Variables ----------------]])
1966 -- alphabetize shortcut docs
1967 local shortcuts = {}
1968 for i in pairs(CommandShortcuts) do
1969 table.insert(shortcuts,i)
1970 end
1971 table.sort(shortcuts)
1972 for _,i in pairs(shortcuts) do
1973 local v = CommandShortcuts[i]
1974 table.insert(Doc,i .. " = " .. v.className)
1975 end
1976
1977 local CommandEnv
1978
1979 local function add()
1980 CommandEnv = getfenv(2)
1981 for i,v in pairs(Commands) do
1982 CommandEnv[i] = v
1983 end
1984 print [[---- CmdUtl has been loaded --------------------------------]]
1985 print [[-- Type "list()" for a list of commands]]
1986 print [[-- or "help()" for help on commands]]
1987 end
1988
1989 local function dispose()
1990 if CommandEnv then
1991 for i,v in pairs(Commands) do
1992 if CommandEnv[i] == v then
1993 CommandEnv[i] = nil
1994 end
1995 Commands[i] = nil
1996 end
1997 end
1998 end
1999 ADR(dispose)
2000
2001 settings().Diagnostics:LegacyScriptMode()
2002 game:GetService("ScriptContext"):SetCollectScriptStats(true)
2003 game:GetService("InsertService"):SetFreeModelUrl("http://www.roblox.com/Game/Tools/InsertAsset.ashx?type=fm&q=%s&pg=%d&rs=%d")
2004 game:GetService("InsertService"):SetFreeDecalUrl("http://www.roblox.com/Game/Tools/InsertAsset.ashx?type=fd&q=%s&pg=%d&rs=%d")
2005
2006 _G.CmdUtl = add
2007 _G.cu = add
2008 _G.CloseCmdUtl = function()
2009 DisposeResources()
2010 end
2011end
2012
2013local AddInitDataType = {
2014 ["tool"] = function(data,built_in)
2015 if built_in then
2016 table.insert(InitData.Main.Tools,data)
2017 else
2018 table.insert(InitData.Plugins.Tools,data)
2019 end
2020 end;
2021 ["menu"] = function(data,built_in)
2022 if built_in then
2023 table.insert(InitData.Main.Menus,data)
2024 else
2025 table.insert(InitData.Plugins.Menus,data)
2026 end
2027 end;
2028 ["control"] = function(data)
2029 table.insert(InitData.Main.Controls,data)
2030 end;
2031 ["command"] = function(data,built_in)
2032 if built_in then
2033 table.insert(InitData.Main.Commands,data)
2034 else
2035 table.insert(InitData.Plugins.Commands,data)
2036 end
2037 end;
2038}
2039ADR(AddInitDataType)
2040
2041function BuildElement(data,built_in)
2042 if not built_in then
2043 PluginDataFromName[data.Name] = data
2044 PluginResources[data.Name] = data.Resources
2045 end
2046 for key,value in pairs(data.Resources) do
2047 if type(value) == "string" then
2048 if IsContent(value) then
2049 ContentProvider:Preload(value)
2050 end
2051 end
2052 end
2053 AddInitDataType[data.Type](data,built_in)
2054end
2055
2056local HandleButtonInfo
2057
2058local button_type = {
2059 ["tool"] = function(value)
2060 if type(value) ~= "string" then return false,"must be a string" end
2061 return true
2062 end;
2063 ["field"] = function(value)
2064 local vtype = type(value)
2065 if vtype ~= "string" and vtype ~= "number" and vtype ~= "boolean" and vtype ~= "table" then
2066 return false,"must be a string, number, or boolean"
2067 elseif vtype == "table" then
2068 if type(value[2]) ~= "function" then
2069 return false,"2nd entry in table must be a function"
2070 end
2071 end
2072 return true
2073 end;
2074 ["label"] = function(value)
2075 if type(value) ~= "string" then return false,"must be a string" end
2076 return true
2077 end;
2078 ["toggle"] = function(value)
2079 if type(value) ~= "boolean" then return false,"must be a boolean" end
2080 return true
2081 end;
2082 ["container"] = function(value,uids)
2083 if type(value) ~= "table" then return false,"must be a table" end
2084 if not IsArray(value) then return false,"must be an array" end
2085 for i,button in pairs(value) do
2086 local e,o = HandleButtonInfo(button,uids,{"tool";"container"})
2087 if not e then
2088 return false,o
2089 end
2090 end
2091 return true
2092 end;
2093}
2094ADR(button_type)
2095
2096HandleButtonInfo = function(button,uids,invalid_types)
2097 local id,btype,value = button[1],button[2],button[3]
2098 if type(id) ~= "string" then return false,"1st index of button info must be a string (ButtonId)" end
2099 if #id == 0 then return false,"ButtonId cannot have 0 characters" end
2100 if uids[id] then return false,"Button \""..id.."\" already exists" end
2101 if id == "Menu" or id == "MenuButton" then return false,"ButtonId cannot be \"Menu\" or \"MenuButton\"" end
2102 if type(btype) ~= "string" then return false,"2nd index of button info \""..id.."\" must be a string (ButtonType)" end
2103 button[2] = btype:lower()
2104 btype = button[2]
2105 invalid_types = invalid_types or {}
2106 local type_handle = button_type[btype]
2107 if type_handle and not invalid_types[btype] then
2108 local e,o = type_handle(value,uids)
2109 if e then
2110 uids[id] = button
2111 return true
2112 else
2113 return false,"3rd index of button info \""..id.."\" ("..btype.."):[ "..o.." ]"
2114 end
2115 else
2116 return false,"2nd index of button info \""..id.."\" is not a valid button type"
2117 end
2118end
2119
2120local EnvMetadata = {}; ADR(EnvMetadata)
2121
2122local function GetSourceMetadata()
2123 local env = getfenv(3)
2124 local md = EnvMetadata[env]
2125 if not md then error("Invalid call",3) end
2126 if md.context.Validated then error("Function is no longer active",3) end
2127 return md.context,md.data
2128end
2129
2130---- Source Processing Framework ------------
2131
2132local SourceAPI = {}; ADR(SourceAPI)
2133-- contains declarations for processing the element source
2134-- comes in two parts:
2135-- Main: declares the initial environment that the source will use
2136-- Type: declares the environment added by SetPluginType
2137
2138-- contexts: contexts that must be present in order to pass validation
2139-- data_init: initial data values that should be added when the environment is added
2140-- validate: custom validates the data; called by Validate
2141-- env: contains the functions that will be added to the source
2142
2143SourceAPI.Type = {
2144 ["tool"] = {
2145 contexts = {"ButtonText";"ToolSelect"};
2146 data_init = function() end;
2147 validate = function(data)
2148 if data.Name == "Menu" or data.Name == "MenuButton" then
2149 return false,"Tool cannot have a name of \"Menu\" or \"MenuButton\""
2150 else
2151 return true
2152 end
2153 end;
2154 env = {
2155 SetButtonText = function(text)
2156 local context,data = GetSourceMetadata()
2157 if context.ButtonText then error("$SetButtonText: Button text has already been set",2) end
2158 if type(text) ~= "string" then error("$SetButtonText: 1st argument must be a string",2) end
2159 data.Text = text
2160 context.ButtonText = true
2161 end;
2162 SetOnSelect = function(listener)
2163 local context,data = GetSourceMetadata()
2164 if context.ToolSelect then error("$SetOnSelect: Selection has already been set",2) end
2165 if type(listener) ~= "function" then error("$SetOnSelect: 1st argument must be a function",2) end
2166 data.SelectListener = listener
2167 context.ToolSelect = true
2168 end;
2169 SetOnDeselect = function(listener)
2170 local context,data = GetSourceMetadata()
2171 if context.ToolDeselect then error("$SetOnDeselect: Deselection has already been set",2) end
2172 if type(listener) ~= "function" then error("$SetOnDeselect: 1st argument must be a function",2) end
2173 data.DeselectListener = listener
2174 context.ToolDeselect = true
2175 end;
2176 SetDescription = function(desc)
2177 local context,data = GetSourceMetadata()
2178 if context.ToolDescription then error("$SetDescription: Description has already been set",2) end
2179 if type(desc) ~= "string" then error("$SetDescription: 1st argument must be a string",2) end
2180 data.Description = desc
2181 context.ToolDescription = true
2182 end;
2183 SetWarnings = function(warn)
2184 local context,data = GetSourceMetadata()
2185 if context.ToolWarnings then error("$SetWarnings: Warnings have already been set",2) end
2186 if type(warn) == "string" then
2187 warn = {warn}
2188 elseif type(warn) == "table" then
2189 if not IsArray(warn) then error("$SetWarnings: Table must be an array",2) end
2190 for i,v in pairs(warn) do
2191 if type(v) ~= "string" then error("$SetWarnings: Table may only contain strings",2) end
2192 end
2193 else
2194 error("$SetWarnings: 1st argument must be a string or table",2)
2195 end
2196
2197 data.Warnings = warn
2198 context.ToolWarnings = true
2199 end;
2200 SetShortcutKey = function(key)
2201 local context,data = GetSourceMetadata()
2202 if context.ShortcutKey then error("$SetShortcutKey: Shortcut key has already been set",2) end
2203 if type(key) ~= "string" then error("$SetShortcutKey: 1st argument must be a string",2) end
2204 local map = shortcuts[key]
2205 if type(map) == "string" and #map == 1 then
2206 key = map
2207 end
2208 if #key == 1 then
2209 data.ShortcutKey = key
2210 end
2211 context.ShortcutKey = true
2212 end
2213 };
2214 };
2215 ["menu"] = {
2216 contexts = {"MenuText";"MenuLayout"};
2217 data_init = function(data)
2218 data.SelectListener = {}
2219 data.DeselectListener = {}
2220 data.ButtonDescription = {}
2221 data.ToolWarnings = {}
2222 data.ShortcutKey = {}
2223 end;
2224 validate = function(context,data)
2225 local bids = data.ButtonIDs
2226 -- check if layout has all needed fields
2227 for id,button in pairs(bids) do
2228 local btype = button[2]
2229 if btype == "tool" then
2230 if not data.SelectListener[id] then return false,"Button \""..id.."\" (tool) does not have a tool select listener" end
2231 else
2232 if data.SelectListener[id] then return false,"Button \""..id.."\" ("..btype..") cannot have a tool select listener" end
2233 if data.DeselectListener[id] then return false,"Button \""..id.."\" ("..btype..") cannot have a tool deselect listener" end
2234 if data.ToolWarnings[id] then return false,"Button \""..id.."\" ("..btype..") cannot have tool warnings" end
2235 if data.ShortcutKey[id] then return false,"Button \""..id.."\" ("..btype..") cannot have a shortcut key" end
2236 end
2237 end
2238 -- check if fields have existing layout
2239 for id in pairs(data.SelectListener) do
2240 if not bids[id] then
2241 return false,"SetOnToolSelect: \""..id.."\" was not defined in layout"
2242 end
2243 end
2244 for id in pairs(data.DeselectListener) do
2245 if not bids[id] then
2246 return false,"SetOnToolDeselect: \""..id.."\" was not defined in layout"
2247 end
2248 end
2249 for id in pairs(data.ButtonDescription) do
2250 if not bids[id] then
2251 return false,"SetButtonDescription: \""..id.."\" was not defined in layout"
2252 end
2253 end
2254 for id in pairs(data.ToolWarnings) do
2255 if not bids[id] then
2256 return false,"SetToolWarnings: \""..id.."\" was not defined in layout"
2257 end
2258 end
2259 for id in pairs(data.ShortcutKey) do
2260 if not bids[id] then
2261 return false,"SetToolShortcutKey: \""..id.."\" was not defined in layout"
2262 end
2263 end
2264 return true
2265 end;
2266 env = {
2267 SetMenuText = function(text)
2268 local context,data = GetSourceMetadata()
2269 if context.MenuText then error("$SetMenuText: Text has already been set",2) end
2270 if type(text) ~= "string" then error("$SetMenuText: 1st argument must be a string",2) end
2271 data.MenuText = text
2272 context.MenuText = true
2273 end;
2274 SetMenuDescription = function(desc)
2275 local context,data = GetSourceMetadata()
2276 if context.MenuDescription then error("$SetMenuDescription: Description has already been set",2) end
2277 if type(desc) ~= "string" then error("$SetMenuDescription: 1st argument must be a string",2) end
2278 data.MenuDescription = desc
2279 context.MenuDescription = true
2280 end;
2281 SetLayout = function(layout)
2282 local context,data = GetSourceMetadata()
2283 if context.MenuLayout then error("$SetLayout: Menu layout has already been set",2) end
2284 if type(layout) ~= "table" then error("$SetLayout: 1st argument must be a table",2) end
2285 if not IsArray(layout) then error("$SetLayout: Layout must be an array") end
2286 local unique_ids = {}
2287 for i,row in pairs(layout) do
2288 if type(row) ~= "table" then error("$SetLayout: Layout may only contain tables (rows)",2) end
2289 if not IsArray(row) then error("$SetLayout: Row ("..i..") must be an array",2) end
2290 for i,button in pairs(row) do
2291 local e,o = HandleButtonInfo(button,unique_ids)
2292 if not e then
2293 error("$SetLayout: "..o,2)
2294 end
2295 end
2296 end
2297 data.MenuLayout = layout
2298 data.ButtonIDs = unique_ids
2299 context.MenuLayout = true
2300 end;
2301 SetOnSelect = function(id, listener)
2302 local context,data = GetSourceMetadata()
2303 if type(id) ~= "string" then error("$SetOnSelect: 1st argument must be a string",2) end
2304 if data.SelectListener[id] then error("$SetOnSelect: The \""..id.."\" tool's selection has already been set",2) end
2305 if type(listener) ~= "function" then error("$SetOnSelect: 2nd argument must be a function",2) end
2306 data.SelectListener[id] = listener
2307 end;
2308 SetOnDeselect = function(id, listener)
2309 local context,data = GetSourceMetadata()
2310 if type(id) ~= "string" then error("$SetOnDeselect: 1st argument must be a string",2) end
2311 if data.DeselectListener[id] then error("$SetOnDeselect: The \""..id.."\" tool's deselection has already been set",2) end
2312 if type(listener) ~= "function" then error("$SetOnDeselect: 2nd argument must be a function",2) end
2313 data.DeselectListener[id] = listener
2314 end;
2315 SetButtonDescription = function(id, text)
2316 local context,data = GetSourceMetadata()
2317 if type(id) ~= "string" then error("$SetButtonDescription: 1st argument must be a string",2) end
2318 if data.ButtonDescription[id] then error("$SetButtonDescription: The \""..id.."\" button's description has already been set",2) end
2319 if type(text) ~= "string" then error("$SetButtonDescription: 2nd argument must be a string",2) end
2320 data.ButtonDescription[id] = text
2321 end;
2322 SetWarnings = function(id,warn)
2323 local context,data = GetSourceMetadata()
2324 if type(id) ~= "string" then error("$SetWarnings: 1st argument must be a string",2) end
2325 if data.ToolWarnings[id] then error("$SetWarnings: The \""..id.."\" tool's warnings have already been set",2) end
2326 if type(warn) == "string" then
2327 warn = {warn}
2328 elseif type(warn) == "table" then
2329 if not IsArray(warn) then error("$SetWarnings: Table must be an array",2) end
2330 for i,v in pairs(warn) do
2331 if type(v) ~= "string" then error("$SetWarnings: Table may only contain strings",2) end
2332 end
2333 else
2334 error("$SetWarnings: 2nd argument must be a string or table",2)
2335 end
2336 data.ToolWarnings[id] = warn
2337 end;
2338 SetShortcutKey = function(id,key)
2339 local context,data = GetSourceMetadata()
2340 if type(id) ~= "string" then error("$SetShortcutKey: 1st argument must be a string",2) end
2341 if data.ShortcutKey[id] then error("$SetShortcutKey: The \""..id.."\" tool's shortcut key has already been set",2) end
2342 if type(key) ~= "string" then error("$SetShortcutKey: 2nd argument must be a string",2) end
2343 local map = shortcuts[key]
2344 if type(map) == "string" and #map == 1 then
2345 key = map
2346 end
2347 if #key == 1 then
2348 data.ShortcutKey[id] = key
2349 end
2350 end;
2351 };
2352 };
2353 ["command"] = {
2354 contexts = {"CommandName";"CommandFunction"};
2355 data_init = function()end;
2356 validate = function() return true end;
2357 env = {
2358 SetCommandName = function(name)
2359 local context,data = GetSourceMetadata()
2360 if context.CommandName then error("$SetCommandName: Command name has already been set",2) end
2361 if type(name) ~= "string" then error("$SetCommandName: 1st argument must be a string",2) end
2362 if #name == 0 then error("$SetCommandName: 1st argument cannot have 0 characters",2) end
2363 if not IsVarName(name) then error("$SetCommandName: Name must contain only letters, numbers, and underscores, with the first character not being a number") end
2364 if #name > 16 then error("$SetCommandName: Name should not contain more than 16 characters",2) end
2365 data.CommandName = name
2366 context.CommandName = true
2367 end;
2368 SetFunction = function(func)
2369 local context,data = GetSourceMetadata()
2370 if context.CommandFunction then error("$SetFunction: Command function has already been set",2) end
2371 if type(func) ~= "function" then error("$SetFunction: 1st argument must be a function",2) end
2372 data.CommandFunction = func
2373 context.CommandFunction = true
2374 end;
2375 SetDescription = function(desc)
2376 local context,data = GetSourceMetadata()
2377 if context.Description then error("$SetDescription: Command description has already been set",2) end
2378 if type(desc) ~= "string" then error("$SetDescription: 1st argument must be a string",2) end
2379 data.Description = desc
2380 context.Description = true
2381 end;
2382 SetArgumentDoc = function(args)
2383 local context,data = GetSourceMetadata()
2384 if context.Arguments then error("$SetArgumentDoc: Argument documentation has already been set",2) end
2385 if type(args) ~= "table" then error("$SetArgumentDoc: 1st argument must be a table",2) end
2386 if not IsArray(args) then error("$SetArgumentDoc: Argument doc must be an array",2) end
2387 local doc = {}
2388 for i,arg in pairs(args) do
2389 if type(arg) ~= "table" then error("$SetArgumentDoc: Argument doc may only contain tables (args)",2) end
2390 local atype,name,default = arg[1],arg[2],arg[3]
2391 if type(atype) ~= "string" then error("$SetArgumentDoc: 1st entry to Argument must be a string",2) end
2392 if #atype == 0 then error("$SetArgumentDoc: 1st entry to Argument cannot have 0 characters",2) end
2393 if atype:match("*") then
2394 if #atype ~= 1 then
2395 error("$SetArgumentDoc: If 1st enty contains \"*\", it must have a length of 1",2)
2396 end
2397 elseif atype:match("[^%w _]") then
2398 error("$SetArgumentDoc: 1st entry contains invalid characters",2)
2399 end
2400 if #atype > 32 then error("$SetArgumentDoc: 1st entry to Argument cannot contain more than 32 characters",2) end
2401 if type(name) ~= "string" then error("$SetArgumentDoc: 2nd entry to Argument must be a string",2) end
2402 if #name == 0 then error("$SetArgumentDoc: 2nd entry to Argument cannot have 0 characters",2) end
2403 if not IsVarName(name) then error("$SetArgumentDoc: 2nd entry to Argument must contain only letters, numbers, and underscores, with the first character not being a number",2) end
2404 if #name > 16 then error("$SetArgumentDoc: 2nd entry to Argument cannot contain more than 16 characters",2) end
2405 local d = atype .. " " .. name
2406 if default ~= nil then
2407 if type(default) ~= "string" then error("$SetArgumentDoc: 3rd entry to Argument must be a string",2) end
2408 if default:match("%c") then error("$SetArgumentDoc: 3rd entry to Argument cannot contain non-printable characters",2) end
2409 if #default > 64 then error("$SetArgumentDoc: 3rd entry to Argument cannot contain more than 64 characters",2) end
2410 d = d .. " = " .. default
2411 end
2412 table.insert(doc,d)
2413 end
2414 local final = "( " .. table.concat(doc,", ") .. (#doc > 0 and " " or "") .. ")"
2415 data.ArgDoc = final
2416 end;
2417 };
2418 };
2419 ["control"] = {
2420 contexts = {"ControlName";"ControlIcon"};
2421 data_init = function()end;
2422 validate = function(context,data)
2423 if data.BuiltIn then
2424 return true
2425 else
2426 return false,"Controls may only be built-in"
2427 end
2428 end;
2429 env = {
2430 SetControlName = function(name)
2431 local context,data = GetSourceMetadata()
2432 if context.ControlName then error("$SetControlName: Control name has already been set",2) end
2433 if type(name) ~= "string" then error("$SetControlName: 1st argument must be a string",2) end
2434 if #name == 0 then error("$SetControlName: 1st argument cannot have 0 characters",2) end
2435 data.ControlName = name
2436 context.ControlName = true
2437 end;
2438 SetDescription = function(desc)
2439 local context,data = GetSourceMetadata()
2440 if context.Description then error("$SetDescription: Control description has already been set",2) end
2441 if type(desc) ~= "string" then error("$SetDescription: 1st argument must be a string",2) end
2442 data.Description = desc
2443 context.Description = true
2444 end;
2445 SetIcon = function(icon)
2446 local context,data = GetSourceMetadata()
2447 if context.ControlIcon then error("$SetIcon: Control icon has already been set",2) end
2448 if not IsContent(icon) then error("$SetIcon: 1st argument must be a valid Content string",2) end
2449 data.ControlIcon = icon
2450 context.ControlIcon = true
2451 end;
2452 SetOnClick = function(listener)
2453 local context,data = GetSourceMetadata()
2454 if context.ControlListener then error("$SetOnClick: Control listener has already been set",2) end
2455 if type(listener) ~= "function" then error("$SetOnClick: 1st argument must be a function",2) end
2456 data.ControlListener = listener
2457 context.ControlListener = true
2458 end;
2459 SetShortcutKey = function(key)
2460 local context,data = GetSourceMetadata()
2461 if context.ShortcutKey then error("$SetShortcutKey: Shortcut key has already been set",2) end
2462 if type(key) ~= "string" then error("$SetShortcutKey: 1st argument must be a string",2) end
2463 local map = shortcuts[key]
2464 if type(map) == "string" and #map == 1 then
2465 key = map
2466 end
2467 if #key == 1 then
2468 data.ShortcutKey = key
2469 end
2470 context.ShortcutKey = true
2471 end
2472 };
2473 };
2474}
2475SourceAPI.Main = {
2476 contexts = {"PluginName";"PluginType"};
2477 data_init = function(data)
2478 data.Name = "<unknown>"
2479 data.SafeMode = true;
2480 data.Resources = {};
2481 end;
2482 validate = function(context,data)
2483 if context.Version then -- if plugin has opted in to version control, verify plugin version
2484 local major,minor,revision,extra = version:match("^(%d+)%.(%d+)%.(%d+)(.-)$")
2485 local vmajor,vminor,vrevision,vextra = data.Version:match("^(%d+)%.(%d+)%.(%d+)(.-)$")
2486 if vmajor == major then -- major matches
2487 if vminor == minor then -- minor matches; success
2488 -- revisions do not need checking; they should always be compatible
2489 -- extra can be ignored; generally used for beta releases
2490 return true
2491 elseif vminor < minor then -- minor less than; incompatible
2492 return false,"version "..data.Version.." is not compatible with the current version of CmdUtl ("..version..")"
2493 elseif vminor > minor then -- minor greater than; possibly incompatible
2494 LogWarning("Plugin \""..data.Name.."\" (v"..data.Version..") may not be compatible with the current version of CmdUtl (v"..version..")")
2495 end
2496 elseif vmajor < major then -- major thess than; incompatible
2497 return false,"version "..data.Version.." is not compatible with the current version of CmdUtl ("..version..")"
2498 elseif vmajor > major then -- major greater than; possible incompatible
2499 LogWarning("Plugin \""..data.Name.."\" (v"..data.Version..") may not be compatible with the current version of CmdUtl (v"..version..")")
2500 end
2501 end
2502 return true
2503 end;
2504 env = {
2505 SetPluginName = function(name)
2506 local context,data = GetSourceMetadata()
2507 if context.PluginName then error("$SetPluginName: Plugin name has already been set",2) end
2508 if type(name) ~= "string" then error("$SetPluginName: 1st argument must be a string",2) end
2509 if #name == 0 then error("$SetPluginName: 1st argument cannot have 0 characters",2) end
2510 if not IsVarName(name) then error("$SetPluginName: 1st argument may only contain letters, numbers, and underscores, and cannot start with a number",2) end
2511 if PluginDataFromName[name] then error("$SetPluginName: There is already a plugin with the name of \""..name.."\"",2) end
2512 data.Name = name
2513 context.PluginName = true
2514 end;
2515 SetPluginType = function(extype)
2516 local context,data = GetSourceMetadata()
2517 if context.PluginType then error("$SetPluginType: Plugin type has already been set",2) end
2518 if type(extype) ~= "string" then error("$SetPluginType: 1st argument must be a string",2) end
2519 extype = extype:lower()
2520 local ctype = SourceAPI.Type[extype]
2521 if not ctype then error("$SetPluginType: "..extype.." is not a valid plugin type",2) end
2522 data.Type = extype
2523 ctype.data_init(data)
2524 local env = getfenv(2)
2525 for i,v in pairs(ctype.env) do
2526 env[i] = v
2527 end
2528 context.PluginType = true
2529 end;
2530 SetPluginSafe = function(safe)
2531 local context,data = GetSourceMetadata()
2532 if context.SafeMode then error("$SetPluginSafe: Safe mode has already been set",2) end
2533 if type(safe) ~= "boolean" then error("$SetPluginSafe: 1st argument must be a boolean",2) end
2534 data.SafeMode = safe
2535 if not safe then
2536 local env = getfenv(2)
2537 setmetatable(env,nil)
2538 for i,v in pairs(Environment.Source.Unsafe) do
2539 env[i] = v
2540 end
2541 end
2542 context.SafeMode = true
2543 end;
2544 AddResource = function(key,value)
2545 local context,data = GetSourceMetadata()
2546 if type(key) ~= "string" then error("$AddResource: 1st argument must be a string",2) end
2547 if data.Resources[key] then error("$AddResource: Index \""..key.."\" has already been added",2) end
2548 if type(value) == "function" then error("$AddResource: 2nd argument cannot be a function",2) end
2549 if type(value) == "thread" then error("$AddResource: 2nd argument cannot be a thread",2) end
2550 if type(value) == "nil" then error("$AddResource: 2nd argument cannot be nil",2) end
2551 data.Resources[key] = value
2552 end;
2553 SetVersion = function(vers)
2554 local context,data = GetSourceMetadata()
2555 if context.Version then error("$Version: Version has already been set",2) end
2556 if type(vers) ~= "string" then
2557 error("$Version: 1st argument must be a string or table",2)
2558 end
2559 if not vers:match("^%d+%.%d+%.%d+.-$") then
2560 error("$Version: \""..vers.."\" is not a valid version number")
2561 end
2562 data.Version = vers
2563 context.Version = true
2564 end;
2565 Validate = function()
2566 local context,data = GetSourceMetadata()
2567 for _,key in pairs(SourceAPI.Main.contexts) do
2568 if not context[key] then
2569 error("$Validate: validation failed (\""..tostring(key).."\" was not set)",2)
2570 end
2571 end
2572 local mval = SourceAPI.Main.validate
2573 local e,o = mval(context,data)
2574 if not e then
2575 error("$Validate: "..tostring(o),2)
2576 end
2577 for _,key in pairs(SourceAPI.Type[data.Type].contexts) do
2578 if not context[key] then
2579 error("$Validate: validation failed (\""..tostring(key).."\" was not set)",2)
2580 end
2581 end
2582 local tval = SourceAPI.Type[data.Type].validate
2583 local e,o = tval(context,data)
2584 if not e then
2585 error("$Validate: "..tostring(o),2)
2586 end
2587 context.Validated = true
2588 end;
2589 };
2590}
2591
2592-- processes plugin sources and whatnot
2593function ProcessElementSource(init,built_in)
2594 local context = {}; ADR(context) -- contains values for controlling what functions may and may no longer be called
2595 local data = { -- contains the data generated by the source
2596 BuiltIn = built_in;
2597 }
2598 ADR(data)
2599 SourceAPI.Main.data_init(data)
2600 local env = {}; ADR(env,true)
2601 local metadata = {
2602 context = context;
2603 data = data;
2604 }
2605 ADR(metadata)
2606 EnvMetadata[env] = metadata
2607 for i,v in pairs(Environment.Source.Safe) do
2608 env[i] = v
2609 end
2610 for i,v in pairs(SourceAPI.Main.env) do
2611 env[i] = v
2612 end
2613 if built_in then
2614 for i,v in pairs(Environment.BuiltIn) do
2615 env[i] = v
2616 end
2617 end
2618 setmetatable(env,restrict_mt)
2619 setfenv(init,env)
2620 local e,o = pcall(init)
2621 if e then
2622 if context.Validated then
2623 if config.plugin_safe_mode then
2624 if data.SafeMode or built_in then -- BuiltIn overrides SafeMode
2625 BuildElement(data,built_in)
2626 else
2627 LogWarning("Plugin:",data.Name," was not loaded because Safe Mode is on")
2628 end
2629 else
2630 BuildElement(data,built_in)
2631 end
2632 else
2633 LogError("Plugin ",data.Name,": plugin was not validated")
2634 end
2635 else
2636 LogError("Plugin ",data.Name,": "..o)
2637 end
2638 EnvMetadata[env] = nil
2639end
2640
2641-- attempts to find plugin locations from 'plugins' table
2642local function GetPluginSources()
2643 local InsertService = game:GetService("InsertService")
2644 for _,id in pairs(plugins) do
2645 local children = {}
2646 -- gets children from asset or object path
2647 if IsPositiveInteger(id) then
2648 local asset = InsertService:LoadAsset(id)
2649 if asset then
2650 children = asset:GetChildren()
2651 asset.Parent = nil
2652 else
2653 LogError("plugin source: \"",id,"\": cannot access asset")
2654 end
2655 elseif pcall(function() return id:IsA"Instance" end) then -- that type check would be useful
2656 children = {id}
2657 else
2658 LogError("plugin source: \"",id,"\": not an asset id or Object path")
2659 end
2660 -- if the 1st child is a model; make the children the model's children
2661 local first = children[1]
2662 if first then
2663 if first.className == "Model" or first.className == "Backpack" then
2664 if #children == 1 then
2665 local fchildren = first:GetChildren()
2666 if #fchildren > 0 then
2667 children = fchildren
2668 else
2669 LogError("plugin source: \"",id,"\": model does not contain any scripts")
2670 end
2671 else
2672 LogError("plugin source: \"",id,"\": model contains invalid objects")
2673 end
2674 end
2675 else
2676 LogError("plugin source: \"",id,"\": model contains no objects")
2677 end
2678 -- finally process children
2679 for _,child in pairs(children) do
2680 if child.className == "Script" then
2681 if #child:GetChildren() == 0 then
2682 local func,msg = loadstring(child.Source,"")
2683 if func then
2684 ProcessElementSource(func)
2685 else
2686 LogError("plugin source: \"",id,"\": syntax error: ",msg)
2687 end
2688 else
2689 LogError("plugin source: \"",id,"\": model contains invalid objects")
2690 end
2691 else
2692 LogError("plugin source: \"",id,"\": model contains invalid objects")
2693 end
2694 end
2695
2696 end
2697end
2698
2699local HandleConnection
2700local HandleObject
2701local HandleTable
2702
2703local function LimitRecurse(item)
2704 if item ~= getfenv() then
2705 for i,v in pairs(item) do
2706 if type(v) == "table" then
2707 LimitRecurse(v)
2708 end
2709 item[i] = nil
2710 end
2711 end
2712end
2713
2714local function LimitedHandle(item)
2715 local itype = type(item)
2716 if itype == "userdata" then
2717 if pcall(function() return item.disconnect end) then -- Connection
2718 item:disconnect()
2719 else -- try Instance
2720 pcall(item.Remove,item)
2721 end
2722 elseif itype == "table" and item ~= getfenv() then -- table
2723 for i,v in pairs(item) do
2724 item[i] = nil
2725 end
2726 end
2727end
2728
2729local function GetHandle(item)
2730 local itype = type(item)
2731 if itype == "userdata" then
2732 if pcall(function() return item.GetChildren end) then -- Instance
2733 return HandleObject
2734 elseif pcall(function() return item.disconnect end) then -- Connection
2735 return HandleConnection
2736 end
2737 elseif itype == "table" then -- table
2738 return HandleTable
2739 end
2740end
2741
2742HandleConnection = function(item)
2743 item:disconnect()
2744end
2745
2746HandleObject = function(item)
2747 pcall(item.Remove,item)
2748end
2749
2750HandleTable = function(item,dis)
2751 if item ~= getfenv() then
2752 for i,v in pairs(item) do
2753 if type(v) == "function" and dis then
2754 v() -- call custom disposal function
2755 end
2756 local handle = GetHandle(v)
2757 if handle then handle(v) end
2758 item[i] = nil
2759 end
2760 end
2761end
2762
2763
2764-- attempts to get rid of everything
2765function DisposeResources()
2766 -- deselect tools
2767 for button,b in pairs(ToolState) do
2768 if b then
2769 DeselectTool(button)
2770 end
2771 end
2772 -- activate disposal management
2773 for _,item in pairs(Disposal.limited) do
2774 LimitedHandle(item)
2775 end
2776 HandleTable(Disposal.normal,true)
2777 -- clear out command env
2778 if CommandEnv then
2779 for i,v in pairs(Commands) do
2780 if CommandEnv[i] == v then
2781 CommandEnv[i] = nil
2782 end
2783 end
2784 end
2785 _G.CmdUtl = nil
2786 _G.cu = nil
2787 _G.CloseCmdUtl = nil
2788 -- clear out top env
2789 local env = getfenv()
2790 for i in pairs(env) do
2791 env[i] = nil
2792 end
2793 -- attempt to collect garbage
2794 pcall(collectgarbage)
2795 -- all done!
2796 print("CmdUtl removed")
2797end
2798
2799---- Generate built-in goods ------------
2800
2801-- controls
2802ProcessElementSource(function()
2803 SetPluginName("Expand")
2804 SetPluginType("control")
2805 SetControlName("ExpandButton")
2806 SetDescription("{h}Show/Hide Panel\nShows or hides the Utility Panel.")
2807 SetIcon("http://www.roblox.com/asset/?id=54479709")
2808 AddResource("collapse_icon","http://www.roblox.com/asset/?id=54479709")
2809 AddResource("expand_icon","http://www.roblox.com/asset/?id=54479716")
2810 SetShortcutKey("Control.Expand")
2811 SetOnClick(function()
2812 if Mode.Enabled then
2813 Mode.Enabled = false
2814 Mode.PanelExpanded = not Mode.PanelExpanded
2815 for _,desc in pairs(ButtonDescription) do
2816 desc.Visible = false
2817 end
2818 for button,b in pairs(ToolState) do
2819 if b then
2820 DeselectTool(button)
2821 end
2822 end
2823 if Mode.PanelExpanded then
2824 if config.tween_panel_enabled then
2825 Panel:TweenPosition(UDim2.new(0,0,0.05,0),"Out","Quad",config.tween_speed,true,function()
2826 Control.Expand.Image = Resource.collapse_icon
2827 Mode.Enabled = true
2828 end)
2829 else
2830 Control.Expand.Image = Resource.collapse_icon
2831 Panel.Position = UDim2.new(0,0,0.05,0)
2832 Mode.Enabled = true
2833 end
2834 else
2835 if config.tween_panel_enabled then
2836 Panel:TweenPosition(UDim2.new(0,-Div.AbsoluteSize.x,0.05,0),"Out","Quad",config.tween_speed,true,function()
2837 Control.Expand.Image = Resource.expand_icon
2838 Mode.Enabled = true
2839 end)
2840 else
2841 Panel.Position = UDim2.new(0,-Div.AbsoluteSize.x,0.05,0)
2842 Control.Expand.Image = Resource.expand_icon
2843 Mode.Enabled = true
2844 end
2845 end
2846 end
2847 end)
2848 Validate()
2849end,true)
2850
2851ProcessElementSource(function()
2852 SetPluginName("Help")
2853 SetPluginType("control")
2854 SetControlName("HelpButton")
2855 SetDescription("{h}Help\nToggles Help Mode.\nIf Help Mode is on, descriptions will be displayed when a button is hovered over.")
2856 SetIcon("http://www.roblox.com/asset/?id=54479720")
2857 SetShortcutKey("Control.Help")
2858 SetOnClick(function()
2859 for _,desc in pairs(ButtonDescription) do
2860 desc.Visible = false
2861 end
2862 if Mode.HelpModeEnabled then
2863 Control.Help.BackgroundColor3 = Resource.control_color
2864 Mode.HelpModeEnabled = false
2865 else
2866 Control.Help.BackgroundColor3 = Resource.control_selected_color
2867 Mode.HelpModeEnabled = true
2868 end
2869 end)
2870 Validate()
2871end,true)
2872
2873ProcessElementSource(function()
2874 SetPluginName("Close")
2875 SetPluginType("control")
2876 SetControlName("CloseButton")
2877 SetDescription("{h}Close\nCloses CmdUtl.\nThis includes the Utility Panel and Command functions.\nMost resources taken up by CmdUtl are released and collected.")
2878 SetIcon("http://www.roblox.com/asset/?id=54479706")
2879 SetOnClick(function() DisposeResources() end)
2880 Validate()
2881end,true)
2882
2883-- tools and menus
2884
2885ProcessElementSource(function()
2886 SetPluginName("Move")
2887 SetPluginType("menu")
2888 SetMenuText("Movement")
2889 SetMenuDescription("{h}Movement Menu\nContains tools for moving parts around.")
2890
2891 AddResource("HandleColor",BrickColor.new("Br. yellowish orange"))
2892 AddResource("SnapSound","rbxasset://Sounds/snap.wav")
2893
2894 SetLayout{
2895 { -- row 1
2896 {"Inc","field",1};
2897 {"AxisSnap","container",{
2898 {"XButton","toggle",true,"X"};
2899 {"YButton","toggle",true,"Y"};
2900 {"ZButton","toggle",true,"Z"};
2901 }}
2902 };
2903 { -- row 2
2904 {"AxisButton","tool","Axis"};
2905 {"AxisSnapButton","tool","Snap"};
2906 };
2907 { -- row 3
2908 {"FirstButton","tool","First"};
2909 {"FirstSnapButton","tool","Snap"};
2910 };
2911 { -- row 4
2912 {"ObjectButton","tool","Object"};
2913 {"Delta","label","0"};
2914 };
2915 }
2916
2917 SetButtonDescription("AxisButton","{h}Move on Axis\nThis tool moves parts on the world axis.\nWhen selected, axis-aligned Handles will appear around all selected parts. When dragged, all the parts will move on the world axis.\nWhen dragging, parts will be snapped by the current Movement Increment.")
2918 SetButtonDescription("AxisSnapButton","{h}Snap on Axis\nThis tool rounds the position of all selected parts to the nearest Movement Increment. This tool depends on the Axis Lock toggle buttons.\nFor example, if a part has a position of (2.6, 3.4, 3.8), and the Movement Increment were 2, it would get snapped to (2, 4, 4). If the Y Axis Lock was deselected, it would be round to (2, 3.4, 4), ignoring the Y axis.")
2919 SetButtonDescription("FirstButton","{h}Move by First\nThis tool moves parts based on the rotation of one part.\nWhen selected, part-aligned Handles will appear around the first selected part. When dragged, the first part will move in the direction of its rotation, and all other parts will move relative to it.\nFor example, if the first part faced upward and to the left, not only would it be dragged upward and left, but so would every other part.")
2920 SetButtonDescription("FirstSnapButton","{h}Snap by First\nThis tool is very similar to the Snap on Axis tool. The only difference is that only the first selection gets snapped. The rest of the selection is moved relative to that part.")
2921 SetButtonDescription("ObjectButton","{h}Move by Object\nThis tool moves parts in the direction of their rotation. When selected, part-aligned Handles will appear around the first selection. When dragged, every part will move based only on it's own rotation, independant of any other part.")
2922 SetButtonDescription("Inc","{h}Movement Increment\nThis number defines how many studs to snap by when moving parts. For example, if it were 3, parts would move every 3 studs.\nIt is used to tell what to round a part's position by when using a snap tool. For example, if it were 3, a part's position would round to the nearest 3rd.")
2923 SetButtonDescription("XButton","{h}X Axis Lock\nThis button toggles whether the X axis will be considered when using a snap tool. If selected, parts will be snapped on the X axis. If not selected, snapping is ignored on the X axis.")
2924 SetButtonDescription("YButton","{h}Y Axis Lock\nThis button toggles whether the Y axis will be considered when using a snap tool. If selected, parts will be snapped on the Y axis. If not selected, snapping is ignored on the Y axis.")
2925 SetButtonDescription("ZButton","{h}Z Axis Lock\nThis button toggles whether the Z axis will be considered when using a snap tool. If selected, parts will be snapped on the Z axis. If not selected, snapping is ignored on the Z axis.")
2926 SetButtonDescription("Delta","{h}Movement Delta\nThis number displays the distance that parts have been dragged, in studs.")
2927
2928 SetWarnings("AxisButton","No parts selected")
2929 SetWarnings("AxisSnapButton","No parts selected")
2930 SetWarnings("FirstButton","No parts selected")
2931 SetWarnings("FirstSnapButton","No parts selected")
2932 SetWarnings("ObjectButton","No parts selected")
2933
2934 SetShortcutKey("AxisButton","Move.Axis")
2935 SetShortcutKey("AxisSnapButton","Move.AxisSnap")
2936 SetShortcutKey("FirstButton","Move.First")
2937 SetShortcutKey("FirstSnapButton","Move.FirstSnap")
2938 SetShortcutKey("ObjectButton","Move.Object")
2939
2940 local facevector = {
2941 [Enum.NormalId.Back] = Vector3.FromNormalId(Enum.NormalId.Back);
2942 [Enum.NormalId.Bottom] = Vector3.FromNormalId(Enum.NormalId.Bottom);
2943 [Enum.NormalId.Front] = Vector3.FromNormalId(Enum.NormalId.Front);
2944 [Enum.NormalId.Left] = Vector3.FromNormalId(Enum.NormalId.Left);
2945 [Enum.NormalId.Right] = Vector3.FromNormalId(Enum.NormalId.Right);
2946 [Enum.NormalId.Top] = Vector3.FromNormalId(Enum.NormalId.Top);
2947 }
2948
2949 SetOnSelect("AxisButton",function()
2950 local selection = GetFilteredSelection("BasePart")
2951 if #selection > 0 then
2952 OverlayHandles.Color = Resource("HandleColor")
2953 OverlayHandles.Visible = true
2954 WrapOverlay(selection,true)
2955 local origin = {}
2956 local ocf = GetOverlayCFrame()
2957 local inc = GetButtonValue("Inc")
2958 Connect(OverlayHandles.MouseButton1Down,function(face)
2959 inc = GetButtonValue("Inc")
2960 for _,part in pairs(selection) do
2961 origin[part] = part.CFrame
2962 end
2963 ocf = GetOverlayCFrame()
2964 SetButtonValue("Delta",0)
2965 end)
2966 Connect(OverlayHandles.MouseDrag,function(face,distance)
2967 local rdis = Round(distance,inc)
2968 local pos = facevector[face]*rdis
2969 for part,cframe in pairs(origin) do
2970 part.CFrame = cframe + pos
2971 end
2972 SetOverlayCFrame(ocf+pos)
2973 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
2974 end)
2975 else
2976 SetWarning()
2977 end
2978 end)
2979 SetOnSelect("AxisSnapButton",function()
2980 local selection = GetFilteredSelection("BasePart")
2981 if #selection > 0 then
2982 local inc = GetButtonValue("Inc")
2983 local incx = GetButtonValue("XButton") and inc or 0
2984 local incy = GetButtonValue("YButton") and inc or 0
2985 local incz = GetButtonValue("ZButton") and inc or 0
2986 for _,part in pairs(selection) do
2987 local pos = part.CFrame.p
2988 part.CFrame = (part.CFrame-pos) + Vector3.new(Round(pos.x,incx),Round(pos.y,incy),Round(pos.z,incz))
2989 end
2990 PlaySound("SnapSound")
2991 SelectPreviousTool()
2992 else
2993 SetWarning()
2994 end
2995 end)
2996 SetOnSelect("FirstButton",function()
2997 local selection = GetFilteredSelection("BasePart")
2998 if #selection > 0 then
2999 OverlayHandles.Color = Resource("HandleColor")
3000 OverlayHandles.Visible = true
3001 local center = selection[1]
3002 WrapOverlay(center)
3003 local origin = {}
3004 local corigin = center.CFrame
3005 local ocf = GetOverlayCFrame()
3006 local inc = GetButtonValue("Inc")
3007 Connect(OverlayHandles.MouseButton1Down,function(face)
3008 inc = GetButtonValue("Inc")
3009 corigin = center.CFrame
3010 for _,part in pairs(selection) do
3011 origin[part] = corigin:toObjectSpace(part.CFrame)
3012 end
3013 ocf = corigin:toObjectSpace(GetOverlayCFrame())
3014 SetButtonValue("Delta",0)
3015 end)
3016 Connect(OverlayHandles.MouseDrag,function(face,distance)
3017 local rdis = Round(distance,inc)
3018 local cf = corigin * CFrame.new(facevector[face]*rdis)
3019 for part,cframe in pairs(origin) do
3020 part.CFrame = cf:toWorldSpace(cframe)
3021 end
3022 SetOverlayCFrame(cf:toWorldSpace(ocf))
3023 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
3024 end)
3025 else
3026 SetWarning()
3027 end
3028 end)
3029 SetOnSelect("FirstSnapButton",function()
3030 local selection = GetFilteredSelection("BasePart")
3031 if #selection > 0 then
3032 local corigin = selection[1].CFrame
3033 local pos = corigin.p
3034 local inc = GetButtonValue("Inc")
3035 local incx = GetButtonValue("XButton") and inc or 0
3036 local incy = GetButtonValue("YButton") and inc or 0
3037 local incz = GetButtonValue("ZButton") and inc or 0
3038 local new = (corigin-pos) + Vector3.new(Round(pos.x,incx),Round(pos.y,incy),Round(pos.z,incz))
3039 for _,part in pairs(selection) do
3040 part.CFrame = new:toWorldSpace(corigin:toObjectSpace(part.CFrame))
3041 end
3042 PlaySound("SnapSound")
3043 SelectPreviousTool()
3044 else
3045 SetWarning()
3046 end
3047 end)
3048 SetOnSelect("ObjectButton",function()
3049 local selection = GetFilteredSelection("BasePart")
3050 if #selection > 0 then
3051 OverlayHandles.Color = Resource("HandleColor")
3052 OverlayHandles.Visible = true
3053 WrapOverlay(selection[1])
3054 local origin = {}
3055 local ocf = GetOverlayCFrame()
3056 local inc = GetButtonValue("Inc")
3057 Connect(OverlayHandles.MouseButton1Down,function(face)
3058 inc = GetButtonValue("Inc")
3059 for _,part in pairs(selection) do
3060 origin[part] = part.CFrame
3061 end
3062 ocf = GetOverlayCFrame()
3063 SetButtonValue("Delta",0)
3064 end)
3065 Connect(OverlayHandles.MouseDrag,function(face,distance)
3066 local rdis = Round(distance,inc)
3067 local cf = CFrame.new(facevector[face]*rdis)
3068 for part,cframe in pairs(origin) do
3069 part.CFrame = cframe * cf
3070 end
3071 SetOverlayCFrame(ocf*cf)
3072 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
3073 end)
3074 else
3075 SetWarning()
3076 end
3077 end)
3078 Validate()
3079end,true)
3080
3081ProcessElementSource(function()
3082 SetPluginName("Rotate")
3083 SetPluginType("menu")
3084 SetMenuText("Rotation")
3085 SetMenuDescription("{h}Rotation Menu\nContains tools for rotating parts.")
3086
3087 AddResource("HandleColor",BrickColor.new("Bright green"))
3088 AddResource("SnapSound","rbxasset://Sounds/snap.wav")
3089
3090 SetLayout{
3091 { -- row 1
3092 {"Inc","field",45};
3093 {"RotateSnap","container",{
3094 {"XButton","toggle",true,"X"};
3095 {"YButton","toggle",true,"Y"};
3096 {"ZButton","toggle",true,"Z"};
3097 }}
3098 };
3099 { -- row 2
3100 {"ObjectButton","tool","Object"};
3101 {"ObjectSnapButton","tool","Snap"};
3102 };
3103 { -- row 3
3104 {"PivotButton","tool","Pivot"};
3105 {"PivotSnapButton","tool","Snap"};
3106 };
3107 { -- row 4
3108 {"GroupButton","tool","Group"};
3109 {"Delta","label","0"};
3110 };
3111 }
3112
3113 SetButtonDescription("ObjectButton","{h}Rotate by Object\nThis tool rotates parts. When selected, ArcHandles will appear around the first selected part. When dragged, each selected part will rotate around it's own center, independant of any other part.\nThe angle of each part will be snapped by the Rotation Increment.")
3114 SetButtonDescription("ObjectSnapButton","{h}Snap Angle by Object\nThis tool rounds the angle of all selected parts to the nearest Rotation Increment.\nFor example, if a part had one axis rotated by 80 degrees, and the Rotation Increment was 45, that axis would be rounded to 90 degrees.\nThis tool depends on the Axis Lock toggle buttons. For example, If the X Axis Lock was deselected, only the Y and Z axes would be rounded.")
3115 SetButtonDescription("PivotButton","{h}Rotate by First\nThis tool rotates parts around one part.\nWhen selected, ArcHandles will appear around the first selected part. When dragged, the first part will be rotated, and the rest of the selected will keep their relative positions and rotations to it.")
3116 SetButtonDescription("PivotSnapButton","{h}Snap Angle by First\nThis tool is very similar to the Snap Angle by Object tool. The difference is that only the first selected part is snapped, and the rest of the selection is moved relative to it.")
3117 SetButtonDescription("GroupButton","{h}Rotate as Group\nThis tool rotates parts as a group, around the center of the group.\nWhen selected, ArcHandles will appear around all selected parts. When dragged, these parts will be rotated around the center of the group.\nNote that the rotation of the ArcHandles resets every time you select the tool.")
3118 SetButtonDescription("Inc","{h}Rotation Increment\nThis number defines how many degrees to snap an angle by when rotating parts. For example, if it were 45, parts would rotate every 45 degrees\nIt is also used to tell what to round a part's rotation by when using a snap tool. For example, if it were 45, a part's position would round to the nearest 45th degree.")
3119 SetButtonDescription("XButton","{h}X Axis Lock\nThis button toggles whether the X axis will be considered when using a snap tool. If selected, parts will be snapped on the X axis. If not selected, snapping is ignored on the X axis.")
3120 SetButtonDescription("YButton","{h}Y Axis Lock\nThis button toggles whether the Y axis will be considered when using a snap tool. If selected, parts will be snapped on the Y axis. If not selected, snapping is ignored on the Y axis.")
3121 SetButtonDescription("ZButton","{h}Z Axis Lock\nThis button toggles whether the Z axis will be considered when using a snap tool. If selected, parts will be snapped on the Z axis. If not selected, snapping is ignored on the Z axis.")
3122 SetButtonDescription("Delta","{h}Rotation Delta\nThis number displays the anglular distance that parts have been dragged, in degrees.")
3123
3124 SetWarnings("ObjectButton","No parts selected")
3125 SetWarnings("ObjectSnapButton","No parts selected")
3126 SetWarnings("PivotButton","No parts selected")
3127 SetWarnings("PivotSnapButton","No parts selected")
3128 SetWarnings("GroupButton","No parts selected")
3129
3130 SetShortcutKey("ObjectButton","Rotate.Object")
3131 SetShortcutKey("ObjectSnapButton","Rotate.ObjectSnap")
3132 SetShortcutKey("PivotButton","Rotate.Pivot")
3133 SetShortcutKey("PivotSnapButton","Rotate.PivotSnap")
3134 SetShortcutKey("GroupButton","Rotate.Group")
3135
3136 local axisnum = {
3137 [Enum.Axis.X] = 1;
3138 [Enum.Axis.Y] = 2;
3139 [Enum.Axis.Z] = 3;
3140 }
3141
3142 SetOnSelect("ObjectButton",function()
3143 local selection = GetFilteredSelection("BasePart")
3144 if #selection > 0 then
3145 OverlayArcHandles.Color = Resource("HandleColor")
3146 OverlayArcHandles.Visible = true
3147 WrapOverlay(selection[1])
3148 local origin = {}
3149 local ocf = GetOverlayCFrame()
3150 local inc = GetButtonValue("Inc")
3151 Connect(OverlayArcHandles.MouseButton1Down,function(axis)
3152 for _,part in pairs(selection) do
3153 origin[part] = part.CFrame
3154 end
3155 ocf = GetOverlayCFrame()
3156 inc = GetButtonValue("Inc")
3157 SetButtonValue("Delta",0)
3158 end)
3159 Connect(OverlayArcHandles.MouseDrag,function(axis,angle)
3160 local rdis = Round(math.deg(angle),inc)
3161 local input = {0;0;0}
3162 input[axisnum[axis]] = math.rad(rdis)
3163 local new = CFrame.Angles(unpack(input))
3164 for part,cframe in pairs(origin) do
3165 part.CFrame = cframe * new
3166 end
3167 SetOverlayCFrame(ocf * new)
3168 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
3169 end)
3170 else
3171 SetWarning()
3172 end
3173 end)
3174 SetOnSelect("ObjectSnapButton",function()
3175 local selection = GetFilteredSelection("BasePart")
3176 if #selection > 0 then
3177 local inc = GetButtonValue("Inc")
3178 local incx = GetButtonValue("XButton") and inc or 0
3179 local incy = GetButtonValue("YButton") and inc or 0
3180 local incz = GetButtonValue("ZButton") and inc or 0
3181 if inc >= 360 then
3182 for _,part in pairs(selection) do
3183 part.CFrame = CFrame.new(part.CFrame.p)
3184 end
3185 elseif inc ~= 0 then
3186 for _,part in pairs(selection) do
3187 local x,y,z = part.CFrame:toEulerAnglesXYZ()
3188 part.CFrame = CFrame.Angles(
3189 math.rad(Round(math.deg(x),incx)),
3190 math.rad(Round(math.deg(y),incy)),
3191 math.rad(Round(math.deg(z),incz))
3192 ) + part.CFrame.p
3193 end
3194 end
3195 PlaySound("SnapSound")
3196 SelectPreviousTool()
3197 else
3198 SetWarning()
3199 end
3200 end)
3201 SetOnSelect("PivotButton",function()
3202 local selection = GetFilteredSelection("BasePart")
3203 if #selection > 0 then
3204 OverlayArcHandles.Color = Resource("HandleColor")
3205 OverlayArcHandles.Visible = true
3206 local center = selection[1]
3207 WrapOverlay(center)
3208 local origin = {}
3209 local corigin = center.CFrame
3210 local ocf = corigin:toObjectSpace(GetOverlayCFrame())
3211 local inc = GetButtonValue("Inc")
3212 Connect(OverlayArcHandles.MouseButton1Down,function(axis)
3213 corigin = center.CFrame
3214 for _,part in pairs(selection) do
3215 origin[part] = corigin:toObjectSpace(part.CFrame)
3216 end
3217 ocf = corigin:toObjectSpace(GetOverlayCFrame())
3218 inc = GetButtonValue("Inc")
3219 SetButtonValue("Delta",0)
3220 end)
3221 Connect(OverlayArcHandles.MouseDrag,function(axis,angle)
3222 local rdis = Round(math.deg(angle),inc)
3223 local input = {0;0;0}
3224 input[axisnum[axis]] = math.rad(rdis)
3225 local new = corigin * CFrame.Angles(unpack(input))
3226 for part,cframe in pairs(origin) do
3227 part.CFrame = new:toWorldSpace(cframe)
3228 end
3229 SetOverlayCFrame(new:toWorldSpace(ocf))
3230 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
3231 end)
3232 else
3233 SetWarning()
3234 end
3235 end)
3236 SetOnSelect("PivotSnapButton",function()
3237 local selection = GetFilteredSelection("BasePart")
3238 if #selection > 0 then
3239 local corigin = selection[1].CFrame
3240 local x,y,z = corigin:toEulerAnglesXYZ()
3241 local inc = GetButtonValue("Inc")
3242 local incx = GetButtonValue("XButton") and inc or 0
3243 local incy = GetButtonValue("YButton") and inc or 0
3244 local incz = GetButtonValue("ZButton") and inc or 0
3245 local new = CFrame.Angles(
3246 math.rad(Round(math.deg(x),incx)),
3247 math.rad(Round(math.deg(y),incy)),
3248 math.rad(Round(math.deg(z),incz))
3249 ) + corigin.p
3250 for _,part in pairs(selection) do
3251 part.CFrame = new:toWorldSpace(corigin:toObjectSpace(part.CFrame))
3252 end
3253 PlaySound("SnapSound")
3254 SelectPreviousTool()
3255 else
3256 SetWarning()
3257 end
3258 end)
3259 SetOnSelect("GroupButton",function()
3260 local selection,bbsize,bbpos = GetSelectionBoundingBox()
3261 if #selection > 0 then
3262 OverlayArcHandles.Color = Resource("HandleColor")
3263 OverlayArcHandles.Visible = true
3264 SetOverlay(bbsize,CFrame.new(bbpos))
3265 local origin = {}
3266 local corigin = GetOverlayCFrame()
3267 local inc = GetButtonValue("Inc")
3268 Connect(OverlayArcHandles.MouseButton1Down,function(axis)
3269 corigin = GetOverlayCFrame()
3270 for _,part in pairs(selection) do
3271 origin[part] = corigin:toObjectSpace(part.CFrame)
3272 end
3273 inc = GetButtonValue("Inc")
3274 SetButtonValue("Delta",0)
3275 end)
3276 Connect(OverlayArcHandles.MouseDrag,function(axis,angle)
3277 local rdis = Round(math.deg(angle),inc)
3278 local input = {0;0;0}
3279 input[axisnum[axis]] = math.rad(rdis)
3280 local new = corigin * CFrame.Angles(unpack(input))
3281 for part,cframe in pairs(origin) do
3282 part.CFrame = new:toWorldSpace(cframe)
3283 end
3284 SetOverlayCFrame(new)
3285 SetButtonValue("Delta",Round(math.abs(rdis),0.00001))
3286 end)
3287 else
3288 SetWarning()
3289 end
3290 end)
3291 Validate()
3292end,true)
3293
3294ProcessElementSource(function()
3295 SetPluginName("Resize")
3296 SetPluginType("menu")
3297 SetMenuText("Resizing")
3298 SetMenuDescription("{h}Resizing Menu\nContains tools for resizing parts.")
3299
3300 AddResource("HandleColor",BrickColor.new("Cyan"))
3301 AddResource("SnapSound","rbxasset://Sounds/snap.wav")
3302
3303 SetLayout{
3304 { -- row 1
3305 {"Inc","field",1};
3306 {"ResizeSnap","container",{
3307 {"XButton","toggle",true,"X"};
3308 {"YButton","toggle",true,"Y"};
3309 {"ZButton","toggle",true,"Z"};
3310 }}
3311 };
3312 { -- row 2
3313 {"ObjectButton","tool","Object"};
3314 {"ObjectSnapButton","tool","Snap"};
3315 };
3316 { -- row 3
3317 {"CenterButton","tool","Center"};
3318 {"Delta","label","0"};
3319 };
3320 }
3321
3322 SetButtonDescription("ObjectButton","{h}Resize by Object\nThis tool resizes parts. When selected, Handles will appear around the first selected part. When dragged, each selected part will be resized accordingly.\nThe amount a part is snapped is described in the description of the Resize Increment.\nRemember that multiple selected parts can have different FormFactors. All parts will be resized depending only on their own FormFactor.")
3323 SetButtonDescription("ObjectSnapButton","{h}Snap Size by Object\nThis tool rounds the size of all selected parts. Unlike the other tools, this tool only rounds the size of each selected part to the nearest Resize Increment.\nThis tool depends on the Axis Lock toggle buttons. For example, If the X Axis Lock was deselected, only the Y and Z axes would be rounded.")
3324 SetButtonDescription("CenterButton","{h}Resize from Center\nThis tool is is similar to the Resize by Object tool. The difference is that it resizes each part from the center of that part, instead of from the face.")
3325 SetButtonDescription("Inc","{h}Resize Increment\nThis number defines how many studs to snap by when resizing parts. How much a part is snapped is an amount depending on the part's FormFactor, multiplied by the Resize Increment.\nFor example, if the Resize Increment were 2, and you were to resize the top face of a part with the Brick FormFactor (1.2), the part would be snapped every 2.4 studs.\nIf the FormFactor is Custom, this is ignored, and it is simply snapped by the Resize Increment.")
3326 SetButtonDescription("XButton","{h}X Axis Lock\nThis button toggles whether the X axis will be considered when snapping. If selected, parts will be snapped on the X axis. If not selected, snapping is ignored on the X axis.")
3327 SetButtonDescription("YButton","{h}Y Axis Lock\nThis button toggles whether the Y axis will be considered when snapping. If selected, parts will be snapped on the Y axis. If not selected, snapping is ignored on the Y axis.")
3328 SetButtonDescription("ZButton","{h}Z Axis Lock\nThis button toggles whether the Z axis will be considered when snapping. If selected, parts will be snapped on the Z axis. If not selected, snapping is ignored on the Z axis.")
3329 SetButtonDescription("Delta","{h}Resize Delta\nThis number displays the size distance a part has been dragged, in studs.")
3330
3331 SetWarnings("ObjectButton","No parts selected")
3332 SetWarnings("ObjectSnapButton","No parts selected")
3333 SetWarnings("CenterButton","No parts selected")
3334
3335 SetShortcutKey("ObjectButton","Resize.Object")
3336 SetShortcutKey("ObjectSnapButton","Resize.ObjectSnap")
3337 SetShortcutKey("CenterButton","Resize.Center")
3338
3339 local facevector = {
3340 [Enum.NormalId.Back] = Vector3.FromNormalId(Enum.NormalId.Back);
3341 [Enum.NormalId.Bottom] = Vector3.FromNormalId(Enum.NormalId.Bottom);
3342 [Enum.NormalId.Front] = Vector3.FromNormalId(Enum.NormalId.Front);
3343 [Enum.NormalId.Left] = Vector3.FromNormalId(Enum.NormalId.Left);
3344 [Enum.NormalId.Right] = Vector3.FromNormalId(Enum.NormalId.Right);
3345 [Enum.NormalId.Top] = Vector3.FromNormalId(Enum.NormalId.Top);
3346 }
3347 local facemult = {
3348 [Enum.NormalId.Back] = 1;
3349 [Enum.NormalId.Bottom] = -1;
3350 [Enum.NormalId.Front] = -1;
3351 [Enum.NormalId.Left] = -1;
3352 [Enum.NormalId.Right] = 1;
3353 [Enum.NormalId.Top] = 1;
3354 }
3355 local facesize = {
3356 [Enum.NormalId.Back] = "z";
3357 [Enum.NormalId.Bottom] = "y";
3358 [Enum.NormalId.Front] = "z";
3359 [Enum.NormalId.Left] = "x";
3360 [Enum.NormalId.Right] = "x";
3361 [Enum.NormalId.Top] = "y";
3362 }
3363
3364 local FFXZ = {
3365 [Enum.FormFactor.Symmetric] = 1;
3366 [Enum.FormFactor.Brick] = 1;
3367 [Enum.FormFactor.Plate] = 1;
3368 [Enum.FormFactor.Custom] = 0.2;
3369 ["TrussPart"] = 2;
3370 }
3371
3372 local FFY = {
3373 [Enum.FormFactor.Symmetric] = 1;
3374 [Enum.FormFactor.Brick] = 1.2;
3375 [Enum.FormFactor.Plate] = 0.4;
3376 [Enum.FormFactor.Custom] = 0.2;
3377 ["TrussPart"] = 2;
3378 }
3379
3380 local formfactormult = {
3381 [Enum.NormalId.Back] = FFXZ;
3382 [Enum.NormalId.Bottom] = FFY;
3383 [Enum.NormalId.Front] = FFXZ;
3384 [Enum.NormalId.Left] = FFXZ;
3385 [Enum.NormalId.Right] = FFXZ;
3386 [Enum.NormalId.Top] = FFY;
3387 }
3388
3389 local function GetFormFactor(object)
3390 if object:IsA"FormFactorPart" then
3391 return object.formFactor
3392 elseif object:IsA"TrussPart" then
3393 return "TrussPart"
3394 else
3395 return Enum.FormFactor.Symmetric
3396 end
3397 end
3398
3399 SetOnSelect("ObjectButton",function()
3400 local selection = GetFilteredSelection("BasePart")
3401 if #selection > 0 then
3402 local first = selection[1]
3403 OverlayHandles.Color = Resource("HandleColor")
3404 OverlayHandles.Visible = true
3405 WrapOverlay(first)
3406 local origin = {}
3407 Connect(OverlayHandles.MouseButton1Down,function(face)
3408 for _,part in pairs(selection) do
3409 local ff = GetFormFactor(part)
3410 origin[part] = {part.CFrame,part.Size,ff,formfactormult[face][ff]}
3411 end
3412 SetButtonValue("Delta",0)
3413 end)
3414 Connect(OverlayHandles.MouseDrag,function(face,distance)
3415 local fm,fs = facemult[face],facesize[face]
3416 local dis = distance*fm
3417 local fvec = facevector[face]
3418 local inc = GetButtonValue("Inc")
3419 local cinc = inc
3420 if inc == 0 then
3421 inc = 1
3422 else
3423 inc = Round(inc,1)
3424 end
3425 for part,info in pairs(origin) do
3426 local sz,ff,ffm = info[2],info[3],info[4]
3427 local mult
3428 if ff == Enum.FormFactor.Custom then
3429 mult = Round(dis,cinc)
3430 else
3431 mult = Round(dis,inc*ffm)
3432 end
3433 local mod = fvec*mult
3434 local fsize = sz[fs]
3435 mod = fsize + mult*fm < ffm and fvec*((ffm-fsize)*fm) or mod
3436 part.Size = sz + mod
3437 part.CFrame = info[1] * CFrame.new(mod*fm/2)
3438 if part == first then SetButtonValue("Delta",Round(mod.magnitude,0.00001)) end
3439 end
3440 SetOverlay(first.Size,first.CFrame)
3441 end)
3442 else
3443 SetWarning()
3444 end
3445 end)
3446 SetOnSelect("ObjectSnapButton",function()
3447 local selection = GetFilteredSelection("BasePart")
3448 if #selection > 0 then
3449 local inc = GetButtonValue("Inc")
3450 local incx = GetButtonValue("XButton") and inc or 0
3451 local incy = GetButtonValue("YButton") and inc or 0
3452 local incz = GetButtonValue("ZButton") and inc or 0
3453 for _,part in pairs(selection) do
3454 local cf = part.CFrame
3455 part.Size = Vector3.new(
3456 Round(part.Size.x,incx),
3457 Round(part.Size.y,incy),
3458 Round(part.Size.z,incz)
3459 )
3460 part.CFrame = cf
3461 end
3462 PlaySound("SnapSound")
3463 SelectPreviousTool()
3464 else
3465 SetWarning()
3466 end
3467 end)
3468 SetOnSelect("CenterButton",function()
3469 local selection = GetFilteredSelection("BasePart")
3470 if #selection > 0 then
3471 local first = selection[1]
3472 OverlayHandles.Color = Resource("HandleColor")
3473 OverlayHandles.Visible = true
3474 WrapOverlay(first)
3475 local origin = {}
3476 Connect(OverlayHandles.MouseButton1Down,function(face)
3477 for _,part in pairs(selection) do
3478 local ff = GetFormFactor(part)
3479 origin[part] = {part.CFrame,part.Size,ff,formfactormult[face][ff]}
3480 end
3481 SetButtonValue("Delta",0)
3482 end)
3483 Connect(OverlayHandles.MouseDrag,function(face,distance)
3484 local fm,fs = facemult[face],facesize[face]
3485 local dis = distance*2*fm
3486 local fvec = facevector[face]
3487 local inc = GetButtonValue("Inc")
3488 local cinc = inc
3489 if inc == 0 then
3490 inc = 1
3491 else
3492 inc = Round(inc,1)
3493 end
3494 for part,info in pairs(origin) do
3495 local sz,ff,ffm = info[2],info[3],info[4]
3496 local mult
3497 if ff == Enum.FormFactor.Custom then
3498 mult = Round(dis,cinc)
3499 else
3500 mult = Round(dis,inc*ffm)
3501 end
3502 local mod = fvec*mult
3503 local fsize = sz[fs]
3504 mod = fsize + mult*fm < ffm and fvec*((ffm-fsize)*fm) or mod
3505 part.Size = sz + mod
3506 part.CFrame = info[1]
3507 if part == first then SetButtonValue("Delta",Round(mod.magnitude,0.00001)) end
3508 end
3509 SetOverlay(first.Size,first.CFrame)
3510 end)
3511 else
3512 SetWarning()
3513 end
3514 end)
3515 Validate()
3516end,true)
3517
3518ProcessElementSource(function()
3519 SetPluginName("Weld")
3520 SetPluginType("menu")
3521 SetMenuText("Welding")
3522 SetMenuDescription("{h}Welding Menu\nContains tools for handling welds.")
3523
3524 AddResource("JoinSound","rbxasset://Sounds/splat.wav")
3525 AddResource("BreakSound","rbxasset://Sounds/snap.wav")
3526
3527 SetLayout{
3528 { -- row 1
3529 {"Type","field",{"Motor6D";
3530 function(text)
3531 local e,o = pcall(Instance.new,text) -- check by attempting to create an instance of the classname
3532 if e and o and o:IsA"JointInstance" then -- only instancable JointInstances are valid
3533 return true,o.className -- success; set value to className
3534 else -- if invalid
3535 return false -- fail; use previous value
3536 end
3537 end};
3538 };
3539 };
3540 { -- row 2
3541 {"JoinButton","tool","Join"};
3542 };
3543 { -- row 3
3544 {"BreakButton","tool","Break"};
3545 };
3546 }
3547
3548 SetButtonDescription("JoinButton","{h}Join Objects\nWhen this tool is selected, the first selected part is weld to each remaining selected part with a joint of the current Weld Type.\nThe relative positions between each object are maintained.\nThe resulting joint object is placed under the first selected part.")
3549 SetButtonDescription("BreakButton","{h}Break Objects\nWhen this tool is selected, one of two things will happen, depending on how many parts are selected.\nIf multiple parts are selected, then any joints of any involved parts, and of the current Weld Type, are removed. That is, if a joint in the first selection is joined with another selected part, that joint is removed.\nIn other words, a reverse of the Join Button occurs.\nIf only one part is selected, then the last weld found, of the current Weld Type, is removed from that part.")
3550 SetButtonDescription("Type","{h}Weld Type\nThis defines what kind of weld to use when joining or breaking.\nThe only valid classes are those that inherit from the JointInstance class, and are instancable.")
3551
3552 SetWarnings("JoinButton",{"No parts selected","Not enough valid selections"})
3553 SetWarnings("BreakButton","No parts selected")
3554
3555 SetShortcutKey("JoinButton","Weld.Join")
3556 SetShortcutKey("BreakButton","Weld.Break")
3557
3558 SetOnSelect("JoinButton",function()
3559 local selection = GetFilteredSelection("BasePart")
3560 if #selection > 1 then
3561 local x = table.remove(selection,1)
3562 local c = CFrame.new(x.Position)
3563 local xcf = x.CFrame:toObjectSpace(c)
3564 local type = GetButtonValue("Type")
3565 for _,y in pairs(selection) do
3566 local w = Instance.new(type)
3567 w.Part0 = x
3568 w.Part1 = y
3569 w.C0 = xcf
3570 w.C1 = y.CFrame:toObjectSpace(c)
3571 w.Parent = x
3572 end
3573 PlaySound("JoinSound")
3574 Deselect()
3575 elseif #selection > 0 then
3576 SetWarning(2)
3577 else
3578 SetWarning()
3579 end
3580 end)
3581 SetOnSelect("BreakButton",function()
3582 local selection = GetFilteredSelection("BasePart")
3583 if #selection > 0 then
3584 local part = table.remove(selection,1)
3585 local type = GetButtonValue("Type")
3586 local joints = {}
3587 for _,joint in pairs(part:GetChildren()) do
3588 if joint.className == type then
3589 table.insert(joints,joint)
3590 end
3591 end
3592 if #selection > 0 then
3593 local joined = {}
3594 for i,v in pairs(selection) do
3595 joined[v] = true
3596 end
3597 for _,joint in pairs(joints) do
3598 if joined[joint.Part1] then
3599 joint:Remove()
3600 end
3601 end
3602 else
3603 local joint = joints[#joints]
3604 if joint then
3605 joint:Remove()
3606 end
3607 end
3608 PlaySound("BreakSound")
3609 Deselect()
3610 else
3611 SetWarning()
3612 end
3613 end)
3614 Validate()
3615end,true)
3616
3617ProcessElementSource(function()
3618 SetPluginName("Scale")
3619 SetPluginType("menu")
3620 SetMenuText("Scaling")
3621 SetMenuDescription("{h}Scaling Menu\nContains tools for scaling objects.")
3622
3623 AddResource("ScaleSound","rbxasset://Sounds/electronicpingshort.wav")
3624
3625 SetLayout{
3626 { -- row 1
3627 {"Factor","field",0.5};
3628 };
3629 { -- row 2
3630 {"ScaleButton","tool","Scale"};
3631 };
3632 }
3633
3634 SetButtonDescription("ScaleButton","{h}Scale Objects\nThis tool scales a group of objects up or down.\nWhen selected, a copy of the selection is made, which is scaled depending o the Scale Factor.\nTo keep relative sizes, parts have their FormFactor automatically converted to Custom. Note that parts that do not inherit from the FormFactorPart class cannot be converted, so they may not scale properly.\nAs well as parts, a few other things are scaled:\n- A mesh's Offset\n- A SpecialMesh's Scale\n- A BevelMesh's Bevel\n- A Texture's StudsPerTile(U/V)\nNote that the selection must contain parts in order to be scaled.")
3635 SetButtonDescription("Factor","{h}Scale Factor\nThis is the factor that the selection will be scaled by. For example, a factor of 2 produces a copy twice the size, while a factor of 0.5 produces a copy half the size.")
3636
3637 SetWarnings("ScaleButton","No parts selected")
3638
3639 SetShortcutKey("ScaleButton","Scale.Scale")
3640
3641 SetOnSelect("ScaleButton",function()
3642 local function RecurseScale(object,scale,center)
3643 if object:IsA"BasePart" then
3644 if object:IsA"FormFactorPart" then
3645 object.formFactor = "Custom"
3646 end
3647 local cf = center:toObjectSpace(object.CFrame)
3648 object.Size = object.Size*scale
3649 object.CFrame = center:toWorldSpace(cf + cf.p * (scale - 1))
3650 elseif object:IsA"DataModelMesh" then
3651 object.Offset = object.Offset * scale
3652 if object:IsA"FileMesh" then
3653 if object:IsA"SpecialMesh" then
3654 if object.MeshType == Enum.MeshType.FileMesh then
3655 object.Scale = object.Scale * scale
3656 end
3657 else
3658 object.Scale = object.Scale * scale
3659 end
3660 elseif object:IsA"BevelMesh" then
3661 object.Bevel = object.Bevel * scale
3662 end
3663 elseif object:IsA"Texture" then
3664 object.StudsPerTileU = object.StudsPerTileU * scale
3665 object.StudsPerTileV = object.StudsPerTileV * scale
3666 end
3667 for _,child in pairs(object:GetChildren()) do
3668 RecurseScale(child,scale,center)
3669 end
3670 end
3671 local selection = GetSelection()
3672 local parts = GetFilteredSelection("BasePart")
3673 if #parts > 0 then
3674 local center = CFrame.new(GetMidpoint(parts))
3675 local scale = GetButtonValue("Factor")
3676 local model = Instance.new("Model",workspace)
3677 model.Name = "ScaledModel"
3678 for _,object in pairs(selection) do
3679 local new = object:Clone()
3680 RecurseScale(new,scale,center)
3681 new.Parent = model
3682 end
3683 PlaySound("ScaleSound")
3684 Deselect()
3685 else
3686 SetWarning()
3687 end
3688 end)
3689 Validate()
3690end,true)
3691
3692-- other menu
3693ProcessElementSource(function()
3694 SetPluginName("DeleteButton")
3695 SetPluginType("tool")
3696 SetButtonText("Delete")
3697 SetDescription("{h}Delete Selection\nDeletes the entire selection.\nThe idea is that deleting doesn't work outside of Studio Mode.")
3698
3699 AddResource("DeleteSound","rbxasset://Sounds/pageturn.wav")
3700
3701 SetShortcutKey("Other.Delete")
3702
3703 SetOnSelect(function()
3704 local selection = GetSelection()
3705 if #selection > 0 then
3706 for i,object in pairs(selection) do
3707 object:Remove()
3708 end
3709 PlaySound("DeleteSound")
3710 end
3711 Deselect()
3712 end)
3713 Validate()
3714end,true)
3715
3716ProcessElementSource(function()
3717 SetPluginName("SlopeButton")
3718 SetPluginType("tool")
3719 SetButtonText("Slope")
3720 SetDescription("{h}Slope Objects\nWhen this tool is selected, the first and second selected parts are used as points. The rest of the selection will be rotated to the slope between those two points. Their positions remain the same.")
3721 SetWarnings{"Invalid 1st selection";"Invalid 2nd selection";"Not enough valid selections"}
3722
3723 AddResource("SlopeSound","rbxasset://Sounds/electronicpingshort.wav")
3724
3725 SetShortcutKey("Other.Slope")
3726
3727 SetOnSelect(function()
3728 local selection = GetFilteredSelection("BasePart")
3729 if #selection > 2 then
3730 local p1 = table.remove(selection,2)
3731 local p0 = table.remove(selection,1)
3732 for _,part in pairs(selection) do
3733 part.CFrame = CFrame.new(part.CFrame.p,part.CFrame.p+(p1.CFrame.p-p0.CFrame.p))
3734 end
3735 PlaySound("SlopeSound")
3736 Deselect()
3737 elseif #selection > 1 then
3738 SetWarning(3)
3739 elseif #selection > 0 then
3740 SetWarning(2)
3741 else
3742 SetWarning()
3743 end
3744 end)
3745 Validate()
3746end,true)
3747
3748ProcessElementSource(function()
3749 SetPluginName("MidpointButton")
3750 SetPluginType("tool")
3751 SetButtonText("Midpoint")
3752 SetDescription("{h}Move to Midpoint\nWhen this tool is selected, the first selected part will be moved to the center of the rest of the selection.")
3753 SetWarnings{"No parts selected";"Not enough valid selections"}
3754
3755 AddResource("MidpointSound","rbxasset://Sounds/electronicpingshort.wav")
3756
3757 SetShortcutKey("Other.Midpoint")
3758
3759 SetOnSelect(function()
3760 local selection = GetFilteredSelection("BasePart")
3761 if #selection > 1 then
3762 local center = table.remove(selection,1)
3763 center.CFrame = (center.CFrame-center.CFrame.p) + GetMidpoint(selection)
3764 PlaySound("MidpointSound")
3765 Deselect()
3766 elseif #selection > 0 then
3767 SetWarning(2)
3768 else
3769 SetWarning()
3770 end
3771 end)
3772 Validate()
3773end,true)
3774
3775-- commands
3776
3777ProcessElementSource(function()
3778 SetPluginName("GetSelection")
3779 SetPluginType("command")
3780 SetCommandName("get")
3781
3782 SetFunction(
3783 function()
3784 return GetSelection()
3785 end
3786 )
3787
3788 SetDescription("Returns the current selection.")
3789
3790 Validate()
3791end)
3792
3793ProcessElementSource(function()
3794 SetPluginName("SetSelection")
3795 SetPluginType("command")
3796 SetCommandName("set")
3797
3798 SetFunction(
3799 function(objects)
3800 SetSelection(objects or {})
3801 end
3802 )
3803
3804 SetDescription("Sets the current selection.\n'objects' should be a table that contains Instances. If 'object' is not specified, the selection will be set to nothing.")
3805 SetArgumentDoc({
3806 {"table";"objects";"{}"};
3807 })
3808
3809 Validate()
3810end)
3811
3812
3813ProcessElementSource(function()
3814 SetPluginName("PropertySet")
3815 SetPluginType("command")
3816 SetCommandName("pset")
3817
3818 SetFunction(
3819 function(property,value,selection)
3820 if type(property) ~= "string" then error("1st argument needs a string",0) end
3821 local function precurse(object,property,value,out)
3822 local e,o = pcall(function() return object[property] == value end)
3823 if e and o then
3824 table.insert(out,object)
3825 end
3826 for _,child in pairs(object:GetChildren()) do
3827 precurse(child,property,value,out)
3828 end
3829 end
3830 local out = {}
3831 if selection then
3832 for _,object in pairs(GetSelection()) do
3833 precurse(object,property,value,out)
3834 end
3835 else
3836 precurse(game,property,value,out)
3837 end
3838 SetSelection(out)
3839 return out
3840 end
3841 )
3842
3843 SetDescription("Recurses through the game and selects Instances with specified properties.\nObjects whose 'property' has a value of 'value' are selected.\nIf the optional 'selection' argument is true, this function will recurse through the current selection instead.\nThis function will also return the resulting selection.\nThis function does not select objects that are not part of the game hierarchy.")
3844 SetArgumentDoc({
3845 {"string";"property"};
3846 {"*";"value"};
3847 {"bool";"selection";"false"};
3848 })
3849
3850 Validate()
3851end)
3852
3853ProcessElementSource(function()
3854 SetPluginName("ClassSet")
3855 SetPluginType("command")
3856 SetCommandName("cset")
3857
3858 SetFunction(
3859 function(class_name,selection)
3860 if type(class_name) ~= "string" then error("1st argument needs a string",0) end
3861 local function crecurse(object,class_name,out)
3862 if object:IsA(class_name) then
3863 table.insert(out,object)
3864 end
3865 for _,child in pairs(object:GetChildren()) do
3866 crecurse(child,class_name,out)
3867 end
3868 end
3869 local out = {}
3870 if selection then
3871 for _,object in pairs(GetSelection()) do
3872 crecurse(object,class_name,out)
3873 end
3874 else
3875 crecurse(game,class_name,out)
3876 end
3877 SetSelection(out)
3878 return out
3879 end
3880 )
3881
3882 SetDescription("Recurses through the game and selects Instances that inherit from a specified class.\nObjects that inherit from 'class_name' are selected.\nIf the optional 'selection' argument is true, this function will recurse through the current selection instead.\nThis function will also return the resulting selection.\nThis function does not select objects that are not part of the game hierarchy.")
3883 SetArgumentDoc({
3884 {"string";"class_name"};
3885 {"bool";"selection";"false"};
3886 })
3887
3888 Validate()
3889end)
3890
3891ProcessElementSource(function()
3892 SetPluginName("FunctionSet")
3893 SetPluginType("command")
3894 SetCommandName("fset")
3895
3896 SetFunction(
3897 function(check,selection)
3898 if type(check) ~= "function" then error("1st argument needs a function",0) end
3899 local function frecurse(object,check,out)
3900 if check(object) then
3901 table.insert(out,object)
3902 end
3903 for _,child in pairs(object:GetChildren()) do
3904 frecurse(child,check,out)
3905 end
3906 end
3907 local out = {}
3908 if selection then
3909 for _,object in pairs(GetSelection()) do
3910 frecurse(object,check,out)
3911 end
3912 else
3913 frecurse(game,check,out)
3914 end
3915 SetSelection(out)
3916 return out
3917 end
3918 )
3919
3920 SetDescription("Recurses through the game and selects Instances that pass a specified test.\nAny instance to which 'check' returns true are selected.\n'check' receives an object, and should return a bool, indicating whether the object should be added.\nIf the optional 'selection' argument is true, this function will recurse through the current selection instead.\nThis function will also return the resulting selection.\nThis function does not select objects that are not part of the game hierarchy.")
3921 SetArgumentDoc({
3922 {"function";"check"};
3923 {"bool";"selection";"false"};
3924 })
3925
3926 Validate()
3927end)
3928
3929ProcessElementSource(function()
3930 SetPluginName("Query")
3931 SetPluginType("command")
3932 SetCommandName("q")
3933
3934 SetFunction(
3935 function(test,scope)
3936 if type(test) ~= "function" then error("1st argument must be a function",0) end
3937 if type(scope) ~= "table" then error("2nd argument must be a table",0) end
3938 local function recurse(object,test,results)
3939 if test(object) then
3940 table.insert(results,object)
3941 end
3942 local ot = type(object)
3943 if ot == "userdata" then
3944 local e,o = pcall(function() return object:IsA"Instance" end)
3945 if e and o then
3946 for _,child in pairs(object:GetChildren()) do
3947 recurse(child,test,results)
3948 end
3949 end
3950 elseif ot == "table" then
3951 for i,v in pairs(object) do
3952 recurse(v,test,results)
3953 end
3954 end
3955 end
3956 local results = {}
3957 for i,v in pairs(scope) do
3958 recurse(v,test,results)
3959 end
3960 return results
3961 end
3962 )
3963
3964 SetDescription("Gathers a list of results from a scope of values based on provided criteria.\n'test' is a function that receives a value, and returns a bool.\n'scope' is a table of values to be recursively searched through.\nIf a value is a table, it's contents are searched.\nIf a value is an Instance, it's children are searched.")
3965 SetArgumentDoc({
3966 {"function";"test"};
3967 {"table";"scope"};
3968 })
3969
3970 Validate()
3971end)
3972
3973ProcessElementSource(function()
3974 SetPluginName("WorldPosition")
3975 SetPluginType("command")
3976 SetCommandName("wp")
3977 SetFunction(
3978 function(x,y,z)
3979 x,y,z = x or 0,y or 0,z or 0
3980 for _,object in pairs(GetFilteredSelection("BasePart")) do
3981 object.CFrame = object.CFrame + Vector3.new(x,y,z)
3982 end
3983 end
3984 )
3985 SetDescription("Moves each selected part based on it's location, but not its rotation.\n'x', 'y', and 'z' represent how many studs to move on their respective axes.")
3986 SetArgumentDoc({
3987 {"number";"x";"0"};
3988 {"number";"y";"0"};
3989 {"number";"z";"0"};
3990 })
3991 Validate()
3992end)
3993
3994ProcessElementSource(function()
3995 SetPluginName("SnapPosition")
3996 SetPluginType("command")
3997 SetCommandName("sp")
3998 SetFunction(
3999 function(xinc,yinc,zinc)
4000 xinc,yinc,zinc = xinc or 0,yinc or 0,zinc or 0
4001 for _,object in pairs(GetFilteredSelection("BasePart")) do
4002 local pos = object.CFrame.p
4003 object.CFrame = (object.CFrame-pos) + Vector3.new(Round(pos.x,xinc),Round(pos.y,yinc),Round(pos.z,zinc))
4004 end
4005 end
4006 )
4007 SetDescription("Snaps the position of each selection on each axis by its respective increment.\nFor example, if 'xinc' were 1, each part would be snapped to the nearest 1 on the X axis.")
4008 SetArgumentDoc({
4009 {"number";"xinc";"0"};
4010 {"number";"yinc";"0"};
4011 {"number";"zinc";"0"};
4012 })
4013 Validate()
4014end)
4015
4016ProcessElementSource(function()
4017 SetPluginName("FirstPosition")
4018 SetPluginType("command")
4019 SetCommandName("fp")
4020
4021 SetFunction(
4022 function(x,y,z)
4023 x,y,z = x or 0,y or 0,z or 0
4024 local selection = GetFilteredSelection("BasePart")
4025 if #selection > 1 then
4026 local corigin = selection[1].CFrame
4027 local new = corigin * CFrame.new(x,y,z)
4028 for _,part in pairs(selection) do
4029 part.CFrame = new:toWorldSpace(corigin:toObjectSpace(part.CFrame))
4030 end
4031 else
4032 error("no vaild selections",0)
4033 end
4034 end
4035 )
4036
4037 SetDescription("Moves the first selection, then moves the rest relative to it.\nThe first selection is moved based on its rotation.\nThe position and rotation of the rest of the selection is kept relative to the first.")
4038 SetArgumentDoc({
4039 {"number";"x";"0"};
4040 {"number";"y";"0"};
4041 {"number";"z";"0"};
4042 })
4043
4044 Validate()
4045end)
4046
4047ProcessElementSource(function()
4048 SetPluginName("SnapFirstPosition")
4049 SetPluginType("command")
4050 SetCommandName("sfp")
4051
4052 SetFunction(
4053 function(xinc,yinc,zinc)
4054 xinc,yinc,zinc = xinc or 0,yinc or 0,zinc or 0
4055 local selection = GetFilteredSelection("BasePart")
4056 if #selection > 1 then
4057 local corigin = selection[1].CFrame
4058 local pos = corigin.p
4059 local new = (corigin-pos) + Vector3.new(Round(pos.x,xinc or 0),Round(pos.y,yinc or 0),Round(pos.z,zinc or 0))
4060 for _,part in pairs(selection) do
4061 part.CFrame = new:toWorldSpace(corigin:toObjectSpace(part.CFrame))
4062 end
4063 else
4064 error("no vaild selections",0)
4065 end
4066 end
4067 )
4068
4069 SetDescription("Snaps the position of the first selection, then moves the rest relative to it.\nThe position and rotation of the rest of the selection is kept relative to the first.\nThe first selection is snapped on each axis by its respective increment.\nFor example, if 'xinc' were 1, each part would be snapped to the nearest 1 on the X axis.")
4070 SetArgumentDoc({
4071 {"number";"xinc";"0"};
4072 {"number";"yinc";"0"};
4073 {"number";"zinc";"0"};
4074 })
4075
4076 Validate()
4077end)
4078
4079ProcessElementSource(function()
4080 SetPluginName("ObjectPosition")
4081 SetPluginType("command")
4082 SetCommandName("op")
4083
4084 SetFunction(
4085 function(x,y,z)
4086 x,y,z = x or 0,y or 0,z or 0
4087 for _,object in pairs(GetFilteredSelection("BasePart")) do
4088 object.CFrame = object.CFrame * CFrame.new(x,y,z)
4089 end
4090 end
4091 )
4092
4093 SetDescription("Moves each selection based on its rotation.\nEach part is moved in the direction of its rotation, independent of any other part.")
4094 SetArgumentDoc({
4095 {"number";"x";"0"};
4096 {"number";"y";"0"};
4097 {"number";"z";"0"};
4098 })
4099
4100 Validate()
4101end)
4102
4103ProcessElementSource(function()
4104 SetPluginName("Position")
4105 SetPluginType("command")
4106 SetCommandName("p")
4107
4108 SetFunction(
4109 function(x,y,z)
4110 x,y,z = x or 0,y or 0,z or 0
4111 for _,object in pairs(GetFilteredSelection("BasePart")) do
4112 object.CFrame = (object.CFrame-object.CFrame.p) + Vector3.new(x,y,z)
4113 end
4114 end
4115 )
4116
4117 SetDescription("Directly sets the position of each selection.\n'x', 'y', and 'z' represent their respective positions on each axis.\nThe rotation of each selection is not affected.")
4118 SetArgumentDoc({
4119 {"number";"x";"0"};
4120 {"number";"y";"0"};
4121 {"number";"z";"0"};
4122 })
4123
4124 Validate()
4125end)
4126
4127ProcessElementSource(function()
4128 SetPluginName("RelativeRotation")
4129 SetPluginType("command")
4130 SetCommandName("rr")
4131
4132 SetFunction(
4133 function(x,y,z)
4134 x,y,z = x or 0,y or 0,z or 0
4135 for _,object in pairs(GetFilteredSelection("BasePart")) do
4136 object.CFrame = object.CFrame * CFrame.Angles(math.rad(x),math.rad(y),math.rad(z))
4137 end
4138 end
4139 )
4140
4141 SetDescription("Rotates each selection based on its current rotation.\n'x', 'y', and 'z' represent their respective rotational axes, in degrees.\nRotation by this command is accumulative.")
4142 SetArgumentDoc({
4143 {"number";"x";"0"};
4144 {"number";"y";"0"};
4145 {"number";"z";"0"};
4146 })
4147
4148 Validate()
4149end)
4150
4151ProcessElementSource(function()
4152 SetPluginName("SnapRotation")
4153 SetPluginType("command")
4154 SetCommandName("sr")
4155
4156 SetFunction(
4157 function(xinc,yinc,zinc)
4158 xinc,yinc,zinc = xinc or 0,yinc or 0,zinc or 0
4159 for _,object in pairs(GetFilteredSelection("BasePart")) do
4160 local x,y,z = object.CFrame:toEulerAnglesXYZ()
4161 object.CFrame = CFrame.Angles(
4162 math.rad(Round(math.deg(x),xinc)),
4163 math.rad(Round(math.deg(y),yinc)),
4164 math.rad(Round(math.deg(z),zinc))
4165 ) + object.CFrame.p
4166 end
4167 end
4168 )
4169
4170 SetDescription("Snaps the rotation of each selection on each axis by its respective increment.\nFor example, if 'xinc' were 45, each part's rotation would be snapped to the nearest 45th degree on the X axis.")
4171 SetArgumentDoc({
4172 {"number";"xinc";"0"};
4173 {"number";"yinc";"0"};
4174 {"number";"zinc";"0"};
4175 })
4176
4177 Validate()
4178end)
4179
4180ProcessElementSource(function()
4181 SetPluginName("PivotRotation")
4182 SetPluginType("command")
4183 SetCommandName("pv")
4184
4185 SetFunction(
4186 function(x,y,z)
4187 x,y,z = x or 0,y or 0,z or 0
4188 local selection = GetFilteredSelection("BasePart")
4189 if #selection > 1 then
4190 local corigin = selection[1].CFrame
4191 local new = corigin * CFrame.Angles(math.rad(x),math.rad(y),math.rad(z))
4192 for _,object in pairs(selection) do
4193 object.CFrame = new:toWorldSpace(corigin:toObjectSpace(object.CFrame))
4194 end
4195 else
4196 error("no vaild selections",0)
4197 end
4198 end
4199 )
4200
4201 SetDescription("Rotates the first selection, then moves the rest of the selection relative to it.\nThe position and rotation of the rest of the selection is kept relative to the first.\nRotation by this command is accumulative.")
4202 SetArgumentDoc({
4203 {"number";"x";"0"};
4204 {"number";"y";"0"};
4205 {"number";"z";"0"};
4206 })
4207
4208 Validate()
4209end)
4210
4211ProcessElementSource(function()
4212 SetPluginName("SnapPivotRotation")
4213 SetPluginType("command")
4214 SetCommandName("spv")
4215
4216 SetFunction(
4217 function(xinc,yinc,zinc)
4218 xinc,yinc,zinc = xinc or 0,yinc or 0,zinc or 0
4219 local selection = GetFilteredSelection("BasePart")
4220 if #selection > 1 then
4221 local corigin = selection[1].CFrame
4222 local x,y,z = corigin:toEulerAnglesXYZ()
4223 local new = CFrame.Angles(
4224 math.rad(Round(math.deg(x),xinc)),
4225 math.rad(Round(math.deg(y),yinc)),
4226 math.rad(Round(math.deg(z),zinc))
4227 ) + corigin.p
4228 for _,object in pairs(selection) do
4229 object.CFrame = new:toWorldSpace(corigin:toObjectSpace(object.CFrame))
4230 end
4231 else
4232 error("no vaild selections",0)
4233 end
4234 end
4235 )
4236
4237 SetDescription("Snaps the rotation of the first selection, then moves the rest relative to it.\nThe position and rotation of the rest of the selection is kept relative to the first.\nThe first selection is snapped on each rotational axis by its respective increment.\nFor example, if 'xinc' were 45, the first part would be snapped to the nearest 45th degree on the X axis.")
4238 SetArgumentDoc({
4239 {"number";"xinc";"0"};
4240 {"number";"yinc";"0"};
4241 {"number";"zinc";"0"};
4242 })
4243
4244 Validate()
4245end)
4246
4247ProcessElementSource(function()
4248 SetPluginName("GroupRotation")
4249 SetPluginType("command")
4250 SetCommandName("gr")
4251
4252 SetFunction(
4253 function(x,y,z)
4254 x,y,z = x or 0,y or 0,z or 0
4255 local selection = GetFilteredSelection("BasePart")
4256 if #selection > 0 then
4257 local corigin = CFrame.new(GetMidpoint(selection))
4258 local new = corigin * CFrame.Angles(math.rad(x),math.rad(y),math.rad(z))
4259 for _,object in pairs(selection) do
4260 object.CFrame = new:toWorldSpace(corigin:toObjectSpace(object.CFrame))
4261 end
4262 else
4263 error("no vaild selections",0)
4264 end
4265 end
4266 )
4267
4268 SetDescription("Rotates the entire selection around the center of that selection.\n'x', 'y', and 'z' represent their respective rotational axes, in degrees.\nRotation by this command is accumulative.")
4269 SetArgumentDoc({
4270 {"number";"x";"0"};
4271 {"number";"y";"0"};
4272 {"number";"z";"0"};
4273 })
4274
4275 Validate()
4276end)
4277
4278ProcessElementSource(function()
4279 SetPluginName("Rotation")
4280 SetPluginType("command")
4281 SetCommandName("r")
4282
4283 SetFunction(
4284 function(x,y,z)
4285 x,y,z = x or 0,y or 0,z or 0
4286 for _,object in pairs(GetFilteredSelection("BasePart")) do
4287 object.CFrame = CFrame.new(object.CFrame.p) * CFrame.Angles(math.rad(x),math.rad(y),math.rad(z))
4288 end
4289 end
4290 )
4291
4292 SetDescription("Directly sets the rotation of each selection, in degrees.\n'x', 'y', and 'z' represent their respective rotational axes, in degrees.\nRotation is set indenpendently of the part's current rotation.\nFor example, if 'xinc' were 90 degrees, the part's rotation would be reset, then rotated to 90 degrees.")
4293 SetArgumentDoc({
4294 {"number";"x";"0"};
4295 {"number";"y";"0"};
4296 {"number";"z";"0"};
4297 })
4298
4299 Validate()
4300end)
4301
4302ProcessElementSource(function()
4303 SetPluginName("Resize")
4304 SetPluginType("command")
4305 SetCommandName("rs")
4306
4307 local facevector = {
4308 [Enum.NormalId.Back] = Vector3.FromNormalId(Enum.NormalId.Back);
4309 [Enum.NormalId.Bottom] = Vector3.FromNormalId(Enum.NormalId.Bottom);
4310 [Enum.NormalId.Front] = Vector3.FromNormalId(Enum.NormalId.Front);
4311 [Enum.NormalId.Left] = Vector3.FromNormalId(Enum.NormalId.Left);
4312 [Enum.NormalId.Right] = Vector3.FromNormalId(Enum.NormalId.Right);
4313 [Enum.NormalId.Top] = Vector3.FromNormalId(Enum.NormalId.Top);
4314 }
4315 local facemult = {
4316 [Enum.NormalId.Back] = 1;
4317 [Enum.NormalId.Bottom] = -1;
4318 [Enum.NormalId.Front] = -1;
4319 [Enum.NormalId.Left] = -1;
4320 [Enum.NormalId.Right] = 1;
4321 [Enum.NormalId.Top] = 1;
4322 }
4323 local facesize = {
4324 [Enum.NormalId.Back] = "z";
4325 [Enum.NormalId.Bottom] = "y";
4326 [Enum.NormalId.Front] = "z";
4327 [Enum.NormalId.Left] = "x";
4328 [Enum.NormalId.Right] = "x";
4329 [Enum.NormalId.Top] = "y";
4330 }
4331
4332 local FFXZ = {
4333 [Enum.FormFactor.Symmetric] = 1;
4334 [Enum.FormFactor.Brick] = 1;
4335 [Enum.FormFactor.Plate] = 1;
4336 [Enum.FormFactor.Custom] = 0.2;
4337 ["TrussPart"] = 2;
4338 }
4339
4340 local FFY = {
4341 [Enum.FormFactor.Symmetric] = 1;
4342 [Enum.FormFactor.Brick] = 1.2;
4343 [Enum.FormFactor.Plate] = 0.4;
4344 [Enum.FormFactor.Custom] = 0.2;
4345 ["TrussPart"] = 2;
4346 }
4347
4348 local formfactormult = {
4349 [Enum.NormalId.Back] = FFXZ;
4350 [Enum.NormalId.Bottom] = FFY;
4351 [Enum.NormalId.Front] = FFXZ;
4352 [Enum.NormalId.Left] = FFXZ;
4353 [Enum.NormalId.Right] = FFXZ;
4354 [Enum.NormalId.Top] = FFY;
4355 }
4356
4357 local function GetFormFactor(object)
4358 if object:IsA"FormFactorPart" then
4359 return object.formFactor
4360 elseif object:IsA"TrussPart" then
4361 return "TrussPart"
4362 else
4363 return Enum.FormFactor.Symmetric
4364 end
4365 end
4366
4367 SetFunction(
4368 function(face,distance)
4369 if type(face) ~= "string" then error("1st argument needs a string",0) end
4370 local stringface = {
4371 ["back"] = Enum.NormalId.Back;
4372 ["bottom"] = Enum.NormalId.Bottom;
4373 ["front"] = Enum.NormalId.Front;
4374 ["left"] = Enum.NormalId.Left;
4375 ["right"] = Enum.NormalId.Right;
4376 ["top"] = Enum.NormalId.Top;
4377 }
4378 distance = distance or 0
4379 face = stringface[face]
4380
4381 local selection = GetFilteredSelection("BasePart")
4382 local fm,fs = facemult[face],facesize[face]
4383 local dis = distance*fm
4384 local fvec = facevector[face]
4385 for i,part in pairs(GetFilteredSelection("BasePart")) do
4386 local cf,sz,ff = part.CFrame,part.Size,GetFormFactor(part)
4387 local ffm = formfactormult[face][ff]
4388 local mult
4389 if ff == Enum.FormFactor.Custom then
4390 mult = dis
4391 else
4392 mult = Round(dis,ffm)
4393 end
4394 local mod = fvec*mult
4395 local fsize = sz[fs]
4396 mod = fsize + mult*fm < ffm and fvec*((ffm-fsize)*fm) or mod
4397 part.Size = sz + mod
4398 part.CFrame = cf * CFrame.new(mod*fm/2)
4399 end
4400 end
4401 )
4402
4403 SetDescription("Resizes each selection on a specified face by a specified distance.\n'face' should be a string that represents the face resize on.\n\"top\", \"bottom\", \"front\", \"back\", \"right\", and \"left\" are valid spellings.\nCapitalization does not matter.\n'distance' is the distance to resize by.\nNote that a part's size may be rounded, depending on its FormFactor.")
4404 SetArgumentDoc({
4405 {"string";"face"};
4406 {"number";"distance";"0"};
4407 })
4408
4409 Validate()
4410end)
4411
4412ProcessElementSource(function()
4413 SetPluginName("SnapSize")
4414 SetPluginType("command")
4415 SetCommandName("ss")
4416
4417 SetFunction(
4418 function(xinc,yinc,zinc)
4419 xinc,yinc,zinc = xinc or 0,yinc or 0,zinc or 0
4420 for _,object in pairs(GetFilteredSelection("BasePart")) do
4421 local cf = object.CFrame
4422 object.Size = Vector3.new(
4423 Round(object.Size.x,xinc),
4424 Round(object.Size.y,yinc),
4425 Round(object.Size.z,zinc)
4426 )
4427 object.CFrame = cf
4428 end
4429 end
4430 )
4431
4432 SetDescription("Snaps the size of each selection on each axis by its repective increment.\nFor example, if 'xinc' were 1, each part's size would be snapped to the nearest 1 on the X axis.\nNote that a part's size may be rounded depending on its FormFactor.")
4433 SetArgumentDoc({
4434 {"number";"xinc";"0"};
4435 {"number";"yinc";"0"};
4436 {"number";"zinc";"0"};
4437 })
4438
4439 Validate()
4440end)
4441
4442ProcessElementSource(function()
4443 SetPluginName("CenterResize")
4444 SetPluginType("command")
4445 SetCommandName("crs")
4446
4447 local facevector = {
4448 [Enum.NormalId.Back] = Vector3.FromNormalId(Enum.NormalId.Back);
4449 [Enum.NormalId.Bottom] = Vector3.FromNormalId(Enum.NormalId.Bottom);
4450 [Enum.NormalId.Front] = Vector3.FromNormalId(Enum.NormalId.Front);
4451 [Enum.NormalId.Left] = Vector3.FromNormalId(Enum.NormalId.Left);
4452 [Enum.NormalId.Right] = Vector3.FromNormalId(Enum.NormalId.Right);
4453 [Enum.NormalId.Top] = Vector3.FromNormalId(Enum.NormalId.Top);
4454 }
4455 local facemult = {
4456 [Enum.NormalId.Back] = 1;
4457 [Enum.NormalId.Bottom] = -1;
4458 [Enum.NormalId.Front] = -1;
4459 [Enum.NormalId.Left] = -1;
4460 [Enum.NormalId.Right] = 1;
4461 [Enum.NormalId.Top] = 1;
4462 }
4463 local facesize = {
4464 [Enum.NormalId.Back] = "z";
4465 [Enum.NormalId.Bottom] = "y";
4466 [Enum.NormalId.Front] = "z";
4467 [Enum.NormalId.Left] = "x";
4468 [Enum.NormalId.Right] = "x";
4469 [Enum.NormalId.Top] = "y";
4470 }
4471
4472 local FFXZ = {
4473 [Enum.FormFactor.Symmetric] = 1;
4474 [Enum.FormFactor.Brick] = 1;
4475 [Enum.FormFactor.Plate] = 1;
4476 [Enum.FormFactor.Custom] = 0.2;
4477 ["TrussPart"] = 2;
4478 }
4479
4480 local FFY = {
4481 [Enum.FormFactor.Symmetric] = 1;
4482 [Enum.FormFactor.Brick] = 1.2;
4483 [Enum.FormFactor.Plate] = 0.4;
4484 [Enum.FormFactor.Custom] = 0.2;
4485 ["TrussPart"] = 2;
4486 }
4487
4488 local formfactormult = {
4489 [Enum.NormalId.Back] = FFXZ;
4490 [Enum.NormalId.Bottom] = FFY;
4491 [Enum.NormalId.Front] = FFXZ;
4492 [Enum.NormalId.Left] = FFXZ;
4493 [Enum.NormalId.Right] = FFXZ;
4494 [Enum.NormalId.Top] = FFY;
4495 }
4496
4497 local function GetFormFactor(object)
4498 if object:IsA"FormFactorPart" then
4499 return object.formFactor
4500 elseif object:IsA"TrussPart" then
4501 return "TrussPart"
4502 else
4503 return Enum.FormFactor.Symmetric
4504 end
4505 end
4506
4507 SetFunction(
4508 function(face,distance)
4509 if type(face) ~= "string" then error("1st argument needs a string",0) end
4510 local stringface = {
4511 ["back"] = Enum.NormalId.Back;
4512 ["bottom"] = Enum.NormalId.Bottom;
4513 ["front"] = Enum.NormalId.Front;
4514 ["left"] = Enum.NormalId.Left;
4515 ["right"] = Enum.NormalId.Right;
4516 ["top"] = Enum.NormalId.Top;
4517 }
4518 distance = distance or 0
4519 face = stringface[face]
4520
4521 local selection = GetFilteredSelection("BasePart")
4522 local fm,fs = facemult[face],facesize[face]
4523 local dis = distance*2*fm
4524 local fvec = facevector[face]
4525 for i,part in pairs(GetFilteredSelection("BasePart")) do
4526 local cf,sz,ff = part.CFrame,part.Size,GetFormFactor(part)
4527 local ffm = formfactormult[face][ff]
4528 local mult
4529 if ff == Enum.FormFactor.Custom then
4530 mult = dis
4531 else
4532 mult = Round(dis,ffm)
4533 end
4534 local mod = fvec*mult
4535 local fsize = sz[fs]
4536 mod = fsize + mult*fm < ffm and fvec*((ffm-fsize)*fm) or mod
4537 part.Size = sz + mod
4538 part.CFrame = cf
4539 end
4540 end
4541 )
4542
4543 SetDescription("Resizes each selection on a specified face by a specified distance, out from the center of the selection.\n'face' should be a string that represents the face resize on.\n\"top\", \"bottom\", \"front\", \"back\", \"right\", and \"left\" are valid spellings.\nCapitalization does not matter.\n'distance' is the distance to resize by.\nNote that a part's size may be rounded, depending on its FormFactor.")
4544 SetArgumentDoc({
4545 {"string";"face"};
4546 {"number";"distance";"0"};
4547 })
4548
4549 Validate()
4550end)
4551
4552ProcessElementSource(function()
4553 SetPluginName("Size")
4554 SetPluginType("command")
4555 SetCommandName("s")
4556
4557 SetFunction(
4558 function(x,y,z)
4559 x,y,z = x or 0,y or 0,z or 0
4560 for _,object in pairs(GetFilteredSelection("BasePart")) do
4561 local cf = object.CFrame
4562 object.Size = Vector3.new(x,y,z)
4563 object.CFrame = cf
4564 end
4565 end
4566 )
4567
4568 SetDescription("Directly sets the size of each selection.\n'x', 'y', and 'z' represent their respective size axes on a part.\n")
4569 SetArgumentDoc({
4570 {"number";"x";"0"};
4571 {"number";"y";"0"};
4572 {"number";"z";"0"};
4573 })
4574
4575 Validate()
4576end)
4577
4578ProcessElementSource(function()
4579 SetPluginName("JoinWeld")
4580 SetPluginType("command")
4581 SetCommandName("jw")
4582
4583 SetFunction(
4584 function(type)
4585 type = type or "Motor6D"
4586 local selection = GetFilteredSelection("BasePart")
4587 if #selection > 1 then
4588 local x = table.remove(selection,1)
4589 local c = CFrame.new(x.Position)
4590 local xcf = x.CFrame:toObjectSpace(c)
4591 for _,y in pairs(selection) do
4592 local w = Instance.new(type)
4593 w.Part0 = x
4594 w.Part1 = y
4595 w.C0 = xcf
4596 w.C1 = y.CFrame:toObjectSpace(c)
4597 w.Parent = x
4598 end
4599 elseif #selection > 0 then
4600 error("not enough valid selections",0)
4601 else
4602 error("no valid selections",0)
4603 end
4604 end
4605 )
4606
4607 SetDescription("Welds each selection to the first selection using a specified joint.\n'type' can be the ClassName of any Instance that inherits from the JointInstance, as long as it's instancable.\n'type' is case-sensitive.")
4608 SetArgumentDoc({
4609 {"string";"type";"Motor6D"};
4610 })
4611
4612 Validate()
4613end)
4614
4615ProcessElementSource(function()
4616 SetPluginName("BreakWeld")
4617 SetPluginType("command")
4618 SetCommandName("bw")
4619
4620 SetFunction(
4621 function(type)
4622 type = type or "Motor6D"
4623 local selection = GetFilteredSelection("BasePart")
4624 if #selection > 0 then
4625 local part = table.remove(selection,1)
4626 local joints = {}
4627 for _,joint in pairs(part:GetChildren()) do
4628 if joint.className == type then
4629 table.insert(joints,joint)
4630 end
4631 end
4632 if #selection > 0 then
4633 local joined = {}
4634 for i,v in pairs(selection) do
4635 joined[v] = true
4636 end
4637 for _,joint in pairs(joints) do
4638 if joined[joint.Part1] then
4639 joint:Remove()
4640 end
4641 end
4642 else
4643 local joint = joints[#joints]
4644 if joint then
4645 joint:Remove()
4646 end
4647 end
4648 else
4649 error("no valid selections",0)
4650 end
4651 end
4652 )
4653
4654 SetDescription("Removes joints, depending on how many parts are selected.\nIf multiple parts are selected, then any joints of any involved parts, and of 'type', are removed.\nThat is, if a joint in the first selection is joined with another selected part, that joint is removed.\nIn other words, a reverse of the JoinWeld command occurs.\nIf only one part is selected, then the last weld of 'type' found, is removed from that part.")
4655 SetArgumentDoc({
4656 {"string";"type";"Motor6D"};
4657 })
4658
4659 Validate()
4660end)
4661
4662ProcessElementSource(function()
4663 SetPluginName("Scale")
4664 SetPluginType("command")
4665 SetCommandName("sc")
4666
4667 SetFunction(
4668 function(factor)
4669 local function RecurseScale(object,factor,center)
4670 if object:IsA"BasePart" then
4671 if object:IsA"FormFactorPart" then
4672 object.formFactor = "Custom"
4673 end
4674 local cf = center:toObjectSpace(object.CFrame)
4675 object.Size = object.Size*factor
4676 object.CFrame = center:toWorldSpace(cf + cf.p * (factor - 1))
4677 elseif object:IsA"DataModelMesh" then
4678 object.Offset = object.Offset * factor
4679 if object:IsA"FileMesh" then
4680 if object:IsA"SpecialMesh" then
4681 if object.MeshType == Enum.MeshType.FileMesh then
4682 object.Scale = object.Scale * factor
4683 end
4684 else
4685 object.Scale = object.Scale * factor
4686 end
4687 elseif object:IsA"BevelMesh" then
4688 object.Bevel = object.Bevel * factor
4689 end
4690 elseif object:IsA"Texture" then
4691 object.StudsPerTileU = object.StudsPerTileU * factor
4692 object.StudsPerTileV = object.StudsPerTileV * factor
4693 end
4694 for _,child in pairs(object:GetChildren()) do
4695 RecurseScale(child,factor,center)
4696 end
4697 end
4698 local selection = GetSelection()
4699 local parts = GetFilteredSelection("BasePart")
4700 if #parts > 0 then
4701 local center = CFrame.new(GetMidpoint(parts))
4702 local model = Instance.new("Model",workspace)
4703 model.Name = "ScaledModel"
4704 for _,object in pairs(selection) do
4705 local new = object:Clone()
4706 RecurseScale(new,factor,center)
4707 new.Parent = model
4708 end
4709 else
4710 error("no valid selections",0)
4711 end
4712 end
4713 )
4714
4715 SetDescription("Copies then scales the entire selection as a group by a specified factor.\n'factor' is a number that scales the selection up or down.\nFor example, if the factor were 0.5, then the result would be half the size.\nIf the factor were 2, then the result would be twice the size.")
4716 SetArgumentDoc({
4717 {"number";"factor";"1"};
4718 })
4719
4720 Validate()
4721end)
4722
4723ProcessElementSource(function()
4724 SetPluginName("Slope")
4725 SetPluginType("command")
4726 SetCommandName("sl")
4727
4728 SetFunction(
4729 function()
4730 local selection = GetFilteredSelection("BasePart")
4731 if #selection > 2 then
4732 local p1 = table.remove(selection,2)
4733 local p0 = table.remove(selection,1)
4734 for _,part in pairs(selection) do
4735 part.CFrame = CFrame.new(part.CFrame.p,part.CFrame.p+(p1.CFrame.p-p0.CFrame.p))
4736 end
4737 elseif #selection > 1 then
4738 error("not enough valid selections",0)
4739 elseif #selection > 0 then
4740 error("invalid second selection",0)
4741 else
4742 error("invalid first selection",0)
4743 end
4744 end
4745 )
4746
4747 SetDescription("Rotates the selection by using the slope between the first and second selections.\nThe first and second selected parts are used as points.\nThe rest of the selection will then be rotated to the slope between those two points. Their positions remain the same.")
4748
4749 Validate()
4750end)
4751ProcessElementSource(function()
4752 SetPluginName("Midpoint")
4753 SetPluginType("command")
4754 SetCommandName("mp")
4755
4756 SetFunction(
4757 function()
4758 local selection = GetFilteredSelection("BasePart")
4759 if #selection > 1 then
4760 local center = table.remove(selection,1)
4761 center.CFrame = (center.CFrame-center.CFrame.p) + GetMidpoint(selection)
4762 elseif #selection > 0 then
4763 error("not enough valid selections",0)
4764 else
4765 error("no valid selections",0)
4766 end
4767 end
4768 )
4769
4770 SetDescription("Moves the first selection to the center of the rest of the selection.\nThe part's rotation is not affected.")
4771
4772 Validate()
4773end)
4774
4775ProcessElementSource(function()
4776 SetPluginName("Skew")
4777 SetPluginType("command")
4778 SetCommandName("sk")
4779
4780 SetFunction(
4781 function(x,y,z,precision)
4782 x,y,z,precision = x or 0,y or 0,z or 0, precision or 1
4783 x,y,z = math.rad(x*precision),math.rad(y*precision),math.rad(z*precision)
4784 for _,object in pairs(GetFilteredSelection("BasePart")) do
4785 object.CFrame = object.CFrame * CFrame.Angles(
4786 math.random(-x,x)/precision,
4787 math.random(-y,y)/precision,
4788 math.random(-z,z)/precision
4789 )
4790 end
4791 end
4792 )
4793
4794 SetDescription("Rotates each selection with random angles.\n'x', 'y', and 'z' represent the maximum possible amount to skew by on each axis.\n'precision' represents the number of decimal places possible.\n(1 yields results like 0 or 1, 100 yields results like 0.01 or 0.99)")
4795 SetArgumentDoc({
4796 {"number";"x";"0"};
4797 {"number";"y";"0"};
4798 {"number";"z";"0"};
4799 {"number";"precision";"1"};
4800 })
4801
4802 Validate()
4803end)
4804
4805-- Start!
4806GetPluginSources()
4807InitializePanel()
4808InitializeCommands()
4809
4810-- All done!
4811print("CmdUtl v"..version.." loaded")