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)