How to make a Daily Shop that updates through all servers?

I have been trying for a long time now to figure out how I could possibly create a daily, self-updating shop that resets every 24 hours.

The problem I am facing is that it is not 100% accurate, and because I am using tick(), I am unable to display the time left before the shop resets.

I have been trying to search for similar topics on the Developer Forum and on YouTube also, but without any success. However, I have been taking some inspiration if not all from this very post:

This is something I would really like to know how to do. If you have any ideas on how I can go about this, then please let me know. I will leave an example of the code I am struggling with below:

local Items = {}

function getAvailableItems(day)
	local rng = Random.new(day)
	local shopItems = {}
	for i = 1, 6 do
		local item = Items[rng:NextInteger(1,#Items)]
		table.insert(shopItems, item)
	end
	return shopItems
end

local currentDay = nil
local currentShopItems = {}
local OFFSET = 0

local currentDay = nil
local currentShopItems = {}
local OFFSET = 0

while true do
    local day = (tick() / 3600) % 24
    if day ~= currentDay then
        currentDay = day
        currentShopItems = getAvailableItems(day)
        for i = 1, 6 do
             game.ReplicatedStorage.assets.shop["daily"..i].Value = currentShopItems[i]
        end
    end
    wait(1)
end
6 Likes

I think if you use random to select the items, because random isn’t truly random, it should hopefully select the same stuff in every server, maybe change the seed every time it resets. As for the resetting daily, I’m not sure, but you could try using (epoch or unix (Not sure which is which but there’s a video on live events by Alvin Blox which explains a bit)). Hope this helps :slight_smile:

I have already tried using some of AlvinBlox’s videos as resources, but it didn’t work very well. And I am not quite sure about what you mean by:

I think if you use random to select the items, because random isn’t truly random

Anyways, I appreciate your reply!

An idea is to save into a datastore what the time would be in 24 hours then minus from there to know where you’re at.

Use UpdateAsync to assure you won’t try to change the time to the same exact one on every server to prevent too many requests. (by returning nil in the update function to cancel)

I cannot test this so you can try it with caution.

Personally, I agree with that method. However, I’ve been told that using DataStore is not the best option. Furthermore, I am unsure whether or not this requires more than one script.

Thank you for your reply!

It utilizes it per 24 hours, I think its effect should be relatively low and alright.

I’m not sure how I would go about that, though. Would I have to change the script I sent above to where it would check every second from that DataStore if the time is the same or above the next day’s expected time?

You update a specific key for the new value 24 hours ahead then the current tick’s difference from that 24 hours ahead tick is what is the counter.

Then when it reaches the 24 hours ahead value, update with a new calculated number with an UpdateAsync function that makes sure the previous value isn’t the same as the new value. If it is, return nothing to cancel.

This will only require an update per 24 hours as it rejects any other updates that try to put in the same number if its already inputted in.

Not sure if I understand. Where should I do this? Do I get rid of my code completely?

I did this 2 weeks ago.

Here’s the method I went with:

Use os.time() as this will give a consistent UTC time, where as tick() is only the machines local time.

Run the result os.time() through os.date() (make sure you use “!*t” as the format, to avoid it converting to local time)

Then extract the year, month, and day out of the date dictionary, and run these through os.time() to get a timestamp. (This step is removing the hours, minutes & seconds from the time to keep it consistent for 24 hour periods). (You’ll have to add hour, min and secs to a new table you’re generating from these, set these to 0)

Make sure your using Random.new() instead of math.random(), and then use the result of the timestamp just made as the seed. This will give you a consistent random seed for any UTC-0 24 hour period, so anything you generate form it (cross server) will have the same result.

Of course you can adjust the time it changes by by adding some kind of offset to the first use of os.time()

3 Likes

So I assume that this will be compatible with my current code? If so, then it won’t be too hard to edit it and make it more efficient.

I outlined a method rather than specifics on how to script it.

To be honest, looking at your method properly, just switching to using os.time() instead of tick() should work.

1 Like

That worked! Thank you so much for your help!

Hello, I can go in depth since I figured this out

(Oops, replied to the wrong person.)

Os.time() is more or less accurate, but I dont use it because there have been reports of it being off by up to 3-5 minutes. I don’t know if os.time()'s inaccuracy is fixed now, but I use a module that gets time externally to make sure everything is ALWAYS in sync.

I’ll show you what I did (thank you for referencing my post haha)

Paste this in a module:

local monthStrMap = {
	Jan=1,
	Feb=2,
	Mar=3,
	Apr=4,
	May=5,
	Jun=6,
	Jul=7,
	Aug=8,
	Sep=9,
	Oct=10,
	Nov=11,
	Dec=12
}
local HttpService = game:GetService("HttpService")
local function RFC2616DateStringToUnixTimestamp(dateStr)
	local day, monthStr, year, hour, min, sec = dateStr:match(".*, (.*) (.*) (.*) (.*):(.*):(.*) .*")
	local month = monthStrMap[monthStr]
	local date = {
		day=day,
		month=month,
		year=year,
		hour=hour,
		min=min,
		sec=sec
	}
	
	return os.time(date)
end

local isInited = false
local originTime = nil
local responseTime = nil
local responseDelay = nil
local function inited()
	return isInited
end

local function init()
	if not isInited then
		local ok = pcall(function()
			local requestTime = tick()
			local response = HttpService:RequestAsync({Url="http://google.com"}) 
			local dateStr = response.Headers.date
			originTime = RFC2616DateStringToUnixTimestamp(dateStr)
			responseTime = tick()
			-- Estimate the response delay due to latency to be half the rtt time
			responseDelay = (responseTime-requestTime)/2
		end)
		if not ok then
			warn("Cannot get time from google.com. Make sure that http requests are enabled!")
			originTime = os.time()
			responseTime = tick()
			responseDelay = 0
		end
		
		isInited = true
	end
end

local function time()
	if not isInited then
		init()
	end
	
	return originTime + tick()-responseTime - responseDelay
end

return {
	inited=inited,
	init=init,
	time=time
}

Here is my shop script (I’ll explain it, too) with weighted randomization for item rarity. If you don’t want item rarity, then just use one table and change the for loop to only get random items in that table.

local syncedtime = require(game.ReplicatedStorage.Utilities.SyncedTime) -- require the module

syncedtime.init() -- Will make the request to google.com to get a time. This will always be synced on all servers, because the time is external.

-- Rarity tables (put any items you want in there)

local commonItems = {
	"Common Item",
	"Common Item",
	"Common Item",
	"Common Item"
}

local uncommonItems = {
	"Uncommon Item",
	"Uncommon Item",
	"Uncommon Item",
	"Uncommon Item" 
}

local rareItems = {
	"Rare Item",
	"Rare Item",
	"Rare Item",
	"Rare Item"
}

local veryrareItems = {
	"Very Rare Item",
	"Very Rare Item",
	"Very Rare Item",
	"Very Rare Item"
}

local legendaryItems = {
	"Legendary Item",
	"Legendary Item",
	"Legendary Item",
	"Legendary Item"
}

local mythicalItems = {
	"Mythical Item",
	"Mythical Item",
	"Mythical Item",
	"Mythical Item"
}


local unseenItems = {
	"Unseen Item",
	"Unseen Item",
	"Unseen Item",
	"Unseen Item"
}

--Used ratios to calculate the percentages below. Make sure your weights don't go over 100 (I made my range 1-100 so it wouldn't get complicated)

local weights = {
	Common = 100, -- 70%
	Uncommon = 35, -- 35%
	Rare = 20, -- 20%
	VeryRare = 10, -- 10%
	Legendary = 1, -- 1%
	Mythical = 0.1, -- 0.1%
	Unseen = 0.01 -- 0.01%
}

local function toHMS(s) -- Just a function for time formatting
	return string.format("%02i:%02i:%02i", s/60^2, s/60%60, s%60)
end


function getAvailableItems(day)
	local rng = Random.new(day) 
	local shopItems = {}
	for i = 1, 10 do
		if rng:NextNumber(0,100) < weights.Unseen then -- Unseen
			local item = unseenItems[rng:NextInteger(1, #unseenItems)]
			table.insert(shopItems, item)
		elseif rng:NextNumber(0,100) < weights.Mythical then -- Mythical
			local item = mythicalItems[rng:NextInteger(1, #mythicalItems)]
			table.insert(shopItems, item)
		elseif rng:NextNumber(0,100) < weights.Legendary then -- Legendary
			local item = legendaryItems[rng:NextInteger(1, #legendaryItems)]
			table.insert(shopItems, item)
		elseif rng:NextNumber(0,100) < weights.VeryRare then -- Very Rare
			local item = veryrareItems[rng:NextInteger(1, #veryrareItems)]
			table.insert(shopItems, item)
		elseif rng:NextNumber(0,100) < weights.Rare then -- Rare
			local item = rareItems[rng:NextInteger(1, #rareItems)]
			table.insert(shopItems, item)
		elseif rng:NextNumber(0,100) < weights.Uncommon then -- Uncommon
			local item = uncommonItems[rng:NextInteger(1, #uncommonItems)]
			table.insert(shopItems, item)
		else -- Common
			local item = commonItems[rng:NextInteger(1, #commonItems)]
			table.insert(shopItems, item)
		end
	end
	return shopItems
end


local currentDay = nil
local currentShopItems = {}
local offset = (60 * 60 * 17) -- Os.time() / this synced time module both return proper Unix time, which is started from 12:00 AM on Thursday, January 1, 1970. I offset it by 17 hours for it to become 5 PM PST.

while wait(1) do
	local day = math.floor((syncedtime.time() + offset) / (60 * 60 * 24))
	local t = (math.floor(syncedtime.time())) + offset -- Sets the date to Thursday 5PM PST
	local origtime = t - offset
	local daypass = origtime % 86400
	local timeleft = 86400 - daypass
	local timeleftstring = toHMS(timeleft)
		
	print("The time left until 5:00 PM PST is ".. timeleftstring) -- Optional printout, obviously I'd use "timeleftstring" for a countdown timer in a shop
	
	if day ~= currentDay then
		currentDay = day
		currentShopItems = getAvailableItems(day)
		print("The items for today are these: a ".. table.concat(currentShopItems, ", "))
		print('Updated shop items')		
	end
end
10 Likes

Thank you so much for the help! That is exactly what I was looking for. I tried my best to work with your one script, but it always reset earlier than wanted. But it should work now, and it looks like it does.

2 Likes

No problem! This had me so confused and I wouldn’t want anyone to go through that same confusion. If you hadn’t reposted my post I don’t think I would’ve seen this in time before it got drowned out in the other topics, so thanks lol

Have fun with your shop! PM me if anything goes wrong or you need some help

1 Like