How could I make it so a Player can only gain a reward every x hours?

Title sums it up, there’s no tutorials on this and I have no clue where to start. Some sample code would be nice. I’m making it so they can only claim a reward every 8 hours when they touch a part. A countdown in server is easy but I want the countdown to still go when player is not in server.

Maybe you could store the os.time() in a datastore for the player when they redeem a reward. Then, assuming they don’t leave the game, you could check after x amount of time if their timer is up, else you could check whenever they rejoin the game?

I made a similar thing where you unlock an item from the shop and it will take irl time. Here I’ll write a code for you to get started

I’d appreciate that thanks bro

with os.time would ik if its the next day? coz if they logged in 2 days later at same time would it think that 8 hours hasnt been up coz its not 8 hours past that time?

Hold up I’ll write the code for you

To explain it verbally, these are the steps:

  1. You need to have a number value to store how long the player has to wait until he can claim reward. We will call this the “cooldown” value measured in seconds.
  2. When a player joins, you will want to know how long he has been away for. Lets say he was away for one hour. You will subtract one hour (3600 seconds) from the cooldown value.
  3. Lastly, don’t forget to put a script in-game that has a one second loop. Subtract one from the cooldown value for each player every second so the timer runs even when the player is in game

For step 1 and step 2 we will need to use the datastore to load and save the values. I’m writing the code right now.

local datastore = game:GetService("DataStoreService"):GetDataStore("Reward Timer")


--when player joins
game.Players.PlayerAdded:Connect(function(plr)
	
	local cooldown = Instance.new("IntValue",plr)
	cooldown.Name = "Cooldown"
	
	
	--how long has the player been away for?
	local timeAway = 0
	
	--when did the player leave?
	local timeLeft = datastore:GetAsync(plr.UserId.."timeleft")
	--this is the time when the player left
	
	--this is the time when the player rejoined
	local timeNow = os.time()
	
	--here is the calculation:
	timeAway = timeNow - timeLeft
	
	--subtract how long the player has been away from the cooldown
	local previousCooldown = datastore:GetAsync(plr.UserId.."cooldown")
	local newCooldown = previousCooldown - timeAway
	
	--make sure that the new cooldown is larger than zero
	--What could happen is that the if the player has a cooldown of 1 minute, and he left for a 2 minutes, when he returns, he will have a new cooldown of negative 1 minute. So just add this code to make sure it doesn't become negative
	if newCooldown < 0 then
		newCooldown = 0
	end
	
	--set the cooldown int value of the player to the new cooldown
	cooldown.Value = newCooldown
	

end)


--when player leaves
game.Players.PlayerRemoving:Connect(function(plr)
	datastore:SetAsync(plr.UserId.."cooldown",plr.Cooldown.Value)
	datastore:SetAsync(plr.UserId.."timeleft",os.time())
end)


--when player leaves
game:BindToClose(function()
	--same thing
	for i, plr in pairs(game.Players:GetPlayers()) do
		datastore:SetAsync(plr.UserId.."cooldown",plr.Cooldown.Value)
		datastore:SetAsync(plr.UserId.."timeleft",os.time())
	end
end)

--the part for reward (change this to your part)
local rewardPart = game.Workspace.Part
rewardPart.Touched:Connect(function(hit)
	if game.Players:GetPlayerFromCharacter(hit.Parent) then
		--player touched this part
		local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
		if plr.Cooldown.Value <= 0 then
			--give the reward somehow
			
			--this will be eight hours as you said
			--you can change this around too
			plr.Cooldown.Value = 8 * 60 * 60
			print(plr.Name.." claimed the reward! Come again in 8 hours.")
		end
	end
end)


--one second loop
while task.wait(1) do
	for i, plr in pairs(game.Players:GetPlayers()) do
		--make sure the player loaded
		if plr:FindFirstChild("Cooldown") then
			--make sure the timer is greater than zero
			if plr.Cooldown.Value > 0 then
				--one second has passed; therefore, subtract one from cooldown
				plr.Cooldown.Value -= 1
			end
		end		
	end
end

If it worked, please set it as the solution so others can see it. I spent a lot of time writing this for you, hope it helps!

4 Likes

Here is the documentation description of os.time()
“Returns how many seconds have passed since the Unix epoch (1 January 1970, 00:00:00), under current UTC time.”

So, for your problem you could keep the os.time() for when the user claimed the reward in a datastore. Then you can just check the time since the user claimed the reward by taking the current os.time() and subtracting the os.time() from the datastore. That gives the difference in time in seconds, so just check if that is more than 28,800s (8 hours).

Also instead of

if newCooldown < 0 then
	newCooldown = 0
end

You could just do newCooldown = math.abs(newCooldown)

Also is it efficient to check every second?

I ended up doing this

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local data = DataStoreService:GetDataStore("LastTimeClaimed")

Players.PlayerAdded:Connect(function(player)
	local LastClaimedTime = Instance.new("IntValue")
	LastClaimedTime.Name = "LastClaimedTime"
	LastClaimedTime.Value = 0
	LastClaimedTime.Parent = player
	
	local LastTime
	local success, errormessage = pcall(function()
		LastTime = data:GetAsync("Player_"..player.UserId)
	end)
	if success then 
		LastClaimedTime.Value = LastTime
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local LastClaimedTime = player.LastClaimedTime
	
	local success, errormessage = pcall(function()
		data:SetAsync("Player_"..player.UserId, LastClaimedTime.Value)
	end)
end)

script.Parent.Touched:Connect(function(hit)
	if hit.Parent:FindFirstChild("Humanoid") then 
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		local LastClaimedTime = player.LastClaimedTime
		if (os.time() - LastClaimedTime.Value) >= 18000 then -- 5 hours
			LastClaimedTime.Value = os.time()
			player.LobbyCheckpoint.Value = 0
			player.Stats.Coins.Value += 150
			hit.Parent.HumanoidRootPart.CFrame = game.Workspace:FindFirstChild("SpawnLocation").CFrame * CFrame.new(0, 3, 0)
		end
	end
end)

Would this be more efficient

1 Like

Yes, that looks good to me.

You will probably want to add a bind to close function so that player data is saved even if the server shuts down or crashes though. Also I think it is good practice to add a coroutine.wrap()() when saving in a bind to close function to guarantee all data is saved in the 30 seconds available.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.