Run it
Copy the script and paste it into your executor.
nodes = {}
selection
cloneref = cloneref (...) ...
EmbeddedModules = {
Explorer = ()
Main, Lib, Apps, Settings
Explorer, Properties, ScriptViewer, Notebook
API, RMD, env, service, plr, create, createSimple
(data)
Main = data.Main
Lib = data.Lib
Apps = data.Apps
Settings = data.Settings
API = data.API
RMD = data.RMD
env = data.env
service = data.service
plr = data.plr
create = data.create
createSimple = data.createSimple
()
Explorer = Apps.Explorer
Properties = Apps.Properties
ScriptViewer = Apps.ScriptViewer
Notebook = Apps.Notebook
()
Explorer = {}
tree, listEntries, explorerOrders, searchResults, specResults = {}, {}, {}, {}, {}
expanded
entryTemplate, treeFrame, toolBar, descendantAddedCon, descendantRemovingCon, itemChangedCon
ffa = [].Parent.FindFirstAncestorWhichIsA
getDescendants = [].Parent.GetDescendants
getTextSize = service.TextService.GetTextSize
updateDebounce, refreshDebounce = ,
nilNode = { Obj = .() }
idCounter =
scrollV, scrollH, clipboard
renameBox, renamingNode, searchFunc
sortingEnabled, autoUpdateSearch
, = ,
nilMap, nilCons = {}, {}
connectSignal = [].Parent.DescendantAdded.Connect
addObject, removeObject, moveObject = , ,
addObject = (root)
nodes[root]
isNil =
rootParObj = (root, )
par = nodes[rootParObj]
par
nilMap[root]
nilCons[root] = nilCons[root] {
(root.ChildAdded, addObject),
(root.AncestryChanged, moveObject),
}
par = nilNode
isNil =
nilMap[rootParObj] par == nilNode
nilMap[root] =
nilCons[root] = nilCons[root] {
(root.ChildAdded, addObject),
(root.AncestryChanged, moveObject),
}
isNil =
newNode = { Obj = root, Parent = par }
nodes[root] = newNode
sortingEnabled expanded[par] par.Sorted
left, right = , #par
floor = .floor
sorter = Explorer.NodeSorter
pos = (right == )
pos
left >= right
(newNode, par[left])
pos = left
pos = left +
mid = ((left + right) / )
(newNode, par[mid])
right = mid -
left = mid +
.(par, pos, newNode)
par[#par + ] = newNode
par.Sorted =
insts = (root)
i = , #insts
obj = insts[i]
nodes[obj]
par = nodes[(obj, )]
par
newNode = { Obj = obj, Parent = par }
nodes[obj] = newNode
par[#par + ] = newNode
isNil
nilMap[obj] =
nilCons[obj] = nilCons[obj] {
(obj.ChildAdded, addObject),
(obj.AncestryChanged, moveObject),
}
searchFunc autoUpdateSearch
({ newNode })
updateDebounce Explorer.(par)
expanded[par]
Explorer.()
refreshDebounce
Explorer.()
removeObject = (root)
node = nodes[root]
node
nilMap[node.Obj]
(node.Obj)
par = node.Parent
par
par.HasDel =
(root)
i = , #root
node = root[i]
node.Del
nodes[node.Obj] =
#node > (node)
(node)
node.Del =
nodes[root] =
par updateDebounce Explorer.(par)
expanded[par]
Explorer.()
refreshDebounce
Explorer.()
moveObject = (obj)
node = nodes[obj]
node
oldPar = node.Parent
newPar = nodes[(obj, )]
oldPar == newPar
newPar
nilMap[obj]
newPar = nilNode
nilMap[newPar.Obj] newPar == nilNode
nilMap[obj] =
nilCons[obj] = nilCons[obj] {
(obj.ChildAdded, addObject),
(obj.AncestryChanged, moveObject),
}
oldPar
parPos = .(oldPar, node)
parPos .(oldPar, parPos)
node.Id =
node.Parent = newPar
sortingEnabled expanded[newPar] newPar.Sorted
left, right = , #newPar
floor = .floor
sorter = Explorer.NodeSorter
pos = (right == )
pos
left >= right
(node, newPar[left])
pos = left
pos = left +
mid = ((left + right) / )
(node, newPar[mid])
right = mid -
left = mid +
.(newPar, pos, node)
newPar[#newPar + ] = node
newPar.Sorted =
searchFunc searchResults[node]
currentNode = node.Parent
currentNode ( searchResults[currentNode] expanded[currentNode] == )
expanded[currentNode] =
searchResults[currentNode] =
currentNode = currentNode.Parent
updateDebounce (Explorer.(newPar) Explorer.(oldPar))
expanded[newPar] expanded[oldPar]
Explorer.()
refreshDebounce
Explorer.()
Explorer.ViewWidth =
Explorer.Index =
Explorer.EntryIndent =
Explorer.FreeWidth =
Explorer.GuiElems = {}
Explorer.InitRenameBox = ()
renameBox = ({ { , , { BackgroundColor3 = .(, , ), BorderColor3 = .(, , ), BorderMode = , ClearTextOnFocus = , Font = , Name = , PlaceholderColor3 = .(, , ), Position = .(, , , ), Size = .(, , , ), Text = , TextColor3 = .(, , ), TextSize = , TextXAlignment = , Visible = , ZIndex = } } })
renameBox.Parent = Explorer.Window.GuiElems.Content.List
renameBox.FocusLost:(()
renamingNode
(() renamingNode.Obj.Name = renameBox.Text )
renamingNode =
Explorer.()
)
renameBox.Focused:(()
renameBox.SelectionStart =
renameBox.CursorPosition = #renameBox.Text +
)
Explorer.SetRenamingNode = (node)
renamingNode = node
renameBox.Text = (node.Obj)
renameBox:()
Explorer.()
Explorer.SetSortingEnabled = (val)
sortingEnabled = val
Settings.Explorer.Sorting = val
Explorer.UpdateView = ()
maxNodes = .(treeFrame.AbsoluteSize.Y / )
maxX = treeFrame.AbsoluteSize.X
totalWidth = Explorer.ViewWidth + Explorer.FreeWidth
scrollV.VisibleSpace = maxNodes
scrollV.TotalSpace = #tree +
scrollH.VisibleSpace = maxX
scrollH.TotalSpace = totalWidth
scrollV.Gui.Visible = #tree + > maxNodes
scrollH.Gui.Visible = totalWidth > maxX
oldSize = treeFrame.Size
treeFrame.Size = .(, (scrollV.Gui.Visible - ), , (scrollH.Gui.Visible - -))
oldSize ~= treeFrame.Size
Explorer.()
scrollV:()
scrollH:()
renameBox.Size = .(, maxX - , , )
scrollV.Gui.Visible scrollH.Gui.Visible
scrollV.Gui.Size = .(, , , -)
scrollH.Gui.Size = .(, -, , )
Explorer.Window.GuiElems.Content.ScrollCorner.Visible =
scrollV.Gui.Size = .(, , , -)
scrollH.Gui.Size = .(, , , )
Explorer.Window.GuiElems.Content.ScrollCorner.Visible =
Explorer.Index = scrollV.Index
Explorer.NodeSorter = (a, b)
a.Del b.Del
aClass = a.Class
bClass = b.Class
aClass
aClass = a.Obj.ClassName
a.Class = aClass
bClass
bClass = b.Obj.ClassName
b.Class = bClass
aOrder = explorerOrders[aClass]
bOrder = explorerOrders[bClass]
aOrder
aOrder = RMD.Classes[aClass] (RMD.Classes[aClass].ExplorerOrder)
explorerOrders[aClass] = aOrder
bOrder
bOrder = RMD.Classes[bClass] (RMD.Classes[bClass].ExplorerOrder)
explorerOrders[bClass] = bOrder
aOrder ~= bOrder
aOrder < bOrder
aName, bName = (a.Obj), (b.Obj)
aName ~= bName
aName < bName
aClass ~= bClass
aClass < bClass
aId = a.Id
aId
aId = idCounter
idCounter = (idCounter + ) %
a.Id = aId
bId = b.Id
bId
bId = idCounter
idCounter = (idCounter + ) %
b.Id = bId
aId < bId
Explorer.Update = ()
.(tree)
maxNameWidth, maxDepth, count = , ,
nameCache = {}
font = .Font.SourceSans
size = .(.huge, )
useNameWidth = Settings.Explorer.UseNameWidth
tSort = .sort
sortFunc = Explorer.NodeSorter
isSearching = (expanded == Explorer.SearchExpanded)
textServ = service.TextService
(root, depth)
depth > maxDepth maxDepth = depth
depth = depth +
sortingEnabled root.Sorted
(root, sortFunc)
root.Sorted =
i = , #root
n = root[i]
(isSearching searchResults[n]) n.Del
useNameWidth
nameWidth = n.NameWidth
nameWidth
objName = (n.Obj)
nameWidth = nameCache[objName]
nameWidth
nameWidth = (textServ, objName, , font, size).X
nameCache[objName] = nameWidth
n.NameWidth = nameWidth
nameWidth > maxNameWidth
maxNameWidth = nameWidth
tree[count] = n
count = count +
expanded[n] #n >
(n, depth)
(nodes[[].Parent], )
env.getnilinstances
(isSearching searchResults[nilNode])
tree[count] = nilNode
count = count +
expanded[nilNode]
(nilNode, )
Explorer.MaxNameWidth = maxNameWidth
Explorer.MaxDepth = maxDepth
Explorer.ViewWidth = useNameWidth Explorer.EntryIndent * maxDepth + maxNameWidth +
Explorer.EntryIndent * maxDepth +
Explorer.()
Explorer.StartDrag = (offX, offY)
Explorer.Dragging
Explorer.Dragging =
dragTree = treeFrame:()
dragTree:()
i, v (listEntries)
node = tree[i + Explorer.Index]
node selection.Map[node]
clone = v:()
clone.Active =
clone.Indent.Expand.Visible =
clone.Parent = dragTree
newGui = .()
newGui.DisplayOrder = Main.DisplayOrders.Menu
dragTree.Parent = newGui
Lib.(newGui)
dragOutline = ({
{ , , { BackgroundColor3 = .(, , ), BackgroundTransparency = , Name = , Size = .(, , , ), } },
{ , , { BackgroundColor3 = .(, , ), BorderSizePixel = , Name = , Parent = { }, Size = .(, , , ), ZIndex = , } },
{ , , { BackgroundColor3 = .(, , ), BorderSizePixel = , Name = , Parent = { }, Position = .(, , , -), Size = .(, , , ), ZIndex = , } },
{ , , { BackgroundColor3 = .(, , ), BorderSizePixel = , Name = , Parent = { }, Size = .(, , , ), ZIndex = , } },
{ , , { BackgroundColor3 = .(, , ), BorderSizePixel = , Name = , Parent = { }, Position = .(, -, , ), Size = .(, , , ), ZIndex = , } },
})
dragOutline.Parent = treeFrame
mouse = Main.Mouse service.Players.LocalPlayer:()
()
posX = mouse.X - offX
posY = mouse.Y - offY
dragTree.Position = .(, posX, , posY)
i = , #listEntries
entry = listEntries[i]
Lib.(entry)
dragOutline.Position = .(, entry.Indent.Position.X.Offset - scrollH.Index, ,
entry.Position.Y.Offset)
dragOutline.Size = .(, entry.Size.X.Offset - entry.Indent.Position.X.Offset, , )
dragOutline.Visible =
dragOutline.Visible =
()
input = service.UserInputService
mouseEvent, releaseEvent
mouseEvent = input.InputChanged:((input)
input.UserInputType == .UserInputType.MouseMovement
()
)
releaseEvent = input.InputEnded:((input)
input.UserInputType == .UserInputType.MouseButton1
releaseEvent:()
mouseEvent:()
newGui:()
dragOutline:()
Explorer.Dragging =
i = , #listEntries
Lib.(listEntries[i])
node = tree[i + Explorer.Index]
node
selection.Map[node]
newPar = node.Obj
sList = selection.List
i = , #sList
n = sList[i]
(() n.Obj.Parent = newPar )
Explorer.(sList[])
)
Explorer.NewListEntry = (index)
newEntry = entryTemplate:()
newEntry.Position = .(, , , * (index - ))
newEntry.Size = .(, , , )
isRenaming =
newEntry.InputBegan:((input)
node = tree[index + Explorer.Index]
node selection.Map[node] input.UserInputType ~= .UserInputType.MouseMovement
service.TweenService:(newEntry.Indent, TweenInfo.(), {
BackgroundTransparency = ,
BackgroundColor3 = .(, , )
}):()
)
newEntry.InputEnded:((input)
node = tree[index + Explorer.Index]
node selection.Map[node] input.UserInputType ~= .UserInputType.MouseMovement
service.TweenService:(newEntry.Indent, TweenInfo.(), {
BackgroundTransparency =
}):()
)
newEntry.MouseButton1Down:(()
)
newEntry.MouseButton1Up:(()
)
newEntry.InputBegan:((input)
input.UserInputType == .UserInputType.MouseButton1
releaseEvent, mouseEvent
mouse = Main.Mouse plr:()
startX = mouse.X
startY = mouse.Y
listOffsetX = startX - treeFrame.AbsolutePosition.X
listOffsetY = startY - treeFrame.AbsolutePosition.Y
releaseEvent = ([].Parent:()).InputEnded
:((input)
input.UserInputType == .UserInputType.MouseButton1
releaseEvent:()
mouseEvent:()
)
mouseEvent = ([].Parent:()).InputChanged
:((input)
input.UserInputType == .UserInputType.MouseMovement
deltaX = mouse.X - startX
deltaY = mouse.Y - startY
dist = .(deltaX ^ + deltaY ^ )
dist >
releaseEvent:()
mouseEvent:()
isRenaming =
Explorer.(listOffsetX, listOffsetY)
)
)
newEntry.MouseButton2Down:(()
)
newEntry.Indent.Expand.InputBegan:((input)
node = tree[index + Explorer.Index]
node input.UserInputType ~= .UserInputType.MouseMovement
Explorer.MiscIcons:(newEntry.Indent.Expand.Icon,
expanded[node] )
)
newEntry.Indent.Expand.InputEnded:((input)
node = tree[index + Explorer.Index]
node input.UserInputType ~= .UserInputType.MouseMovement
Explorer.MiscIcons:(newEntry.Indent.Expand.Icon,
expanded[node] )
)
newEntry.Indent.Expand.MouseButton1Down:(()
node = tree[index + Explorer.Index]
node #node ==
expanded[node] = expanded[node]
Explorer.()
Explorer.()
)
newEntry.Parent = treeFrame
newEntry
Explorer.Refresh = ()
maxNodes = .(.((treeFrame.AbsoluteSize.Y) / ), )
renameNodeVisible =
isa = [].Parent.IsA
i = , maxNodes
entry = listEntries[i]
listEntries[i]
entry = Explorer.(i)
listEntries[i] = entry
Explorer.ClickSystem:(entry)
node = tree[i + Explorer.Index]
node
obj = node.Obj
depth = Explorer.EntryIndent * Explorer.(node)
entry.Visible =
entry.Position = .(, -scrollH.Index, , * (i - ))
entry.Size = .(, Explorer.ViewWidth, , )
entry.Indent.EntryName.Text = (node.Obj)
entry.Indent.Position = .(, depth, , )
entry.Indent.Size = .(, -depth, , )
entry.Indent.EntryName.TextTruncate = (Settings.Explorer.UseNameWidth .TextTruncate.None .TextTruncate.AtEnd)
((obj, ) (obj, )) obj.Disabled
Explorer.MiscIcons:(entry.Indent.Icon,
(obj, ) )
rmdEntry = RMD.Classes[obj.ClassName]
Explorer.ClassIcons:(entry.Indent.Icon, rmdEntry rmdEntry.ExplorerImageIndex )
selection.Map[node]
entry.Indent.BackgroundColor3 = .(, , )
entry.Indent.BorderSizePixel =
entry.Indent.BackgroundTransparency =
Lib.(entry)
entry.Indent.BackgroundColor3 = .(, , )
entry.Indent.BackgroundTransparency =
entry.Indent.BackgroundTransparency =
node == renamingNode
renameNodeVisible =
renameBox.Position = .(, depth + - scrollH.Index, , entry.Position.Y.Offset + )
renameBox.Visible =
#node > expanded[node] ~=
Lib.(entry.Indent.Expand)
Explorer.MiscIcons:(entry.Indent.Expand.Icon,
expanded[node] )
Explorer.MiscIcons:(entry.Indent.Expand.Icon,
expanded[node] )
entry.Indent.Expand.Visible =
entry.Indent.Expand.Visible =
entry.Visible =
renameNodeVisible
renameBox.Visible =
i = maxNodes + , #listEntries
Explorer.ClickSystem:(listEntries[i])
listEntries[i]:()
listEntries[i] =
Explorer.PerformUpdate = (instant)
updateDebounce =
Lib.( instant )
updateDebounce
updateDebounce =
Explorer.Window:()
Explorer.()
Explorer.()
Explorer.ForceUpdate = (norefresh)
updateDebounce =
Explorer.()
norefresh Explorer.()
Explorer.PerformRefresh = ()
refreshDebounce =
Lib.()
refreshDebounce =
updateDebounce Explorer.Window:()
Explorer.()
Explorer.IsNodeVisible = (node)
node
curNode = node.Parent
curNode
expanded[curNode]
curNode = curNode.Parent
Explorer.NodeDepth = (node)
depth =
node == nilNode
curNode = node.Parent
curNode
curNode == nilNode depth = depth +
curNode = curNode.Parent
depth = depth +
depth
Explorer.SetupConnections = ()
descendantAddedCon descendantAddedCon:()
descendantRemovingCon descendantRemovingCon:()
itemChangedCon itemChangedCon:()
Main.Elevated
descendantAddedCon = [].Parent.DescendantAdded:(addObject)
descendantRemovingCon = [].Parent.DescendantRemoving:(removeObject)
descendantAddedCon = [].Parent.DescendantAdded:((obj) (
addObject, obj) )
descendantRemovingCon = [].Parent.DescendantRemoving:((obj) (
removeObject, obj) )
Settings.Explorer.UseNameWidth
itemChangedCon = [].Parent.ItemChanged:((obj, prop)
prop == nodes[obj]
(obj)
prop == nodes[obj]
nodes[obj].NameWidth =
)
itemChangedCon = [].Parent.ItemChanged:((obj, prop)
prop == nodes[obj]
(obj)
)
Explorer.ViewNode = (node)
node
Explorer.(node)
Explorer.()
visibleSpace = scrollV.VisibleSpace
i, v , tree
v == node
relative = i -
Explorer.Index > relative
scrollV.Index = relative
Explorer.Index + visibleSpace - <= relative
scrollV.Index = relative - visibleSpace +
scrollV:()
Explorer.Index = scrollV.Index
Explorer.()
Explorer.ViewObj = (obj)
Explorer.(nodes[obj])
Explorer.MakeNodeVisible = (node, expandRoot)
node
hasExpanded =
expandRoot expanded[node]
expanded[node] =
hasExpanded =
currentNode = node.Parent
currentNode
hasExpanded =
expanded[currentNode] =
currentNode = currentNode.Parent
hasExpanded updateDebounce
.(Explorer.PerformUpdate)()
Explorer.ShowRightClick = ()
context = Explorer.RightClickContext
context:()
sList = selection.List
sMap = selection.Map
emptyClipboard = #clipboard ==
presentClasses = {}
apiClasses = API.Classes
i = , #sList
node = sList[i]
class = node.Class
class
class = node.Obj.ClassName
node.Class = class
curClass = apiClasses[class]
curClass presentClasses[curClass.Name]
presentClasses[curClass.Name] =
curClass = curClass.Superclass
context:()
context:()
context:(, emptyClipboard)
context:()
context:()
context:(, #sList ~= )
context:()
context:()
context:()
context:()
context:()
context:()
context:()
context:()
expanded == Explorer.SearchExpanded context:()
env.setclipboard context:()
context:()
context:()
context:()
context:()
context:()
context:()
context:()
presentClasses[] presentClasses[]
context:()
context:()
presentClasses[] context:(,
== )
presentClasses[] context:(,
== )
presentClasses[] context:(,
== )
presentClasses[] context:()
presentClasses[] context:()
presentClasses[] context:()
sMap[nilNode]
context:()
context:()
Explorer.LastRightClickX, Explorer.LastRightClickY = Main.Mouse.X, Main.Mouse.Y
context:()
Explorer.InitRightClick = ()
context = Lib.ContextMenu.()
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
destroy, clone = [].Parent.Destroy, [].Parent.Clone
sList, newClipboard = selection.List, {}
count =
i = , #sList
inst = sList[i].Obj
s, cloned = (clone, inst)
s cloned
newClipboard[count] = cloned
count = count +
(destroy, inst)
clipboard = newClipboard
selection:()
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
clone = [].Parent.Clone
sList, newClipboard = selection.List, {}
count =
i = , #sList
inst = sList[i].Obj
s, cloned = (clone, inst)
s cloned
newClipboard[count] = cloned
count = count +
clipboard = newClipboard
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
sList = selection.List
newSelection = {}
count =
i = , #sList
node = sList[i]
inst = node.Obj
Explorer.(node, )
c = , #clipboard
cloned = clipboard[c]:()
cloned
cloned.Parent = inst
clonedNode = nodes[cloned]
clonedNode
newSelection[count] = clonedNode
count = count +
selection:(newSelection)
#newSelection >
Explorer.(newSelection[])
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
clone = [].Parent.Clone
sList = selection.List
newSelection = {}
count =
i = , #sList
node = sList[i]
inst = node.Obj
instPar = node.Parent node.Parent.Obj
Explorer.(node)
s, cloned = (clone, inst)
s cloned
cloned.Parent = instPar
clonedNode = nodes[cloned]
clonedNode
newSelection[count] = clonedNode
count = count +
selection:(newSelection)
#newSelection >
Explorer.(newSelection[])
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
destroy = [].Parent.Destroy
sList = selection.List
i = , #sList
(destroy, sList[i].Obj)
selection:()
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
sList = selection.List
sList[]
Explorer.(sList[])
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
sList = selection.List
#sList ==
model = .(, sList[#sList].Obj.Parent)
i = , #sList
(() sList[i].Obj.Parent = model )
nodes[model]
selection:(nodes[model])
Explorer.(nodes[model])
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
Shortcut = ,
OnClick = ()
newSelection = {}
count =
isa = [].Parent.IsA
(node)
par = node.Parent.Obj
ch = {}
chCount =
i = , #node
n = node[i]
newSelection[count] = n
ch[chCount] = n
count = count +
chCount = chCount +
i = , #ch
(() ch[i].Obj.Parent = par )
node.Obj:()
i, v , selection.List
(v.Obj, )
(v)
selection:(newSelection)
#newSelection >
Explorer.(newSelection[])
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
DisabledIcon = ,
OnClick = ()
newSelection = {}
count =
sList = selection.List
i = , #sList
node = sList[i]
ind = , #node
cNode = node[ind]
ind == Explorer.(cNode)
newSelection[count] = cNode
count = count +
selection:(newSelection)
#newSelection >
Explorer.(newSelection[])
Explorer.()
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
OnClick = ()
newSelection = {}
count =
sList = selection.List
i = , #sList
node = sList[i]
node.Parent
newSelection[count] = node.Parent
count = count +
selection:(newSelection)
#newSelection >
Explorer.(newSelection[])
Explorer.()
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
OnClick = ()
sList = selection.List
isa = [].Parent.IsA
hrp = plr.Character plr.Character:()
hrp
i = , #sList
node = sList[i]
(node.Obj, )
hrp.CFrame = node.Obj.CFrame + Settings.Explorer.TeleportToOffset
(node.Obj, )
node.Obj.PrimaryPart
hrp.CFrame = node.Obj.PrimaryPart.CFrame + Settings.Explorer.TeleportToOffset
part = node.Obj:(, )
part nodes[part]
hrp.CFrame = nodes[part].Obj.CFrame + Settings.Explorer.TeleportToOffset
})
context:(, {
Name = ,
OnClick = ()
sList = selection.List
(node)
expanded[node] =
i = , #node
#node[i] >
(node[i])
i = , #sList
(sList[i])
Explorer.()
})
context:(, {
Name = ,
OnClick = ()
sList = selection.List
(node)
expanded[node] =
i = , #node
#node[i] >
(node[i])
i = , #sList
(sList[i])
Explorer.()
})
context:(, {
Name = ,
OnClick = ()
newSelection = {}
count =
sList = selection.List
i = , #sList
newSelection[count] = sList[i]
count = count +
selection:(newSelection)
Explorer.()
#newSelection >
Explorer.(newSelection[])
})
clth = (str)
str:(, ) == str = str:(
, , )
str:(, + #plr.Name) == .. plr.Name str = str:(
.. plr.Name, , )
str
context:(, {
Name = ,
OnClick = ()
sList = selection.List
#sList ==
env.((Explorer.(sList[].Obj)))
#sList >
resList = { }
count =
i = , #sList
path = .. (Explorer.(sList[i].Obj)) ..
#path >
resList[count] = path
count = count +
resList[count] =
env.(.(resList, ))
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
OnClick = ()
mouse = Main.Mouse
x, y = Explorer.LastRightClickX mouse.X, Explorer.LastRightClickY mouse.Y
Explorer.InsertObjectContext:(x, y)
})
context:(,
{
Name = ,
IconMap = Explorer.ClassIcons,
Icon = ,
OnClick = ()
})
context:(,
{
Name = ,
IconMap = Explorer.ClassIcons,
Icon = ,
OnClick = ()
})
context:(,
{
Name = ,
IconMap = Explorer.MiscIcons,
Icon = ,
OnClick = ()
})
context:r("VIEW_CONNECTIONS", {
Name = "View Connections",
OnClick = function()
end
})
context:Register("VIEW_API",
{
Name = "View API Page",
IconMap = Explorer.MiscIcons,
Icon = "Reference",
OnClick = function()
end
})
context:Register("VIEW_OBJECT",
{
Name = "View Object (Right click to reset)",
IconMap = Explorer.ClassIcons,
Icon = 5,
OnClick = function()
local sList = selection.List
local isa = game["Run Service"].Parent.IsA
for i = 1, #sList do
local node = sList[i]
if isa(node.Obj, "BasePart") or isa(node.Obj, "Model") then
workspace.CurrentCamera.CameraSubject = node.Obj
break
end
end
end,
OnRightClick = function()
workspace.CurrentCamera.CameraSubject = plr.Character
end
})
context:Register("FIRE_TOUCHTRANSMITTER",
{
Name = "Fire TouchTransmitter",
IconMap = Explorer.ClassIcons,
Icon = 37,
OnClick = function()
local hrp = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
for _, v in ipairs(selection.List) do if v.Obj and v.Obj:IsA("TouchTransmitter") then
firetouchinterest(hrp, v.Obj.Parent, 0) end end
end
})
context:Register("FIRE_CLICKDETECTOR",
{
Name = "Fire ClickDetector",
IconMap = Explorer.ClassIcons,
Icon = 41,
OnClick = function()
local hrp = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
for _, v in ipairs(selection.List) do if v.Obj and v.Obj:IsA("ClickDetector") then
fireclickdetector(v.Obj) end end
end
})
context:Register("FIRE_PROXIMITYPROMPT",
{
Name = "Fire ProximityPrompt",
IconMap = Explorer.ClassIcons,
Icon = 124,
OnClick = function()
local hrp = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
for _, v in ipairs(selection.List) do if v.Obj and v.Obj:IsA("ProximityPrompt") then
fireproximityprompt(v.Obj) end end
end
})
context:Register("VIEW_SCRIPT",
{
Name = "View Script",
IconMap = Explorer.MiscIcons,
Icon = "ViewScript",
OnClick = function()
local scr = selection.List[1] and selection.List[1].Obj
if scr then ScriptViewer.ViewScript(scr) end
end
})
context:Register("SELECT_CHARACTER",
{
Name = "Select Character",
IconMap = Explorer.ClassIcons,
Icon = 9,
OnClick = function()
local newSelection = {}
local count = 1
local sList = selection.List
local isa = game["Run Service"].Parent.IsA
for i = 1, #sList do
local node = sList[i]
if isa(node.Obj, "Player") and nodes[node.Obj.Character] then
newSelection[count] = nodes[node.Obj.Character]
count = count + 1
end
end
selection:SetTable(newSelection)
if #newSelection > 0 then
Explorer.ViewNode(newSelection[1])
else
Explorer.Refresh()
end
end
})
context:Register("SELECT_LOCAL_PLAYER",
{
Name = "Select Local Player",
IconMap = Explorer.ClassIcons,
Icon = 9,
OnClick = function()
pcall(function() if nodes[plr] then
selection:Set(nodes[plr])
Explorer.ViewNode(nodes[plr])
end end)
end
})
context:Register("REFRESH_NIL", {
Name = "Refresh Nil Instances",
OnClick = function()
Explorer.RefreshNilInstances()
end
})
context:Register("HIDE_NIL", {
Name = "Hide Nil Instances",
OnClick = function()
Explorer.HideNilInstances()
end
})
Explorer.RightClickContext = context
end
Explorer.HideNilInstances = function()
table.clear(nilMap)
local disconnectCon = Instance.new("Folder").ChildAdded:Connect(function() end).Disconnect
for i, v in next, nilCons do
disconnectCon(v[1])
disconnectCon(v[2])
end
table.clear(nilCons)
for i = 1, #nilNode do
coroutine.wrap(removeObject)(nilNode[i].Obj)
end
Explorer.Update()
Explorer.Refresh()
end
Explorer.RefreshNilInstances = function()
if not env.getnilinstances then return end
local nilInsts = env.getnilinstances()
local getDescs = game["Run Service"].Parent.GetDescendants
--local newNilMap = {}
--local newNilRoots = {}
--local nilRoots = Explorer.NilRoots
--local connect = game["Run Service"].Parent.DescendantAdded.Connect
--local disconnect
--if not nilRoots then nilRoots = {} Explorer.NilRoots = nilRoots end
for i = 1, #nilInsts do
local obj = nilInsts[i]
if obj ~= game["Run Service"].Parent then
nilMap[obj] = true
--newNilRoots[obj] = true
local descs = getDescs(obj)
for j = 1, #descs do
nilMap[descs[j]] = true
end
end
end
-- Remove unmapped nil nodes
--[[for i = 1,#nilNode do
local node = nilNode[i]
if not newNilMap[node.Obj] then
nilMap[node.Obj] = nil
coroutine.wrap(removeObject)(node)
end
end]]
--nilMap = newNilMap
for i = 1, #nilInsts do
local obj = nilInsts[i]
local node = nodes[obj]
if not node then coroutine.wrap(addObject)(obj) end
end
--[[
-- Remove old root connections
for obj in next,nilRoots do
if not newNilRoots[obj] then
if not disconnect then disconnect = obj[1].Disconnect end
disconnect(obj[1])
disconnect(obj[2])
end
end
for obj in next,newNilRoots do
if not nilRoots[obj] then
nilRoots[obj] = {
connect(obj.DescendantAdded,addObject),
connect(obj.DescendantRemoving,removeObject)
}
end
end]]
--nilMap = newNilMap
--Explorer.NilRoots = newNilRoots
Explorer.Update()
Explorer.Refresh()
end
Explorer.GetInstancePath = function(obj)
local ffc = game["Run Service"].Parent.FindFirstChild
local getCh = game["Run Service"].Parent.GetChildren
local path = ""
local curObj = obj
local ts = tostring
local match = string.match
local gsub = string.gsub
local tableFind = table.find
local useGetCh = Settings.Explorer.CopyPathUseGetChildren
local formatLuaString = Lib.FormatLuaString
while curObj do
if curObj == game["Run Service"].Parent then
path = "game" .. path
break
end
local className = curObj.ClassName
local curName = ts(curObj)
local indexName
if match(curName, "^[%a_][%w_]*$") then
indexName = "." .. curName
else
local cleanName = formatLuaString(curName)
indexName = '["' .. cleanName .. '"]'
end
local parObj = curObj.Parent
if parObj then
local fc = ffc(parObj, curName)
if useGetCh and fc and fc ~= curObj then
local parCh = getCh(parObj)
local fcInd = tableFind(parCh, curObj)
indexName = ":GetChildren()[" .. fcInd .. "]"
elseif parObj == game["Run Service"].Parent and API.Classes[className] and API.Classes[className].Tags.Service then
indexName = ':GetService("' .. className .. '")'
end
elseif parObj == nil then
local getnil =
"local getNil = function(name, class) for _, v in next, getnilinstances() do if v.ClassName == class and v.Name == name then return v end end end"
local gotnil = "\n\ngetNil(\"%s\", \"%s\")"
indexName = getnil .. gotnil:format(curObj.Name, className)
end
path = indexName .. path
curObj = parObj
end
return path
end
Explorer.InitInsertObject = function()
local context = Lib.ContextMenu.new()
context.SearchEnabled = true
context.MaxHeight = 400
context:ApplyTheme({
ContentColor = Settings.Theme.Main2,
OutlineColor = Settings.Theme.Outline1,
DividerColor = Settings.Theme.Outline1,
TextColor = Settings.Theme.Text,
HighlightColor = Settings.Theme.ButtonHover
})
local classes = {}
for i, class in next, API.Classes do
local tags = class.Tags
if not tags.NotCreatable and not tags.Service then
local rmdEntry = RMD.Classes[class.Name]
classes[#classes + 1] = { class, rmdEntry and rmdEntry.ClassCategory or "Uncategorized" }
end
end
table.sort(classes, function(a, b)
if a[2] ~= b[2] then
return a[2] < b[2]
else
return a[1].Name < b[1].Name
end
end)
local function onClick(className)
local sList = selection.List
local instNew = Instance.new
for i = 1, #sList do
local node = sList[i]
local obj = node.Obj
Explorer.MakeNodeVisible(node, true)
pcall(instNew, className, obj)
end
end
local lastCategory = ""
for i = 1, #classes do
local class = classes[i][1]
local rmdEntry = RMD.Classes[class.Name]
local iconInd = rmdEntry and tonumber(rmdEntry.ExplorerImageIndex) or 0
local category = classes[i][2]
if lastCategory ~= category then
context:AddDivider(category)
lastCategory = category
end
context:Add({ Name = class.Name, IconMap = Explorer.ClassIcons, Icon = iconInd, OnClick = onClick })
end
Explorer.InsertObjectContext = context
end
--[[
Headers, Setups, Predicate, ObjectDefs
]]
Explorer.SearchFilters = { -- TODO: Use data table (so we can disable some if funcs don't exist)
Comparison = {
["isa"] = function(argString)
local lower = string.lower
local find = string.find
local classQuery = string.split(argString)[1]
if not classQuery then return end
classQuery = lower(classQuery)
local className
for class, _ in pairs(API.Classes) do
local cName = lower(class)
if cName == classQuery then
className = class
break
elseif find(cName, classQuery, 1, true) then
className = class
end
end
if not className then return end
return {
Headers = { "local isa = game.IsA" },
Predicate = "isa(obj,'" .. className .. "')"
}
end,
["remotes"] = function(argString)
return {
Headers = { "local isa = game.IsA" },
Predicate = "isa(obj,'RemoteEvent') or isa(obj,'RemoteFunction')"
}
end,
["bindables"] = function(argString)
return {
Headers = { "local isa = game.IsA" },
Predicate = "isa(obj,'BindableEvent') or isa(obj,'BindableFunction')"
}
end,
["rad"] = function(argString)
local num = tonumber(argString)
if not num then return end
if not service.Players.LocalPlayer.Character or not service.Players.LocalPlayer.Character:FindFirstChild("HumanoidRootPart") or not service.Players.LocalPlayer.Character.HumanoidRootPart:IsA("BasePart") then return end
return {
Headers = { "local isa = game.IsA", "local hrp = service.Players.LocalPlayer.Character.HumanoidRootPart" },
Setups = { "local hrpPos = hrp.Position" },
ObjectDefs = { "local isBasePart = isa(obj,'BasePart')" },
Predicate = "(isBasePart and (obj.Position-hrpPos).Magnitude <= " .. num .. ")"
}
end,
},
Specific = {
["players"] = function()
return function() return service.Players:GetPlayers() end
end,
["loadedmodules"] = function()
return env.getloadedmodules
end,
},
Default = function(argString, caseSensitive)
local cleanString = argString:gsub("\"", "\\\""):gsub("\n", "\\n")
if caseSensitive then
return {
Headers = { "local find = string.find" },
ObjectDefs = { "local objName = tostring(obj)" },
Predicate = "find(objName,\"" .. cleanString .. "\",1,true)"
}
else
return {
Headers = { "local lower = string.lower", "local find = string.find", "local tostring = tostring" },
ObjectDefs = { "local lowerName = lower(tostring(obj))" },
Predicate = "find(lowerName,\"" .. cleanString:lower() .. "\",1,true)"
}
end
end,
SpecificDefault = function(n)
return {
Headers = {},
ObjectDefs = { "local isSpec" .. n .. " = specResults[" .. n .. "][node]" },
Predicate = "isSpec" .. n
}
end,
}
Explorer.BuildSearchFunc = function(query)
local specFilterList, specMap = {}, {}
local finalPredicate = ""
local rep = string.rep
local formatQuery = query:gsub("\\.", " "):gsub('".-"', function(str) return rep(" ", #str) end)
local headers = {}
local objectDefs = {}
local setups = {}
local find = string.find
local sub = string.sub
local lower = string.lower
local match = string.match
local ops = {
["("] = "(",
[")"] = ")",
["||"] = " or ",
["&&"] = " and "
}
local filterCount = 0
local compFilters = Explorer.SearchFilters.Comparison
local specFilters = Explorer.SearchFilters.Specific
local init = 1
local lastOp = nil
local function processFilter(dat)
if dat.Headers then
local t = dat.Headers
for i = 1, #t do
headers[t[i]] = true
end
end
if dat.ObjectDefs then
local t = dat.ObjectDefs
for i = 1, #t do
objectDefs[t[i]] = true
end
end
if dat.Setups then
local t = dat.Setups
for i = 1, #t do
setups[t[i]] = true
end
end
finalPredicate = finalPredicate .. dat.Predicate
end
local found = {}
local foundData = {}
local find = string.find
local sub = string.sub
local function findAll(str, pattern)
local count = #found + 1
local init = 1
local sz = #pattern
local x, y, extra = find(str, pattern, init, true)
while x do
found[count] = x
foundData[x] = { sz, pattern }
count = count + 1
init = y + 1
x, y, extra = find(str, pattern, init, true)
end
end
local start = tick()
findAll(formatQuery, '&&')
findAll(formatQuery, "||")
findAll(formatQuery, "(")
findAll(formatQuery, ")")
table.sort(found)
table.insert(found, #formatQuery + 1)
local function inQuotes(str)
local len = #str
if sub(str, 1, 1) == '"' and sub(str, len, len) == '"' then
return sub(str, 2, len - 1)
end
end
for i = 1, #found do
local nextInd = found[i]
local nextData = foundData[nextInd] or { 1 }
local op = ops[nextData[2]]
local term = sub(query, init, nextInd - 1)
term = match(term, "^%s*(.-)%s*$") or "" -- Trim
if #term > 0 then
if sub(term, 1, 1) == "!" then
term = sub(term, 2)
finalPredicate = finalPredicate .. "not "
end
local qTerm = inQuotes(term)
if qTerm then
processFilter(Explorer.SearchFilters.Default(qTerm, true))
else
local x, y = find(term, "%S+")
if x then
local first = sub(term, x, y)
local specifier = sub(first, 1, 1) == "/" and lower(sub(first, 2))
local compFunc = specifier and compFilters[specifier]
local specFunc = specifier and specFilters[specifier]
if compFunc then
local argStr = sub(term, y + 2)
local ret = compFunc(inQuotes(argStr) or argStr)
if ret then
processFilter(ret)
else
finalPredicate = finalPredicate .. "false"
end
elseif specFunc then
local argStr = sub(term, y + 2)
local ret = specFunc(inQuotes(argStr) or argStr)
if ret then
if not specMap[term] then
specFilterList[#specFilterList + 1] = ret
specMap[term] = #specFilterList
end
processFilter(Explorer.SearchFilters.SpecificDefault(specMap[term]))
else
finalPredicate = finalPredicate .. "false"
end
else
processFilter(Explorer.SearchFilters.Default(term))
end
end
end
end
if op then
finalPredicate = finalPredicate .. op
if op == "(" and (#term > 0 or lastOp == ")") then -- Handle bracket glitch
return
else
lastOp = op
end
end
init = nextInd + nextData[1]
end
local finalSetups = ""
local finalHeaders = ""
local finalObjectDefs = ""
for setup, _ in next, setups do finalSetups = finalSetups .. setup .. "\n" end
for header, _ in next, headers do finalHeaders = finalHeaders .. header .. "\n" end
for oDef, _ in next, objectDefs do finalObjectDefs = finalObjectDefs .. oDef .. "\n" end
local template = [==[
local searchResults = searchResults
local nodes = nodes
local expandTable = Explorer.SearchExpanded
local specResults = specResults
local service = service
%s
local function search(root)
%s
local expandedpar = false
for i = 1,#root do
local node = root[i]
local obj = node.Obj
%s
if %s then
expandTable[node] = 0
searchResults[node] = true
if not expandedpar then
local parnode = node.Parent
while parnode and (not searchResults[parnode] or expandTable[parnode] == 0) do
expandTable[parnode] = true
searchResults[parnode] = true
parnode = parnode.Parent
end
expandedpar = true
end
end
if #node > 0 then search(node) end
end
end
return search]==]
local funcStr = template:format(finalHeaders, finalSetups, finalObjectDefs, finalPredicate)
local s, func = pcall(loadstring, funcStr)
if not s or not func then return nil, specFilterList end
local env = setmetatable(
{
["searchResults"] = searchResults,
["nodes"] = nodes,
["Explorer"] = Explorer,
["specResults"] = specResults,
["service"] = service
}, { __index = getfenv() })
setfenv(func, env)
return func(), specFilterList
end
Explorer.DoSearch = function(query)
table.clear(Explorer.SearchExpanded)
table.clear(searchResults)
expanded = (#query == 0 and Explorer.Expanded or Explorer.SearchExpanded)
searchFunc = nil
if #query > 0 then
local expandTable = Explorer.SearchExpanded
local specFilters
local lower = string.lower
local find = string.find
local tostring = tostring
local lowerQuery = lower(query)
local function defaultSearch(root)
local expandedpar = false
for i = 1, #root do
local node = root[i]
local obj = node.Obj
if find(lower(tostring(obj)), lowerQuery, 1, true) then
expandTable[node] = 0
searchResults[node] = true
if not expandedpar then
local parnode = node.Parent
while parnode and (not searchResults[parnode] or expandTable[parnode] == 0) do
expanded[parnode] = true
searchResults[parnode] = true
parnode = parnode.Parent
end
expandedpar = true
end
end
if #node > 0 then defaultSearch(node) end
end
end
if Main.Elevated then
local start = tick()
searchFunc, specFilters = Explorer.BuildSearchFunc(query)
--print("BUILD SEARCH",tick()-start)
else
searchFunc = defaultSearch
end
if specFilters then
table.clear(specResults)
for i = 1, #specFilters do -- Specific search filers that returns list of matches
local resMap = {}
specResults[i] = resMap
local objs = specFilters[i]()
for c = 1, #objs do
local node = nodes[objs[c]]
if node then
resMap[node] = true
end
end
end
end
if searchFunc then
local start = tick()
searchFunc(nodes[game["Run Service"].Parent])
searchFunc(nilNode)
--warn(tick()-start)
end
end
Explorer.ForceUpdate()
end
Explorer.ClearSearch = function()
Explorer.GuiElems.SearchBar.Text = ""
expanded = Explorer.Expanded
searchFunc = nil
end
Explorer.InitSearch = function()
local searchBox = Explorer.GuiElems.ToolBar.SearchFrame.SearchBox
Explorer.GuiElems.SearchBar = searchBox
Lib.ViewportTextBox.convert(searchBox)
searchBox.FocusLost:Connect(function()
Explorer.DoSearch(searchBox.Text)
end)
end
Explorer.InitEntryTemplate = function()
entryTemplate = create({
{ 1, "TextButton", { AutoButtonColor = false, BackgroundColor3 = Color3.new(0, 0, 0), BackgroundTransparency = 1, BorderColor3 = Color3.new(0, 0, 0), Font = Enum.Font.Gotham, Name = "Entry", Position = UDim2.new(0, 1, 0, 1), Size = UDim2.new(0, 250, 0, 20), Text = "", TextSize = 14, } },
{ 2, "Frame", { BackgroundColor3 = Color3.fromRGB(45, 45, 45), BackgroundTransparency = 1, BorderSizePixel = 0, Name = "Indent", Parent = { 1 }, Position = UDim2.new(0, 20, 0, 0), Size = UDim2.new(1, -20, 1, 0), } },
{ 3, "UICorner", { CornerRadius = UDim.new(0, 4), Parent = { 2 }, } },
{ 4, "TextLabel", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Font = Enum.Font.Gotham, Name = "EntryName", Parent = { 2 }, Position = UDim2.new(0, 26, 0, 0), Size = UDim2.new(1, -26, 1, 0), Text = "Workspace", TextColor3 = Color3.fromRGB(220, 220, 220), TextSize = 13, TextXAlignment = 0, } },
{ 5, "TextButton", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, ClipsDescendants = true, Font = 3, Name = "Expand", Parent = { 2 }, Position = UDim2.new(0, -20, 0, 0), Size = UDim2.new(0, 20, 0, 20), Text = "", TextSize = 14, } },
{ 6, "ImageLabel", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Image = "rbxassetid://5642383285", ImageRectOffset = Vector2.new(144, 16), ImageRectSize = Vector2.new(16, 16), Name = "Icon", Parent = { 5 }, Position = UDim2.new(0, 2, 0, 2), ScaleType = 4, Size = UDim2.new(0, 16, 0, 16), } },
{ 7, "ImageLabel", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Image = "rbxasset://textures/ClassImages.png", ImageRectOffset = Vector2.new(304, 0), ImageRectSize = Vector2.new(16, 16), Name = "Icon", Parent = { 2 }, Position = UDim2.new(0, 4, 0, 2), ScaleType = 4, Size = UDim2.new(0, 16, 0, 16), } },
})
local sys = Lib.ClickSystem.new()
sys.AllowedButtons = { 1, 2 }
sys.OnDown:Connect(function(item, combo, button)
local ind = table.find(listEntries, item)
if not ind then return end
local node = tree[ind + Explorer.Index]
if not node then return end
local entry = listEntries[ind]
if button == 1 then
if combo == 2 then
if node.Obj:IsA("LuaSourceContainer") then
ScriptViewer.ViewScript(node.Obj)
elseif #node > 0 and expanded[node] ~= 0 then
expanded[node] = not expanded[node]
Explorer.Update()
end
end
if Properties.SelectObject(node.Obj) then
sys.IsRenaming = false
return
end
sys.IsRenaming = selection.Map[node]
if Lib.IsShiftDown() then
if not selection.Piviot then return end
local fromIndex = table.find(tree, selection.Piviot)
local toIndex = table.find(tree, node)
if not fromIndex or not toIndex then return end
fromIndex, toIndex = math.min(fromIndex, toIndex), math.max(fromIndex, toIndex)
local sList = selection.List
for i = #sList, 1, -1 do
local elem = sList[i]
if selection.ShiftSet[elem] then
selection.Map[elem] = nil
table.remove(sList, i)
end
end
selection.ShiftSet = {}
for i = fromIndex, toIndex do
local elem = tree[i]
if not selection.Map[elem] then
selection.ShiftSet[elem] = true
selection.Map[elem] = true
sList[#sList + 1] = elem
end
end
selection.Changed:Fire()
elseif Lib.IsCtrlDown() then
selection.ShiftSet = {}
if selection.Map[node] then selection:Remove(node) else selection:Add(node) end
selection.Piviot = node
sys.IsRenaming = false
elseif not selection.Map[node] then
selection.ShiftSet = {}
selection:Set(node)
selection.Piviot = node
end
elseif button == 2 then
if Properties.SelectObject(node.Obj) then
return
end
if not Lib.IsCtrlDown() and not selection.Map[node] then
selection.ShiftSet = {}
selection:Set(node)
selection.Piviot = node
Explorer.Refresh()
end
end
Explorer.Refresh()
end)
sys.OnRelease:Connect(function(item, combo, button)
local ind = table.find(listEntries, item)
if not ind then return end
local node = tree[ind + Explorer.Index]
if not node then return end
if button == 1 then
if selection.Map[node] and not Lib.IsShiftDown() and not Lib.IsCtrlDown() then
selection.ShiftSet = {}
selection:Set(node)
selection.Piviot = node
Explorer.Refresh()
end
local id = sys.ClickId
Lib.FastWait(sys.ComboTime)
if combo == 1 and id == sys.ClickId and sys.IsRenaming and selection.Map[node] then
Explorer.SetRenamingNode(node)
end
elseif button == 2 then
Explorer.ShowRightClick()
end
end)
Explorer.ClickSystem = sys
end
Explorer.InitDelCleaner = function()
coroutine.wrap(function()
local fw = Lib.FastWait
while true do
local processed = false
local c = 0
for _, node in next, nodes do
if node.HasDel then
local delInd
for i = 1, #node do
if node[i].Del then
delInd = i
break
end
end
if delInd then
for i = delInd + 1, #node do
local cn = node[i]
if not cn.Del then
node[delInd] = cn
delInd = delInd + 1
end
end
for i = delInd, #node do
node[i] = nil
end
end
node.HasDel = false
processed = true
fw()
end
c = c + 1
if c > 10000 then
c = 0
fw()
end
end
if processed and not refreshDebounce then Explorer.PerformRefresh() end
fw(0.5)
end
end)()
end
Explorer.UpdateSelectionVisuals = function()
local holder = Explorer.SelectionVisualsHolder
local isa = game["Run Service"].Parent.IsA
local clone = game["Run Service"].Parent.Clone
if not holder then
holder = Instance.new("ScreenGui")
holder.Name = "ExplorerSelections"
holder.DisplayOrder = Main.DisplayOrders.Core
Lib.ShowGui(holder)
Explorer.SelectionVisualsHolder = holder
Explorer.SelectionVisualCons = {}
local guiTemplate = create({
{ 1, "Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Size = UDim2.new(0, 100, 0, 100), } },
{ 2, "Frame", { BackgroundColor3 = Color3.new(0.04313725605607, 0.35294118523598, 0.68627452850342), BorderSizePixel = 0, Parent = { 1 }, Position = UDim2.new(0, -1, 0, -1), Size = UDim2.new(1, 2, 0, 1), } },
{ 3, "Frame", { BackgroundColor3 = Color3.new(0.04313725605607, 0.35294118523598, 0.68627452850342), BorderSizePixel = 0, Parent = { 1 }, Position = UDim2.new(0, -1, 1, 0), Size = UDim2.new(1, 2, 0, 1), } },
{ 4, "Frame", { BackgroundColor3 = Color3.new(0.04313725605607, 0.35294118523598, 0.68627452850342), BorderSizePixel = 0, Parent = { 1 }, Position = UDim2.new(0, -1, 0, 0), Size = UDim2.new(0, 1, 1, 0), } },
{ 5, "Frame", { BackgroundColor3 = Color3.new(0.04313725605607, 0.35294118523598, 0.68627452850342), BorderSizePixel = 0, Parent = { 1 }, Position = UDim2.new(1, 0, 0, 0), Size = UDim2.new(0, 1, 1, 0), } },
})
Explorer.SelectionVisualGui = guiTemplate
local boxTemplate = Instance.new("SelectionBox")
boxTemplate.LineThickness = 0.03
boxTemplate.Color3 = Color3.fromRGB(0, 170, 255)
Explorer.SelectionVisualBox = boxTemplate
end
holder:ClearAllChildren()
-- Updates theme
for i, v in pairs(Explorer.SelectionVisualGui:GetChildren()) do
v.BackgroundColor3 = Color3.fromRGB(0, 170, 255)
end
local attachCons = Explorer.SelectionVisualCons
for i = 1, #attachCons do
attachCons[i].Destroy()
end
table.clear(attachCons)
local partEnabled = Settings.Explorer.PartSelectionBox
local guiEnabled = Settings.Explorer.GuiSelectionBox
if not partEnabled and not guiEnabled then return end
local svg = Explorer.SelectionVisualGui
local svb = Explorer.SelectionVisualBox
local attachTo = Lib.AttachTo
local sList = selection.List
local count = 1
local boxCount = 0
local workspaceNode = nodes[workspace]
for i = 1, #sList do
if boxCount > 1000 then break end
local node = sList[i]
local obj = node.Obj
if node ~= workspaceNode then
if isa(obj, "GuiObject") and guiEnabled then
local newVisual = clone(svg)
attachCons[count] = attachTo(newVisual, { Target = obj, Resize = true })
count = count + 1
newVisual.Parent = holder
boxCount = boxCount + 1
elseif isa(obj, "PVInstance") and partEnabled then
local newBox = clone(svb)
newBox.Adornee = obj
newBox.Parent = holder
boxCount = boxCount + 1
end
end
end
end
Explorer.Init = function()
Explorer.ClassIcons = Lib.IconMap.newLinear("rbxasset://textures/ClassImages.png", 16, 16)
Explorer.MiscIcons = Main.MiscIcons
clipboard = {}
selection = Lib.Set.new()
selection.ShiftSet = {}
selection.Changed:Connect(Properties.ShowExplorerProps)
Explorer.Selection = selection
Explorer.InitRightClick()
Explorer.InitInsertObject()
Explorer.SetSortingEnabled(Settings.Explorer.Sorting)
Explorer.Expanded = setmetatable({}, { __mode = "k" })
Explorer.SearchExpanded = setmetatable({}, { __mode = "k" })
expanded = Explorer.Expanded
nilNode.Obj.Name = "Nil Instances"
nilNode.Locked = true
local explorerItems = create({
{ 1, "Folder", { Name = "ExplorerItems", } },
{ 2, "Frame", { BackgroundColor3 = Color3.fromRGB(30, 30, 30), BorderSizePixel = 0, Name = "ToolBar", Parent = { 1 }, Size = UDim2.new(1, 0, 0, 32), } },
{ 3, "Frame", { BackgroundColor3 = Color3.fromRGB(20, 20, 20), BorderSizePixel = 0, Name = "SearchFrame", Parent = { 2 }, Position = UDim2.new(0, 6, 0, 4), Size = UDim2.new(1, -12, 0, 24), } },
{ 4, "TextBox", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, ClearTextOnFocus = false, Font = Enum.Font.Gotham, Name = "SearchBox", Parent = { 3 }, PlaceholderColor3 = Color3.fromRGB(100, 100, 100), PlaceholderText = "Search explorer...", Position = UDim2.new(0, 6, 0, 0), Size = UDim2.new(1, -30, 0, 24), Text = "", TextColor3 = Color3.new(1, 1, 1), TextSize = 13, TextXAlignment = 0, } },
{ 5, "UICorner", { CornerRadius = UDim.new(0, 6), Parent = { 3 }, } },
{ 6, "TextButton", { AutoButtonColor = false, BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, BorderSizePixel = 0, Font = 3, Name = "Reset", Parent = { 3 }, Position = UDim2.new(1, -22, 0, 4), Size = UDim2.new(0, 16, 0, 16), Text = "", TextColor3 = Color3.new(1, 1, 1), TextSize = 14, } },
{ 7, "ImageLabel", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Image = "rbxassetid://5034718129", ImageColor3 = Color3.fromRGB(150, 150, 150), Parent = { 6 }, Size = UDim2.new(0, 16, 0, 16), } },
{ 8, "TextButton", { AutoButtonColor = false, BackgroundColor3 = Color3.new(0.12549020349979, 0.12549020349979, 0.12549020349979), BackgroundTransparency = 1, BorderSizePixel = 0, Font = 3, Name = "Refresh", Parent = { 2 }, Position = UDim2.new(1, -20, 0, 1), Size = UDim2.new(0, 18, 0, 18), Text = "", TextColor3 = Color3.new(1, 1, 1), TextSize = 14, Visible = false, } },
{ 9, "ImageLabel", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, Image = "rbxassetid://5642310344", Parent = { 8 }, Position = UDim2.new(0, 3, 0, 3), Size = UDim2.new(0, 12, 0, 12), } },
{ 10, "Frame", { BackgroundColor3 = Color3.fromRGB(20, 20, 20), BorderSizePixel = 0, Name = "ScrollCorner", Parent = { 1 }, Position = UDim2.new(1, -16, 1, -16), Size = UDim2.new(0, 16, 0, 16), Visible = false, } },
{ 11, "Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 1, ClipsDescendants = true, Name = "List", Parent = { 1 }, Position = UDim2.new(0, 0, 0, 32), Size = UDim2.new(1, 0, 1, -32), } },
})
toolBar = explorerItems.ToolBar
treeFrame = explorerItems.List
Explorer.GuiElems.ToolBar = toolBar
Explorer.GuiElems.TreeFrame = treeFrame
scrollV = Lib.ScrollBar.new()
scrollV.WheelIncrement = 3
scrollV.Gui.Position = UDim2.new(1, -16, 0, 23)
scrollV:SetScrollFrame(treeFrame)
scrollV.Scrolled:Connect(function()
Explorer.Index = scrollV.Index
Explorer.Refresh()
end)
scrollH = Lib.ScrollBar.new(true)
scrollH.Increment = 5
scrollH.WheelIncrement = Explorer.EntryIndent
scrollH.Gui.Position = UDim2.new(0, 0, 1, -16)
scrollH.Gui.ZIndex = 5
scrollH.Scrolled:Connect(function()
Explorer.Refresh()
end)
local window = Lib.Window.new()
Explorer.Window = window
window:SetTitle("Explorer")
window.GuiElems.Line.Position = UDim2.new(0, 0, 0, 32)
Explorer.InitEntryTemplate()
toolBar.Parent = window.GuiElems.Content
treeFrame.Parent = window.GuiElems.Content
explorerItems.ScrollCorner.Parent = window.GuiElems.Content
scrollV.Gui.Parent = window.GuiElems.Content
scrollH.Gui.Parent = window.GuiElems.Content
-- Init stuff that requires the window
Explorer.InitRenameBox()
Explorer.InitSearch()
Explorer.InitDelCleaner()
selection.Changed:Connect(Explorer.UpdateSelectionVisuals)
-- Window events
window.GuiElems.Main:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
if Explorer.Active then
Explorer.UpdateView()
Explorer.Refresh()
end
end)
window.OnActivate:Connect(function()
Explorer.Active = true
Explorer.UpdateView()
Explorer.Update()
Explorer.Refresh()
end)
window.OnRestore:Connect(function()
Explorer.Active = true
Explorer.UpdateView()
Explorer.Update()
Explorer.Refresh()
end)
window.OnDeactivate:Connect(function() Explorer.Active = false end)
window.OnMinimize:Connect(function() Explorer.Active = false end)
-- Settings
autoUpdateSearch = Settings.Explorer.AutoUpdateSearch
-- Fill in nodes
nodes[game["Run Service"].Parent] = { Obj = game["Run Service"].Parent }
expanded[nodes[game["Run Service"].Parent]] = true
-- Nil Instances
if env.getnilinstances then
nodes[nilNode.Obj] = nilNode
end
Explorer.SetupConnections()
local insts = getDescendants(game["Run Service"].Parent)
if Main.Elevated then
for i = 1, #insts do
local obj = insts[i]
local par = nodes[ffa(obj, "Instance")]
if not par then continue end
local newNode = {
Obj = obj,
Parent = par,
}
nodes[obj] = newNode
par[#par + 1] = newNode
end
else
for i = 1, #insts do
local obj = insts[i]
local s, parObj = pcall(ffa, obj, "Instance")
local par = nodes[parObj]
if not par then continue end
local newNode = {
Obj = obj,
Parent = par,
}
nodes[obj] = newNode
par[#par + 1] = newNode
end
end
end
return Explorer
end
return { InitDeps = initDeps, InitAfterMain = initAfterMain, Main = main }
end,
Properties = function()
--[[
Properties App Module
The main properties interface
]]
-- Common Locals
local Main, Lib, Apps, Settings -- Main Containers
local Explorer, Properties, ScriptViewer, Notebook -- Major Apps
local API, RMD, env, service, plr, create, createSimple -- Main Locals
local function initDeps(data)
Main = data.Main
Lib = data.Lib
Apps = data.Apps
Settings = data.Settings
API = data.API
RMD = data.RMD
env = data.env
service = data.service
plr = data.plr
create = data.create
createSimple = data.createSimple
end
local function initAfterMain()
Explorer = Apps.Explorer
Properties = Apps.Properties
ScriptViewer = Apps.ScriptViewer
Notebook = Apps.Notebook
end
local function main()
local Properties = {}
local window, toolBar, propsFrame
local scrollV, scrollH
local categoryOrder
local props, viewList, expanded, indexableProps, propEntries, autoUpdateObjs = {}, {}, {}, {}, {}, {}
local inputBox, inputTextBox, inputProp
local checkboxes, propCons = {}, {}
local table, string = table, string
local getPropChangedSignal = game["Run Service"].Parent.GetPropertyChangedSignal
local getAttributeChangedSignal = game["Run Service"].Parent.GetAttributeChangedSignal
local isa = game["Run Service"].Parent.IsA
local getAttribute = game["Run Service"].Parent.GetAttribute
local setAttribute = game["Run Service"].Parent.SetAttribute
Properties.GuiElems = {}
Properties.Index = 0
Properties.ViewWidth = 0
Properties.MinInputWidth = 100
Properties.EntryIndent = 16
Properties.EntryOffset = 4
Properties.NameWidthCache = {}
Properties.SubPropCache = {}
Properties.ClassLists = {}
Properties.SearchText = ""
Properties.AddAttributeProp = { Category = "Attributes", Class = "", Name = "", SpecialRow = "AddAttribute", Tags = {} }
Properties.SoundPreviewProp = { Category = "Data", ValueType = { Name = "SoundPlayer" }, Class = "Sound", Name =
"Preview", Tags = {} }
Properties.IgnoreProps = {
["DataModel"] = {
["PrivateServerId"] = true,
["PrivateServerOwnerId"] = true,
["VIPServerId"] = true,
["VIPServerOwnerId"] = true
}
}
Properties.ExpandableTypes = {
["Vector2"] = true,
["Vector3"] = true,
["UDim"] = true,
["UDim2"] = true,
["CFrame"] = true,
["Rect"] = true,
["PhysicalProperties"] = true,
["Ray"] = true,
["NumberRange"] = true,
["Faces"] = true,
["Axes"] = true,
}
Properties.ExpandableProps = {
["Sound.SoundId"] = true
}
Properties.CollapsedCategories = {
["Surface Inputs"] = true,
["Surface"] = true
}
Properties.ConflictSubProps = {
["Vector2"] = { "X", "Y" },
["Vector3"] = { "X", "Y", "Z" },
["UDim"] = { "Scale", "Offset" },
["UDim2"] = { "X", "X.Scale", "X.Offset", "Y", "Y.Scale", "Y.Offset" },
["CFrame"] = { "Position", "Position.X", "Position.Y", "Position.Z",
"RightVector", "RightVector.X", "RightVector.Y", "RightVector.Z",
"UpVector", "UpVector.X", "UpVector.Y", "UpVector.Z",
"LookVector", "LookVector.X", "LookVector.Y", "LookVector.Z" },
["Rect"] = { "Min.X", "Min.Y", "Max.X", "Max.Y" },
["PhysicalProperties"] = { "Density", "Elasticity", "ElasticityWeight", "Friction", "FrictionWeight" },
["Ray"] = { "Origin", "Origin.X", "Origin.Y", "Origin.Z", "Direction", "Direction.X", "Direction.Y", "Direction.Z" },
["NumberRange"] = { "Min", "Max" },
["Faces"] = { "Back", "Bottom", "Front", "Left", "Right", "Top" },
["Axes"] = { "X", "Y", "Z" }
}
Properties.ConflictIgnore = {
["BasePart"] = {
["ResizableFaces"] = true
}
}
Properties.RoundableTypes = {
["float"] = true,
["double"] = true,
["Color3"] = true,
["UDim"] = true,
["UDim2"] = true,
["Vector2"] = true,
["Vector3"] = true,
["NumberRange"] = true,
["Rect"] = true,
["NumberSequence"] = true,
["ColorSequence"] = true,
["Ray"] = true,
["CFrame"] = true
}
Properties.TypeNameConvert = {
["number"] = "double",
["boolean"] = "bool"
}
Properties.ToNumberTypes = {
["int"] = true,
["int64"] = true,
["float"] = true,
["double"] = true
}
Properties.DefaultPropValue = {
string = "",
bool = false,
double = 0,
UDim = UDim.new(0, 0),
UDim2 = UDim2.new(0, 0, 0, 0),
BrickColor = BrickColor.new("Medium stone grey"),
Color3 = Color3.new(1, 1, 1),
Vector2 = Vector2.new(0, 0),
Vector3 = Vector3.new(0, 0, 0),
NumberSequence = NumberSequence.new(1),
ColorSequence = ColorSequence.new(Color3.new(1, 1, 1)),
NumberRange = NumberRange.new(0),
Rect = Rect.new(0, 0, 0, 0)
}
Properties.AllowedAttributeTypes = { "string", "boolean", "number", "UDim", "UDim2", "BrickColor", "Color3",
"Vector2", "Vector3", "NumberSequence", "ColorSequence", "NumberRange", "Rect" }
Properties.StringToValue = function(prop, str)
local typeData = prop.ValueType
local typeName = typeData.Name
if typeName == "string" or typeName == "Content" then
return str
elseif Properties.ToNumberTypes[typeName] then
return tonumber(str)
elseif typeName == "Vector2" then
local vals = str:split(",")
local x, y = tonumber(vals[1]), tonumber(vals[2])
if x and y and #vals >= 2 then return Vector2.new(x, y) end
elseif typeName == "Vector3" then
local vals = str:split(",")
local x, y, z = tonumber(vals[1]), tonumber(vals[2]), tonumber(vals[3])
if x and y and z and #vals >= 3 then return Vector3.new(x, y, z) end
elseif typeName == "UDim" then
local vals = str:split(",")
local scale, offset = tonumber(vals[1]), tonumber(vals[2])
if scale and offset and #vals >= 2 then return UDim.new(scale, offset) end
elseif typeName == "UDim2" then
local vals = str:gsub("[{}]", ""):split(",")
local xScale, xOffset, yScale, yOffset = tonumber(vals[1]), tonumber(vals[2]), tonumber(vals[3]),
tonumber(vals[4])
if xScale and xOffset and yScale and yOffset and #vals >= 4 then return UDim2.new(xScale, xOffset,
yScale, yOffset) end
elseif typeName == "CFrame" then
local vals = str:split(",")
local s, result = pcall(CFrame.new, unpack(vals))
if s and #vals >= 12 then return result end
elseif typeName == "Rect" then
local vals = str:split(",")
local s, result = pcall(Rect.new, unpack(vals))
if s and #vals >= 4 then return result end
elseif typeName == "Ray" then
local vals = str:gsub("[{}]", ""):split(",")
local s, origin = pcall(Vector3.new, unpack(vals, 1, 3))
local s2, direction = pcall(Vector3.new, unpack(vals, 4, 6))
if s and s2 and #vals >= 6 then return Ray.new(origin, direction) end
elseif typeName == "NumberRange" then
local vals = str:split(",")
local s, result = pcall(NumberRange.new, unpack(vals))
if s and #vals >= 1 then return result end
elseif typeName == "Color3" then
local vals = str:gsub("[{}]", ""):split(",")
local s, result = pcall(Color3.fromRGB, unpack(vals))
if s and #vals >= 3 then return result end
end
return nil
end
Properties.ValueToString = function(prop, val)
local typeData = prop.ValueType
local typeName = typeData.Name
if typeName == "Color3" then
return Lib.ColorToBytes(val)
elseif typeName == "NumberRange" then
return val.Min .. ", " .. val.Max
end
return tostring(val)
end
Properties.GetIndexableProps = function(obj, classData)
if not Main.Elevated then
if not pcall(function() return obj.ClassName end) then return nil end
end
local ignoreProps = Properties.IgnoreProps[classData.Name] or {}
local result = {}
local count = 1
local props = classData.Properties
for i = 1, #props do
local prop = props[i]
if not ignoreProps[prop.Name] then
local s = pcall(function() return obj[prop.Name] end)
if s then
result[count] = prop
count = count + 1
end
end
end
return result
end
Properties.FindFirstObjWhichIsA = function(class)
local classList = Properties.ClassLists[class] or {}
if classList and #classList > 0 then
return classList[1]
end
return nil
end
Properties.ComputeConflicts = function(p)
local maxConflictCheck = Settings.Properties.MaxConflictCheck
local sList = Explorer.Selection.List
local classLists = Properties.ClassLists
local stringSplit = string.split
local t_clear = table.clear
local conflictIgnore = Properties.ConflictIgnore
local conflictMap = {}
local propList = p and { p } or props
if p then
local gName = p.Class .. "." .. p.Name
autoUpdateObjs[gName] = nil
local subProps = Properties.ConflictSubProps[p.ValueType.Name] or {}
for i = 1, #subProps do
autoUpdateObjs[gName .. "." .. subProps[i]] = nil
end
else
table.clear(autoUpdateObjs)
end
if #sList > 0 then
for i = 1, #propList do
local prop = propList[i]
local propName, propClass = prop.Name, prop.Class
local typeData = prop.RootType or prop.ValueType
local typeName = typeData.Name
local attributeName = prop.AttributeName
local gName = propClass .. "." .. propName
local checked = 0
local subProps = Properties.ConflictSubProps[typeName] or {}
local subPropCount = #subProps
local toCheck = subPropCount + 1
local conflictsFound = 0
local indexNames = {}
local ignored = conflictIgnore[propClass] and conflictIgnore[propClass][propName]
local truthyCheck = (typeName == "PhysicalProperties")
local isAttribute = prop.IsAttribute
local isMultiType = prop.MultiType
t_clear(conflictMap)
if not isMultiType then
local firstVal, firstObj, firstSet
local classList = classLists[prop.Class] or {}
for c = 1, #classList do
local obj = classList[c]
if not firstSet then
if isAttribute then
firstVal = getAttribute(obj, attributeName)
if firstVal ~= nil then
firstObj = obj
firstSet = true
end
else
firstVal = obj[propName]
firstObj = obj
firstSet = true
end
if ignored then break end
else
local propVal, skip
if isAttribute then
propVal = getAttribute(obj, attributeName)
if propVal == nil then skip = true end
else
propVal = obj[propName]
end
if not skip then
if not conflictMap[1] then
if truthyCheck then
if (firstVal and true or false) ~= (propVal and true or false) then
conflictMap[1] = true
conflictsFound = conflictsFound + 1
end
elseif firstVal ~= propVal then
conflictMap[1] = true
conflictsFound = conflictsFound + 1
end
end
if subPropCount > 0 then
for sPropInd = 1, subPropCount do
local indexes = indexNames[sPropInd]
if not indexes then
indexes = stringSplit(subProps[sPropInd], ".")
indexNames[sPropInd] = indexes
end
local firstValSub = firstVal
local propValSub = propVal
for j = 1, #indexes do
if not firstValSub or not propValSub then break end -- PhysicalProperties
local indexName = indexes[j]
firstValSub = firstValSub[indexName]
propValSub = propValSub[indexName]
end
local mapInd = sPropInd + 1
if not conflictMap[mapInd] and firstValSub ~= propValSub then
conflictMap[mapInd] = true
conflictsFound = conflictsFound + 1
end
end
end
if conflictsFound == toCheck then break end
end
end
checked = checked + 1
if checked == maxConflictCheck then break end
end
if not conflictMap[1] then autoUpdateObjs[gName] = firstObj end
for sPropInd = 1, subPropCount do
if not conflictMap[sPropInd + 1] then
autoUpdateObjs[gName .. "." .. subProps[sPropInd]] = firstObj
end
end
end
end
end
if p then
Properties.Refresh()
end
end
-- Fetches the properties to be displayed based on the explorer selection
Settings.Properties.ShowAttributes = true -- im making it true anyway since its useful by default and people complain
Properties.ShowExplorerProps = function()
local maxConflictCheck = Settings.Properties.MaxConflictCheck
local sList = Explorer.Selection.List
local foundClasses = {}
local propCount = 1
local elevated = Main.Elevated
local showDeprecated, showHidden = Settings.Properties.ShowDeprecated, Settings.Properties.ShowHidden
local Classes = API.Classes
local classLists = {}
local lower = string.lower
local RMDCustomOrders = RMD.PropertyOrders
local getAttributes = game["Run Service"].Parent.GetAttributes
local maxAttrs = Settings.Properties.MaxAttributes
local showingAttrs = Settings.Properties.ShowAttributes
local foundAttrs = {}
local attrCount = 0
local typeof = typeof
local typeNameConvert = Properties.TypeNameConvert
table.clear(props)
for i = 1, #sList do
local node = sList[i]
local obj = node.Obj
local class = node.Class
if not class then
class = obj.ClassName
node.Class = class
end
local apiClass = Classes[class]
while apiClass do
local APIClassName = apiClass.Name
if not foundClasses[APIClassName] then
local apiProps = indexableProps[APIClassName]
if not apiProps then
apiProps = Properties.GetIndexableProps(obj, apiClass)
indexableProps[APIClassName] = apiProps
end
for i = 1, #apiProps do
local prop = apiProps[i]
local tags = prop.Tags
if (not tags.Deprecated or showDeprecated) and (not tags.Hidden or showHidden) then
props[propCount] = prop
propCount = propCount + 1
end
end
foundClasses[APIClassName] = true
end
local classList = classLists[APIClassName]
if not classList then
classList = {}
classLists[APIClassName] = classList
end
classList[#classList + 1] = obj
apiClass = apiClass.Superclass
end
if showingAttrs and attrCount < maxAttrs then
local attrs = getAttributes(obj)
for name, val in pairs(attrs) do
local typ = typeof(val)
if not foundAttrs[name] then
local category = (typ == "Instance" and "Class") or (typ == "EnumItem" and "Enum") or
"Other"
local valType = { Name = typeNameConvert[typ] or typ, Category = category }
local attrProp = { IsAttribute = true, Name = "ATTR_" .. name, AttributeName = name, DisplayName =
name, Class = "Instance", ValueType = valType, Category = "Attributes", Tags = {} }
props[propCount] = attrProp
propCount = propCount + 1
attrCount = attrCount + 1
foundAttrs[name] = { typ, attrProp }
if attrCount == maxAttrs then break end
elseif foundAttrs[name][1] ~= typ then
foundAttrs[name][2].MultiType = true
foundAttrs[name][2].Tags.ReadOnly = true
foundAttrs[name][2].ValueType = { Name = "string" }
end
end
end
end
table.sort(props, function(a, b)
if a.Category ~= b.Category then
return (categoryOrder[a.Category] or 9999) < (categoryOrder[b.Category] or 9999)
else
local aOrder = (RMDCustomOrders[a.Class] and RMDCustomOrders[a.Class][a.Name]) or 9999999
local bOrder = (RMDCustomOrders[b.Class] and RMDCustomOrders[b.Class][b.Name]) or 9999999
if aOrder ~= bOrder then
return aOrder < bOrder
else
return lower(a.Name) < lower(b.Name)
end
end
end)
-- Find conflicts and get auto-update instances
Properties.ClassLists = classLists
Properties.ComputeConflicts()
--warn("CONFLICT",tick()-start)
if #props > 0 then
props[#props + 1] = Properties.AddAttributeProp
end
Properties.Update()
Properties.Refresh()
end
Properties.UpdateView = function()
local maxEntries = math.ceil(propsFrame.AbsoluteSize.Y / 23)
local maxX = propsFrame.AbsoluteSize.X
local totalWidth = Properties.ViewWidth + Properties.MinInputWidth
scrollV.VisibleSpace = maxEntries
scrollV.TotalSpace = #viewList + 1
scrollH.VisibleSpace = maxX
scrollH.TotalSpace = totalWidth
scrollV.Gui.Visible = #viewList + 1 > maxEntries
scrollH.Gui.Visible = Settings.Properties.ScaleType == 0 and totalWidth > maxX
local oldSize = propsFrame.Size
propsFrame.Size = UDim2.new(1, (scrollV.Gui.Visible and -16 or 0), 1,
(scrollH.Gui.Visible and -39 or -23))
if oldSize ~= propsFrame.Size then
Properties.UpdateView()
else
scrollV:Update()
scrollH:Update()
if scrollV.Gui.Visible and scrollH.Gui.Visible then
scrollV.Gui.Size = UDim2.new(0, 16, 1, -39)
scrollH.Gui.Size = UDim2.new(1, -16, 0, 16)
Properties.Window.GuiElems.Content.ScrollCorner.Visible = true
else
scrollV.Gui.Size = UDim2.new(0, 16, 1, -23)
scrollH.Gui.Size = UDim2.new(1, 0, 0, 16)
Properties.Window.GuiElems.Content.ScrollCorner.Visible = false
end
Properties.Index = scrollV.Index
end
end
Properties.MakeSubProp = function(prop, subName, valueType, displayName)
local subProp = {}
for i, v in pairs(prop) do
subProp[i] = v
end
subProp.RootType = subProp.RootType or subProp.ValueType
subProp.ValueType = valueType
subProp.SubName = subProp.SubName and (subProp.SubName .. subName) or subName
subProp.DisplayName = displayName
return subProp
end
Properties.GetExpandedProps = function(prop) -- TODO: Optimize using table
local result = {}
local typeData = prop.ValueType
local typeName = typeData.Name
local makeSubProp = Properties.MakeSubProp
if typeName == "Vector2" then
result[1] = makeSubProp(prop, ".X", { Name = "float" })
result[2] = makeSubProp(prop, ".Y", { Name = "float" })
elseif typeName == "Vector3" then
result[1] = makeSubProp(prop, ".X", { Name = "float" })
result[2] = makeSubProp(prop, ".Y", { Name = "float" })
result[3] = makeSubProp(prop, ".Z", { Name = "float" })
elseif typeName == "CFrame" then
result[1] = makeSubProp(prop, ".Position", { Name = "Vector3" })
result[2] = makeSubProp(prop, ".RightVector", { Name = "Vector3" })
result[3] = makeSubProp(prop, ".UpVector", { Name = "Vector3" })
result[4] = makeSubProp(prop, ".LookVector", { Name = "Vector3" })
elseif typeName == "UDim" then
result[1] = makeSubProp(prop, ".Scale", { Name = "float" })
result[2] = makeSubProp(prop, ".Offset", { Name = "int" })
elseif typeName == "UDim2" then
result[1] = makeSubProp(prop, ".X", { Name = "UDim" })
result[2] = makeSubProp(prop, ".Y", { Name = "UDim" })
elseif typeName == "Rect" then
result[1] = makeSubProp(prop, ".Min.X", { Name = "float" }, "X0")
result[2] = makeSubProp(prop, ".Min.Y", { Name = "float" }, "Y0")
result[3] = makeSubProp(prop, ".Max.X", { Name = "float" }, "X1")
result[4] = makeSubProp(prop, ".Max.Y", { Name = "float" }, "Y1")
elseif typeName == "PhysicalProperties" then
result[1] = makeSubProp(prop, ".Density", { Name = "float" })
result[2] = makeSubProp(prop, ".Elasticity", { Name = "float" })
result[3] = makeSubProp(prop, ".ElasticityWeight", { Name = "float" })
result[4] = makeSubProp(prop, ".Friction", { Name = "float" })
result[5] = makeSubProp(prop, ".FrictionWeight", { Name = "float" })
elseif typeName == "Ray" then
result[1] = makeSubProp(prop, ".Origin", { Name = "Vector3" })
result[2] = makeSubProp(prop, ".Direction", { Name = "Vector3" })
elseif typeName == "NumberRange" then
result[1] = makeSubProp(prop, ".Min", { Name = "float" })
result[2] = makeSubProp(prop, ".Max", { Name = "float" })
elseif typeName == "Faces" then
result[1] = makeSubProp(prop, ".Back", { Name = "bool" })
result[2] = makeSubProp(prop, ".Bottom", { Name = "bool" })
result[3] = makeSubProp(prop, ".Front", { Name = "bool" })
result[4] = makeSubProp(prop, ".Left", { Name = "bool" })
result[5] = makeSubProp(prop, ".Right", { Name = "bool" })
result[6] = makeSubProp(prop, ".Top", { Name = "bool" })
elseif typeName == "Axes" then
result[1] = makeSubProp(prop, ".X", { Name = "bool" })
result[2] = makeSubProp(prop, ".Y", { Name = "bool" })
result[3] = makeSubProp(prop, ".Z", { Name = "bool" })
end
if prop.Name == "SoundId" and prop.Class == "Sound" then
result[1] = Properties.SoundPreviewProp
end
return result
end
Properties.Update = function()
table.clear(viewList)
local nameWidthCache = Properties.NameWidthCache
local lastCategory
local count = 1
local maxWidth, maxDepth = 0, 1
local textServ = service.TextService
local getTextSize = textServ.GetTextSize
local font = Enum.Font.SourceSans
local size = Vector2.new(math.huge, 20)
local stringSplit = string.split
local entryIndent = Properties.EntryIndent
local isFirstScaleType = Settings.Properties.ScaleType == 0
local find, lower = string.find, string.lower
local searchText = (#Properties.SearchText > 0 and lower(Properties.SearchText))
local function recur(props, depth)
for i = 1, #props do
local prop = props[i]
local propName = prop.Name
local subName = prop.SubName
local category = prop.Category
local visible
if searchText and depth == 1 then
if find(lower(propName), searchText, 1, true) then
visible = true
end
else
visible = true
end
if visible and lastCategory ~= category then
viewList[count] = { CategoryName = category }
count = count + 1
lastCategory = category
end
if (expanded["CAT_" .. category] and visible) or prop.SpecialRow then
if depth > 1 then
prop.Depth = depth
if depth > maxDepth then maxDepth = depth end
end
if isFirstScaleType then
local nameArr = subName and stringSplit(subName, ".")
local displayName = prop.DisplayName or (nameArr and nameArr[#nameArr]) or propName
local nameWidth = nameWidthCache[displayName]
if not nameWidth then
nameWidth = getTextSize(textServ, displayName, 14, font, size).X
nameWidthCache[displayName] = nameWidth
end
local totalWidth = nameWidth + entryIndent * depth
if totalWidth > maxWidth then
maxWidth = totalWidth
end
end
viewList[count] = prop
count = count + 1
local fullName = prop.Class .. "." .. prop.Name .. (prop.SubName or "")
if expanded[fullName] then
local nextDepth = depth + 1
local expandedProps = Properties.GetExpandedProps(prop)
if #expandedProps > 0 then
recur(expandedProps, nextDepth)
end
end
end
end
end
recur(props, 1)
inputProp = nil
Properties.ViewWidth = maxWidth + 9 + Properties.EntryOffset
Properties.UpdateView()
end
Properties.NewPropEntry = function(index)
local newEntry = Properties.EntryTemplate:Clone()
local nameFrame = newEntry.NameFrame
local valueFrame = newEntry.ValueFrame
local newCheckbox = Lib.Checkbox.new(1)
newCheckbox.Gui.Position = UDim2.new(0, 3, 0, 3)
newCheckbox.Gui.Parent = valueFrame
newCheckbox.OnInput:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
if prop.ValueType.Name == "PhysicalProperties" then
Properties.SetProp(prop, newCheckbox.Toggled and true or nil)
else
Properties.SetProp(prop, newCheckbox.Toggled)
end
end)
checkboxes[index] = newCheckbox
local iconFrame = Main.MiscIcons:GetLabel()
iconFrame.Position = UDim2.new(0, 2, 0, 3)
iconFrame.Parent = newEntry.ValueFrame.RightButton
newEntry.Position = UDim2.new(0, 0, 0, 23 * (index - 1))
nameFrame.Expand.InputBegan:Connect(function(input)
local prop = viewList[index + Properties.Index]
if not prop or input.UserInputType ~= Enum.UserInputType.MouseMovement then return end
local fullName = (prop.CategoryName and "CAT_" .. prop.CategoryName) or
prop.Class .. "." .. prop.Name .. (prop.SubName or "")
Main.MiscIcons:DisplayByKey(newEntry.NameFrame.Expand.Icon,
expanded[fullName] and "Collapse_Over" or "Expand_Over")
end)
nameFrame.Expand.InputEnded:Connect(function(input)
local prop = viewList[index + Properties.Index]
if not prop or input.UserInputType ~= Enum.UserInputType.MouseMovement then return end
local fullName = (prop.CategoryName and "CAT_" .. prop.CategoryName) or
prop.Class .. "." .. prop.Name .. (prop.SubName or "")
Main.MiscIcons:DisplayByKey(newEntry.NameFrame.Expand.Icon,
expanded[fullName] and "Collapse" or "Expand")
end)
nameFrame.Expand.MouseButton1Down:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
local fullName = (prop.CategoryName and "CAT_" .. prop.CategoryName) or
prop.Class .. "." .. prop.Name .. (prop.SubName or "")
if not prop.CategoryName and not Properties.ExpandableTypes[prop.ValueType and prop.ValueType.Name] and not Properties.ExpandableProps[fullName] then return end
expanded[fullName] = not expanded[fullName]
Properties.Update()
Properties.Refresh()
end)
nameFrame.PropName.InputBegan:Connect(function(input)
local prop = viewList[index + Properties.Index]
if not prop then return end
if input.UserInputType == Enum.UserInputType.MouseMovement and not nameFrame.PropName.TextFits then
local fullNameFrame = Properties.FullNameFrame
local nameArr = string.split(prop.Class .. "." .. prop.Name .. (prop.SubName or ""), ".")
local dispName = prop.DisplayName or nameArr[#nameArr]
local sizeX = service.TextService:GetTextSize(dispName, 14, Enum.Font.SourceSans,
Vector2.new(math.huge, 20)).X
fullNameFrame.TextLabel.Text = dispName
--fullNameFrame.Position = UDim2.new(0,Properties.EntryIndent*(prop.Depth or 1) + Properties.EntryOffset,0,23*(index-1))
fullNameFrame.Size = UDim2.new(0, sizeX + 4, 0, 22)
fullNameFrame.Visible = true
Properties.FullNameFrameIndex = index
Properties.FullNameFrameAttach.SetData(fullNameFrame, { Target = nameFrame })
Properties.FullNameFrameAttach.Enable()
end
end)
nameFrame.PropName.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement and Properties.FullNameFrameIndex == index then
Properties.FullNameFrame.Visible = false
Properties.FullNameFrameAttach.Disable()
end
end)
valueFrame.ValueBox.MouseButton1Down:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
Properties.SetInputProp(prop, index)
end)
valueFrame.ColorButton.MouseButton1Down:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
Properties.SetInputProp(prop, index, "color")
end)
valueFrame.RightButton.MouseButton1Click:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
local fullName = prop.Class .. "." .. prop.Name .. (prop.SubName or "")
local inputFullName = inputProp and (inputProp.Class .. "." .. inputProp.Name .. (inputProp.SubName or ""))
if fullName == inputFullName and inputProp.ValueType.Category == "Class" then
inputProp = nil
Properties.SetProp(prop, nil)
else
Properties.SetInputProp(prop, index, "right")
end
end)
nameFrame.ToggleAttributes.MouseButton1Click:Connect(function()
Settings.Properties.ShowAttributes = not Settings.Properties.ShowAttributes
Properties.ShowExplorerProps()
end)
newEntry.RowButton.MouseButton1Click:Connect(function()
Properties.DisplayAddAttributeWindow()
end)
newEntry.EditAttributeButton.MouseButton1Down:Connect(function()
local prop = viewList[index + Properties.Index]
if not prop then return end
Properties.DisplayAttributeContext(prop)
end)
valueFrame.SoundPreview.ControlButton.MouseButton1Click:Connect(function()
if Properties.PreviewSound and Properties.PreviewSound.Playing then
Properties.SetSoundPreview(false)
else
local soundObj = Properties.FindFirstObjWhichIsA("Sound")
if soundObj then Properties.SetSoundPreview(soundObj) end
end
end)
valueFrame.SoundPreview.InputBegan:Connect(function(input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
local releaseEvent, mouseEvent
releaseEvent = service.UserInputService.InputEnded:Connect(function(input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
releaseEvent:Disconnect()
mouseEvent:Disconnect()
end)
local timeLine = newEntry.ValueFrame.SoundPreview.TimeLine
local soundObj = Properties.FindFirstObjWhichIsA("Sound")
if soundObj then Properties.SetSoundPreview(soundObj, true) end
local function update(input)
local sound = Properties.PreviewSound
if not sound or sound.TimeLength == 0 then return end
local mouseX = input.Position.X
local timeLineSize = timeLine.AbsoluteSize
local relaX = mouseX - timeLine.AbsolutePosition.X
if timeLineSize.X <= 1 then return end
if relaX < 0 then relaX = 0 elseif relaX >= timeLineSize.X then relaX = timeLineSize.X - 1 end
local perc = (relaX / (timeLineSize.X - 1))
sound.TimePosition = perc * sound.TimeLength
timeLine.Slider.Position = UDim2.new(perc, -4, 0, -8)
end
update(input)
mouseEvent = service.UserInputService.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
update(input)
end
end)
end)
newEntry.Parent = propsFrame
return {
Gui = newEntry,
GuiElems = {
NameFrame = nameFrame,
ValueFrame = valueFrame,
PropName = nameFrame.PropName,
ValueBox = valueFrame.ValueBox,
Expand = nameFrame.Expand,
ColorButton = valueFrame.ColorButton,
ColorPreview = valueFrame.ColorButton.ColorPreview,
Gradient = valueFrame.ColorButton.ColorPreview.UIGradient,
EnumArrow = valueFrame.EnumArrow,
Checkbox = valueFrame.Checkbox,
RightButton = valueFrame.RightButton,
RightButtonIcon = iconFrame,
RowButton = newEntry.RowButton,
EditAttributeButton = newEntry.EditAttributeButton,
ToggleAttributes = nameFrame.ToggleAttributes,
SoundPreview = valueFrame.SoundPreview,
SoundPreviewSlider = valueFrame.SoundPreview.TimeLine.Slider
}
}
end
Properties.GetSoundPreviewEntry = function()
for i = 1, #viewList do
if viewList[i] == Properties.SoundPreviewProp then
return propEntries[i - Properties.Index]
end
end
end
Properties.SetSoundPreview = function(soundObj, noplay)
local sound = Properties.PreviewSound
if not sound then
sound = Instance.new("Sound")
sound.Name = "Preview"
sound.Paused:Connect(function()
local entry = Properties.GetSoundPreviewEntry()
if entry then Main.MiscIcons:DisplayByKey(entry.GuiElems.SoundPreview.ControlButton.Icon, "Play") end
end)
sound.Resumed:Connect(function() Properties.Refresh() end)
sound.Ended:Connect(function()
local entry = Properties.GetSoundPreviewEntry()
if entry then entry.GuiElems.SoundPreviewSlider.Position = UDim2.new(0, -4, 0, -8) end
Properties.Refresh()
end)
sound.Parent = window.Gui
Properties.PreviewSound = sound
end
if not soundObj then
sound:Pause()
else
local newId = sound.SoundId ~= soundObj.SoundId
sound.SoundId = soundObj.SoundId
sound.PlaybackSpeed = soundObj.PlaybackSpeed
sound.Volume = soundObj.Volume
if newId then sound.TimePosition = 0 end
if not noplay then sound:Resume() end
coroutine.wrap(function()
local previewTime = tick()
Properties.SoundPreviewTime = previewTime
while previewTime == Properties.SoundPreviewTime and sound.Playing do
local entry = Properties.GetSoundPreviewEntry()
if entry then
local tl = sound.TimeLength
local perc = sound.TimePosition / (tl == 0 and 1 or tl)
entry.GuiElems.SoundPreviewSlider.Position = UDim2.new(perc, -4, 0, -8)
end
Lib.FastWait()
end
end)()
Properties.Refresh()
end
end
Properties.DisplayAttributeContext = function(prop)
local context = Properties.AttributeContext
if not context then
context = Lib.ContextMenu.new()
context.Iconless = true
context.Width = 80
end
context:Clear()
context:Add({
Name = "Edit",
OnClick = function()
Properties.DisplayAddAttributeWindow(prop)
end
})
context:Add({
Name = "Delete",
OnClick = function()
Properties.SetProp(prop, nil, true)
Properties.ShowExplorerProps()
end
})
context:Show()
end
Properties.DisplayAddAttributeWindow = function(editAttr)
local win = Properties.AddAttributeWindow
if not win then
win = Lib.Window.new()
win.Alignable = false
win.Resizable = false
win:SetTitle("Add Attribute")
win:SetSize(200, 130)
local saveButton = Lib.Button.new()
local nameLabel = Lib.Label.new()
nameLabel.Text = "Name"
nameLabel.Position = UDim2.new(0, 30, 0, 10)
nameLabel.Size = UDim2.new(0, 40, 0, 20)
win:Add(nameLabel)
local nameBox = Lib.ViewportTextBox.new()
nameBox.Position = UDim2.new(0, 75, 0, 10)
nameBox.Size = UDim2.new(0, 120, 0, 20)
win:Add(nameBox, "NameBox")
nameBox.TextBox:GetPropertyChangedSignal("Text"):Connect(function()
saveButton:SetDisabled(#nameBox:GetText() == 0)
end)
local typeLabel = Lib.Label.new()
typeLabel.Text = "Type"
typeLabel.Position = UDim2.new(0, 30, 0, 40)
typeLabel.Size = UDim2.new(0, 40, 0, 20)
win:Add(typeLabel)
local typeChooser = Lib.DropDown.new()
typeChooser.CanBeEmpty = false
typeChooser.Position = UDim2.new(0, 75, 0, 40)
typeChooser.Size = UDim2.new(0, 120, 0, 20)
typeChooser:SetOptions(Properties.AllowedAttributeTypes)
win:Add(typeChooser, "TypeChooser")
local errorLabel = Lib.Label.new()
errorLabel.Text = ""
errorLabel.Position = UDim2.new(0, 5, 1, -45)
errorLabel.Size = UDim2.new(1, -10, 0, 20)
errorLabel.TextColor3 = Settings.Theme.Important
win.ErrorLabel = errorLabel
win:Add(errorLabel, "Error")
local cancelButton = Lib.Button.new()
cancelButton.Text = "Cancel"
cancelButton.Position = UDim2.new(1, -97, 1, -25)
cancelButton.Size = UDim2.new(0, 92, 0, 20)
cancelButton.OnClick:Connect(function()
win:Close()
end)
win:Add(cancelButton)
saveButton.Text = "Save"
saveButton.Position = UDim2.new(0, 5, 1, -25)
saveButton.Size = UDim2.new(0, 92, 0, 20)
saveButton.OnClick:Connect(function()
local name = nameBox:GetText()
if #name > 100 then
errorLabel.Text = "Error: Name over 100 chars"
return
elseif name:sub(1, 3) == "RBX" then
errorLabel.Text = "Error: Name begins with 'RBX'"
return
end
local typ = typeChooser.Selected
local valType = { Name = Properties.TypeNameConvert[typ] or typ, Category = "DataType" }
local attrProp = { IsAttribute = true, Name = "ATTR_" .. name, AttributeName = name, DisplayName =
name, Class = "Instance", ValueType = valType, Category = "Attributes", Tags = {} }
Settings.Properties.ShowAttributes = true
Properties.SetProp(attrProp, Properties.DefaultPropValue[valType.Name], true,
Properties.EditingAttribute)
Properties.ShowExplorerProps()
win:Close()
end)
win:Add(saveButton, "SaveButton")
Properties.AddAttributeWindow = win
end
Properties.EditingAttribute = editAttr
win:SetTitle(editAttr and "Edit Attribute " .. editAttr.AttributeName or "Add Attribute")
win.Elements.Error.Text = ""
win.Elements.NameBox:SetText("")
win.Elements.SaveButton:SetDisabled(true)
win.Elements.TypeChooser:SetSelected(1)
win:Show()
end
Properties.IsTextEditable = function(prop)
local typeData = prop.ValueType
local typeName = typeData.Name
return typeName ~= "bool" and typeData.Category ~= "Enum" and typeData.Category ~= "Class" and
typeName ~= "BrickColor"
end
Properties.DisplayEnumDropdown = function(entryIndex)
local context = Properties.EnumContext
if not context then
context = Lib.ContextMenu.new()
context.Iconless = true
context.MaxHeight = 200
context.ReverseYOffset = 22
Properties.EnumDropdown = context
end
if not inputProp or inputProp.ValueType.Category ~= "Enum" then return end
local prop = inputProp
local entry = propEntries[entryIndex]
local valueFrame = entry.GuiElems.ValueFrame
local enum = Enum[prop.ValueType.Name]
if not enum then return end
local sorted = {}
for name, enum in next, enum:GetEnumItems() do
sorted[#sorted + 1] = enum
end
table.sort(sorted, function(a, b) return a.Name < b.Name end)
context:Clear()
local function onClick(name)
if prop ~= inputProp then return end
local enumItem = enum[name]
inputProp = nil
Properties.SetProp(prop, enumItem)
end
for i = 1, #sorted do
local enumItem = sorted[i]
context:Add({ Name = enumItem.Name, OnClick = onClick })
end
context.Width = valueFrame.AbsoluteSize.X
context:Show(valueFrame.AbsolutePosition.X, valueFrame.AbsolutePosition.Y + 22)
end
Properties.DisplayBrickColorEditor = function(prop, entryIndex, col)
local editor = Properties.BrickColorEditor
if not editor then
editor = Lib.BrickColorPicker.new()
editor.Gui.DisplayOrder = Main.DisplayOrders.Menu
editor.ReverseYOffset = 22
editor.OnSelect:Connect(function(col)
if not editor.CurrentProp or editor.CurrentProp.ValueType.Name ~= "BrickColor" then return end
if editor.CurrentProp == inputProp then inputProp = nil end
Properties.SetProp(editor.CurrentProp, BrickColor.new(col))
end)
editor.OnMoreColors:Connect(function() -- TODO: Special Case BasePart.BrickColor to BasePart.Color
editor:Close()
local colProp
for i, v in pairs(API.Classes.BasePart.Properties) do
if v.Name == "Color" then
colProp = v
break
end
end
Properties.DisplayColorEditor(colProp, editor.SavedColor.Color)
end)
Properties.BrickColorEditor = editor
end
local entry = propEntries[entryIndex]
local valueFrame = entry.GuiElems.ValueFrame
editor.CurrentProp = prop
editor.SavedColor = col
if prop and prop.Class == "BasePart" and prop.Name == "BrickColor" then
editor:SetMoreColorsVisible(true)
else
editor:SetMoreColorsVisible(false)
end
editor:Show(valueFrame.AbsolutePosition.X, valueFrame.AbsolutePosition.Y + 22)
end
Properties.DisplayColorEditor = function(prop, col)
local editor = Properties.ColorEditor
if not editor then
editor = Lib.ColorPicker.new()
editor.OnSelect:Connect(function(col)
if not editor.CurrentProp then return end
local typeName = editor.CurrentProp.ValueType.Name
if typeName ~= "Color3" and typeName ~= "BrickColor" then return end
local colVal = (typeName == "Color3" and col or BrickColor.new(col))
if editor.CurrentProp == inputProp then inputProp = nil end
Properties.SetProp(editor.CurrentProp, colVal)
end)
Properties.ColorEditor = editor
end
editor.CurrentProp = prop
if col then
editor:SetColor(col)
else
local firstVal = Properties.GetFirstPropVal(prop)
if firstVal then editor:SetColor(firstVal) end
end
editor:Show()
end
Properties.DisplayNumberSequenceEditor = function(prop, seq)
local editor = Properties.NumberSequenceEditor
if not editor then
editor = Lib.NumberSequenceEditor.new()
editor.OnSelect:Connect(function(val)
if not editor.CurrentProp or editor.CurrentProp.ValueType.Name ~= "NumberSequence" then return end
if editor.CurrentProp == inputProp then inputProp = nil end
Properties.SetProp(editor.CurrentProp, val)
end)
Properties.NumberSequenceEditor = editor
end
editor.CurrentProp = prop
if seq then
editor:SetSequence(seq)
else
local firstVal = Properties.GetFirstPropVal(prop)
if firstVal then editor:SetSequence(firstVal) end
end
editor:Show()
end
Properties.DisplayColorSequenceEditor = function(prop, seq)
local editor = Properties.ColorSequenceEditor
if not editor then
editor = Lib.ColorSequenceEditor.new()
editor.OnSelect:Connect(function(val)
if not editor.CurrentProp or editor.CurrentProp.ValueType.Name ~= "ColorSequence" then return end
if editor.CurrentProp == inputProp then inputProp = nil end
Properties.SetProp(editor.CurrentProp, val)
end)
Properties.ColorSequenceEditor = editor
end
editor.CurrentProp = prop
if seq then
editor:SetSequence(seq)
else
local firstVal = Properties.GetFirstPropVal(prop)
if firstVal then editor:SetSequence(firstVal) end
end
editor:Show()
end
Properties.GetFirstPropVal = function(prop)
local first = Properties.FindFirstObjWhichIsA(prop.Class)
if first then
return Properties.GetPropVal(prop, first)
end
end
Properties.GetPropVal = function(prop, obj)
if prop.MultiType then return "<Multiple Types>" end
if not obj then return end
local propVal
if prop.IsAttribute then
propVal = getAttribute(obj, prop.AttributeName)
if propVal == nil then return nil end
local typ = typeof(propVal)
local currentType = Properties.TypeNameConvert[typ] or typ
if prop.RootType then
if prop.RootType.Name ~= currentType then
return nil
end
elseif prop.ValueType.Name ~= currentType then
return nil
end
else
propVal = obj[prop.Name]
end
if prop.SubName then
local indexes = string.split(prop.SubName, ".")
for i = 1, #indexes do
local indexName = indexes[i]
if #indexName > 0 and propVal then
propVal = propVal[indexName]
end
end
end
return propVal
end
Properties.SelectObject = function(obj)
if inputProp and inputProp.ValueType.Category == "Class" then
local prop = inputProp
inputProp = nil
if isa(obj, prop.ValueType.Name) then
Properties.SetProp(prop, obj)
else
Properties.Refresh()
end
return true
end
return false
end
Properties.DisplayProp = function(prop, entryIndex)
local propName = prop.Name
local typeData = prop.ValueType
local typeName = typeData.Name
local tags = prop.Tags
local gName = prop.Class .. "." .. prop.Name .. (prop.SubName or "")
local propObj = autoUpdateObjs[gName]
local entryData = propEntries[entryIndex]
local UDim2 = UDim2
local guiElems = entryData.GuiElems
local valueFrame = guiElems.ValueFrame
local valueBox = guiElems.ValueBox
local colorButton = guiElems.ColorButton
local colorPreview = guiElems.ColorPreview
local gradient = guiElems.Gradient
local enumArrow = guiElems.EnumArrow
local checkbox = guiElems.Checkbox
local rightButton = guiElems.RightButton
local soundPreview = guiElems.SoundPreview
local propVal = Properties.GetPropVal(prop, propObj)
local inputFullName = inputProp and (inputProp.Class .. "." .. inputProp.Name .. (inputProp.SubName or ""))
local offset = 4
local endOffset = 6
-- Offsetting the ValueBox for ValueType specific buttons
if (typeName == "Color3" or typeName == "BrickColor" or typeName == "ColorSequence") then
colorButton.Visible = true
enumArrow.Visible = false
if propVal then
gradient.Color = (typeName == "Color3" and ColorSequence.new(propVal)) or
(typeName == "BrickColor" and ColorSequence.new(propVal.Color)) or propVal
else
gradient.Color = ColorSequence.new(Color3.new(1, 1, 1))
end
colorPreview.BorderColor3 = (typeName == "ColorSequence" and Color3.new(1, 1, 1) or Color3.new(0, 0, 0))
offset = 22
endOffset = 24 + (typeName == "ColorSequence" and 20 or 0)
elseif typeData.Category == "Enum" then
colorButton.Visible = false
enumArrow.Visible = not prop.Tags.ReadOnly
endOffset = 22
elseif (gName == inputFullName and typeData.Category == "Class") or typeName == "NumberSequence" then
colorButton.Visible = false
enumArrow.Visible = false
endOffset = 26
else
colorButton.Visible = false
enumArrow.Visible = false
end
valueBox.Position = UDim2.new(0, offset, 0, 0)
valueBox.Size = UDim2.new(1, -endOffset, 1, 0)
-- Right button
if inputFullName == gName and typeData.Category == "Class" then
Main.MiscIcons:DisplayByKey(guiElems.RightButtonIcon, "Delete")
guiElems.RightButtonIcon.Visible = true
rightButton.Text = ""
rightButton.Visible = true
elseif typeName == "NumberSequence" or typeName == "ColorSequence" then
guiElems.RightButtonIcon.Visible = false
rightButton.Text = "..."
rightButton.Visible = true
else
rightButton.Visible = false
end
-- Displays the correct ValueBox for the ValueType, and sets it to the prop value
if typeName == "bool" or typeName == "PhysicalProperties" then
valueBox.Visible = false
checkbox.Visible = true
soundPreview.Visible = false
checkboxes[entryIndex].Disabled = tags.ReadOnly
if typeName == "PhysicalProperties" and autoUpdateObjs[gName] then
checkboxes[entryIndex]:SetState(propVal and true or false)
else
checkboxes[entryIndex]:SetState(propVal)
end
elseif typeName == "SoundPlayer" then
valueBox.Visible = false
checkbox.Visible = false
soundPreview.Visible = true
local playing = Properties.PreviewSound and Properties.PreviewSound.Playing
Main.MiscIcons:DisplayByKey(soundPreview.ControlButton.Icon, playing and "Pause" or "Play")
else
valueBox.Visible = true
checkbox.Visible = false
soundPreview.Visible = false
if propVal ~= nil then
if typeName == "Color3" then
valueBox.Text = "[" .. Lib.ColorToBytes(propVal) .. "]"
elseif typeData.Category == "Enum" then
valueBox.Text = propVal.Name
elseif Properties.RoundableTypes[typeName] and Settings.Properties.NumberRounding then
local rawStr = Properties.ValueToString(prop, propVal)
valueBox.Text = rawStr:gsub("-?%d+%.%d+", function(num)
return tostring(tonumber(("%." .. Settings.Properties.NumberRounding .. "f"):format(num)))
end)
else
valueBox.Text = Properties.ValueToString(prop, propVal)
end
else
valueBox.Text = ""
end
valueBox.TextColor3 = tags.ReadOnly and Settings.Theme.PlaceholderText or Settings.Theme.Text
end
end
Properties.Refresh = function()
local maxEntries = math.max(math.ceil((propsFrame.AbsoluteSize.Y) / 24), 0)
local maxX = propsFrame.AbsoluteSize.X
local valueWidth = math.max(Properties.MinInputWidth, maxX - Properties.ViewWidth)
local inputPropVisible = false
local isa = game["Run Service"].Parent.IsA
local UDim2 = UDim2
local stringSplit = string.split
local scaleType = Settings.Properties.ScaleType
-- Clear connections
for i = 1, #propCons do
propCons[i]:Disconnect()
end
table.clear(propCons)
-- Hide full name viewer
Properties.FullNameFrame.Visible = false
Properties.FullNameFrameAttach.Disable()
for i = 1, maxEntries do
local entryData = propEntries[i]
if not propEntries[i] then
entryData = Properties.NewPropEntry(i)
propEntries[i] = entryData
end
local entry = entryData.Gui
local guiElems = entryData.GuiElems
local nameFrame = guiElems.NameFrame
local propNameLabel = guiElems.PropName
local valueFrame = guiElems.ValueFrame
local expand = guiElems.Expand
local valueBox = guiElems.ValueBox
local propNameBox = guiElems.PropName
local rightButton = guiElems.RightButton
local editAttributeButton = guiElems.EditAttributeButton
local toggleAttributes = guiElems.ToggleAttributes
local prop = viewList[i + Properties.Index]
if prop then
local entryXOffset = (scaleType == 0 and scrollH.Index or 0)
entry.Visible = true
entry.Position = UDim2.new(0, -entryXOffset, 0, 24 * (i - 1))
entry.Size = UDim2.new(scaleType == 0 and 0 or 1,
scaleType == 0 and Properties.ViewWidth + valueWidth or 0, 0, 24)
if prop.SpecialRow then
if prop.SpecialRow == "AddAttribute" then
nameFrame.Visible = false
valueFrame.Visible = false
guiElems.RowButton.Visible = true
end
else
-- Revert special row stuff
nameFrame.Visible = true
guiElems.RowButton.Visible = false
local depth = Properties.EntryIndent * (prop.Depth or 1)
local leftOffset = depth + Properties.EntryOffset
nameFrame.Position = UDim2.new(0, leftOffset, 0, 0)
propNameLabel.Size = UDim2.new(1, -2 - (scaleType == 0 and 0 or 6), 1, 0)
local gName = (prop.CategoryName and "CAT_" .. prop.CategoryName) or
prop.Class .. "." .. prop.Name .. (prop.SubName or "")
if prop.CategoryName then
entry.BackgroundColor3 = Settings.Theme.Main1
valueFrame.Visible = false
propNameBox.Text = prop.CategoryName
propNameBox.Font = Enum.Font.GothamBold
expand.Visible = true
propNameBox.TextColor3 = Color3.fromRGB(240, 240, 240)
nameFrame.BackgroundTransparency = 1
nameFrame.Size = UDim2.new(1, 0, 1, 0)
editAttributeButton.Visible = false
local showingAttrs = Settings.Properties.ShowAttributes
toggleAttributes.Position = UDim2.new(1, -85 - leftOffset, 0, 0)
toggleAttributes.Text = (showingAttrs and "[Setting: ON]" or "[Setting: OFF]")
toggleAttributes.TextColor3 = Settings.Theme.Text
toggleAttributes.Visible = (prop.CategoryName == "Attributes")
else
local propName = prop.Name
local typeData = prop.ValueType
local typeName = typeData.Name
local tags = prop.Tags
local propObj = autoUpdateObjs[gName]
local attributeOffset = (prop.IsAttribute and 20 or 0)
editAttributeButton.Visible = (prop.IsAttribute and not prop.RootType)
toggleAttributes.Visible = false
-- Moving around the frames
if scaleType == 0 then
nameFrame.Size = UDim2.new(0, Properties.ViewWidth - leftOffset - 1, 1, 0)
valueFrame.Position = UDim2.new(0, Properties.ViewWidth, 0, 0)
valueFrame.Size = UDim2.new(0, valueWidth - attributeOffset, 1, 0)
else
nameFrame.Size = UDim2.new(0.5, -leftOffset - 1, 1, 0)
valueFrame.Position = UDim2.new(0.5, 0, 0, 0)
valueFrame.Size = UDim2.new(0.5, -attributeOffset, 1, 0)
end
local nameArr = stringSplit(gName, ".")
propNameBox.Text = prop.DisplayName or nameArr[#nameArr]
propNameBox.Font = Enum.Font.Gotham
entry.BackgroundColor3 = Color3.fromRGB(35, 35, 35)
valueFrame.Visible = true
expand.Visible = typeData.Category == "DataType" and Properties.ExpandableTypes
[typeName] or Properties.ExpandableProps[gName]
propNameBox.TextColor3 = tags.ReadOnly and Settings.Theme.PlaceholderText or
Settings.Theme.Text
-- Display property value
Properties.DisplayProp(prop, i)
if propObj then
if prop.IsAttribute then
propCons[#propCons + 1] = getAttributeChangedSignal(propObj, prop.AttributeName)
:Connect(function()
Properties.DisplayProp(prop, i)
end)
else
propCons[#propCons + 1] = getPropChangedSignal(propObj, propName):Connect(function()
Properties.DisplayProp(prop, i)
end)
end
end
-- Position and resize Input Box
local beforeVisible = valueBox.Visible
local inputFullName = inputProp and
(inputProp.Class .. "." .. inputProp.Name .. (inputProp.SubName or ""))
if gName == inputFullName then
nameFrame.BackgroundColor3 = Color3.fromRGB(0, 162, 255)
nameFrame.BackgroundTransparency = 0.5
if typeData.Category == "Class" or typeData.Category == "Enum" or typeName == "BrickColor" then
valueFrame.BackgroundColor3 = Color3.fromRGB(45, 45, 45)
valueFrame.BackgroundTransparency = 0
valueBox.Visible = true
else
inputPropVisible = true
local scale = (scaleType == 0 and 0 or 0.5)
local offset = (scaleType == 0 and Properties.ViewWidth - scrollH.Index or 0)
local endOffset = 0
if typeName == "Color3" or typeName == "ColorSequence" then
offset = offset + 22
end
if typeName == "NumberSequence" or typeName == "ColorSequence" then
endOffset = 20
end
inputBox.Position = UDim2.new(scale, offset, 0, 24 * (i - 1))
inputBox.Size = UDim2.new(1 - scale, -offset - endOffset - attributeOffset, 0, 24)
inputBox.Visible = true
valueBox.Visible = false
end
else
nameFrame.BackgroundColor3 = Settings.Theme.Main1
nameFrame.BackgroundTransparency = 1
valueFrame.BackgroundColor3 = Settings.Theme.Main1
valueFrame.BackgroundTransparency = 1
valueBox.Visible = beforeVisible
end
end
-- Expand
if prop.CategoryName or Properties.ExpandableTypes[prop.ValueType and prop.ValueType.Name] or Properties.ExpandableProps[gName] then
if Lib.CheckMouseInGui(expand) then
Main.MiscIcons:DisplayByKey(expand.Icon,
expanded[gName] and "Collapse_Over" or "Expand_Over")
else
Main.MiscIcons:DisplayByKey(expand.Icon, expanded[gName] and "Collapse" or "Expand")
end
expand.Visible = true
else
expand.Visible = false
end
end
entry.Visible = true
else
entry.Visible = false
end
end
if not inputPropVisible then
inputBox.Visible = false
end
for i = maxEntries + 1, #propEntries do
propEntries[i].Gui:Destroy()
propEntries[i] = nil
checkboxes[i] = nil
end
end
Properties.SetProp = function(prop, val, noupdate, prevAttribute)
local sList = Explorer.Selection.List
local propName = prop.Name
local subName = prop.SubName
local propClass = prop.Class
local typeData = prop.ValueType
local typeName = typeData.Name
local attributeName = prop.AttributeName
local rootTypeData = prop.RootType
local rootTypeName = rootTypeData and rootTypeData.Name
local fullName = prop.Class .. "." .. prop.Name .. (prop.SubName or "")
local Vector3 = Vector3
for i = 1, #sList do
local node = sList[i]
local obj = node.Obj
if isa(obj, propClass) then
pcall(function()
local setVal = val
local root
if prop.IsAttribute then
root = getAttribute(obj, attributeName)
else
root = obj[propName]
end
if prevAttribute then
if prevAttribute.ValueType.Name == typeName then
setVal = getAttribute(obj, prevAttribute.AttributeName) or setVal
end
setAttribute(obj, prevAttribute.AttributeName, nil)
end
if rootTypeName then
if rootTypeName == "Vector2" then
setVal = Vector2.new((subName == ".X" and setVal) or root.X,
(subName == ".Y" and setVal) or root.Y)
elseif rootTypeName == "Vector3" then
setVal = Vector3.new((subName == ".X" and setVal) or root.X,
(subName == ".Y" and setVal) or root.Y, (subName == ".Z" and setVal) or root.Z)
elseif rootTypeName == "UDim" then
setVal = UDim.new((subName == ".Scale" and setVal) or root.Scale,
(subName == ".Offset" and setVal) or root.Offset)
elseif rootTypeName == "UDim2" then
local rootX, rootY = root.X, root.Y
local X_UDim = (subName == ".X" and setVal) or
UDim.new((subName == ".X.Scale" and setVal) or rootX.Scale,
(subName == ".X.Offset" and setVal) or rootX.Offset)
local Y_UDim = (subName == ".Y" and setVal) or
UDim.new((subName == ".Y.Scale" and setVal) or rootY.Scale,
(subName == ".Y.Offset" and setVal) or rootY.Offset)
setVal = UDim2.new(X_UDim, Y_UDim)
elseif rootTypeName == "CFrame" then
local rootPos, rootRight, rootUp, rootLook = root.Position, root.RightVector,
root.UpVector, root.LookVector
local pos = (subName == ".Position" and setVal) or
Vector3.new((subName == ".Position.X" and setVal) or rootPos.X,
(subName == ".Position.Y" and setVal) or rootPos.Y,
(subName == ".Position.Z" and setVal) or rootPos.Z)
local rightV = (subName == ".RightVector" and setVal) or
Vector3.new((subName == ".RightVector.X" and setVal) or rootRight.X,
(subName == ".RightVector.Y" and setVal) or rootRight.Y,
(subName == ".RightVector.Z" and setVal) or rootRight.Z)
local upV = (subName == ".UpVector" and setVal) or
Vector3.new((subName == ".UpVector.X" and setVal) or rootUp.X,
(subName == ".UpVector.Y" and setVal) or rootUp.Y,
(subName == ".UpVector.Z" and setVal) or rootUp.Z)
local lookV = (subName == ".LookVector" and setVal) or
Vector3.new((subName == ".LookVector.X" and setVal) or rootLook.X,
(subName == ".RightVector.Y" and setVal) or rootLook.Y,
(subName == ".RightVector.Z" and setVal) or rootLook.Z)
setVal = CFrame.fromMatrix(pos, rightV, upV, -lookV)
elseif rootTypeName == "Rect" then
local rootMin, rootMax = root.Min, root.Max
local min = Vector2.new((subName == ".Min.X" and setVal) or rootMin.X,
(subName == ".Min.Y" and setVal) or rootMin.Y)
local max = Vector2.new((subName == ".Max.X" and setVal) or rootMax.X,
(subName == ".Max.Y" and setVal) or rootMax.Y)
setVal = Rect.new(min, max)
elseif rootTypeName == "PhysicalProperties" then
local rootProps = PhysicalProperties.new(obj.Material)
local density = (subName == ".Density" and setVal) or (root and root.Density) or
rootProps.Density
local friction = (subName == ".Friction" and setVal) or (root and root.Friction) or
rootProps.Friction
local elasticity = (subName == ".Elasticity" and setVal) or
(root and root.Elasticity) or rootProps.Elasticity
local frictionWeight = (subName == ".FrictionWeight" and setVal) or
(root and root.FrictionWeight) or rootProps.FrictionWeight
local elasticityWeight = (subName == ".ElasticityWeight" and setVal) or
(root and root.ElasticityWeight) or rootProps.ElasticityWeight
setVal = PhysicalProperties.new(density, friction, elasticity, frictionWeight,
elasticityWeight)
elseif rootTypeName == "Ray" then
local rootOrigin, rootDirection = root.Origin, root.Direction
local origin = (subName == ".Origin" and setVal) or
Vector3.new((subName == ".Origin.X" and setVal) or rootOrigin.X,
(subName == ".Origin.Y" and setVal) or rootOrigin.Y,
(subName == ".Origin.Z" and setVal) or rootOrigin.Z)
local direction = (subName == ".Direction" and setVal) or
Vector3.new((subName == ".Direction.X" and setVal) or rootDirection.X,
(subName == ".Direction.Y" and setVal) or rootDirection.Y,
(subName == ".Direction.Z" and setVal) or rootDirection.Z)
setVal = Ray.new(origin, direction)
elseif rootTypeName == "Faces" then
local faces = {}
local faceList = { "Back", "Bottom", "Front", "Left", "Right", "Top" }
for _, face in pairs(faceList) do
local val
if subName == "." .. face then
val = setVal
else
val = root[face]
end
if val then faces[#faces + 1] = Enum.NormalId[face] end
end
setVal = Faces.new(unpack(faces))
elseif rootTypeName == "Axes" then
local axes = {}
local axesList = { "X", "Y", "Z" }
for _, axe in pairs(axesList) do
local val
if subName == "." .. axe then
val = setVal
else
val = root[axe]
end
if val then axes[#axes + 1] = Enum.Axis[axe] end
end
setVal = Axes.new(unpack(axes))
elseif rootTypeName == "NumberRange" then
setVal = NumberRange.new(subName == ".Min" and setVal or root.Min,
subName == ".Max" and setVal or root.Max)
end
end
if typeName == "PhysicalProperties" and setVal then
setVal = root or PhysicalProperties.new(obj.Material)
end
if prop.IsAttribute then
setAttribute(obj, attributeName, setVal)
else
obj[propName] = setVal
end
end)
end
end
if not noupdate then
Properties.ComputeConflicts(prop)
I uploaded the code itself rather than the raw file because people's GitHub accounts can get banned or scripts can be deleted from the sites where they're hosted, so I wanted to upload it here.

Universal