| |
|
| |
|
| | 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)
|
| |
|