409 lines
13 KiB
Lua
409 lines
13 KiB
Lua
local partStateModule = require(script.Parent.PartStateModule)
|
|
local saves = {}
|
|
local id_parts = {}
|
|
local parts_loaded
|
|
spawn(function()
|
|
game.ReplicatedStorage:WaitForChild("Parts")
|
|
local function added(object)
|
|
if object.Parent:IsA("Folder") then
|
|
local id = object:GetAttribute("PartId")
|
|
if id then id_parts[id] = object end
|
|
end
|
|
end
|
|
for _,object in pairs(_G.Storage.Parts:GetDescendants()) do
|
|
added(object)
|
|
end
|
|
_G.Storage.Parts.DescendantAdded:Connect(added)
|
|
wait(1)
|
|
parts_loaded = true
|
|
end)
|
|
do
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local isa = Instance.new("Part").IsA
|
|
local no_cframe = {}
|
|
local cframe_of = function(object)
|
|
if isa(object,"BasePart") then
|
|
return object.CFrame
|
|
elseif isa(object,"Model") then
|
|
return (object.PrimaryPart or no_cframe)["CFrame"]
|
|
end
|
|
end
|
|
local cframe_set = function(object,cframe)
|
|
if isa(object,"Model") then
|
|
object:SetPrimaryPartCFrame(cframe)
|
|
elseif isa(object,"BasePart") then
|
|
object.CFrame = cframe
|
|
end
|
|
end
|
|
local weld_is_build = {["BuiltWeldConstraint"] = true}
|
|
local insert = table.insert
|
|
local typeof = typeof
|
|
local is_table = {["table"]=true}
|
|
local id_module = _G.Methods.Ids
|
|
local colorPart = _G.Methods.colorPart
|
|
local ts = tostring
|
|
local tn = tonumber
|
|
local format = string.format
|
|
local concat = table.concat
|
|
local cframe_new = CFrame.new
|
|
local vector3_new = Vector3.new
|
|
local table_create = table.create
|
|
local form_triangle = partStateModule.form_triangle
|
|
local get_nodes = partStateModule.get_nodes
|
|
local apply_nodes = partStateModule.apply_nodes
|
|
local apply_shift = partStateModule.apply_shift
|
|
local apply_size = partStateModule.apply_size
|
|
local apply_material = partStateModule.apply_material
|
|
local apply_internal_welds = partStateModule.apply_internal_welds
|
|
local hexxie = require(script.Parent.HexxieModule)
|
|
local hd = hexxie.decode
|
|
local he = hexxie.encode
|
|
local hexxie_cframe_match = string.rep("(...........)",12)
|
|
local hexxie_position_match = string.rep("(...........)",3)
|
|
local default_cframe_format = string.rep("%s,",10).."%s"
|
|
local id_attach = {}
|
|
local compress_cframe_decode = function(s)
|
|
if not s then return nil end
|
|
local a,b,c,d,e,f,g,h,i,j,k,l = s:match(hexxie_cframe_match)
|
|
return cframe_new(hd(a),hd(b),hd(c),hd(d),hd(e),hd(f),hd(g),hd(h),hd(i),hd(j),hd(k),hd(l))
|
|
end
|
|
local compress_cframe_encode = function(c)
|
|
local a,b,c,d,e,f,g,h,i,j,k,l = c:GetComponents()
|
|
return concat({he(a),he(b),he(c),he(d),he(e),he(f),he(g),he(h),he(i),he(j),he(k),he(l)})
|
|
end
|
|
local compress_position_decode = function(s)
|
|
local a,b,c = s:match(hexxie_position_match)
|
|
return vector3_new(hd(a),hd(b),hd(c))
|
|
end
|
|
local compress_position_encode = function(v)
|
|
return concat({he(v.X),he(v.Y),he(v.Z)})
|
|
end
|
|
local position
|
|
local http = game:GetService("HttpService")
|
|
local http_json_encode = http.JSONEncode
|
|
local http_json_decode = http.JSONDecode
|
|
local json_decode = function(value)
|
|
return http_json_decode(http,value)
|
|
end
|
|
local json_encode = function(value)
|
|
return http_json_encode(http,value)
|
|
end
|
|
local ID_INDEX = 1
|
|
local CFRAMES_INDEX = 2
|
|
local EDIT_INDEX = 3
|
|
saves["4"] = {
|
|
decode = function(raw,options)
|
|
--print(raw)
|
|
repeat wait() until parts_loaded
|
|
local final = {errors = {}}
|
|
local function err(text)
|
|
warn(text)
|
|
table.insert(final.errors,text)
|
|
end
|
|
options = options or {}
|
|
local _compress_cframe_decode = compress_cframe_decode
|
|
local _compress_position_decode = compress_position_decode
|
|
if options.mirror then
|
|
_compress_cframe_decode = function(str)
|
|
local cf = compress_cframe_decode(str)
|
|
local x,y,z = cf:ToEulerAnglesYXZ()
|
|
return CFrame.fromEulerAnglesYXZ(x,-y,-z)+Vector3.new(cf.X,-cf.Y,cf.Z)
|
|
end
|
|
_compress_position_decode = function(str)
|
|
local pos = compress_position_decode(str)
|
|
return vector3_new(-pos.X,pos.Y,pos.Z)
|
|
end
|
|
end
|
|
local compressed = raw:match("\"compress\"%:true")
|
|
if compressed then raw = hexxie.rle_decode(raw) end
|
|
local data = _G.Http:JSONDecode(raw)
|
|
_G.Union(options,data.options or {})
|
|
local physics = options.physics
|
|
local template_cframe = Instance.new("CFrameValue")
|
|
local weld_template = Instance.new("WeldConstraint")
|
|
local built_weld_template = weld_template:Clone()
|
|
built_weld_template.Name = "BuiltWeldConstraint"
|
|
local model = Instance.new("Model")
|
|
final.model = model
|
|
local parts = {}
|
|
local editActions = {
|
|
c = function(edit,part)
|
|
local color = Color3.new(unpack(edit))
|
|
colorPart(part,color)
|
|
end,
|
|
h = function(edit,part)
|
|
local hinge = part:FindFirstChild("Hinge")
|
|
if hinge then hinge.CFrame = compress_cframe_decode(edit) end
|
|
end,
|
|
r = function(edit,part)
|
|
local slider = part:FindFirstChild("Slider")
|
|
if slider then slider.CFrame = compress_cframe_decode(edit) end
|
|
end,
|
|
m = function(edit,part)
|
|
if edit then apply_material(part,Enum.Material[edit]) end
|
|
end,
|
|
s = function(edit,part)
|
|
apply_size(part,Vector3.new(unpack(edit)))
|
|
end,
|
|
S = function(edit,part)
|
|
apply_shift(part,edit)
|
|
end,
|
|
}
|
|
local poly_to_internally_weld = {}
|
|
local data_cframes = data.cframes
|
|
local data_edits = data.edits
|
|
for id,object in pairs(data.objects) do
|
|
local template = id_parts[object[ID_INDEX]]
|
|
if not template then err("Missing part with id "..tostring(id)) continue end
|
|
local instance = template:Clone()
|
|
instance.Parent = model
|
|
parts[id] = instance
|
|
|
|
local nodes = {}
|
|
local transform_ids = object[CFRAMES_INDEX]
|
|
-- BEGIN PARITY 4.1 (CAN BE REMOVED LATER)
|
|
if typeof(transform_ids) ~= "table" then transform_ids = {transform_ids} end
|
|
-- END PARITY 4.1
|
|
if #transform_ids == 1 then
|
|
insert(nodes,compress_cframe_decode(data_cframes[transform_ids[1]]))
|
|
elseif #transform_ids == 2 then
|
|
insert(nodes,compress_cframe_decode(data_cframes[transform_ids[1]]))
|
|
insert(nodes,hd(data_cframes[transform_ids[2]]))
|
|
else
|
|
for _,id in ipairs(transform_ids) do
|
|
insert(nodes,compress_position_decode(data_cframes[id]))
|
|
end
|
|
insert(poly_to_internally_weld,instance)
|
|
end
|
|
apply_nodes(instance,nodes)
|
|
-- Edit part
|
|
if object[EDIT_INDEX] then
|
|
for _,edit_id in pairs(object[EDIT_INDEX]) do
|
|
local key,value = unpack(data_edits[edit_id]);
|
|
(editActions[key] or function(value,instance)
|
|
local edit = instance:FindFirstChild(key)
|
|
if not edit then
|
|
err(string.format("No key %s for %s",key,instance:GetFullName()))
|
|
else
|
|
if typeof(value) == "table" then value = _G.Http:JSONEncode(value) end
|
|
edit.Value = value
|
|
end
|
|
end)(value,instance)
|
|
end
|
|
end
|
|
-- Change anchoring
|
|
if not options.physics then
|
|
if instance:IsA("Model") then
|
|
instance.PrimaryPart.Anchored = true
|
|
else
|
|
instance.Anchored = true
|
|
end
|
|
end
|
|
end
|
|
if options.physics then
|
|
for _,weld in pairs(data.welds) do
|
|
local w = built_weld_template:Clone()
|
|
local at,bt = unpack(weld)
|
|
local ap = parts[at[1]]
|
|
local bp = parts[bt[1]]
|
|
w.Parent = ap
|
|
if ap:IsA("Model") then
|
|
if at[2] then
|
|
ap = ap:FindFirstChild(at[2]) or ap.PrimaryPart
|
|
else
|
|
ap = ap.PrimaryPart
|
|
err("Missing subpart reference for weld on model A")
|
|
end
|
|
end
|
|
if bp:IsA("Model") then
|
|
if bt[2] then
|
|
bp = bp:FindFirstChild(bt[2]) or bp.PrimaryPart
|
|
else
|
|
bp = bp.PrimaryPart
|
|
err("Missing subpart reference for weld on model B")
|
|
end
|
|
end
|
|
w.Part0 = ap
|
|
w.Part1 = bp
|
|
end
|
|
for _,poly in ipairs(poly_to_internally_weld) do
|
|
apply_internal_welds(poly)
|
|
end
|
|
else
|
|
for _,part in pairs(model:GetDescendants()) do
|
|
if part:IsA("BasePart") then part.CanCollide = false part.Anchored = true end
|
|
end
|
|
end
|
|
local root = parts[1]
|
|
if not root then error("Fatal: Missing root part; implying #parts = 0") end
|
|
if root:IsA("Model") then root = root.PrimaryPart end
|
|
model.PrimaryPart = root
|
|
final.model = model
|
|
return final
|
|
end,
|
|
encode = function(root,options)
|
|
repeat wait() until parts_loaded
|
|
local final = {errors = {}}
|
|
local function err(text)
|
|
warn(text)
|
|
table.insert(final.errors,text)
|
|
end
|
|
id_module.forEachConnectedObject(root,function(object)
|
|
if object:IsA("VehicleSeat") then root = object end
|
|
end)
|
|
final.root = root
|
|
|
|
if not options then options = {} end
|
|
|
|
local origin = cframe_of(root)
|
|
if not origin then err("Root CFrame could not be encoded, it is probably broken") return final end
|
|
|
|
local ids = {}
|
|
|
|
local cframes = {}
|
|
local edits = {}
|
|
local welds = {}
|
|
local objects = {}
|
|
local data = {
|
|
options = options,
|
|
objects = objects,
|
|
cframes = cframes,
|
|
edits = edits,
|
|
welds = welds
|
|
}
|
|
|
|
local should_optimise_cframe = not not options.optimise_cframe
|
|
local cframe_epsilon = options.cframe_epsilon or 0.1
|
|
local optimise_edit = {}
|
|
local optimise_cframe = {}
|
|
|
|
local function cframe_id(c)
|
|
local existing = optimise_cframe[c]
|
|
if not existing then
|
|
insert(cframes,c)
|
|
local len = #cframes
|
|
optimise_cframe[c] = len
|
|
return len
|
|
else
|
|
return existing
|
|
end
|
|
end
|
|
|
|
local function edit_id(key,value)
|
|
local str
|
|
if type(value) == "table" then
|
|
str = json_encode(value)
|
|
elseif type(value) ~="string" then
|
|
str = tostring(value)
|
|
else
|
|
str = value
|
|
end
|
|
local tab = optimise_edit[key]
|
|
if not tab then tab = {} optimise_edit[key] = tab end
|
|
if tab[str] then
|
|
return tab[str]
|
|
else
|
|
insert(edits,{key,value})
|
|
tab[str] = #edits
|
|
return #edits
|
|
end
|
|
end
|
|
|
|
local errors = {}
|
|
|
|
local index = 0
|
|
id_module.forEachConnectedObject(root,function(object)
|
|
index = index + 1
|
|
ids[object] = index
|
|
|
|
local id = object:GetAttribute("PartId") or object.Name
|
|
if not id then insert(errors,string.format("Part without an ID! %s",ts(object))) end
|
|
|
|
-- ENCODE TRANSFORM
|
|
local transform = get_nodes(object)
|
|
if #transform >= 3 then
|
|
for index,position in pairs(transform) do
|
|
transform[index] = cframe_id(compress_position_encode(origin:PointToObjectSpace(position)))
|
|
end
|
|
elseif #transform == 2 then
|
|
transform[1] = cframe_id(compress_cframe_encode(origin:ToObjectSpace(transform[1])))
|
|
transform[2] = cframe_id(he(transform[2]))
|
|
elseif #transform == 1 then
|
|
transform[1] = cframe_id(compress_cframe_encode(origin:ToObjectSpace(transform[1])))
|
|
end
|
|
|
|
-- ENCODE EDITS
|
|
local edits = {}
|
|
for index,edit in pairs(object:GetChildren()) do
|
|
if edit:HasTag("Edit") or edit:HasTag("EditHidden") then
|
|
if edit:HasTag("JSON") then
|
|
insert(edits,edit_id(edit.Name,json_decode(edit.Value)))
|
|
else
|
|
local value = edit.Value
|
|
if (edit:GetAttribute("Default") ~= value) then
|
|
insert(edits,edit_id(edit.Name,value))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ENCODE CANDID DISPLACEMENT
|
|
local hinge = object:FindFirstChild("Hinge")
|
|
if hinge then insert(edits,edit_id("h",compress_cframe_encode(origin:ToObjectSpace(hinge.CFrame)))) end
|
|
local slider = object:FindFirstChild("Slider")
|
|
if slider then insert(edits,edit_id("r",compress_cframe_encode(origin:ToObjectSpace(slider.CFrame)))) end
|
|
|
|
-- ENCODE PROPERTIES
|
|
local color = object:GetAttribute("Color") -- TODO HEXXIE OPTIMISE COLOUR AND SIZE
|
|
if color then insert(edits,edit_id("c",{color.R,color.G,color.B})) end
|
|
local material = object:GetAttribute("Material")
|
|
if material then insert(edits,edit_id("m",material)) end
|
|
local size = object:GetAttribute("Size")
|
|
if size then insert(edits,edit_id("s",{size.X,size.Y,size.Z})) end
|
|
local shift = object:GetAttribute("Shift")
|
|
if shift then insert(edits,edit_id("S",shift)) end
|
|
|
|
-- INSERT OBJECT
|
|
local data = {id,transform}
|
|
if #edits > 0 then insert(data,edits) end
|
|
|
|
insert(objects,data)
|
|
end)
|
|
|
|
-- ENCODE WELDS
|
|
for object,id in pairs(ids) do
|
|
for _,weld in pairs(object:GetDescendants()) do
|
|
if weld_is_build[weld.Name] then
|
|
if weld.Part0 and weld.Part1 then
|
|
local first = {ids[weld.Part0]}
|
|
if not first[1] then
|
|
first[1] = ids[weld.Part0.Parent]
|
|
first[2] = weld.Part0.Name
|
|
if not first[1] then err("There was a stray first weld! "..tostring(weld:GetFullName())) continue end
|
|
end
|
|
local second = {ids[weld.Part1]}
|
|
if not second[1] then
|
|
second[1] = ids[weld.Part1.Parent]
|
|
second[2] = weld.Part1.Name
|
|
if not second[1] then err("There was a stray second weld! "..tostring(weld:GetFullName())) continue end
|
|
end
|
|
insert(data.welds,{first,second})
|
|
else
|
|
err("There was a dead weld!"..tostring(weld:GetFullName()))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local json = _G.Http:JSONEncode(data)
|
|
local result = json
|
|
if options.compress then result=hexxie.rle_encode(json) end
|
|
local check = hexxie.rle_decode(result)
|
|
result = "v4save"..result
|
|
final.data = result
|
|
return final
|
|
end,
|
|
}
|
|
end
|
|
return saves |