Files
PlaneBuilding/scripts/modules/SerialisationModule.lua
T

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