Help on Making a Daily, Self-Updating Shop?

Yeah, you’re right, but right now the highest priority for me is to find a way to get this done all by code.

1 Like

To get time, use os.time or os.date, called from a server script of course.

For the random numbers, use math.randomseed to make the seed the same in all server instances, perhaps by using the current year and day of year. This was pointed out by another person here as well. For those who don’t know, math.random is pseudorandom, so if the seed is the same across servers, the random numbers will be the same across all servers. Or maybe you can use a non-random rotation.

You may also be able to use datastores for some things, but be aware of its limits.

If you want to get crazy, use httpservice and set up your own server or something.

2 Likes

Maybe try adding a script that uses data stores to save a new random code for the shop every 2 minutes. Sort of like this:

local dss = game:GetService("DataStoreService")
local ShopDS = dss:GetDataStore("ShopDataStore")

local TodaysShop = ShopDS:GetAsync(ShopDS:GetAsync("CurrentDate"))

while wait(120) do
    local totalDates = ShopDS:GetAsync("TotalDates")
    ShopDS:SetAsync(totalDates, RandomNumberHere) -- Replace with your random number thingy
    ShopDS:SetAsync("TotalDates", totalDates + 1)
    if ReplaceWithCurrentHour == 24 and ReplaceWithCurrentMinute > 58 then -- Replace the variables with current hour/minute
        --Shop needs to be updated!
        local yesterday = ShopDS:GetAsync("CurrentDate")
        wait(120)
        ShopDS:SetAsync(yesterday + 1)
        TodaysShop = yesterday + 1
end

That may seem confusing, but if it works it should update the list every 2 minutes. The only limitations I see are the data store limitation, and the fact that you would need to have at least on server running every day at midnight (or whenever you switch shops) and you would need to reset everything every 99999999999999999999 days (so you wouldn’t break the max length of a datastore’s key).

This is probably over-complicated though.

2 Likes

This just gave me an idea. Maybe, I could generate a set of items, and then save it to a data store WITH the seed generated for that day. Then, I could add a conditional where it does :GetAsync and then check to see if the seed value in the data store is equal to the seed generated for the current day (which would be the same for every server, as long as its the same day). That way, if the value isn’t the same, it would generate a new set and update the data store with the new seed value. Only thing is wouldn’t all of the servers end up doing :GetAsync at the same time? So, they would all detect an old value because there’s no way for a server to “wait” for another server to update it? Or would it still work because all the servers are still updating the Async regardless?

All I need to know how to do to solve this problem is one of these options:

  • Find a way to generate new items with a seed and keep weighted randomization (to keep different rarities)

  • Update a data store or a global value somewhere + make sure servers aren’t overwriting each other

Or, any other way someone can offer.

Here’s my code so far for it to update every 24 hours at 5:00 PM PST. I still need to add a seed, of course.

local syncedtime = require(game.ReplicatedStorage.SyncedTime) -- returns a synced time, because it's external. I'm not using os.time() because there have been a lot of reports of it not being synced
 
local lt = require(game.ServerScriptService.LootTable) -- The module I'm using for rolling and picking items
syncedtime.init()

local offset = (60 * 60 * 17)  -- 17 hours, or 61,200 seconds

while wait(1) do
	local t = (math.floor(syncedtime.time())) + offset -- Sets the date to Thursday 5PM PST
	local origtime = t - offset
	
	local daypass = origtime % 86400 -- basically, this would output how far into the day we are in seconds
	print(daypass)
	
	if daypass == 0 then --- we know exactly one day has passed, I'd do all of my shop functions under this
		print("It is 5:00 PM PST.")  

	else
		local timeleft = 86400 - daypass  -- finds out how much time is left until the next day
		
		local timeleftstring = toHMS(timeleft)  -  just formatting to get it to HH:MM:SS 
		
		--print("The time left until 5:00 PM PST is ".. timeleftstring)  <-- Obviously I would just use "timeleftstring" for a timer in a shop
	end
end

Keep in mind this may be too complicated, as you are probably able to arrange for each server to select the items on their own. The items would all be the same, as the random number seeds would all be the same.

Otherwise, to have different weights, just use different random number ranges. You may use
a = math.random(1, 10) for regular items and b = math.random(1, 100) for rare items.

To make sure severs don’t overwrite each other, use the year and day of year as a key (such as 2020150) for each new real life day and use UpdateAsync to check if the data store has been set up for that day. If not, write to the data store. Be sure to keep in the mind the limits of data stores.

Yeah, that’s what I was trying to get at in my previous reply - updating a data store if the seed in it doesn’t match the seed for the current day. Basically, what you said.

Let’s say that the seed for the previous day is 2020519, and then 5:00 PST hits. The seed becomes 2020520, and the servers try to update. So, if one server updates the data store, the other ones running at the same time wouldn’t have to, right, because of the conditional?

The only reason why I’m kinda confused about this is because I’m thinking that all of the servers running would see the old key because all of them are checking at the same time (so nothing would be updated yet), and then all of them would try to update it. Unless, there’s some sort of latency so only one server is able to update it, so the other ones wouldn’t have to because the data store is already updated.

I’ll be back, I have to go now

This just gave me an idea that was so simple! Basically, what I can do is regenerate the shop items separately, using a time increment of let’s say like 1 hour (just to not exhaust the data store), and then just have the shop update to whatever’s in the data store when a new day hits.

And yeah, the limitation to this is that I’d always need a server running. But, if I just lowered the time increment, then by the time the shop updates again the values should already be something different. So, I’ll go with this as the solution for now. Thanks!

Edit: This is still the solution, I just thought of a smarter way to do this.
I can just connect the item generation/update function to PlayerAdded, with a cooldown, so that as long as 1 person joins/stays in the game longer than the cooldown, the shop will rotate by itself.

Yes, there could be an issue if a bunch of servers access one key at the same time. That’s why I don’t recommend it. Theoretically, a bunch of servers would try to update the value using UpdateAsync, and they would then see that the value has already been updated and would stop. But in reality, something may break because of the limitations.

see “game limits”

So then do you have any ideas on how to send out a value only once (from any server) and then have all the other servers update without changing the value?

I was thinking of making ID’s for each server and using them in some way, but I’m stumped and can’t flesh out any ideas.

(preferably without using external services, unless it’s a universal time)

How it would work is at 2am (or some other time there won’t be a lot of servers online), the servers would all check for that new data in the data store. One server would see that the data is old and would update it. Read up on UpdateAsync if you need to. This idea could still break, though. Also, perhaps you can use some kind of server instance ID to make only one server change the data store value. I’m not familiar with those functions.

Again, I don’t think the data store thing is a good idea unless you do it manually. Having each server instance check the current time (using os.time or os.date) and update the store on its own it much easier. And again, that random seed would be the same across all servers.

I agree with your opinion on my solution, it’s pretty risky. I’m gonna try to change my item generating format to a PRNG, and then find a way to implement weight into it. Then, from there it should be really easy.

1 Like

I’m not sure what PRNG is, but it must be pseudorandom for this to work. A true random number generator will have different results across servers (or it should, at least).

For using weight, if I understand what you mean, you can just use different ranges and call math.random multiple times as needed.

a = math.random(1, 10)
if a == 1
   --put common item on shop
end

b = math.random(1, 100)
if a == 1
   --put rare item on shop
end
1 Like

PRNG = Pseudorandom number generator

1 Like

I can’t believe I hadn’t thought of this. How would I manipulate math.random (or some other math function) to output the same number for every server?

1 Like

When 5pm occurs, use math.randomseed and pass in the current year and day of year as one number (maybe multiply by 100, I don’t know if it matters but it can’t hurt), and then put new items in the shop.

1 Like

I’ve made a similar system to what you are trying to do. Example code below. Definitely use the Random.new() object. I’d also make sure that your “syncedtime” module is using os.time()


local commonItems = {"Red Balloon", "Yellow Balloon", "Green Balloon", "Blue Balloon"}
local rareItems = {"Gold Balloon", "Rainbow Balloon"}

function getAvailableItems(day)
local rng = Random.new(day)
-- Use rng variable as much as you want inside this function, it should be completely deterministic as long as you use the same day parameter.
local shopItems = {}
for i=1, 3 do
if rng:NextNumber() < .1 then
local item = rareItems[rng:NextInteger(1,#rareItems)]
table.insert(shopItems, item)
else
local item = commonItems[rng:NextInteger(1,#commonItems)]
table.insert(shopItems, item)
end
end
return shopItems
end


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

while true do
local day = math.floor((os.time() + OFFSET) / (60*60*24))
if day ~= currentDay then
currentDay = day
currentShopItems = getAvailableItems(day)
print('Updated shop items')
end
wait(1)
end

I’m very sorry about the lack of indentation. I wrote this code by hand, and am not sure how to indent since tab doesn’t work here.

EDIT: Forgot to add a wait(1) to the while loop. Oof!

12 Likes

Thank so you much!! This is literally exactly what I’m trying to do. I’ll fuse my code and your code and test for results. All I’d have to do to add more rarities, I’m assuming, is to add more tables and more else-if’s inside of the conditional, right?

Okay so I just took a good look at this:

  • I could add more tables for more rarities
  • Change the end number in the for loop for how many items I want to be picked
  • Add more else-ifs and change the threshold (the < .1) to correspond with the rarity I want
4 Likes

Yeah, to be honest, you can use any sort of item picking you want. You can even have it return multiple tables, and use rng variable in different ways. Just be sure to use the rng variable for your determining source inside that function, and only inside that function.

2 Likes

Thank you so much man, I’ve been stumped on this for a few days now and I couldn’t find a way around it

3 Likes