Datastore timer Problem

Problem with my Datastore

So, i need that the Buff time is getting saved and reloaded and that i can stack it again after rejoining the game BUT its not working. When i rejoin the game it just has the new time for a second and then switches back to the normal time it has before but when i have no timers active, the time stacks perfectly

Here is the example Video of the Problem:

An here is the Module script for the Apple:

return {
	Name = "Apple",
	Image = "rbxassetid://8221939314",
	Description = "Multiplies your totalMultiplier by 1.5 for 5 minutes. Reusing stacks the time.",
	OnUse = function(player)
		local values = player:FindFirstChild("Values")
		if not values then return end

		local multiplier = values:FindFirstChild("totalMultiplier")
		if not multiplier or not multiplier:IsA("NumberValue") then return end

		local buffName = "AppleBuff"
		local existingBuff = values:FindFirstChild(buffName)

		if not existingBuff then
			-- Create the buff
			existingBuff = Instance.new("BoolValue")
			existingBuff.Name = buffName
			existingBuff.Parent = values

			-- Store original multiplier so we restore it later
			existingBuff:SetAttribute("OriginalValue", multiplier.Value)

			-- Apply buff
			multiplier.Value = multiplier.Value * 1.5

			-- Initial time
			existingBuff:SetAttribute("TimeLeft", 20) -- Adjust to 300 for 5 mins

			-- Start countdown
			task.spawn(function()
				while existingBuff and existingBuff.Parent and existingBuff:GetAttribute("TimeLeft") > 0 do
					task.wait(1)
					local newTime = existingBuff:GetAttribute("TimeLeft") - 1
					existingBuff:SetAttribute("TimeLeft", newTime)
				end

				-- Restore multiplier when done
				if existingBuff and existingBuff.Parent then
					multiplier.Value = existingBuff:GetAttribute("OriginalValue")
					existingBuff:Destroy()
				end
			end)
		else
			-- Extend existing time
			local currentTime = existingBuff:GetAttribute("TimeLeft") or 0
			existingBuff:SetAttribute("TimeLeft", currentTime + 20) -- Stack more time
		end
	end
}

And here is my Datastore Script:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local buffStore = DataStoreService:GetDataStore("BuffDataStore_Test51230012")

Players.PlayerAdded:Connect(function(player)
	local values = player:WaitForChild("Values")

	-- Restore buffs (AppleBuff, etc.)
	local success, data = pcall(function()
		return buffStore:GetAsync(player.UserId)
	end)

	if success and data then
		for buffName, buffData in pairs(data) do
			-- Restore the buff itself
			local buff = Instance.new("BoolValue")
			buff.Name = buffName
			buff.Parent = values
			buff:SetAttribute("TimeLeft", buffData.timeLeft)

			-- Apply the buff effect dynamically by checking the buff's name
			local multiplier = values:FindFirstChild("totalMultiplier")
			if buffName == "AppleBuff" and multiplier then
				local original = multiplier.Value
				multiplier.Value = original * 1.5
			end

			local multiplier = values:FindFirstChild("totalTimeReduce")
			if buffName == "BananaBuff" and multiplier then
				local original = multiplier.Value
				multiplier.Value = original * 1.5
			end

			-- Handle the timer countdown
			local startTime = os.time()
			local expirationTime = startTime + buffData.timeLeft  -- timeLeft is in seconds

			-- Timer logic for the buff duration
			task.spawn(function()
				while expirationTime > os.time() do
					task.wait(1)
					local timeLeft = expirationTime - os.time()
					buff:SetAttribute("TimeLeft", timeLeft)
				end

				-- If the buff has expired, clean it up
				if buff and buff.Parent then
					-- Restore multiplier to the original state after the buff ends
					if buffName == "AppleBuff" then
						local multiplier = values:FindFirstChild("totalMultiplier")
						if multiplier then
							local original = multiplier.Value / 1.5 -- Undo the multiplier increase
							multiplier.Value = original
						end
					end
					buff:Destroy()

					if buffName == "BananaBuff" then
						local multiplier = values:FindFirstChild("totalTimeReduce")
						if multiplier then
							local original = multiplier.Value / 1.5 -- Undo the multiplier increase
							multiplier.Value = original
						end
					end
					buff:Destroy()
				end
			end)
		end
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local buffData = {}
	local values = player:FindFirstChild("Values")
	if values then
		for _, buff in pairs(values:GetChildren()) do
			if buff:GetAttribute("TimeLeft") then
				buffData[buff.Name] = { timeLeft = buff:GetAttribute("TimeLeft") }
			end
		end

		-- Save the data dynamically (all buffs with time left)
		pcall(function()
			buffStore:SetAsync(player.UserId, buffData)
		end)
	end
end)
1 Like

I hope someone can help me because its nesseccary for my game

1 Like

I believe you have a race condition going on between your OnUse function in the Apple module and the count-down loop that restores the buff in your DataStore loader

The conflict happens on these two bits of code:

-- Apple module – tries to extend the timer
local currentTime = existingBuff:GetAttribute("TimeLeft") or 0
existingBuff:SetAttribute("TimeLeft", currentTime + 20)
-- DataStore script – overwrites the timer every second
while expirationTime > os.time() do
    task.wait(1)
    local timeLeft = expirationTime - os.time()
    buff:SetAttribute("TimeLeft", timeLeft)   -- <- clobbers the value set above
end

OnUse adds +20 seconds, but one frame later the coroutine still thinks the old expirationTime is correct, recalculates timeLeft, and writes the smaller number back – so the UI “snaps” to the old value.

You’ll wanna use an ExpireAt timestamp to fix this, because every script just pushes that value further into the future; the coroutine always derives the latest time from the same source of truth

Heres your updated scripts:

Apple module

return {
	Name = "Apple",
	Image = "rbxassetid://8221939314",
	Description = "Multiplies your totalMultiplier by 1.5 for 5 minutes. Reusing stacks the time.",
	OnUse = function(player)
		local values = player:FindFirstChild("Values")
		if not values then return end

		local multiplier = values:FindFirstChild("totalMultiplier")
		if not multiplier or not multiplier:IsA("NumberValue") then return end

		local buffName = "AppleBuff"
		local existingBuff = values:FindFirstChild(buffName)
		local duration = 20                               -- change to 300 for 5 min
		local now = os.time()

		if not existingBuff then
			existingBuff = Instance.new("BoolValue")
			existingBuff.Name = buffName
			existingBuff.Parent = values

			existingBuff:SetAttribute("OriginalValue", multiplier.Value)
			multiplier.Value = multiplier.Value * 1.5

			existingBuff:SetAttribute("ExpireAt", now + duration)

			task.spawn(function()
				while existingBuff and existingBuff.Parent do
					local left = (existingBuff:GetAttribute("ExpireAt") or now) - os.time()
					if left <= 0 then break end
					existingBuff:SetAttribute("TimeLeft", left)
					task.wait(1)
				end
				if existingBuff and existingBuff.Parent then
					multiplier.Value = existingBuff:GetAttribute("OriginalValue")
					existingBuff:Destroy()
				end
			end)
		else
			local expireAt = existingBuff:GetAttribute("ExpireAt") or now
			existingBuff:SetAttribute("ExpireAt", math.max(expireAt, now) + duration)
		end
	end
}
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local buffStore = DataStoreService:GetDataStore("BuffDataStore_Test51230012")

Players.PlayerAdded:Connect(function(player)
	local values = player:WaitForChild("Values")

	local success, data = pcall(function()
		return buffStore:GetAsync(player.UserId)
	end)

	if success and data then
		for buffName, buffData in pairs(data) do
			if buffData.expireAt and buffData.expireAt > os.time() then
				local buff = Instance.new("BoolValue")
				buff.Name = buffName
				buff.Parent = values
				buff:SetAttribute("ExpireAt", buffData.expireAt)

				if buffName == "AppleBuff" then
					local mult = values:FindFirstChild("totalMultiplier")
					if mult then
						buff:SetAttribute("OriginalValue", mult.Value)
						mult.Value = mult.Value * 1.5
					end
				elseif buffName == "BananaBuff" then
					local mult = values:FindFirstChild("totalTimeReduce")
					if mult then
						buff:SetAttribute("OriginalValue", mult.Value)
						mult.Value = mult.Value * 1.5
					end
				end

				task.spawn(function()
					while buff and buff.Parent do
						local left = (buff:GetAttribute("ExpireAt") or 0) - os.time()
						if left <= 0 then break end
						buff:SetAttribute("TimeLeft", left)
						task.wait(1)
					end
					if buff and buff.Parent then
						local field = buffName == "AppleBuff" and "totalMultiplier" or "totalTimeReduce"
						local mult = values:FindFirstChild(field)
						if mult then
							mult.Value = buff:GetAttribute("OriginalValue")
						end
						buff:Destroy()
					end
				end)
			end
		end
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local values = player:FindFirstChild("Values")
	if not values then return end

	local payload = {}
	for _, buff in pairs(values:GetChildren()) do
		local expireAt = buff:GetAttribute("ExpireAt")
		if expireAt then
			payload[buff.Name] = { expireAt = expireAt }
		end
	end

	if next(payload) then
		pcall(function()
			buffStore:SetAsync(player.UserId, payload)
		end)
	end
end)

Lmk if this helps at all

Thank you mate, you really saved my day. It worked! :))

1 Like