Restock issue | Please, I beg of you, look into this!

Please, I beg of you, help me fix this: I’ve been on this for days, it’s been eating into me and I can’t seem to find a way, it’s been stopping my game’s progress because I decided to rewrite it from 0 but it’s been driving me crazy. I REALLY but REALLY could use some help here.

My roblox game is similar to Grow a Garden, and it’s based off of it.

I’m working on a global stock system, but it’s been driving me crazy: I really don’t know what’s going on inside my code. The issues I’ve been encountering are that upon player leave, after buying something from the shop, the stock should update how they bought on rejoin, but it doesn’t, it just resets. Thing is that this used to work: stopped working after I fixed stock not loading when joining game. I’m really going insane. I beg for someoen to help me get through this.

If you’re willing to help, first of all, thank you so much, and let me know if you want the studio file, I’ll send it right away, or I can paste the codes in here, provide clips, and else.

Thank you so much.

1 Like

Provide the code.
You haven’t saved data in datastore or dont checking store initiation for a player.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local stockStore = DataStoreService:GetDataStore("PlayerEggStock")
local globalStockStore = DataStoreService:GetDataStore("GlobalEggRestocks")
local ServerStorage = game:GetService("ServerStorage")
local ToolStorage = ServerStorage:WaitForChild("Tools")

local DEBUG = true

local playerStockCache = {}
local globalStockCache = {}

local RESTOCK_INTERVAL = 5 * 60
local now = os.time()
local START_TIME

-- Load START_TIME from DataStore or create it on first-ever run
local success, savedStart = pcall(function()
	return globalStockStore:GetAsync("GlobalStartTime")
end)

if success and savedStart then
	START_TIME = savedStart
	if DEBUG then warn("[Startup] Loaded START_TIME:", START_TIME) end
else
	-- Only fallback for first-ever run
	START_TIME = now - (now % RESTOCK_INTERVAL)
	if DEBUG then warn("[Startup] Using fallback START_TIME:", START_TIME) end
end


local EggData = require(ReplicatedStorage.GUIs.Egg_Shop:WaitForChild("Egg_Data_Module"))
local restockEvent = ReplicatedStorage.RemoteEvents:WaitForChild("SendRestock")

local lastRestockNumber = -1

local function getRestockNumber()
	local currentTime = os.time()
	return math.floor((currentTime - START_TIME) / RESTOCK_INTERVAL)
end

local function generateStock()
	local restockNumber = getRestockNumber()
	math.randomseed(restockNumber)

	local stock = {}
	local shuffledEggs = {}

	for eggName, eggInfo in pairs(EggData) do
		table.insert(shuffledEggs, {Name = eggName, Info = eggInfo})
	end

	for i = #shuffledEggs, 2, -1 do
		local j = math.random(i)
		shuffledEggs[i], shuffledEggs[j] = shuffledEggs[j], shuffledEggs[i]
	end

	for _, egg in ipairs(shuffledEggs) do
		local eggName = egg.Name
		local eggInfo = egg.Info
		local chance = eggInfo.RestockChance or 1
		local willRestock = math.random() <= chance

		if willRestock then
			local min, max = 5, 10
			if eggInfo.RestockRange and typeof(eggInfo.RestockRange) == "table" and #eggInfo.RestockRange == 2 then
				min = eggInfo.RestockRange[1]
				max = eggInfo.RestockRange[2]
			end
			local count = math.random(min, max)
			stock[eggName] = count
		else
			stock[eggName] = 0
		end
	end

	return stock
end

local function getOrCreateGlobalStock(restockNumber)
	local success, data = pcall(function()
		return globalStockStore:GetAsync(tostring(restockNumber))
	end)

	if success then
		if data then
			globalStockCache[restockNumber] = data
			return data
		else
			if DEBUG then warn("[GlobalStock] No saved global stock found for restock #"..restockNumber..", generating new.") end
			local stock = generateStock()
			globalStockCache[restockNumber] = stock

			local saveSuccess, err = pcall(function()
				globalStockStore:SetAsync(tostring(restockNumber), stock)
			end)

			if not saveSuccess then
				warn("[GlobalStock] Failed to save global stock:", err)
			end

			return stock
		end
	else
		warn("[GlobalStock] Failed to fetch stock from DataStore:", data)
		-- Fallback: do NOT generate, just return nil to avoid corruption
		return nil
	end

end

local function restockLoop()
	while true do
		local lockedRestock = getRestockNumber()
		lastRestockNumber = lockedRestock

		local stock = getOrCreateGlobalStock(lockedRestock)
		
		if not globalStockCache["_StartTimeSaved"] then
			local success, _ = pcall(function()
				globalStockStore:SetAsync("GlobalStartTime", START_TIME)
			end)
			globalStockCache["_StartTimeSaved"] = true
		end


		for _, player in ipairs(Players:GetPlayers()) do
			local playerRestock = player:GetAttribute("RestockNumber")

			-- If the player has no stock, or is on a previous cycle, give them new stock
			if not playerStockCache[player] or playerRestock ~= lockedRestock then
				playerStockCache[player] = table.clone(stock)
				player:SetAttribute("RestockNumber", lockedRestock)

				-- Save fresh stock for this player
				local success, err = pcall(function()
					stockStore:SetAsync(player.UserId, {
						restockNumber = lockedRestock,
						stock = playerStockCache[player]
					})
				end)
				if not success then
					warn("[DataStore Error] Could not save stock for", player.Name, ":", err)
				end
			end

			-- Always send the player's current stock to their client
			restockEvent:FireClient(player, {
				stock = playerStockCache[player],
				restockNumber = lockedRestock,
				startTime = START_TIME
			})
		end


		local now = os.time()
		local secondsUntilNext = RESTOCK_INTERVAL - ((now - START_TIME) % RESTOCK_INTERVAL)
		local targetTime = os.time() + secondsUntilNext
		while os.time() < targetTime do
			task.wait(1)
		end

	end
end

Players.PlayerAdded:Connect(function(player)
	local userId = player.UserId
	local currentRestock = getRestockNumber()

	-- 🔒 Try to fetch global stock ONLY — do NOT generate here
	local globalStock = globalStockCache[currentRestock]
	if not globalStock then
		local success, data = pcall(function()
			return globalStockStore:GetAsync(tostring(currentRestock))
		end)

		if success and data then
			globalStockCache[currentRestock] = data
			globalStock = data
		else
			warn("[PlayerJoin] No global stock available for restock #"..currentRestock..". Blocking egg shop for player.")
			-- Do not assign anything — skip the egg shop entirely
			return
		end
	end

	-- ✅ Now try loading the player's personal stock
	local success, data = pcall(function()
		return stockStore:GetAsync(userId)
	end)

	if success and data and data.restockNumber == currentRestock then
		playerStockCache[player] = data.stock
		player:SetAttribute("RestockNumber", data.restockNumber)
	else
		playerStockCache[player] = table.clone(globalStock)
		player:SetAttribute("RestockNumber", currentRestock)

		local saveSuccess, err = pcall(function()
			stockStore:SetAsync(userId, {
				restockNumber = currentRestock,
				stock = playerStockCache[player]
			})
		end)
		if not saveSuccess then
			warn("[PlayerJoin] Failed to save initial stock for", player.Name, ":", err)
		end
	end

	-- Send stock to client
	restockEvent:FireClient(player, {
		stock = playerStockCache[player],
		restockNumber = currentRestock,
		startTime = START_TIME
	})

	if DEBUG then
		print("[PlayerJoin]", player.Name, "received stock:", playerStockCache[player])
	end
end)



Players.PlayerRemoving:Connect(function(player)
	local userId = player.UserId
	local stock = playerStockCache[player]
	local restockNumber = player:GetAttribute("RestockNumber")

	if stock and restockNumber then
		local success, err = pcall(function()
			stockStore:SetAsync(userId, {
				restockNumber = restockNumber,
				stock = stock
			})
		end)
		if not success then
			warn("[DataStore Error] Could not save on leave for", player.Name, ":", err)
		end
	end

	playerStockCache[player] = nil
end)

local purchaseEvent = ReplicatedStorage.RemoteEvents:WaitForChild("RequestEggPurchase")

purchaseEvent.OnServerEvent:Connect(function(player, eggName)
	local restockNumber = player:GetAttribute("RestockNumber")
	if not restockNumber or not globalStockCache[restockNumber] then
		warn("Missing global stock for restock number:", restockNumber, " — aborting purchase")
		return
	end
	local stock = playerStockCache[player]
	local eggData = EggData[eggName]
	if not stock or not eggData then return end

	if (stock[eggName] or 0) <= 0 then return end
	if not player:FindFirstChild("leaderstats") then return end
	if not player.leaderstats:FindFirstChild("Clams") then return end

	local clams = player.leaderstats.Clams.Value
	if clams < eggData.Cost then return end

	player.leaderstats.Clams.Value -= eggData.Cost
	stock[eggName] -= 1

	local restockNumber = player:GetAttribute("RestockNumber")
	if restockNumber then
		local success, err = pcall(function()
			stockStore:SetAsync(player.UserId, {
				restockNumber = restockNumber,
				stock = stock
			})
		end)
		if not success then
			warn("[DataStore Error] Failed to save stock after purchase for", player.Name, ":", err)
		end
	end

	local tool = ToolStorage:FindFirstChild(eggName)
	if tool then
		local clone = tool:Clone()
		clone.Parent = player:WaitForChild("Backpack")
		clone:Clone().Parent = player:WaitForChild("StarterGear")
	end

	ReplicatedStorage.RemoteEvents.EggPurchaseConfirmed:FireClient(player, eggName, stock[eggName])
end)

task.spawn(restockLoop)

I don’t know how your restock system works but I struggle too with my restock system in my game