Data not saving when players game crashes

I dont know why but when the players game crashes it doesnt save even though the character is removed… Also occasionally it wont save certain characters…?

local dss = game:GetService("DataStoreService")
local ds = dss:GetDataStore("PlayerData_1.2")
local yenLeaderboard = dss:GetOrderedDataStore("YenLeaderboard2.0")
local PlayerLoaded = {}

local function GetData(Key, MaxRetries)
	local Retries = 0
	while true do 

		local Success, Result = pcall(function()
			return ds:GetAsync(Key)
		end)

		if Success then 
			return true, Result
		else 
			warn("[GetData]: "..Result)

			Retries += 1
			if Retries > MaxRetries then -- Give up :(
				return false, nil
			end

			-- Exponential backoff (increases the wait, to alleviate the load on roblox's servers if they go down)
			task.wait(2^Retries)

			continue
		end

	end
end

local function savedata(plr,MaxRetries)
	if not PlayerLoaded[plr.UserId] then return end

	local retries = 0
	PlayerLoaded[plr.UserId] = nil

	plr.Data.TimeLeft.Value = os.time()

	local savedata = {}
	local basevalues = {}
	for _,v in pairs(plr.Data:GetDescendants()) do
		if v:IsA("ValueBase") and v.Parent.Name ~= "Items" then
			table.insert(basevalues,{v.Name,v.Value})
		end
	end
	table.insert(savedata,basevalues)
	local items = {}
	for _,v in pairs(plr.Data.Items:GetChildren()) do
		table.insert(items,v.Name)
	end
	table.insert(savedata,items)

	pcall(function()
		yenLeaderboard:SetAsync(plr.UserId, math.round(plr.Data.Yen.Value))
	end)

	for i=1,MaxRetries do
		print("took "..i.." try(s) to save data")
		local success, err = pcall(function()
			ds:SetAsync(plr.UserId, savedata)
		end)

		if not success then
			warn("Failed to save data for "..plr.Name..": "..err)
		end

		if success then
			break
		end
	end	
end


local function LoadData(plr,plot,items,timeleft)
	local Success, Result = GetData(plr.UserId,5)

	if not Success then
		warn("Datastore Error: "..Result) -- In case of an error, Result is the error message
		return
	end

	PlayerLoaded[plr.UserId] = true

	if Result then -- If there are no errors, Result is the data
		print(Result)
		print("got savedata")
		local savedValues = Result[1]
		local savedItems = Result[2]

		-- Cache Data descendants by name for fast lookup
		local dataDescendants = {}
		for _, valObj in pairs(plr.Data:GetDescendants()) do
			dataDescendants[valObj.Name] = valObj
		end

		-- Apply saved values using the cache
		for _, savedVal in pairs(savedValues) do
			local name = savedVal[1]
			local value = savedVal[2]

			local valObj = dataDescendants[name]

			if valObj then
				valObj.Value = value
			end
		end

		-- Recreate saved items
		for _, itemName in pairs(savedItems) do
			local item = Instance.new("StringValue")
			item.Name = itemName
			item.Parent = items
		end

		wait(1)
		for _,v in pairs(plot.Spots:GetChildren()) do
			if v:FindFirstChildWhichIsA("Model") then
				local income = v:FindFirstChildWhichIsA("Model"):GetAttribute("Income")
				local secondsgone = os.time() - timeleft.Value
				local profit = math.round((income*secondsgone)/2)

				plr.Data.Characters[v.Name]["CashGenerated"..v.Name].Value+=profit
			end
		end
	end
end

game.Players.PlayerAdded:Connect(function(plr)
	local plot = game.Workspace.Plots:FindFirstChild("Unclaimed")
	plot.Name = plr.Name
	plot.NameTag.BillboardGui.TextLabel.Text = plr.Name.."'s Base"

	plr.CharacterAdded:Connect(function()
		print("pivoted character")
		plr.Character:PivotTo(plot.Lasers.Block.CFrame*CFrame.new(0,0,-10))

		for _,v in pairs(plr.Data:WaitForChild("ToolPasses"):GetChildren()) do
			if v.Value == true then
				local tool = game.ReplicatedStorage.ToolGamepasses[v.Name]:Clone()
				tool.Parent = plr.Backpack
			end

			v.Changed:Connect(function()
				if v.Value == true then
					local tool = game.ReplicatedStorage.ToolGamepasses[v.Name]:Clone()
					tool.Parent = plr.Backpack
				end
			end)
		end
	end)

	local data = Instance.new("Folder",plr)
	data.Name = "Data"
	
	local bannedbool = Instance.new("BoolValue")
	bannedbool.Parent = data
	bannedbool.Name = "Banned"
	bannedbool.Value = false

	local codes = Instance.new("Folder",data)
	codes.Name = "Codes"

	for _,v in pairs(game.ReplicatedStorage.Codes:GetChildren()) do
		local codebool = Instance.new("BoolValue",codes)
		codebool.Parent = codes
		codebool.Name = v.Name
	end

	local leaderstats = Instance.new("Folder")
	leaderstats.Parent = plr
	leaderstats.Name = "leaderstats"
	local yenleaderstat = Instance.new("NumberValue")
	yenleaderstat.Parent = leaderstats
	yenleaderstat.Name = "Yen"
	local rebirthsleaderstat = Instance.new("NumberValue")
	rebirthsleaderstat.Parent = leaderstats
	rebirthsleaderstat.Name = "Rebirths"

	local yen = Instance.new("NumberValue",data)
	yen.Name = "Yen"
	yen.Value = 50
	yenleaderstat.Value = 50

	local timeleft = Instance.new("NumberValue",data)
	timeleft.Name = "TimeLeft"

	local rebirths = Instance.new("NumberValue",data)
	rebirths.Name = "Rebirths"

	local ownedcharacters = Instance.new("Folder",data)
	ownedcharacters.Name = "Characters"

	local items = Instance.new("Folder",data)
	items.Name = "Items"

	local yenmulti = Instance.new("NumberValue",data)
	yenmulti.Name = "YenMultiplier"
	yenmulti.Value = 1

	local quests = Instance.new("Folder",data)
	quests.Name = "Quests"
	
	local ownsfloor2 = Instance.new("BoolValue",data)
	ownsfloor2.Name = "OwnsFloor2"
	
	ownsfloor2.Changed:Connect(function()
		local floor2 = plot.Floor2Val.Value
		floor2.Parent = plot
		floor2.Character11.Parent = plot.Spots
		floor2.Character12.Parent = plot.Spots
		floor2.Character13.Parent = plot.Spots
		floor2.Character14.Parent = plot.Spots
		floor2.Character15.Parent = plot.Spots
	end)

	local toolgamepasses = Instance.new("Folder",data)
	toolgamepasses.Name = "ToolPasses"
	for _,v in pairs(game.ReplicatedStorage.ToolGamepasses:GetChildren()) do
		local toolbool = Instance.new("BoolValue",toolgamepasses)
		toolbool.Name = v.Name
	end

	for _,v in pairs(game.ReplicatedStorage.Quests:GetChildren()) do
		local questbool = Instance.new("BoolValue",quests)
		questbool.Name = v.Name
		questbool.Value = false

		local claimed = Instance.new("BoolValue",questbool)
		claimed.Name = "Claimed"..v.Name
		claimed.Value = false
	end

	local statsfolder = Instance.new("Folder",data)
	statsfolder.Name = "Stats"
	local CharactersSold = Instance.new("NumberValue",statsfolder) CharactersSold.Name = "CharactersSold"
	local Steals = Instance.new("NumberValue",statsfolder) Steals.Name = "Steals"

	local index = Instance.new("Folder",data)
	index.Name = "Index"
	for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
		if v:IsA("Model") then
			local indexed = Instance.new("BoolValue",index)
			indexed.Name = v.Name
		end
	end

	local function setupcharacterval(i)
		local character = Instance.new("StringValue",ownedcharacters)
		character.Name = "Character"..i

		local amounttoclaim = Instance.new("NumberValue",character)
		amounttoclaim.Name = "CashGenerated".."Character"..i

		local mutation = Instance.new("StringValue",character)
		mutation.Name = "Mutation".."Character"..i

		--ignore this its just to load the characters when they're changed
		character.Changed:Connect(function(val)
			if val == "" then
				if workspace.Plots:FindFirstChild(plr.Name).Spots:FindFirstChild(character.Name):FindFirstChildWhichIsA("Model") then
					workspace.Plots:FindFirstChild(plr.Name).Spots:FindFirstChild(character.Name):FindFirstChildWhichIsA("Model"):Destroy()
				end
				mutation.Value = ""
				amounttoclaim.Value = 0
			else
				index:FindFirstChild(val).Value = true
				local characterbody
				local rarity
				local raritycolor
				local mutationtype

				for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
					if v.Name == val then
						characterbody = v:Clone()
						rarity = v.Parent.Name
					end
				end

				if rarity == "Secret" then
					raritycolor = Color3.new(0, 1, 0.584314)
				elseif rarity == "Godly" then
					raritycolor = Color3.new(0.533333, 0, 1)
				elseif rarity == "Mythic" then
					raritycolor = Color3.new(1, 0.0980392, 0.85098)
				elseif rarity == "Legendary" then
					raritycolor = Color3.new(1, 0.745098, 0.0941176)
				elseif rarity == "Rare" then
					raritycolor = Color3.new(0.0901961, 0.486275, 1)
				elseif rarity == "Common" then
					raritycolor = Color3.new(0.6, 0.6, 0.6)
				end

				characterbody.HumanoidRootPart.Anchored = true
				characterbody.Parent = game.Workspace.Plots:FindFirstChild(plr.Name).Spots:WaitForChild(character.Name)
				characterbody.PrimaryPart.CFrame = game.Workspace.Plots:FindFirstChild(plr.Name).Spots:WaitForChild(character.Name).CFrame*CFrame.new(0,3,0)

				local idle = characterbody.Humanoid.Animator:LoadAnimation(characterbody.Idle)
				idle:Play()

				local label = game.ReplicatedStorage.CharacterLabel:Clone()
				label.Parent = characterbody
				label.Position = characterbody.Head.Position

				label.BillboardGui.CharName.Text = characterbody.Name
				label.BillboardGui.Rarity.Text = rarity
				if raritycolor then
					label.BillboardGui.Rarity.TextColor3 = raritycolor
				else
					label.BillboardGui.Rarity.TextColor3 = Color3.new(1,1,1)
					local gradient = game.ReplicatedStorage.CelestialGradient:Clone()
					gradient.Parent = label.BillboardGui.Rarity
				end

				label.BillboardGui.Income.Text = characterbody:GetAttribute("Income").."/s"
				label.BillboardGui.Price.Text = "¥"..characterbody:GetAttribute("Price")
				
				if rarity == "Celestial" and mutation.Value ~= "" then
					mutation.Value = ""
				end

				if mutation.Value ~= "" then
					mutationtype = mutation.Value
					label.BillboardGui.Mutations.Text = mutationtype
					characterbody:FindFirstChildWhichIsA("Shirt"):Destroy()
					characterbody:FindFirstChildWhichIsA("Pants"):Destroy()
					local mutationinstance = Instance.new("StringValue",characterbody)
					mutationinstance.Value = mutationtype
					mutationinstance.Name = "Mutation"
				end

				if mutationtype == "Golden" then
					local oldincome
					for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
						if v.Name == characterbody.Name then
							oldincome = v:GetAttribute("Income")
						end
					end

					characterbody:SetAttribute("Income",oldincome*3)
					label.BillboardGui.Income.Text = (characterbody:GetAttribute("Income")).."/s"

					local particles = game.ReplicatedStorage.GemMutation:Clone()
					particles.Parent = characterbody.Torso
					particles.Stars.Color = ColorSequence.new(Color3.new(1, 0.737255, 0.0745098))
					particles.Cross.Color = ColorSequence.new(Color3.new(1, 0.737255, 0.0745098))
					label.BillboardGui.Mutations.TextColor3 = Color3.new(1, 0.737255, 0.0745098)
					for _,v in pairs(characterbody:GetDescendants()) do
						if v:IsA("SpecialMesh") then
							v.TextureId = ""
						end

						if v:IsA("BasePart") then
							v.Color = Color3.new(1, 0.737255, 0.0745098)
						end
					end
				elseif mutationtype == "Diamond" then
					local oldincome
					for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
						if v.Name == characterbody.Name then
							oldincome = v:GetAttribute("Income")
						end
					end

					characterbody:SetAttribute("Income",oldincome*5)
					label.BillboardGui.Income.Text = (characterbody:GetAttribute("Income")).."/s"
					local particles = game.ReplicatedStorage.GemMutation:Clone()
					particles.Parent = characterbody.Torso
					particles.Stars.Color = ColorSequence.new(Color3.new(0.101961, 0.654902, 1))
					particles.Cross.Color = ColorSequence.new(Color3.new(0.101961, 0.654902, 1))
					label.BillboardGui.Mutations.TextColor3 = Color3.new(0.101961, 0.654902, 1)
					for _,v in pairs(characterbody:GetDescendants()) do
						if v:IsA("SpecialMesh") then
							v.TextureId = ""
						end

						if v:IsA("BasePart") then
							v.Color = Color3.new(0.101961, 0.654902, 1)
						end
					end
				elseif mutationtype == "Emerald" then
					local oldincome
					for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
						if v.Name == characterbody.Name then
							oldincome = v:GetAttribute("Income")
						end
					end

					characterbody:SetAttribute("Income",oldincome*7)
					label.BillboardGui.Income.Text = (characterbody:GetAttribute("Income")).."/s"
					local particles = game.ReplicatedStorage.GemMutation:Clone()
					particles.Parent = characterbody.Torso
					particles.Stars.Color = ColorSequence.new(Color3.new(0.0980392, 1, 0.231373))
					particles.Cross.Color = ColorSequence.new(Color3.new(0.0980392, 1, 0.231373))
					label.BillboardGui.Mutations.TextColor3 = Color3.new(0.0980392, 1, 0.231373)
					for _,v in pairs(characterbody:GetDescendants()) do
						if v:IsA("SpecialMesh") then
							v.TextureId = ""
						end

						if v:IsA("BasePart") then
							v.Color = Color3.new(0.0980392, 1, 0.231373)
						end
					end
				elseif mutationtype == "Molten" then
					local oldincome
					for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
						if v.Name == characterbody.Name then
							oldincome = v:GetAttribute("Income")
						end
					end
					label.BillboardGui.Mutations.TextColor3 = Color3.new(1, 0.364706, 0.113725)

					characterbody:SetAttribute("Income",oldincome*10)
					label.BillboardGui.Income.Text = (characterbody:GetAttribute("Income")).."/s"
					local moltenrocks = game.ReplicatedStorage.MoltenRocks:Clone()
					moltenrocks.Parent = characterbody
					moltenrocks.CFrame = characterbody.HumanoidRootPart.CFrame
					local wc = Instance.new("WeldConstraint")
					wc.Parent = moltenrocks
					wc.Part0 = characterbody.HumanoidRootPart
					wc.Part1 = moltenrocks

					for _,v in pairs(game.ReplicatedStorage.MoltenMutation:GetChildren()) do
						for __,vv in pairs(characterbody:GetChildren()) do
							if vv:IsA("BasePart") then
								v:Clone().Parent = vv
							end
						end
					end

					for _,v in pairs(characterbody:GetDescendants()) do
						if v:IsA("SpecialMesh") then
							v.TextureId = ""
						end

						if v:IsA("BasePart") then
							v.Color = Color3.new(1, 0.470588, 0.164706)
						end
					end
				elseif mutationtype == "Scorched" then
					local oldincome
					for _,v in pairs(game.ReplicatedStorage.Characters:GetDescendants()) do
						if v.Name == characterbody.Name then
							oldincome = v:GetAttribute("Income")
						end
					end

					label.BillboardGui.Mutations.TextColor3 = Color3.new(0.239216, 0.207843, 0.172549)
					characterbody:SetAttribute("Income",oldincome*2)
					label.BillboardGui.Income.Text = (characterbody:GetAttribute("Income")).."/s"
					
					for _,v in pairs(game.ReplicatedStorage.ScorchedMutation:GetChildren()) do
						for __,vv in pairs(characterbody:GetChildren()) do
							if vv:IsA("BasePart") then
								v:Clone().Parent = vv
							end
						end
					end

					for _,v in pairs(characterbody:GetDescendants()) do
						if v:IsA("SpecialMesh") then
							v.TextureId = ""
						end

						if v:IsA("BasePart") then
							v.Color = Color3.new(0.219608, 0.196078, 0.184314)
						end
					end
				end

				local wc = Instance.new("WeldConstraint",label)
				wc.Part0 = characterbody.Head
				wc.Part1 = label

				local stealpromptpart = Instance.new("Part")
				stealpromptpart.CanCollide = false
				stealpromptpart.Size = Vector3.new(0.1, 0.1, 0.1)
				stealpromptpart.Anchored = true
				stealpromptpart.CFrame = characterbody.HumanoidRootPart.CFrame*CFrame.new(0,3.5,0)
				stealpromptpart.Transparency = 1
				stealpromptpart.Name = "StealPrompt"
				stealpromptpart.Parent = characterbody

				local stealprompt = game.ReplicatedStorage.StealPrompt:Clone()
				stealprompt.Parent = stealpromptpart

				stealprompt.Triggered:Connect(function(plr)
					if not plr.Character:FindFirstChild("Stealing") then
						game.ReplicatedStorage.Notify:FireClient(plr,"Someone is stealing your "..val)
						game.ReplicatedStorage.StealingFrom:FireClient(plr)
					end
				end)

				wait(1)
				game.ReplicatedStorage.ChangeToSell:FireClient(plr,characterbody)
				print("changed to sell")
			end	
		end)
	end

	for i=1,15 do
		setupcharacterval(i)
	end

	pcall(function()
		yenLeaderboard:SetAsync(plr.UserId, math.round(plr.Data.Yen.Value))
	end)

	if yen.Value > 50000000000000 then
		bannedbool.Value = true
	end

	yen.Changed:Connect(function(val)
		yenleaderstat.Value = val
	end)

	rebirths.Changed:Connect(function(val)
		rebirthsleaderstat.Value = val
		if val >= 3 then
			plr.Data.OwnsFloor2.Value = true
		end
	end)
	
	if bannedbool.Value == true then
		plr:Kick("You've been banned")
	end
	
	bannedbool.Changed:Connect(function()
		if bannedbool.Value == true then
			plr:Kick("You've been banned")
		end
	end)

	LoadData(plr,plot,items,timeleft)
	
	while task.wait(120) do
		if plr then
			savedata(plr,1)
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	local plot = game.Workspace.Plots:FindFirstChild(plr.Name)
	
	for _,v in pairs(plot.Spots:GetChildren()) do
		if v:FindFirstChildWhichIsA("Model") then
			v:FindFirstChildWhichIsA("Model"):Destroy()
			v.Claim.BillboardGui.TextLabel.Text = ""
		end
	end
	
	if plot:FindFirstChild("Floor2") then
		local floor2 = plot.Floor2Val.Value
		floor2.Parent = game.ReplicatedStorage
		plot.Spots.Character11.Parent = floor2
		plot.Spots.Character12.Parent = floor2
		plot.Spots.Character13.Parent = floor2
		plot.Spots.Character14.Parent = floor2
		plot.Spots.Character15.Parent = floor2
	end
	
	plot.Lasers.Block.CanCollide = false
	for _,v in pairs(plot.Lasers:GetChildren()) do
		if v.Name == "Part" then
			v.Transparency = 1
		end
	end
	
	plot.Name = "Unclaimed"
	plot.NameTag.BillboardGui.TextLabel.Text = "Empty Base"

	savedata(plr,3)
end)

game:BindToClose(function()
	-- Wait for all PlayerRemoving connections to finish
	for _, plr in pairs(game.Players:GetPlayers()) do
		-- Save data for all players still in game
		local success, err = pcall(function()
			savedata(plr,4)
		end)

		if not success then
			warn("Failed to save data for "..plr.Name..": "..err)
		end
	end
	-- Give time for datastore requests to complete
	wait(4)
end)
2 Likes

citizen that a goodthink to keep data archival to be goodluau and having constant intel over badthink citizens using auto saving every time passes minute.
That could be a goodthinkPlusGoodLuau approach

1 Like

what does this mean i am so confused

bump as i am having the same issue

He’s might be saying that you should use autosave every time or minutes passess or not to use that method.

You can either autosave frequently OR:

Track each player in a table, and check every once in a while if the players still exist (don’t replace PlayerRemoved with this though, its just a bonus)