mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
729 lines
18 KiB
Lua
729 lines
18 KiB
Lua
--[[
|
|
| This file was obtained through the combined efforts
|
|
| of Madbluntz & Plymouth Antiquarian Society.
|
|
|
|
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
|
| Maloy, DrPepper10 @ RIP, Atle!
|
|
|
|
|
| Visit for more: https://plymouth.thetwilightzone.ru/
|
|
--]]
|
|
|
|
local L = pace.LanguageString
|
|
|
|
-- load only when hovered above
|
|
local function add_expensive_submenu_load(pnl, callback)
|
|
local old = pnl.OnCursorEntered
|
|
pnl.OnCursorEntered = function(...)
|
|
callback()
|
|
pnl.OnCursorEntered = old
|
|
return old(...)
|
|
end
|
|
end
|
|
|
|
file.CreateDir("pac3")
|
|
file.CreateDir("pac3/__backup/")
|
|
file.CreateDir("pac3/__backup_save/")
|
|
|
|
function pace.SaveParts(name, prompt_name, override_part, overrideAsUsual)
|
|
if not name or prompt_name then
|
|
Derma_StringRequest(
|
|
L"save parts",
|
|
L"filename:",
|
|
prompt_name or pace.LastSaveName or "autoload",
|
|
|
|
function(name)
|
|
pace.LastSaveName = name
|
|
pace.SaveParts(name, nil, override_part, overrideAsUsual)
|
|
|
|
pace.RefreshFiles()
|
|
end
|
|
)
|
|
|
|
return
|
|
end
|
|
|
|
pac.dprint("saving parts %s", name)
|
|
|
|
local data = {}
|
|
|
|
if not overrideAsUsual then
|
|
if pace.use_current_part_for_saveload and pace.current_part:IsValid() then
|
|
override_part = pace.current_part
|
|
end
|
|
|
|
if override_part then
|
|
data = override_part:ToSaveTable()
|
|
end
|
|
elseif override_part then
|
|
table.insert(data, override_part:ToSaveTable())
|
|
override_part = nil
|
|
end
|
|
|
|
if #data == 0 then
|
|
for key, part in pairs(pac.GetLocalParts()) do
|
|
if not part:HasParent() and part:GetShowInEditor() then
|
|
table.insert(data, part:ToSaveTable())
|
|
end
|
|
end
|
|
end
|
|
|
|
data = hook.Run("pac_pace.SaveParts", data) or data
|
|
|
|
if not override_part and #file.Find("pac3/sessions/*", "DATA") > 0 and not name:find("/") then
|
|
pace.luadata.WriteFile("pac3/sessions/" .. name .. ".txt", data)
|
|
else
|
|
if file.Exists("pac3/" .. name .. ".txt", "DATA") then
|
|
local date = os.date("%y-%m-%d-%H_%M_%S")
|
|
local read = file.Read("pac3/" .. name .. ".txt", "DATA")
|
|
file.Write("pac3/__backup_save/" .. name .. "_" .. date .. ".txt", read)
|
|
|
|
local files, folders = file.Find("pac3/__backup_save/*", "DATA")
|
|
|
|
if #files > 30 then
|
|
local targetFiles = {}
|
|
|
|
for i, filename in ipairs(files) do
|
|
local time = file.Time("pac3/__backup_save/" .. filename, "DATA")
|
|
table.insert(targetFiles, {"pac3/__backup_save/" .. filename, time})
|
|
end
|
|
|
|
table.sort(targetFiles, function(a, b)
|
|
return a[2] > b[2]
|
|
end)
|
|
|
|
for i = 31, #files do
|
|
file.Delete(targetFiles[i][1])
|
|
end
|
|
end
|
|
end
|
|
|
|
pace.luadata.WriteFile("pac3/" .. name .. ".txt", data)
|
|
end
|
|
|
|
pace.Backup(data, name)
|
|
end
|
|
|
|
local last_backup
|
|
local maxBackups = CreateConVar("pac_backup_limit", "100", {FCVAR_ARCHIVE}, "Maximal amount of backups")
|
|
|
|
function pace.Backup(data, name)
|
|
name = name or ""
|
|
|
|
if not data then
|
|
data = {}
|
|
for key, part in pairs(pac.GetLocalParts()) do
|
|
if not part:HasParent() and part:GetShowInEditor() then
|
|
table.insert(data, part:ToSaveTable())
|
|
end
|
|
end
|
|
end
|
|
|
|
if #data > 0 then
|
|
|
|
local files, folders = file.Find("pac3/__backup/*", "DATA")
|
|
|
|
if #files > maxBackups:GetInt() then
|
|
local temp = {}
|
|
for key, name in pairs(files) do
|
|
local time = file.Time("pac3/__backup/" .. name, "DATA")
|
|
table.insert(temp, {path = "pac3/__backup/" .. name, time = time})
|
|
end
|
|
|
|
table.sort(temp, function(a, b)
|
|
return a.time > b.time
|
|
end)
|
|
|
|
for i = maxBackups:GetInt() + 1, #files do
|
|
file.Delete(temp[i].path, "DATA")
|
|
end
|
|
end
|
|
|
|
local date = os.date("%y-%m-%d-%H_%M_%S")
|
|
local str = pace.luadata.Encode(data)
|
|
|
|
if str ~= last_backup then
|
|
file.Write("pac3/__backup/" .. (name=="" and name or (name..'_')) .. date .. ".txt", str)
|
|
last_backup = str
|
|
end
|
|
end
|
|
end
|
|
|
|
function pace.LoadParts(name, clear, override_part)
|
|
if not name then
|
|
local frm = vgui.Create("DFrame")
|
|
frm:SetTitle(L"parts")
|
|
local pnl = pace.CreatePanel("browser", frm)
|
|
|
|
pnl.OnLoad = function(node)
|
|
pace.LoadParts(node.FileName, clear, override_part)
|
|
end
|
|
|
|
if #file.Find("pac3/sessions/*", "DATA") > 0 then
|
|
pnl:SetDir("sessions/")
|
|
else
|
|
pnl:SetDir("")
|
|
end
|
|
|
|
pnl:Dock(FILL)
|
|
|
|
frm:SetSize(300, 500)
|
|
frm:MakePopup()
|
|
frm:Center()
|
|
|
|
local btn = vgui.Create("DButton", frm)
|
|
btn:Dock(BOTTOM)
|
|
btn:SetText(L"load from url")
|
|
btn.DoClick = function()
|
|
Derma_StringRequest(
|
|
L"load part",
|
|
L"pastebin urls also work!",
|
|
"",
|
|
function(name)
|
|
pace.LoadParts(name, clear, override_part)
|
|
end
|
|
)
|
|
end
|
|
|
|
else
|
|
if hook.Run("PrePACLoadOutfit", name) == false then
|
|
return
|
|
end
|
|
|
|
pac.dprint("loading Parts %s", name)
|
|
|
|
if name:find("https?://") then
|
|
local function callback(str)
|
|
if string.find( str, "<!DOCTYPE html>" ) then
|
|
pace.MessagePrompt("Invalid URL, the website returned a HTML file. If you're using Github then use the RAW option.", "URL Failed", "OK")
|
|
return
|
|
end
|
|
|
|
local data, err = pace.luadata.Decode(str)
|
|
if not data then
|
|
local message = string.format("URL fail: %s : %s\n", name, err)
|
|
pace.MessagePrompt(message, "URL Failed", "OK")
|
|
return
|
|
end
|
|
|
|
pace.LoadPartsFromTable(data, clear, override_part)
|
|
end
|
|
|
|
pac.HTTPGet(name, callback, function(err)
|
|
pace.MessagePrompt(err, "HTTP Request Failed for " .. name, "OK")
|
|
end)
|
|
else
|
|
name = name:gsub("%.txt", "")
|
|
|
|
local data,err = pace.luadata.ReadFile("pac3/" .. name .. ".txt")
|
|
|
|
if name == "autoload" and (not data or not next(data)) then
|
|
local err
|
|
data,err = pace.luadata.ReadFile("pac3/sessions/" .. name .. ".txt",nil,true)
|
|
if not data then
|
|
if err then
|
|
ErrorNoHalt(("Autoload failed: %s\n"):format(err))
|
|
end
|
|
return
|
|
end
|
|
elseif not data then
|
|
ErrorNoHalt(("Decoding %s failed: %s\n"):format(name,err))
|
|
return
|
|
end
|
|
|
|
pace.LoadPartsFromTable(data, clear, override_part)
|
|
end
|
|
end
|
|
end
|
|
|
|
concommand.Add('pac_load_url', function(ply, cmd, args)
|
|
if not args[1] then return print('[PAC3] No URL specified') end
|
|
local url = args[1]:Trim()
|
|
if not url:find("https?://") then return print('[PAC3] Invalid URL specified') end
|
|
pac.Message('Loading specified URL')
|
|
if args[2] == nil then args[2] = '1' end
|
|
pace.LoadParts(url, tobool(args[2]))
|
|
end)
|
|
|
|
function pace.LoadPartsFromTable(data, clear, override_part)
|
|
if pace.use_current_part_for_saveload and pace.current_part:IsValid() then
|
|
override_part = pace.current_part
|
|
end
|
|
|
|
if clear then
|
|
pace.ClearParts()
|
|
pace.ClearUndo()
|
|
else
|
|
--pace.RecordUndoHistory()
|
|
end
|
|
|
|
local partsLoaded = {}
|
|
|
|
local copy_id = tostring(data)
|
|
|
|
if data.self then
|
|
local part
|
|
|
|
if override_part then
|
|
part = override_part
|
|
part:SetTable(data)
|
|
else
|
|
part = override_part or pac.CreatePart(data.self.ClassName, nil, data, pac.GetPartFromUniqueID(pac.Hash(pac.LocalPlayer), data.self.UniqueID):IsValid() and copy_id)
|
|
end
|
|
|
|
table.insert(partsLoaded, part)
|
|
else
|
|
data = pace.FixBadGrouping(data)
|
|
data = pace.FixUniqueIDs(data)
|
|
|
|
for key, tbl in pairs(data) do
|
|
local part = pac.CreatePart(tbl.self.ClassName, nil, tbl, pac.GetPartFromUniqueID(pac.Hash(pac.LocalPlayer), tbl.self.UniqueID):IsValid() and copy_id)
|
|
table.insert(partsLoaded, part)
|
|
end
|
|
end
|
|
|
|
pace.RefreshTree(true)
|
|
|
|
for i, part in ipairs(partsLoaded) do
|
|
part:CallRecursive('OnOutfitLoaded')
|
|
part:CallRecursive('PostApplyFixes')
|
|
end
|
|
|
|
pac.LocalPlayer.pac_fix_show_from_render = SysTime() + 1
|
|
|
|
pace.RecordUndoHistory()
|
|
end
|
|
|
|
local function add_files(tbl, dir)
|
|
local files, folders = file.Find("pac3/" .. dir .. "/*", "DATA")
|
|
|
|
if folders then
|
|
for key, folder in pairs(folders) do
|
|
if folder == "__backup" or folder == "objcache" or folder == "__animations" or folder == "__backup_save" then goto CONTINUE end
|
|
tbl[folder] = {}
|
|
add_files(tbl[folder], dir .. "/" .. folder)
|
|
::CONTINUE::
|
|
end
|
|
end
|
|
|
|
if files then
|
|
for i, name in pairs(files) do
|
|
if name:find("%.txt") then
|
|
local path = "pac3/" .. dir .. "/" .. name
|
|
|
|
if file.Exists(path, "DATA") then
|
|
local data = {}
|
|
data.Name = name:gsub("%.txt", "")
|
|
data.FileName = name
|
|
data.Size = string.NiceSize(file.Size(path, "DATA"))
|
|
local time = file.Time(path, "DATA")
|
|
data.LastModified = os.date("%m/%d/%Y %H:%M", time)
|
|
data.Time = file.Time(path, "DATA")
|
|
data.Path = path
|
|
data.RelativePath = (dir .. "/" .. data.Name):sub(2)
|
|
|
|
local dat,err=pace.luadata.ReadFile(path)
|
|
data.Content = dat
|
|
|
|
if dat then
|
|
table.insert(tbl, data)
|
|
else
|
|
pac.dprint(("Decoding %s failed: %s\n"):format(path,err))
|
|
chat.AddText(("Could not load: %s\n"):format(path))
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(tbl, function(a,b)
|
|
if a.Time and b.Time then
|
|
return a.Name < b.Name
|
|
end
|
|
|
|
return true
|
|
end)
|
|
end
|
|
|
|
function pace.GetSavedParts(dir)
|
|
if pace.CachedFiles then
|
|
return pace.CachedFiles
|
|
end
|
|
|
|
local out = {}
|
|
|
|
add_files(out, dir or "")
|
|
|
|
pace.CachedFiles = out
|
|
|
|
return out
|
|
end
|
|
|
|
local function populate_part(menu, part, override_part, clear)
|
|
local name = part.self.Name or ""
|
|
|
|
if name == "" then
|
|
name = part.self.ClassName .. " (no name)"
|
|
end
|
|
|
|
if #part.children > 0 then
|
|
local menu, pnl = menu:AddSubMenu(name, function()
|
|
pace.LoadPartsFromTable(part, nil, override_part)
|
|
end)
|
|
pnl:SetImage(part.self.Icon)
|
|
menu.GetDeleteSelf = function() return false end
|
|
local old = menu.Open
|
|
menu.Open = function(...)
|
|
if not menu.pac_opened then
|
|
for key, part in pairs(part.children) do
|
|
populate_part(menu, part, override_part, clear)
|
|
end
|
|
menu.pac_opened = true
|
|
end
|
|
|
|
return old(...)
|
|
end
|
|
else
|
|
menu:AddOption(name, function()
|
|
pace.LoadPartsFromTable(part, clear, override_part)
|
|
end):SetImage(part.self.Icon)
|
|
end
|
|
end
|
|
|
|
local function populate_parts(menu, tbl, override_part, clear)
|
|
for key, data in pairs(tbl) do
|
|
if not data.Path then
|
|
local menu, pnl = menu:AddSubMenu(key, function()end, data)
|
|
pnl:SetImage(pace.MiscIcons.load)
|
|
menu.GetDeleteSelf = function() return false end
|
|
local old = menu.Open
|
|
menu.Open = function(...)
|
|
if not menu.pac_opened then
|
|
populate_parts(menu, data, override_part, clear)
|
|
menu.pac_opened = true
|
|
end
|
|
|
|
return old(...)
|
|
end
|
|
else
|
|
local icon = pace.MiscIcons.outfit
|
|
local parts = data.Content
|
|
|
|
if parts.self then
|
|
icon = parts.self.Icon
|
|
parts = {parts}
|
|
end
|
|
|
|
local outfit, pnl = menu:AddSubMenu(data.Name, function()
|
|
pace.LoadParts(data.RelativePath, clear, override_part)
|
|
end)
|
|
pnl:SetImage(icon)
|
|
outfit.GetDeleteSelf = function() return false end
|
|
|
|
local old = outfit.Open
|
|
outfit.Open = function(...)
|
|
if not outfit.pac_opened then
|
|
for key, part in pairs(parts) do
|
|
populate_part(outfit, part, override_part, clear)
|
|
end
|
|
outfit.pac_opened = true
|
|
end
|
|
|
|
return old(...)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function pace.AddSavedPartsToMenu(menu, clear, override_part)
|
|
menu.GetDeleteSelf = function() return false end
|
|
|
|
menu:AddOption(L"load from url", function()
|
|
Derma_StringRequest(
|
|
L"load parts",
|
|
L"Some indirect urls from on pastebin, dropbox, github, etc are handled automatically. Pasting the outfit's file contents into the input field will also work.",
|
|
"",
|
|
|
|
function(name)
|
|
pace.LoadParts(name, clear, override_part)
|
|
end
|
|
)
|
|
end):SetImage(pace.MiscIcons.url)
|
|
|
|
menu:AddOption(L"load from clipboard", function()
|
|
pace.MultilineStringRequest(
|
|
L"load parts from clipboard",
|
|
L"Paste the outfits content here.",
|
|
"",
|
|
|
|
function(name)
|
|
local data,err = pace.luadata.Decode(name)
|
|
if data then
|
|
pace.LoadPartsFromTable(data, clear, override_part)
|
|
end
|
|
end
|
|
)
|
|
end):SetImage(pace.MiscIcons.paste)
|
|
|
|
if not override_part and pace.example_outfits then
|
|
local examples, pnl = menu:AddSubMenu(L"examples")
|
|
pnl:SetImage(pace.MiscIcons.help)
|
|
examples.GetDeleteSelf = function() return false end
|
|
|
|
local sorted = {}
|
|
for k,v in pairs(pace.example_outfits) do sorted[#sorted + 1] = {k = k, v = v} end
|
|
table.sort(sorted, function(a, b) return a.k < b.k end)
|
|
|
|
for _, data in pairs(sorted) do
|
|
examples:AddOption(data.k, function() pace.LoadPartsFromTable(data.v) end)
|
|
:SetImage(pace.MiscIcons.outfit)
|
|
end
|
|
end
|
|
|
|
menu:AddSpacer()
|
|
|
|
local tbl = pace.GetSavedParts()
|
|
populate_parts(menu, tbl, override_part, clear)
|
|
|
|
menu:AddSpacer()
|
|
|
|
local backups, pnl = menu:AddSubMenu(L"backups")
|
|
pnl:SetImage(pace.MiscIcons.clone)
|
|
backups.GetDeleteSelf = function() return false end
|
|
|
|
add_expensive_submenu_load(pnl, function()
|
|
local files = file.Find("pac3/__backup/*", "DATA")
|
|
local files2 = {}
|
|
|
|
for i, filename in ipairs(files) do
|
|
table.insert(files2, {filename, file.Time("pac3/__backup/" .. filename, "DATA")})
|
|
end
|
|
|
|
table.sort(files2, function(a, b)
|
|
return a[2] > b[2]
|
|
end)
|
|
|
|
for _, data in pairs(files2) do
|
|
local name = data[1]
|
|
local full_path = "pac3/__backup/" .. name
|
|
local friendly_name = os.date("%m/%d/%Y %H:%M:%S ", file.Time(full_path, "DATA")) .. string.NiceSize(file.Size(full_path, "DATA"))
|
|
backups:AddOption(friendly_name, function() pace.LoadParts("__backup/" .. name, true) end)
|
|
:SetImage(pace.MiscIcons.outfit)
|
|
end
|
|
end)
|
|
|
|
local backups, pnl = menu:AddSubMenu(L"outfit backups")
|
|
pnl:SetImage(pace.MiscIcons.clone)
|
|
backups.GetDeleteSelf = function() return false end
|
|
|
|
add_expensive_submenu_load(pnl, function()
|
|
local files = file.Find("pac3/__backup_save/*", "DATA")
|
|
local files2 = {}
|
|
|
|
for i, filename in ipairs(files) do
|
|
table.insert(files2, {filename, file.Time("pac3/__backup_save/" .. filename, "DATA")})
|
|
end
|
|
|
|
table.sort(files2, function(a, b)
|
|
return a[2] > b[2]
|
|
end)
|
|
|
|
for _, data in pairs(files2) do
|
|
local name = data[1]
|
|
local stamp = data[2]
|
|
local nicename = name
|
|
local date = os.date("_%y-%m-%d-%H_%M_%S", stamp)
|
|
|
|
if nicename:find(date, 1, true) then
|
|
nicename = nicename:Replace(date, os.date(" %m/%d/%Y %H:%M:%S", stamp))
|
|
end
|
|
|
|
backups:AddOption(nicename:Replace(".txt", "") .. " (" .. string.NiceSize(file.Size("pac3/__backup_save/" .. name, "DATA")) .. ")",
|
|
function()
|
|
pace.LoadParts("__backup_save/" .. name, true)
|
|
end)
|
|
:SetImage(pace.MiscIcons.outfit)
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function populate_parts(menu, tbl, dir, override_part)
|
|
dir = dir or ""
|
|
menu:AddOption(L"new file", function() pace.SaveParts(nil, dir .. "/", override_part) end)
|
|
:SetImage("icon16/page_add.png")
|
|
|
|
menu:AddOption(L"new directory", function()
|
|
Derma_StringRequest(
|
|
L"new directory",
|
|
L"name:",
|
|
"",
|
|
|
|
function(name)
|
|
file.CreateDir("pac3/" .. dir .. "/" .. name)
|
|
pace.RefreshFiles()
|
|
end
|
|
)
|
|
end)
|
|
:SetImage("icon16/folder_add.png")
|
|
|
|
menu:AddOption(L"to clipboard", function()
|
|
local data = {}
|
|
for key, part in pairs(pac.GetLocalParts()) do
|
|
if not part:HasParent() and part:GetShowInEditor() then
|
|
table.insert(data, part:ToSaveTable())
|
|
end
|
|
end
|
|
SetClipboardText(pace.luadata.Encode(data):sub(1, -1))
|
|
end)
|
|
:SetImage(pace.MiscIcons.copy)
|
|
|
|
menu:AddSpacer()
|
|
for key, data in pairs(tbl) do
|
|
if not data.Path then
|
|
local menu, pnl = menu:AddSubMenu(key, function()end, data)
|
|
pnl:SetImage(pace.MiscIcons.load)
|
|
menu.GetDeleteSelf = function() return false end
|
|
populate_parts(menu, data, dir .. "/" .. key, override_part)
|
|
else
|
|
local parts = data.Content
|
|
|
|
if parts[1] then
|
|
local menu, pnl = menu:AddSubMenu(data.Name, function() pace.SaveParts(nil, data.RelativePath, override_part) end)
|
|
menu.GetDeleteSelf = function() return false end
|
|
pnl:SetImage(pace.MiscIcons.outfit)
|
|
|
|
menu:AddOption(L"delete", function()
|
|
file.Delete("pac3/" .. data.RelativePath .. ".txt", "DATA")
|
|
pace.RefreshFiles()
|
|
end):SetImage(pace.MiscIcons.clear)
|
|
|
|
pnl:SetImage(pace.MiscIcons.outfit)
|
|
elseif parts.self then
|
|
menu:AddOption(data.Name, function() pace.SaveParts(nil, data.RelativePath, override_part) end)
|
|
:SetImage(parts.self.Icon)
|
|
end
|
|
end
|
|
end
|
|
|
|
if dir ~= "" then
|
|
menu:AddSpacer()
|
|
|
|
menu:AddOption(L"delete directory", function()
|
|
Derma_Query(
|
|
L"Are you sure you want to delete data/pac3" .. dir .. "/* and all its files?\nThis cannot be undone!",
|
|
L"delete directory",
|
|
|
|
L"yes", function()
|
|
local function delete_directory(dir)
|
|
local files, folders = file.Find(dir .. "*", "DATA")
|
|
|
|
for k,v in ipairs(files) do
|
|
file.Delete(dir .. v)
|
|
end
|
|
|
|
for k,v in ipairs(folders) do
|
|
delete_directory(dir .. v .. "/")
|
|
end
|
|
|
|
if file.Find(dir .. "*", "DATA")[1] then
|
|
Derma_Message("Cannot remove the directory.\nMaybe it contains hidden files?", "unable to remove directory", L"ok")
|
|
else
|
|
file.Delete(dir)
|
|
end
|
|
end
|
|
delete_directory("pac3/" .. dir .. "/")
|
|
pace.RefreshFiles()
|
|
end,
|
|
|
|
L"no", function()
|
|
|
|
end
|
|
)
|
|
end):SetImage("icon16/folder_delete.png")
|
|
end
|
|
end
|
|
|
|
function pace.AddSaveMenuToMenu(menu, override_part)
|
|
menu.GetDeleteSelf = function() return false end
|
|
|
|
if not override_part then
|
|
menu:AddOption(L"auto load (your spawn outfit)", function()
|
|
pace.SaveParts("autoload", nil, override_part)
|
|
pace.RefreshFiles()
|
|
end)
|
|
:SetImage(pace.MiscIcons.autoload)
|
|
menu:AddSpacer()
|
|
end
|
|
|
|
local tbl = pace.GetSavedParts()
|
|
populate_parts(menu, tbl, nil, override_part)
|
|
end
|
|
|
|
-- this fixes parts that are using the same uniqueid as other parts because of some bugs in older versions
|
|
function pace.FixUniqueIDs(data)
|
|
local ids = {}
|
|
|
|
local function iterate(part)
|
|
ids[part.self.UniqueID] = ids[part.self.UniqueID] or {}
|
|
|
|
table.insert(ids[part.self.UniqueID], part)
|
|
|
|
for key, part in pairs(part.children) do
|
|
iterate(part)
|
|
end
|
|
end
|
|
|
|
for key, part in pairs(data) do
|
|
iterate(part)
|
|
end
|
|
|
|
for key, val in pairs(ids) do
|
|
if #val > 1 then
|
|
for key, part in pairs(val) do
|
|
pac.dprint("Part (%s using model %s) named %q has %i other parts with the same unique id. Fixing!", part.self.ClassName, part.self.Name, part.self.Model or "", #val)
|
|
part.self.UniqueID = pac.Hash()
|
|
end
|
|
end
|
|
end
|
|
|
|
return data
|
|
end
|
|
|
|
-- this is for fixing parts that are not in a group
|
|
|
|
function pace.FixBadGrouping(data)
|
|
local parts = {}
|
|
local other = {}
|
|
|
|
for key, part in pairs(data) do
|
|
if part.self.ClassName ~= "group" then
|
|
table.insert(parts, part)
|
|
else
|
|
table.insert(other, part)
|
|
end
|
|
end
|
|
|
|
if #parts > 0 then
|
|
local out = {
|
|
{
|
|
["self"] = {
|
|
["EditorExpand"] = true,
|
|
["ClassName"] = "group",
|
|
["UniqueID"] = pac.Hash(),
|
|
["Name"] = "automatic group",
|
|
},
|
|
|
|
["children"] = parts,
|
|
},
|
|
}
|
|
|
|
for k,v in pairs(other) do
|
|
table.insert(out, v)
|
|
end
|
|
|
|
return out
|
|
end
|
|
|
|
return data
|
|
end
|