503 lines
15 KiB
Lua
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() |