|
|
|
|
| local DataStoreService = game:GetService("DataStoreService")
|
| local HttpService = game:GetService("HttpService")
|
| local Players = game:GetService("Players")
|
|
|
| local PlotDataStore
|
| local datastoreAvailable = false
|
|
|
| local success, err = pcall(function()
|
| PlotDataStore = DataStoreService:GetDataStore("Timberbound_PlayerData_v2")
|
| end)
|
|
|
| if success then
|
| datastoreAvailable = true
|
| else
|
| warn("DataStore not available (place may not be published): " .. tostring(err))
|
| end
|
|
|
| local AUTO_SAVE_INTERVAL = 120
|
| local MAX_RETRIES = 3
|
|
|
| local function serializePlotItems(plotPart)
|
| local dataToSave = {}
|
|
|
| for _, item in pairs(plotPart:GetChildren()) do
|
| if not (item:IsA("Model") or item:IsA("BasePart")) then continue end
|
|
|
| local itemId = item:GetAttribute("ItemId") or item.Name
|
| local relativeCFrame = plotPart.CFrame:ToObjectSpace(item:GetPivot())
|
| local px, py, pz, r00, r01, r02, r10, r11, r12, r20, r21, r22 = relativeCFrame:GetComponents()
|
|
|
| local stateData = {}
|
| if item:GetAttribute("FillLevel") then
|
| stateData.FillLevel = item:GetAttribute("FillLevel")
|
| end
|
| if item:GetAttribute("WoodFilled") then
|
| stateData.WoodFilled = item:GetAttribute("WoodFilled")
|
| end
|
| if item:GetAttribute("ProcessState") then
|
| stateData.ProcessState = item:GetAttribute("ProcessState")
|
| end
|
|
|
| table.insert(dataToSave, {
|
| id = itemId,
|
| cframe = {px, py, pz, r00, r01, r02, r10, r11, r12, r20, r21, r22},
|
| state = stateData,
|
| })
|
| end
|
|
|
| return dataToSave
|
| end
|
|
|
| local function buildSaveData(player, plotPart)
|
| local data = _G.GetPlayerData(player)
|
| local leaderstats = player:FindFirstChild("leaderstats")
|
|
|
| local saveData = {
|
| version = 2,
|
| cash = 0,
|
| woodChopped = 0,
|
| inventory = {},
|
| questProgress = {},
|
| achievements = {},
|
| biomesVisited = {},
|
| totalEarned = 0,
|
| totalWoodSold = 0,
|
| totalBuilt = 0,
|
| plotItems = {},
|
| }
|
|
|
|
|
| if leaderstats then
|
| local cash = leaderstats:FindFirstChild("Cash")
|
| if cash then saveData.cash = cash.Value end
|
| local wood = leaderstats:FindFirstChild("WoodChopped")
|
| if wood then saveData.woodChopped = wood.Value end
|
| end
|
|
|
|
|
| if data then
|
| saveData.inventory = data.Inventory or {}
|
| saveData.questProgress = data.QuestProgress or {}
|
| saveData.biomesVisited = data.BiomesVisited or {}
|
| saveData.totalEarned = data.TotalEarned or 0
|
| saveData.totalWoodSold = data.TotalWoodSold or 0
|
| saveData.totalBuilt = data.TotalBuilt or 0
|
| end
|
|
|
|
|
| local quests = _G.GetPlayerQuests and _G.GetPlayerQuests(player)
|
| if quests then
|
| saveData.questProgress = quests
|
| end
|
|
|
|
|
| local achievements = _G.GetPlayerAchievements and _G.GetPlayerAchievements(player)
|
| if achievements then
|
| saveData.achievements = achievements
|
| end
|
|
|
|
|
| if plotPart then
|
| saveData.plotItems = serializePlotItems(plotPart)
|
| end
|
|
|
| return saveData
|
| end
|
|
|
|
|
| local function saveWithRetry(userId, saveData)
|
| if not datastoreAvailable then return false end
|
| for attempt = 1, MAX_RETRIES do
|
| local success, err = pcall(function()
|
| local jsonString = HttpService:JSONEncode(saveData)
|
| PlotDataStore:SetAsync(tostring(userId), jsonString)
|
| end)
|
|
|
| if success then
|
| return true
|
| else
|
| warn("Save attempt " .. attempt .. " failed for userId " .. userId .. ": " .. tostring(err))
|
| if attempt < MAX_RETRIES then
|
| task.wait(2 ^ attempt)
|
| end
|
| end
|
| end
|
| return false
|
| end
|
|
|
|
|
| _G.SavePlayerData = function(player, plotPart)
|
| if not datastoreAvailable then
|
| print("DataStore not available, skipping save for " .. player.Name)
|
| return false
|
| end
|
| local saveData = buildSaveData(player, plotPart)
|
| local success = saveWithRetry(player.UserId, saveData)
|
|
|
| if success then
|
| print("Successfully saved data for " .. player.Name)
|
| else
|
| warn("Failed to save data for " .. player.Name .. " after " .. MAX_RETRIES .. " retries")
|
| end
|
|
|
| return success
|
| end
|
|
|
|
|
| _G.LoadPlayerData = function(player, plotPart)
|
| if not datastoreAvailable then
|
| print("DataStore not available, skipping load for " .. player.Name)
|
| return
|
| end
|
| local success, result = pcall(function()
|
| return PlotDataStore:GetAsync(tostring(player.UserId))
|
| end)
|
|
|
| if not success then
|
| warn("Failed to load data for " .. player.Name .. ": " .. tostring(result))
|
| return
|
| end
|
|
|
| if not result then
|
| print("No saved data found for " .. player.Name .. " (new player)")
|
| return
|
| end
|
|
|
| local savedData = HttpService:JSONDecode(result)
|
|
|
|
|
| local leaderstats = player:FindFirstChild("leaderstats")
|
| if leaderstats and savedData.cash then
|
| local cash = leaderstats:FindFirstChild("Cash")
|
| if cash then cash.Value = savedData.cash end
|
| local wood = leaderstats:FindFirstChild("WoodChopped")
|
| if wood and savedData.woodChopped then wood.Value = savedData.woodChopped end
|
| end
|
|
|
|
|
| local data = _G.GetPlayerData(player)
|
| if data then
|
| if savedData.inventory then data.Inventory = savedData.inventory end
|
| if savedData.biomesVisited then data.BiomesVisited = savedData.biomesVisited end
|
| if savedData.totalEarned then data.TotalEarned = savedData.totalEarned end
|
| if savedData.totalWoodSold then data.TotalWoodSold = savedData.totalWoodSold end
|
| if savedData.totalBuilt then data.TotalBuilt = savedData.totalBuilt end
|
|
|
|
|
| if savedData.inventory and savedData.inventory.Tools and #savedData.inventory.Tools > 0 then
|
| local lastTool = savedData.inventory.Tools[#savedData.inventory.Tools]
|
| data.EquippedAxe = lastTool
|
| player:SetAttribute("EquippedAxe", lastTool)
|
| end
|
| end
|
|
|
|
|
| if plotPart and savedData.plotItems then
|
| for _, itemData in pairs(savedData.plotItems) do
|
|
|
| local newPart = Instance.new("Part")
|
| newPart.Name = itemData.id
|
| newPart:SetAttribute("ItemId", itemData.id)
|
|
|
|
|
| if itemData.cframe and #itemData.cframe >= 12 then
|
| local cf = CFrame.new(unpack(itemData.cframe))
|
| newPart:PivotTo(plotPart.CFrame * cf)
|
| end
|
|
|
|
|
| if itemData.state then
|
| for key, value in pairs(itemData.state) do
|
| newPart:SetAttribute(key, value)
|
| end
|
| end
|
|
|
| newPart.Anchored = true
|
| newPart.Size = Vector3.new(4, 4, 4)
|
| newPart.Material = Enum.Material.WoodPlanks
|
| newPart.Parent = plotPart
|
| end
|
| end
|
|
|
| print("Successfully loaded data for " .. player.Name)
|
| end
|
|
|
|
|
| task.spawn(function()
|
| while true do
|
| task.wait(AUTO_SAVE_INTERVAL)
|
| for _, player in ipairs(Players:GetPlayers()) do
|
| task.spawn(function()
|
| pcall(function()
|
|
|
| local plotPart = nil
|
| local plotsFolder = workspace:FindFirstChild("WorldStructures")
|
| if plotsFolder then
|
| local plots = plotsFolder:FindFirstChild("Plots")
|
| if plots then
|
| for _, plot in pairs(plots:GetChildren()) do
|
| if plot:GetAttribute("OwnerId") == player.UserId then
|
| plotPart = plot
|
| break
|
| end
|
| end
|
| end
|
| end
|
| _G.SavePlayerData(player, plotPart)
|
| end)
|
| end)
|
| end
|
| end
|
| end)
|
|
|
|
|
| game:BindToClose(function()
|
| for _, player in ipairs(Players:GetPlayers()) do
|
| pcall(function()
|
| local plotPart = nil
|
| local plotsFolder = workspace:FindFirstChild("WorldStructures")
|
| if plotsFolder then
|
| local plots = plotsFolder:FindFirstChild("Plots")
|
| if plots then
|
| for _, plot in pairs(plots:GetChildren()) do
|
| if plot:GetAttribute("OwnerId") == player.UserId then
|
| plotPart = plot
|
| break
|
| end
|
| end
|
| end
|
| end
|
| _G.SavePlayerData(player, plotPart)
|
| end)
|
| end
|
| end)
|
|
|