Daily reward system algorithm flawed

I have a simple script that would detect whenever a player joined, the time elapsed since their last join and award the player.

     local before = dataf.Get(player, "Time", 0) -- time joined, can be converted to readable time format
     local store = dataStore2("Sent", player)          
     local sent = store:Get(0)
     sentv.Value = sent

     store:OnUpdate(function(new)  -- DataStore2 API
     sentv.Value = new
     end) 

--  in the current way a player could wait 24hrs in a game and not receive a reward

         local curr = os.time()
         if curr - before >= 24 * 60^2 then -- 86400 seconds
         print("24hrs have passed, or you are new")
         -- os.time() is set to the Time store
         else   
                      -- else if 24 hrs didnt pass
         print("24 hrs didn't pass yet!")
        end
     end   

With this code if a player happened to pass 24hrs elapsed since their last reward claim, it would not award them unless they rejoined.

What is the best way to take that fact into consideration (that players could pass 24 hours while in game) by implementation? A simple while loop to constantly check using the algorithm above and grant a reward, or would I have to do something with saving on PlayerRemoving ?

What is the appropriate method for this?

How do I cache it on the client and update the time on the client and make sure the reward can be given mid-game?

A thread like this in the past was left incomplete, not addressing this scenario.

Prediction. If 24 hours hasn’t elapsed, store this value and give it to the client. Client can count down how long they need to wait and it doesn’t have to be exactly accurate either, just they know what time is left and count down from there. As for anything beyond that, you’re going to have to use some kind of loop or determine points in your game to repeatedly call the reward check.

Problems like this are typically why a live rewards system is dependent on the client and aren’t automatic. In a couple of Dungeon Quest-like games (e.g. Adventure Up and Rumble Quest), there’s an area you can go to to claim your daily rewards. You press this button and it requests the server to check if the time’s elapsed before giving rewards. Also, monetisation opportunity: Rumble Quest allows you to either restore with Robux or reset your daily streak if you miss a claim period (there’s a grace of some time before it’s determined you missed the claiming point).

4 Likes

Hi, I am having the same problem but I could not understand what @lesserfantasy said. Could you possibly explain it in more simpler terms?

1 Like

The idea behind my response is prediction.

The server’s main responsibility in a daily reward system, other than giving the rewards for the day of course, will be to check if the player has already claimed their reward for the day or not. The server will grant a reward either when a day passes or if the player has not claimed rewards before, then will store the time at which the player claimed the reward to be checked for next time.

As for what this thread was having trouble on, it was in regards to if a player joined before their allotted collection time. Say they collect a reward on Wednesday at 10:00 AM EST and then they return the next day, Thursday at 9:35 AM EST. They still have to wait 25 minutes before they can collect their reward. So the problem comes down to “how do I show the player that they have 25 minutes left?”.

The answer is for the server to give the client the time it has recorded, which was the last time the player collected rewards. The client then is responsible for doing a few calculations on this value: it must determine how long 24 hours from the last claim time is (Wednesday 10:00 AM EST - Thursday 10:00 AM EST is a difference of 86400 seconds) and figure out what time it is now (Thursday 9:35 AM EST). The client then uses these two times - the current time and the calculated time 24 hours in advance from the last claim time - to find out how much time is left. Using this value, the client can then continually keep counting this number down to show a live counter of how long is left to claim rewards.

That’s about as simple as I can explain it, but I’m not exactly the best at articulating things. If you want a completely different solution to this problem that might be easier to manage, then you can ignore the time completely and just check if a new day has arrived before giving the player their rewards. os.date provides you with lots of information about the current date so you’d just need to use what it gives you in order to construct a system such that when a new day arrives, a player can claim rewards.

1 Like

I do get what you are trying to say here, the thing is, are you able to write some pseudo code further explaining your example of a player Joining in on Wednesday 10:00am EST and waiting 25 minutes? I have been trying to work out a solution but I have been getting no where for the past few hours, so this would be very beneficial to me. Thank you!

I suppose, though I feel my explanation should be enough for you to at least make a renewed attempt at writing some yourself. If you understand the principle, you should also be able to conceive some of your own; and if you’re having trouble with writing that, then supply your attempt as well.

Server:

player join
    > get data
    > has not claimed before? or os.time() greater than recorded time?
        > give reward
        > update time of last claim
    > has claimed before? and os.time() not greater than recorded time?
        > take recorded time and add 86400 seconds
        > get current os.time(), subtract from above calculated time
        > send to client via remote event

Client:

scripts started
    > server sent time from remote? time is how long left until next claim
        > store time in variable
        > connect to heartbeat to count this number down
        > update a gui showing the above time counting down, with formatting
2 Likes

Thank you. I have managed to get it to work now, though, what should I do if I want to make it a consecutive system? So the player has a daily log in streak of 7 days and if they miss a day the streak goes back to 0, and this could be a monetisation opportunity as well as I could have the player pay to get their streak back but how would I begin to code these?

That’s an entirely different question so it’s probably best to create a new topic to address that.
Since you’re working with datastores here, it should be fairly straightforward to implement. We can create a streak variable and update it by 1 each time our condition is met. If the condition isn’t met, (recorded time is less then some value), set the streak back to 0.

Here’s a thread that’s similar to what you’re asking.

Also nice name change @lesserfantasy.

Okay, But I have encountered a problem and here it is:

local Players = game:GetService("Players")
local SStorage = game:GetService("ServerStorage")

local DataStore2 = require(script.Parent.MainModule)

local JoinTimes = {}

DataStore2.Combine("Data","RewardClaimed","PlayTime","DayStreak")

local function LoadLeaderstats(Player)
	local PlayTimeData = DataStore2("PlayTime",Player)
	local RewardClaimedData = DataStore2("RewardClaimed",Player)
	local DayStreakData = DataStore2("RewardClaimed",Player)

	JoinTimes[Player] = os.time()
    
	local PlayTime = Instance.new("IntValue",Player)
	PlayTime.Name = "PlayTime"
	PlayTime.Value = PlayTimeData:Get(0)
	
	local DayStreak = Instance.new("NumberValue",Player)
	DayStreak.Name = "DayStreak"
	
	if DayStreakData:Get() ~= nil then
		DayStreak.Value = DayStreakData:Get()
		print(DayStreak.Value)
	else
		DayStreak.Value = 0
	end
	DayStreak.Changed:Connect(function()
		DayStreakData:Set(DayStreak.Value)
	end)
	
	local RewardClaimed = Instance.new("BoolValue",Player)
	RewardClaimed.Name = "RewardClaimed"
	
	if RewardClaimedData:Get() ~= nil then
		RewardClaimed.Value = RewardClaimedData:Get()
		print(RewardClaimed.Value)
	else
		RewardClaimed.Value = false
	end
	RewardClaimed.Changed:Connect(function()
		RewardClaimedData:Set(RewardClaimed.Value)
	end)
end

local function OnPlayerRemoving(Player)
	if JoinTimes[Player] then
		local PlayTime = os.time() - JoinTimes[Player]
		local PlayTimeData = DataStore2("PlayTime",Player)

		JoinTimes[Player] = nil
		PlayTimeData:Get()
		PlayTimeData:Increment(PlayTime)
		print(PlayTimeData:Get())
	end
end

game.Workspace.Part.ClickDetector.MouseClick:Connect(function(Player)
	if Player then
		local PlayTimeData = DataStore2("PlayTime",Player)
		local RewardClaimedData = DataStore2("RewardClaimed",Player)
		local DayStreakData = DataStore2("RewardClaimed",Player)

		local RewardClaimed = Player.RewardClaimed
		local PlayTime = Player.PlayTime
		if RewardClaimed.Value == false and PlayTime.Value >= 5000 then
			print("Giving Player Reward!")
			RewardClaimed.Value = true
			PlayTimeData:Set(0)
			print(PlayTimeData:Get())
		end
		if RewardClaimed.Value == true and PlayTime.Value <= 5000 then
			print("Player Already Claimed Reward!")
			RewardClaimed.Value = false
			print(PlayTimeData:Get())
		elseif RewardClaimed.Value == false and PlayTime.Value <= 5000 then
			print("Too Early To Claimed Reward!")
			print(PlayTimeData:Get())
		end
	end
end)

Players.PlayerAdded:Connect(LoadLeaderstats)
Players.PlayerRemoving:Connect(OnPlayerRemoving)

I have modified the script above to work for my game but I have a problem, I could not find a way where I can get the timer to update Live, right now it only updates when you leave the game, so if a player wants to get the reward and joins before a day has passed and waits until the reward is ready, the player has to leave. How can I make it so that it updates live? I have tried loops and remote events but nothing seems to work.