so, i was making a incremental game called “Slap Battles Incremental” and i need to save the current state of the map and the current state of workspace.Money (the map is workspace.Map)
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local mapStore = DataStoreService:GetDataStore("MapSaveStore")
local moneyStore = DataStoreService:GetDataStore("MoneySaveStore")
local function serializeInstance(instance)
local cloned = instance:Clone()
cloned.Archivable = true
return HttpService:JSONEncode(cloned:Clone():GetChildren())
end
local function deserializeInstance(jsonData, parent)
local success, data = pcall(function()
return HttpService:JSONDecode(jsonData)
end)
if success and typeof(data) == "table" then
for _, childData in pairs(data) do
local newObj = Instance.new(childData.ClassName)
for prop, value in pairs(childData) do
if prop ~= "ClassName" then
pcall(function()
newObj[prop] = value
end)
end
end
newObj.Parent = parent
end
end
end
local function saveData(userId)
local success, err = pcall(function()
local mapClone = workspace:FindFirstChild("Map"):Clone()
local moneyClone = workspace:FindFirstChild("Money"):Clone()
mapClone.Archivable = true
moneyClone.Archivable = true
local mapData = HttpService:JSONEncode(mapClone:Clone():GetChildren())
local moneyData = HttpService:JSONEncode(moneyClone:Clone():GetChildren())
mapStore:SetAsync("Map_" .. userId, mapData)
moneyStore:SetAsync("Money_" .. userId, moneyData)
end)
if not success then
warn("Failed to save data for user " .. userId .. ": " .. tostring(err))
end
end
local function loadData(userId)
local success, mapData = pcall(function()
return mapStore:GetAsync("Map_" .. userId)
end)
if success and mapData then
workspace.Map:ClearAllChildren()
deserializeInstance(mapData, workspace.Map)
end
local success2, moneyData = pcall(function()
return moneyStore:GetAsync("Money_" .. userId)
end)
if success2 and moneyData then
workspace.Money:ClearAllChildren()
deserializeInstance(moneyData, workspace.Money)
end
end
-- Save when any player leaves
Players.PlayerRemoving:Connect(function(player)
saveData(player.UserId)
end)
-- Load when any player joins
Players.PlayerAdded:Connect(function(player)
wait(1) -- Give time for workspace to initialize
loadData(player.UserId)
end)
if you want to take a look at the game here is a Link
local MainModule = {}
-- Services
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Constants
local SAVE_INTERVAL = 60 * 5 -- Auto-save every 5 minutes
local RETRY_DELAY = 5 -- Seconds to wait between retries
local MAX_RETRIES = 3 -- Maximum number of retry attempts
-- Data stores
local mapStore = DataStoreService:GetDataStore("MapSaveStore")
local moneyStore = DataStoreService:GetDataStore("MoneySaveStore")
-- Cache for player data to prevent frequent saves of unchanged data
local playerDataCache = {}
-- Serialization functions
function MainModule.serializeInstance(instance)
local serializedChildren = {}
for _, child in ipairs(instance:GetChildren()) do
if child.Archivable then
local childData = {
ClassName = child.ClassName
}
-- Get all properties
local success, properties = pcall(function()
return child:GetAttributes()
end)
if success then
for attrName, attrValue in pairs(properties) do
childData[attrName] = attrValue
end
end
-- Add specific important properties
for _, prop in ipairs({"Name", "Position", "Size", "Color", "Value", "Transparency"}) do
if pcall(function() return child[prop] end) then
childData[prop] = child[prop]
end
end
-- Handle children recursively if needed
if #child:GetChildren() > 0 then
childData.Children = MainModule.serializeInstance(child)
end
table.insert(serializedChildren, childData)
end
end
return serializedChildren
end
function MainModule.deserializeInstance(data, parent)
if not data then return end
for _, childData in ipairs(data) do
local newObj = Instance.new(childData.ClassName)
newObj.Name = childData.Name or newObj.ClassName
-- Set properties
for prop, value in pairs(childData) do
if prop ~= "ClassName" and prop ~= "Children" and prop ~= "Name" then
pcall(function()
newObj[prop] = value
end)
end
end
-- Set attributes
for attr, value in pairs(childData) do
if not newObj[attr] then -- Only set if not a built-in property
pcall(function()
newObj:SetAttribute(attr, value)
end)
end
end
newObj.Parent = parent
-- Recursively create children
if childData.Children then
MainModule.deserializeInstance(childData.Children, newObj)
end
end
end
-- Data handling functions with retry logic
function MainModule.saveData(userId)
if not workspace:FindFirstChild("Map") or not workspace:FindFirstChild("Money") then
warn("Map or Money folder not found in workspace")
return false
end
local currentData = {
map = MainModule.serializeInstance(workspace.Map),
money = MainModule.serializeInstance(workspace.Money)
}
-- Check if data has changed before saving
if playerDataCache[userId] and HttpService:JSONEncode(playerDataCache[userId]) == HttpService:JSONEncode(currentData) then
return true -- Data hasn't changed, no need to save
end
-- Data has changed, proceed with save
for attempt = 1, MAX_RETRIES do
local success, err = pcall(function()
local mapData = HttpService:JSONEncode(currentData.map)
local moneyData = HttpService:JSONEncode(currentData.money)
mapStore:SetAsync("Map_" .. userId, mapData)
moneyStore:SetAsync("Money_" .. userId, moneyData)
-- Update cache on successful save
playerDataCache[userId] = currentData
end)
if success then
return true
else
warn(string.format("Save attempt %d/%d failed for user %d: %s", attempt, MAX_RETRIES, userId, tostring(err)))
if attempt < MAX_RETRIES then
wait(RETRY_DELAY)
end
end
end
return false
end
function MainModule.loadData(userId)
if not workspace:FindFirstChild("Map") or not workspace:FindFirstChild("Money") then
warn("Map or Money folder not found in workspace")
return false
end
local loadedSuccessfully = false
-- Load map data
for attempt = 1, MAX_RETRIES do
local success, mapData = pcall(function()
return mapStore:GetAsync("Map_" .. userId)
end)
if success and mapData then
workspace.Map:ClearAllChildren()
local decodedData = HttpService:JSONDecode(mapData)
MainModule.deserializeInstance(decodedData, workspace.Map)
loadedSuccessfully = true
break
elseif attempt == MAX_RETRIES then
warn("Failed to load map data for user " .. userId)
else
wait(RETRY_DELAY)
end
end
-- Load money data
for attempt = 1, MAX_RETRIES do
local success, moneyData = pcall(function()
return moneyStore:GetAsync("Money_" .. userId)
end)
if success and moneyData then
workspace.Money:ClearAllChildren()
local decodedData = HttpService:JSONDecode(moneyData)
MainModule.deserializeInstance(decodedData, workspace.Money)
loadedSuccessfully = true
break
elseif attempt == MAX_RETRIES then
warn("Failed to load money data for user " .. userId)
else
wait(RETRY_DELAY)
end
end
-- Cache the loaded data
if loadedSuccessfully then
playerDataCache[userId] = {
map = MainModule.serializeInstance(workspace.Map),
money = MainModule.serializeInstance(workspace.Money)
}
end
return loadedSuccessfully
end
-- Player event handlers
function MainModule.setupPlayer(player)
-- Wait for workspace to initialize
local startTime = os.time()
while not workspace:FindFirstChild("Map") or not workspace:FindFirstChild("Money") do
if os.time() - startTime > 10 then
warn("Timed out waiting for Map/Money folders to appear for player " .. player.Name)
return
end
wait(1)
end
-- Load player data
MainModule.loadData(player.UserId)
-- Set up auto-save
local autoSaveConnection
autoSaveConnection = game:GetService("RunService").Heartbeat:Connect(function()
if os.time() % SAVE_INTERVAL == 0 then
MainModule.saveData(player.UserId)
end
end)
-- Clean up on player leaving
player:GetPropertyChangedSignal("Parent"):Connect(function()
if not player.Parent then
autoSaveConnection:Disconnect()
MainModule.saveData(player.UserId)
playerDataCache[userId] = nil
end
end)
end
-- Initialize the module
function MainModule.init()
-- Set up existing players
for _, player in ipairs(Players:GetPlayers()) do
coroutine.wrap(MainModule.setupPlayer)(player)
end
-- Set up future players
Players.PlayerAdded:Connect(function(player)
MainModule.setupPlayer(player)
end)
-- Global save function that can be called from elsewhere
ReplicatedStorage:WaitForChild("SaveAllData", 10).OnServerEvent:Connect(function()
for _, player in ipairs(Players:GetPlayers()) do
MainModule.saveData(player.UserId)
end
end)
-- Handle game closing
game:BindToClose(function()
for _, player in ipairs(Players:GetPlayers()) do
MainModule.saveData(player.UserId)
end
end)
end
return MainModule