Rotating market store synced through all servers

Last three days I’ve been stuck on this code. The goal for this script is the have the server script tell the client which frames need to be shown in the store for the day.

  1. I need help fixing my code to save the previous dated frames that were shown. (or if someone can recommend another solution, ideally I would like to have this display frame idea so we can add frames later on easily.)

  2. The issue is the script is not remembering what were the pervious frames shown (Yesterdays frames).

[My though process was that this script can just see what was shown yesterday and make sure not to have repeating frames displayed for the player, which would be back to back days.]

  1. I’ve learned a lot of this great post here but couldn’t get my version to work.
    How to Make Server-Synced Daily Shops

Server Sided Script to handle rotation

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownTimer = ReplicatedStorage:FindFirstChild("DailyTowerSkinCountDownTimer")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

local DataStoreService = game:GetService("DataStoreService")
local framesDataStore = DataStoreService:GetDataStore("MarketFramesDataStore")



local manualDate = { year = 2023, month = 1, day = 7 } -- Change this date as needed for testing

-- Function to get today's date as a seed or a specified manual date
local function getTodaySeed()
	local now = os.time(manualDate)
	local date = os.date("!*t", now)
	local seed = date.year * 10000 + date.month * 100 + date.day -- YYYYMMDD format

	-- Print the date and the seed for debugging
	print(string.format("Using Date: %04d-%02d-%02d", date.year, date.month, date.day))
	print("Current Seed:", seed)

	return seed
end



local function saveFramesState(framesToShow)
	local success, errorMessage = pcall(function()
		framesDataStore:SetAsync("LastShownFrames", framesToShow)
	end)
	if not success then
		warn("Failed to save frames state: "..errorMessage)
	end
end


local function loadFramesState()
	local success, framesToShow = pcall(function()
		return framesDataStore:GetAsync("LastShownFrames")
	end)
	if success and framesToShow then
		return framesToShow
	else
		warn("Failed to load frames state.")
		return nil
	end
end



local Common = {
	"ArcherTowerGreen",
	"ArcherTowerGreenTest1",
	"ArcherTowerGreenTest2",
	"ArcherTowerGreenTest3",
	"ArcherTowerGreenTest4",
	"ArcherTowerGreenTest5",
	"ArcherTowerGreenTest6",
	"ArcherTowerGreenTest7",
}

local Uncommon = {
	"ArcherTowerUncommonTest1", 
	"ArcherTowerUncommonTest2", 
	"ArcherTowerUncommonTest3",
	"ArcherTowerUncommonTest4", 
	"ArcherTowerUncommonTest5", 
	"ArcherTowerUncommonTest6", 
}

local Rare = {
	"ArcherTowerTiny", 
	"ArcherTowerPost",
	"ArcherTowerPostTest1",
	"ArcherTowerPostTest2",
	"ArcherTowerPostTest3",
	"ArcherTowerPostTest4",
}

local lastShown = {
	Common = {},
	Uncommon = {},
	Rare = {}
}



local function selectRandomFrames(frameList, numToSelect, lastShown)
	local seed = getTodaySeed()
	local rng = Random.new(seed)
	local tempList = {}
	for _, frameName in ipairs(frameList) do
		if not table.find(lastShown, frameName) then
			table.insert(tempList, frameName)
		end
	end

	if #tempList == 0 then tempList = frameList end

	local selectedFrames = {}
	while #selectedFrames < numToSelect and #tempList > 0 do
		local index = rng:NextInteger(1, #tempList)
		table.insert(selectedFrames, tempList[index])
		table.remove(tempList, index)
	end
	return selectedFrames
end


local function activateFrames()
	-- Select new frames based on the last shown
	local selectedCommon = selectRandomFrames(Common, 3, lastShown.Common)
	local selectedUncommon = selectRandomFrames(Uncommon, 2, lastShown.Uncommon)
	local selectedRare = selectRandomFrames(Rare, 1, lastShown.Rare)

	local framesToShow = {
		Show = {
			Common = selectedCommon,
			Uncommon = selectedUncommon,
			Rare = selectedRare
		}
	}

	-- Update the global lastShown with the newly shown frames for the next cycle
	lastShown = {
		Common = selectedCommon,
		Uncommon = selectedUncommon,
		Rare = selectedRare
	}

	-- Save the current state to DataStore
	saveFramesState(framesToShow)

	-- Log the frames being shown
	print("Activated new frames.")
	for rarity, frames in pairs(framesToShow.Show) do
		print(rarity .. " Show:", table.concat(frames, ", "))
	end

	-- Notify all clients about the new frames
	updateEvent:FireAllClients(framesToShow)
end


local function getTimeUntilMidnightPST()
	local now = os.time()
	local utcOffset = -7 * 3600 -- Adjusted for PST
	local pstTime = now + utcOffset
	local midnightPST = os.date("!*t", pstTime)
	midnightPST.hour = 24; midnightPST.min = 0; midnightPST.sec = 0
	local midnightPSTinUTC = os.time(midnightPST) - utcOffset
	local secondsUntilMidnight = midnightPSTinUTC - now
	if secondsUntilMidnight <= 0 then
		secondsUntilMidnight = secondsUntilMidnight + 86400 -- Next day
	end
	return secondsUntilMidnight
end



local function updateCountdown()
	while true do
		local secondsUntilMidnight = getTimeUntilMidnightPST()
		CountdownTimer.Value = secondsUntilMidnight
		if secondsUntilMidnight <= 0 then
			activateFrames()
		end
		wait(1)
	end
end


game.Players.PlayerAdded:Connect(function(player)
	local framesToShow = loadFramesState() or activateFrames()
	updateEvent:FireClient(player, framesToShow)
end)

updateCountdown()

client sided script to handle visual update

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

-- Ensure the player's GUI is fully loaded
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local shopGUI = playerGui:WaitForChild("Shop", 10) -- Timeout for safety
assert(shopGUI, "Shop GUI not found")

local framePath = shopGUI:WaitForChild("Frame"):WaitForChild("TowerSkinShop"):WaitForChild("TowerShopScroll"):WaitForChild("DailySkinsFrames")

local function updateFramesVisibility(framesToShow)
	print("Received frames to update on client.")


	for category, frames in pairs(framesToShow.Show or {}) do
		print("Showing frames in category: "..category)
		for _, frameName in ipairs(frames) do
			local frame = framePath:FindFirstChild(frameName)
			if frame then
				frame.Visible = true
				print("Showing frame: "..frameName)
			else
				print("Frame not found: "..frameName)
			end
		end
	end

	for category, frames in pairs(framesToShow.Hide or {}) do
		print("Hiding frames in category: "..category)
		for _, frameName in ipairs(frames) do
			local frame = framePath:FindFirstChild(frameName)
			if frame then
				frame.Visible = false
				print("Hiding frame: "..frameName)
			else
				print("Frame not found: "..frameName)
			end
		end
	end
end

updateEvent.OnClientEvent:Connect(updateFramesVisibility)

Thank you

1 Like

I made some changes, but still not getting the result what I wanted, I’m still struggling to prevent the same frames from being selected for each day. The frames should not repeat back to back.

Update code/ changed

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownTimer = ReplicatedStorage:FindFirstChild("DailyTowerSkinCountDownTimer")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

local DataStoreService = game:GetService("DataStoreService")
local framesDataStore = DataStoreService:GetDataStore("MarketFramesDataStore")

local lastSelections = {}
local currentSelections = {}


local manualDate = { year = 2023, month = 1, day = 20 } -- Change this date as needed for testing


local function getTodaySeed()
	local now = os.time(manualDate)
	local date = os.date("!*t", now)
	local seed = date.year * 10000 + date.month * 100 + date.day
	print(string.format("Using Date: %04d-%02d-%02d", date.year, date.month, date.day))
	print("Current Seed:", seed)
	return seed
end



local Common = {
	"ArcherTowerGreen",
	"ArcherTowerGreenTest1",
	"ArcherTowerGreenTest2",
	"ArcherTowerGreenTest3",
	"ArcherTowerGreenTest4",
	"ArcherTowerGreenTest5",
	"ArcherTowerGreenTest6",
	"ArcherTowerGreenTest7",
}

local Uncommon = {
	"ArcherTowerUncommonTest1", 
	"ArcherTowerUncommonTest2", 
	"ArcherTowerUncommonTest3",
	"ArcherTowerUncommonTest4", 
	"ArcherTowerUncommonTest5", 
	"ArcherTowerUncommonTest6", 
}

local Rare = {
	"ArcherTowerTiny", 
	"ArcherTowerPost",
	"ArcherTowerPostTest1",
	"ArcherTowerPostTest2",
	"ArcherTowerPostTest3",
	"ArcherTowerPostTest4",
}

local function shuffleTable(t, seed)
	math.randomseed(seed)
	for i = #t, 2, -1 do
		local j = math.random(i)
		t[i], t[j] = t[j], t[i]
	end
end

local function selectTowers()
	local seed = getTodaySeed()
	local availableCommon, availableUncommon, availableRare = {}, {}, {}

	-- Filter out previous day's selections
	for _, tower in ipairs(Common) do
		if not lastSelections[tower] then
			table.insert(availableCommon, tower)
		end
	end
	for _, tower in ipairs(Uncommon) do
		if not lastSelections[tower] then
			table.insert(availableUncommon, tower)
		end
	end
	for _, tower in ipairs(Rare) do
		if not lastSelections[tower] then
			table.insert(availableRare, tower)
		end
	end

	shuffleTable(availableCommon, seed)
	shuffleTable(availableUncommon, seed)
	shuffleTable(availableRare, seed)

	local selectedCommon = {availableCommon[1], availableCommon[2], availableCommon[3]}
	local selectedUncommon = {availableUncommon[1], availableUncommon[2]}
	local selectedRare = {availableRare[1]}

	-- Clear previous day's selections and update with today's selections
	lastSelections = {}
	for _, item in ipairs(selectedCommon) do lastSelections[item] = true end
	for _, item in ipairs(selectedUncommon) do lastSelections[item] = true end
	for _, item in ipairs(selectedRare) do lastSelections[item] = true end

	print("Selected Common Towers: ", table.concat(selectedCommon, ", "))
	print("Selected Uncommon Towers: ", table.concat(selectedUncommon, ", "))
	print("Selected Rare Tower: ", selectedRare[1])

	return selectedCommon, selectedUncommon, selectedRare
end

local function updateClientDisplays(selectedCommon, selectedUncommon, selectedRare)
	local framesToShow = {unpack(selectedCommon), unpack(selectedUncommon), unpack(selectedRare)}
	local framesToHide = {}

	-- Clear previous selections from client display
	for frame, _ in pairs(currentSelections) do
		if not lastSelections[frame] then
			table.insert(framesToHide, frame)
		end
	end

	currentSelections = lastSelections -- Update current selections to the last for the next day transition
	updateEvent:FireAllClients({Show = framesToShow, Hide = framesToHide})
end

-- When selecting towers and updating clients
local common, uncommon, rare = selectTowers()
updateClientDisplays(common, uncommon, rare)

out put logs

  18:15:27.023  Using Date: 2023-01-16  -  Server - DailyTowerSkinRotationManager:19
  18:15:27.023  Current Seed: 20230116  -  Server - DailyTowerSkinRotationManager:20
  18:15:27.023  Selected Common Towers:  ArcherTowerGreenTest1, ArcherTowerGreen, ArcherTowerGreenTest5  -  Server - DailyTowerSkinRotationManager:98
  18:15:27.023  Selected Uncommon Towers:  ArcherTowerUncommonTest1, ArcherTowerUncommonTest6  -  Server - DailyTowerSkinRotationManager:99
  18:15:27.023  Selected Rare Tower:  ArcherTowerTiny  -  Server - DailyTowerSkinRotationManager:100

  18:15:40.525  Using Date: 2023-01-17  -  Server - DailyTowerSkinRotationManager:19
  18:15:40.525  Current Seed: 20230117  -  Server - DailyTowerSkinRotationManager:20
  18:15:40.525  Selected Common Towers:  ArcherTowerGreenTest4, ArcherTowerGreenTest2, ArcherTowerGreenTest5  -  Server - DailyTowerSkinRotationManager:98
  18:15:40.525  Selected Uncommon Towers:  ArcherTowerUncommonTest5, ArcherTowerUncommonTest2  -  Server - DailyTowerSkinRotationManager:99
  18:15:40.525  Selected Rare Tower:  ArcherTowerPostTest3  -  Server - DailyTowerSkinRotationManager:100

  18:16:13.569  Using Date: 2023-01-18  -  Server - DailyTowerSkinRotationManager:19
  18:16:13.569  Current Seed: 20230118  -  Server - DailyTowerSkinRotationManager:20
  18:16:13.570  Selected Common Towers:  ArcherTowerGreen, ArcherTowerGreenTest4, ArcherTowerGreenTest1  -  Server - DailyTowerSkinRotationManager:98
  18:16:13.570  Selected Uncommon Towers:  ArcherTowerUncommonTest1, ArcherTowerUncommonTest4  -  Server - DailyTowerSkinRotationManager:99
  18:16:13.570  Selected Rare Tower:  ArcherTowerTiny  -  Server - DailyTowerSkinRotationManager:100


  18:16:50.399  Using Date: 2023-01-19  -  Server - DailyTowerSkinRotationManager:19
  18:16:50.399  Current Seed: 20230119  -  Server - DailyTowerSkinRotationManager:20
  18:16:50.399  Selected Common Towers:  ArcherTowerGreenTest3, ArcherTowerGreenTest2, ArcherTowerGreenTest7  -  Server - DailyTowerSkinRotationManager:98
  18:16:50.399  Selected Uncommon Towers:  ArcherTowerUncommonTest2, ArcherTowerUncommonTest3  -  Server - DailyTowerSkinRotationManager:99
  18:16:50.399  Selected Rare Tower:  ArcherTowerPost  -  Server - DailyTowerSkinRotationManager:100

  18:17:37.205  Using Date: 2023-01-20  -  Server - DailyTowerSkinRotationManager:19
  18:17:37.205  Current Seed: 20230120  -  Server - DailyTowerSkinRotationManager:20
  18:17:37.205  Selected Common Towers:  ArcherTowerGreenTest4, ArcherTowerGreenTest3, ArcherTowerGreenTest7  -  Server - DailyTowerSkinRotationManager:98
  18:17:37.205  Selected Uncommon Towers:  ArcherTowerUncommonTest3, ArcherTowerUncommonTest6  -  Server - DailyTowerSkinRotationManager:99
  18:17:37.205  Selected Rare Tower:  ArcherTowerPostTest1  -  Server - DailyTowerSkinRotationManager:100

Greetings OP!

If I’m not mistaken. When you use shuffleTable it doesn’t create a deep copy of that table. If you shuffle the table using the same seed it will not return the same value. Think of it as shuffling a shuffled card. It’s not the same even with if you give the randomizer a seed.

I’ve tried creating a shop system when I saw this problem. You can check it out on how it works and compare it to your own system. Since both server and client are in sync using the current time as the seed. It will only fires the client if there’s a custom products that needs to be displayed instead.

ShopSystem.rbxl (72.7 KB)

1 Like

Wow thank you for this. I’m going to study it now. Last night I continued to work on my project. I believe I got closer but the problem is when I restart the server and come back it swaps to 2 different saved seeds if that makes sense. For example first start up ill get “seed1” just for example. then I end the server and start again without changing a date, ill get “seed2”. I restart again and get “seed1” again for the same date, back to “seed2” esc… I will study your work and see I can incorpoprate your improved script. Thank you. here is the new script btw im talking about.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownTimer = ReplicatedStorage:FindFirstChild("DailyTowerSkinCountDownTimer")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

local DataStoreService = game:GetService("DataStoreService")
local framesDataStore = DataStoreService:GetDataStore("MarketFramesDataStore")

local manualTestDate = {year = 3000, month = 1, day = 11}  -- Manual date for testing

local lastSelectionsKey = "LastShownFrames"
local initializationKey = "InitializationDate"


local Common = {
	"ArcherTowerGreen",
	"ArcherTowerGreenTest1",
	"ArcherTowerGreenTest2",
	"ArcherTowerGreenTest3",
	"ArcherTowerGreenTest4",
	"ArcherTowerGreenTest5",
	"ArcherTowerGreenTest6",
	"ArcherTowerGreenTest7",
}

local Uncommon = {
	"ArcherTowerUncommonTest1", 
	"ArcherTowerUncommonTest2", 
	"ArcherTowerUncommonTest3",
	"ArcherTowerUncommonTest4", 
	"ArcherTowerUncommonTest5", 
	"ArcherTowerUncommonTest6", 
}

local Rare = {
	"ArcherTowerTiny", 
	"ArcherTowerPost",
	"ArcherTowerPostTest1",
	"ArcherTowerPostTest2",
	"ArcherTowerPostTest3",
	"ArcherTowerPostTest4",
}


local cachedSeed = nil

local function getTodaySeed()
	local today = os.date("%Y-%m-%d", os.time(manualTestDate or os.date("*t")))
	local seed = today:gsub("-", "")
	cachedSeed = cachedSeed or framesDataStore:GetAsync("DailySeed") or seed
	if not framesDataStore:GetAsync("DailySeed") then
		framesDataStore:SetAsync("DailySeed", seed)
	end
	return cachedSeed
end

local function shuffleTable(t)
	local seed = getTodaySeed()
	math.randomseed(seed)
	for i = #t, 2, -1 do
		local j = math.random(i)
		t[i], t[j] = t[j], t[i]
	end
end

local function selectRandomFrames(towerList, count, lastShown)
	local available = {}
	for _, frame in ipairs(towerList) do
		if not lastShown[frame] then
			table.insert(available, frame)
		end
	end
	shuffleTable(available)
	return {table.unpack(available, 1, math.min(count, #available))}
end

local function fetchLastSelections()
	local success, result = pcall(function()
		return framesDataStore:GetAsync(lastSelectionsKey)
	end)
	return success and result or {}
end

local function saveSelections(selections)
	local success, errorMessage = pcall(function()
		framesDataStore:SetAsync(lastSelectionsKey, selections)
	end)
	if not success then
		error("Failed to save current selections: " .. (errorMessage or "unknown error"))
	end
end

local function updateFrameVisibility()
	local lastShown = fetchLastSelections()
	local selectedCommon = selectRandomFrames(Common, 3, lastShown)
	local selectedUncommon = selectRandomFrames(Uncommon, 2, lastShown)
	local selectedRare = selectRandomFrames(Rare, 1, lastShown)

	local framesToShow = {
		Common = selectedCommon,
		Uncommon = selectedUncommon,
		Rare = selectedRare
	}

	local newSelections = {}
	for _, list in pairs(framesToShow) do
		for _, frame in ipairs(list) do
			newSelections[frame] = true
		end
	end

	saveSelections(newSelections)

	local dataToSend = {
		Show = framesToShow,
		Hide = {}
	}

	for frame, _ in pairs(lastShown) do
		if not newSelections[frame] then
			local category = (table.find(Common, frame) and "Common") or (table.find(Uncommon, frame) and "Uncommon") or (table.find(Rare, frame) and "Rare")
			dataToSend.Hide[category] = dataToSend.Hide[category] or {}
			table.insert(dataToSend.Hide[category], frame)
		end
	end

	updateEvent:FireAllClients(dataToSend)
end

local function initializeIfNeeded()
	local today = os.date("%Y-%m-%d")
	local lastInitDate = framesDataStore:GetAsync(initializationKey)
	if lastInitDate ~= today then
		framesDataStore:SetAsync(initializationKey, today)
		updateFrameVisibility()
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:WaitForChild("Humanoid")
		initializeIfNeeded()
		updateFrameVisibility()
	end)
end)

-- Check if initialization is needed at server start
initializeIfNeeded()

As an update to my script, I almost got it to where I want. But the issue now is I cant figure out why when players join the game in session, Their GUIs aren’t updated to display any frames from the server script. Only works when I start the server as I play the game in Studio, not if I test with players or the official servers, when the servers are already running.

Server Script

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownTimer = ReplicatedStorage:FindFirstChild("DailyTowerSkinCountDownTimer")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

local DataStoreService = game:GetService("DataStoreService")
local framesDataStore = DataStoreService:GetDataStore("MarketFramesDataStore")

local manualTestDate = nil  -- Manual date for testing format: {year = 3000, month = 1, day = 1} or nil 

local lastSelectionsKey = "LastShownFrames"
local initializationKey = "InitializationDate"


local Common = {
	"ArcherTowerGreen",
	"ArcherTowerGreenTest1",
	"ArcherTowerGreenTest2",
	"ArcherTowerGreenTest3",
	"ArcherTowerGreenTest4",
	"ArcherTowerGreenTest5",
	"ArcherTowerGreenTest6",
	"ArcherTowerGreenTest7",
}

local Uncommon = {
	"ArcherTowerUncommonTest1", 
	"ArcherTowerUncommonTest2", 
	"ArcherTowerUncommonTest3",
	"ArcherTowerUncommonTest4", 
	"ArcherTowerUncommonTest5", 
	"ArcherTowerUncommonTest6", 
}

local Rare = {
	"ArcherTowerTiny", 
	"ArcherTowerPost",
	"ArcherTowerPostTest1",
	"ArcherTowerPostTest2",
	"ArcherTowerPostTest3",
	"ArcherTowerPostTest4",
}


local cachedSeed = nil

local function getTodaySeed()
	local today = os.date("%Y-%m-%d")
	local seed = today:gsub("-", "")
	cachedSeed = cachedSeed or framesDataStore:GetAsync("DailySeed") or seed
	if not framesDataStore:GetAsync("DailySeed") then
		framesDataStore:SetAsync("DailySeed", seed)
	end
	print("Using seed: " .. seed)
	return seed
end


local function shuffleTable(t)
	local seed = getTodaySeed()
	math.randomseed(seed)
	local initialState = table.concat(t, ", ")
	for i = #t, 2, -1 do
		local j = math.random(i)
		t[i], t[j] = t[j], t[i]
	end
end


local function selectRandomFrames(towerList, count, lastShown)
	local available = {}
	for _, frame in ipairs(towerList) do
		if not lastShown[frame] then
			table.insert(available, frame)
		end
	end
	shuffleTable(available)
	return {table.unpack(available, 1, math.min(count, #available))}
end

local function fetchLastSelections()
	local success, result = pcall(function()
		return framesDataStore:GetAsync(lastSelectionsKey)
	end)
	return success and result or {}
end

local function saveSelections(selections)
	local success, errorMessage = pcall(function()
		framesDataStore:SetAsync(lastSelectionsKey, selections)
	end)
	if not success then
		error("Failed to save current selections: " .. (errorMessage or "unknown error"))
	end
end

local function updateFrameVisibility()
	local lastShown = fetchLastSelections()
	local selectedCommon = selectRandomFrames(Common, 3, lastShown)
	local selectedUncommon = selectRandomFrames(Uncommon, 2, lastShown)
	local selectedRare = selectRandomFrames(Rare, 1, lastShown)

	local framesToShow = {
		Common = selectedCommon,
		Uncommon = selectedUncommon,
		Rare = selectedRare
	}

	local newSelections = {}
	for _, list in pairs(framesToShow) do
		for _, frame in ipairs(list) do
			newSelections[frame] = true
		end
	end

	saveSelections(newSelections)

	local dataToSend = {
		Show = framesToShow,
		Hide = {}
	}

	for frame, _ in pairs(lastShown) do
		if not newSelections[frame] then
			local category = (table.find(Common, frame) and "Common") or (table.find(Uncommon, frame) and "Uncommon") or (table.find(Rare, frame) and "Rare")
			dataToSend.Hide[category] = dataToSend.Hide[category] or {}
			table.insert(dataToSend.Hide[category], frame)
		end
	end

	updateEvent:FireAllClients(dataToSend)
end

local function checkDateChange()
	local currentDate = os.date("%Y-%m-%d")
	if cachedDate ~= currentDate then
		cachedDate = currentDate  -- Update the cached date
		print("Date has changed to: " .. currentDate)
		updateFrameVisibility()  -- Update the frame visibility for all clients
	end
end

local function initializeIfNeeded()
	local today = os.date("%Y-%m-%d")
	local lastInitDate = framesDataStore:GetAsync(initializationKey)
	print("Last initialization date from DataStore: " .. (lastInitDate or "none"))
	if lastInitDate ~= today then
		framesDataStore:SetAsync(initializationKey, today)
		cachedDate = today  -- Set cached date on initialization
		updateFrameVisibility()
	end
end

local function getTimeUntilMidnightPST()
	local now = os.time()
	local utcOffset = -7 * 3600  -- PST is UTC-7
	local midnight = os.date("!*t", now + 86400)  -- Get noon tomorrow in UTC
	midnight.hour = 0
	midnight.min = 0
	midnight.sec = 0
	local midnightPST = os.time(midnight) + utcOffset
	return midnightPST - now
end

local function updateCountdownTimer()
	while true do
		local secondsUntilMidnight = getTimeUntilMidnightPST()
		CountdownTimer.Value = secondsUntilMidnight
		wait(1)  -- Update every second
	end
end

spawn(updateCountdownTimer)  -- Run the countdown in a separate thread

-- Periodic check for date change every minute
while wait(1) do
	checkDateChange()
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:WaitForChild("Humanoid")
		initializeIfNeeded()
		updateFrameVisibility()
	end)
end)

-- Check if initialization is needed at server start
initializeIfNeeded()

Client Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local updateEvent = ReplicatedStorage:WaitForChild("UpdateTowerSkinsDisplay")

-- Ensure the player's GUI is fully loaded
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local shopGUI = playerGui:WaitForChild("Shop", 10) -- Timeout for safety
assert(shopGUI, "Shop GUI not found")

local framePath = shopGUI:WaitForChild("Frame"):WaitForChild("TowerSkinShop"):WaitForChild("TowerShopScroll"):WaitForChild("DailySkinsFrames")

local guiReady = false

-- Setting up the GUI and marking ready
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local shopGUI = playerGui:WaitForChild("Shop", 10) -- Timeout for safety
assert(shopGUI, "Shop GUI not found")
local framePath = shopGUI:WaitForChild("Frame"):WaitForChild("TowerSkinShop"):WaitForChild("TowerShopScroll"):WaitForChild("DailySkinsFrames")
guiReady = true


local function updateFramesVisibility(data)
	if not guiReady then
		print("GUI not ready. Delaying update...")
		return
	end
	print("Received frames data on client.")
	if data.Show then
		for category, frames in pairs(data.Show) do
			print("Showing frames in category:", category)
			for _, frameName in ipairs(frames) do
				local frame = framePath:FindFirstChild(frameName)
				if frame then
					frame.Visible = true
					print("Showing frame:", frameName)
				else
					print("Frame not found:", frameName)
				end
			end
		end
	end

	if data.Hide then
		for category, frames in pairs(data.Hide) do
			print("Hiding frames in category:", category)
			for _, frameName in ipairs(frames) do
				local frame = framePath:FindFirstChild(frameName)
				if frame then
					frame.Visible = false
					print("Hiding frame:", frameName)
				else
					print("Frame not found:", frameName)
				end
			end
		end
	end
end

updateEvent.OnClientEvent:Connect(updateFramesVisibility)