Files
PlaneBuilding/scripts/modules/SaveModule.lua
T

503 lines
15 KiB
Lua

-- 41458171
require(game.ReplicatedFirst.ReadyModule)("SaveModule")
local Commands = require(_G.Modules.CommandsModule)
Placement = require(_G.Modules.PlacementModule)
Debris = _G.Debris
SaveFrame = nil
Stroke = nil
SaveData = nil
SaveTabs = nil
SaveLists = nil
Templates = nil
PlayerSaveList = nil
SelectionBox = nil
parts = _G.Storage:WaitForChild("Parts")
SubassemblyData = ""
SubassemblyModel = nil
CurrentSaveData = ""
CurrentPlacementConfig = nil
CurrentSaveRoot = nil
LastInteractedPart = nil
SelectionBoxes = {}
local function OffloadSelection()
if CurrentPlacementConfig then CurrentPlacementConfig:stop() end
CurrentSaveRoot = nil
CurrentSaveData = nil
SelectionBox.Adornee = nil
for _,object in pairs(SelectionBoxes) do
object:Destroy()
end
SelectionBoxes = {}
end
local function LoadModel(key,group)
wait()
local raw = _G.Remotes.Load:InvokeServer(group,key)
local success,result = pcall(function()
return _G.Methods.saves.dataToModel(raw,false)
end)
local model
if success then
model = result
else
_G.Error(string.format("Load Model Error (%s:%s): %s",key,group,result))
end
if not model then
return _G.Storage.Missing:Clone()
end
local primary = model:FindFirstChildWhichIsA("VehicleSeat",true)
if primary then
model.PrimaryPart = primary
end
return model
end
local _tab = {}
_tab.__index = _tab
local _slot = {}
_slot.__index = _slot
_slot.__newindex_operations = {
name = function(this,name)
this.frame.NameBox.Text = name
end,
order = function(this,index)
this.frame.LayoutOrder = tonumber(index) or 0
this.frame.IdLabel.Text = tostring(index):upper()
if index == "autosave" then
this.frame.IdLabel.BackgroundColor3 = Color3.new(1, 0.768627, 0.223529)
elseif index == "subassembly" then
this.frame.IdLabel.BackgroundColor3 = Color3.new(0.623529, 0.0941176, 1)
end
end,
on_mirror_clicked = function(this,callback)
_G.Error("Mirror function unimplemented")
end,
on_name_change = function(this,callback)
this.frame.NameBox.TextEditable = true
this.frame.NameBox.FocusLost:Connect(function(enter)
if enter then callback(this.frame.NameBox.Text) end
end)
end,
on_save_clicked = function(this,callback)
this.frame.SaveButton.Visible = true
this.frame.SaveButton.Activated:Connect(function()
callback(CurrentSaveData)
end)
end,
on_load_clicked = function(this,callback)
this.frame.LoadButton.Activated:Connect(function()
callback(this)
end)
end,
model = function(this,model)
local old = this.viewport:FindFirstChildWhichIsA("Model")
if old then old:Destroy() end
if not model then
this.frame.OldLabel.Visible = true
this.frame.OldLabel.Text = "CORRUPT"
return
else
if not (model.Name == "Model") then this.frame.NameBox.Text = model.Name end
end
model.Parent = this.viewport
this.frame.OldLabel.Visible = model:HasTag("Old")
this.frame.OldLabel.Text = model:GetAttribute("Version") or "Err"
local cframe,size = model:GetBoundingBox()
this.camera.CFrame = cframe * CFrame.fromEulerAnglesYXZ(-0.5,-2.5,0)
this.camera.CFrame = this.camera.CFrame + this.camera.CFrame.LookVector * -(size.Magnitude/2)
end
}
_slot.__newindex = function(this,index,value)
if not _slot.__newindex_operations[index] then error("No index to set: "..index,2) end
_slot.__newindex_operations[index](this,value)
end
_slot.show = function(this)
this.frame.Visible = true
_G.Tween:Create(this.frame.UIPadding,TweenInfo.new(0.5,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{PaddingLeft = UDim.new(0,0)}):Play()
end
_tab.CreateSlot = function(this)
local new = {}
new.frame = Templates.Frame:Clone()
new.frame.Parent = this.list
new.raw = ""
_G.Union(new.frame,{
Parent = this.list,
Visible = true,
})
new.frame.NameBox.TextEditable = false
new.frame.SaveButton.Visible = false
new.angle = 45
new.viewport = new.frame.ViewportFrame
new.undo = new.frame.UndoButton
new.refresh = new.frame.RefreshButton
new.camera = Instance.new("Camera")
new.camera.Parent = new.viewport
new.camera.CFrame = CFrame.Angles(0,math.rad(new.angle),0)
new.viewport.CurrentCamera = new.camera
local hover = false
new.viewport.MouseEnter:Connect(function()
hover = true
new.viewport.BackgroundColor3 = Color3.new(0.75,0.75,0.75)
while wait() and hover do
new.camera.CFrame = CFrame.Angles(0,math.rad(2),0) * new.camera.CFrame
end
end)
new.viewport.MouseLeave:Connect(function()
hover = false
new.viewport.BackgroundColor3 = Color3.new(1,1,1)
end)
new.frame.Visible = false
new.frame.OldLabel.Visible = false
return setmetatable(new,_slot)
end
_tab.FocusSection = function(this)
for _,list in pairs(SaveLists:GetChildren()) do
if list:IsA("ScrollingFrame") then list.Visible = false end
end
this.list.Visible = true
Stroke.Parent = this.tab
end
local function CreateSection(title)
local new = setmetatable({},_tab)
new.list = Templates.List:Clone()
_G.Union(new.list,{
Parent = SaveLists,
Visible = false
})
new.tab = Templates.Tab:Clone()
_G.Union(new.tab,{
Parent = SaveTabs,
Visible = true,
Text = title
})
new.slots = {}
new.tab.CloseButton.Activated:Connect(function()
Stroke.Parent = PlayerSaveList
new.tab:Destroy()
new.list:Destroy()
PlayerSaveList:FocusSection()
end)
new.tab.Activated:Connect(function() new:FocusSection() end)
return new
end
_G.SaveIndices = {}
local function CreateUserSection(userid,modify,close)
userid = tostring(userid or _G.UserId)
local title = (game.Players:GetPlayerByUserId(tonumber(userid) or 0) or {DisplayName = userid}).DisplayName
local section = CreateSection(title)
if userid == tostring(_G.UserId) then section:FocusSection() end
local save_info = {}
local save_info_locked = false
local saves = {}
local slots = {}
if not userid:match(":") and not close then section.tab.CloseButton.Visible = false end
local function update_save_info(callback)
repeat
wait()
until not save_info_locked
save_info_locked = true
if callback then callback(save_info) end
_G.Remotes.SetSaveInfo:InvokeServer(save_info,userid)
save_info = _G.Remotes.GetSaveInfo:InvokeServer(userid)
for index,name in pairs(save_info) do
local slot = slots[tostring(index)] or slots[index]
if slot then
slot.name = name
end
end
save_info_locked = false
end
spawn(function()
save_info = _G.Remotes.GetSaveInfo:InvokeServer(userid)
local indices = {}
if not save_info then return end
for index,_ in pairs(save_info) do
table.insert(indices,index)
end
_G.SaveIndices[userid] = indices
if not userid:match(":") then
table.sort(indices,function(before,after)
before = tonumber(before)
after = tonumber(after)
if not before then return true end
if not after then return false end
return before < after
end)
end
for _,index in pairs(indices) do
spawn(function()
local name = save_info[index]
index = tostring(index)
local slot = section:CreateSlot()
slots[index] = slot
local model = LoadModel(index,userid)
saves[index] = model
_G.Union(slot,{
name = name,
order = index,
on_name_change = function(text)
update_save_info(function()
save_info[tonumber(index) or index] = text
end)
end,
on_load_clicked = function()
if not saves[index] or not saves[index].PrimaryPart then _G.Events.Notification:Fire("Vehicle Load Failed","There is no vehicle.") return end
OffloadSelection()
CurrentPlacementConfig = Placement.begin(saves[index]:Clone(),{
confirm = function(config)
_G.Remotes.Place:InvokeServer(config.model.PrimaryPart.CFrame,index,userid)
config:stop()
end,
stopped = function(config)
config.model:Destroy()
CurrentPlacementConfig = nil
end,
disabled = {
resize = true
}
})
end,
model = model
})
if modify then
slot.on_save_clicked = function()
if (not CurrentSaveData) or (#CurrentSaveData == 0) then
_G.Events.Notification:Fire("Vehicle Save Failed","Please select a vehicle to save")
return
end
_G.Remotes.Save:InvokeServer(CurrentSaveData,index,userid)
model = LoadModel(index,userid)
saves[index] = model
slot.model = model
OffloadSelection()
end
slot.viewport.MouseEnter:Connect(function()
slot.undo.Visible = true
slot.refresh.Visible = true
end)
slot.viewport.MouseLeave:Connect(function()
slot.undo.Visible = false
slot.refresh.Visible = false
end)
end
slot.undo.Activated:Connect(function()
CreateUserSection(userid..":"..index,false)
end)
slot.refresh.Activated:Connect(function()
model = LoadModel(index,userid)
saves[index] = model
slot.model = model
OffloadSelection()
end)
slot:show()
end)
wait(0.2)
end
if tostring(_G.UserId) == userid then -- autosave
spawn(function()
wait(5)
repeat
if not LastInteractedPart then continue end
local part = LastInteractedPart.part
if part and part.Parent and _G.Methods.playerOwnsPart(game.Players.LocalPlayer,part) then
_G.Events.Notification:Fire("Auto Saving","Attempting Autosave")
CurrentSaveData = _G.Methods.saves.modelToData(part)
_G.Remotes.Save:InvokeServer(CurrentSaveData,"autosave",userid)
_G.Events.Save:Fire(CurrentSaveData)
local selection_template = Instance.new("SelectionBox")
for index,item in pairs({
SurfaceTransparency = 0.75,
SurfaceColor3 = Color3.fromRGB(231, 72, 183),
LineThickness = 0,
}) do
selection_template[index] = item
end
_G.Methods.forEachConnectedObject(part,function(model)
wait()
local selection = selection_template:Clone()
selection.Adornee = model
selection.Parent = model
Debris:AddItem(selection,0.1)
end)
update_save_info(function()
save_info["autosave"] = tostring(os.date("%c",os.time()))
end)
local model = LoadModel("autosave",userid)
slots["autosave"].model = model
saves["autosave"] = model
end
until not wait(60*2.5)
end)
end
end)
return section,saves
end
_G.Events.Create.Event:Connect(function(part)
LastInteractedPart = part
end)
local function RenderStepped(delta) -- General procedure for allowing us to pick a part from a vehicle to later select (per frame)
if CurrentPlacementConfig then return end
local result = _G.Methods.getMouseHit()
if result and result.Instance then
local part
if _G.Methods.playerOwnsPart(_G.Player,result.Instance) then
part = result.Instance
elseif _G.Methods.playerOwnsPart(_G.Player,result.Instance.Parent) then
part = result.Instance.Parent
end
SelectionBox.Adornee = part
CurrentSaveRoot = part
end
end
function InitTool(script)
SelectionBox = script:WaitForChild("SelectionBox")
local renderSteppedEvent = nil
script.Parent.Activated:Connect(function()
if CurrentSaveRoot then
local root = CurrentSaveRoot
OffloadSelection()
CurrentSaveData = _G.Methods.saves.modelToData(root)
local selection_template = Instance.new("SelectionBox")
for index,item in pairs({
SurfaceTransparency = 0.75,
SurfaceColor3 = Color3.fromRGB(154, 46, 231),
LineThickness = 0
}) do
selection_template[index] = item
end
_G.Methods.forEachConnectedObject(root,function(model)
local selection = selection_template:Clone()
selection.Adornee = model
selection.Parent = model
table.insert(SelectionBoxes,selection)
end)
end
end)
script.Parent.Unequipped:Connect(function()
OffloadSelection()
SaveFrame.Visible = false
SelectionBox.Adornee = nil
if renderSteppedEvent then renderSteppedEvent:Disconnect() end
end)
script.Parent.Equipped:Connect(function()
SaveFrame.Visible = true
if renderSteppedEvent then renderSteppedEvent:Disconnect() end
renderSteppedEvent = game:GetService("RunService").RenderStepped:Connect(RenderStepped)
end)
end
local player_saves
function InitUI(script)
SaveFrame = script.Parent
Stroke = script:WaitForChild("UIStroke")
SaveData = script.Parent:WaitForChild("SaveData")
SaveTabs = script.Parent:WaitForChild("SaveTabs")
SaveLists = script.Parent:WaitForChild("SaveLists")
Templates = script.Parent:WaitForChild("Template")
SaveFrame.Visible = false
Templates.Frame.Visible = false
PlayerSaveList,player_saves = CreateUserSection(_G.UserId,true)
for i = 1,2 do
local SubSlot = PlayerSaveList:CreateSlot()
SubSlot.order = string.format("SUB %i",i)
SubSlot.name = string.format("Subassembly %i",i)
SubSlot.on_save_clicked = function()
if (not CurrentSaveData) or (#CurrentSaveData == 0) then
_G.Events.Notification:Fire("Vehicle Save Failed","Please select a vehicle to save")
return
end
SubassemblyData = CurrentSaveData
SubassemblyModel = _G.Methods.saves.dataToModel(CurrentSaveData,false)
SubSlot.model = SubassemblyModel
OffloadSelection()
end
SubSlot.on_mirror_clicked = function()
OffloadSelection()
if not SubassemblyData then return end
if SubassemblyModel then SubassemblyModel:Destroy() end
SubassemblyModel = _G.Methods.saves.dataToModel(SubassemblyData,false,true)
SubSlot.model = SubassemblyModel
end
SubSlot.on_load_clicked = function()
OffloadSelection()
if not SubassemblyModel then return end
CurrentPlacementConfig = Placement.begin(SubassemblyModel:Clone(),{
confirm = function(config)
_G.Events.Notification:Fire("Subassembly Placing!")
_G.Remotes.PlaceSubassembly:InvokeServer(config.model.PrimaryPart.CFrame,SubassemblyData,config.touching)
config:stop()
end,
stopped = function(config)
config.model:Destroy()
CurrentPlacementConfig = nil
end,
})
end
SubSlot:show()
end
CreateUserSection("public",_G.Remotes.Admin:InvokeServer())
end
Commands.new("open save (.+)","open save <userid>: Opens saves in your tool.",function(userid)
CreateUserSection(userid,false)
_G.Events.Notification:Fire("Opened Read-Only Save",userid)
end)
Commands.new("open write save (.+)","open write save <userid>: Opens write saves in your tool.",function(userid)
CreateUserSection(userid,true)
_G.Events.Notification:Fire("Opened Write Save",userid)
end)
Commands.new("export save (.+) (.+)","export save <userid> <number>: Prints out your save",function(userid,key)
_G.Events.Notification:Fire("Save Export",tostring(_G.Remotes.Load:InvokeServer(userid,key)))
end)
Commands.new("show indices","show indices: Prints out save indices",function()
local result = ""
local indices = _G.SaveIndices
for userid,tab in pairs(indices) do
result = result .. "Userid: "..tostring(userid).."\n"
for index,item in pairs(tab) do
result = result .. " "..tostring(index)..": "..tostring(item).."\n"
end
end
print(result)
_G.Events.Notification:Fire("Save Indicies",result)
end)
return getfenv()