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