Behavior involving tick() doesn't replicate from Studio to Ingame

  1. What do you want to achieve? A cooldown system which works on how much time has elapsed.

  2. What is the issue? tick() behavior works fine in Studio, but this behavior does not replicate outside of Studio.

  3. What solutions have you tried so far? I have tried rearranging my code, but to no avail.

The way my system works is when an ability is used, tick() is called, then subsequently stored in a table. Whenever the player tries to use it again, if tick() - LastUse is called in studio, it functions normally, returning the elapsed time, but not ingame. Ingame, the elapsed time returned is usually around 25,000.

Example code:

local now = tick()

		if now - Data.LastTick > Data.Cooldown then
			canProceed = true
		else
			canProceed = false
		end

The time on an actual server is often much different. Was Data.LastTick measured on the same server that ‘now’ is?

The variable called “now” is created on the client. The Data.LastTick was created on the server.

If I’m interpreting what you said correctly, the Client and Server both differ on what they return for tick()?

Yes. In my experience the client and server tick() time cannot be relied on the be synched correctly. There are a lot of ways to work around this. One way is to only deal with “relative” times on the client. For example, the client asks the server if an ability is off cooldown and provides the client’s tick() time, the server replies with “you asked me at (client time), it will be off cooldown at (client time + some offset)”. This lets the client and server keep track of any “started at” time even if they aren’t synched, as long as they only communicate using offsets.

I’m just confused as to why such a stark difference exists between Studio and actual ingame. In Studio, it counts down properly, eg: from 5 to 0, but ingame, it counts down from 25,000 to 0.

It just has to do with the implementation of tick(). It gives very precise time between calls on the same machine, much finer than the actual rick 60-ish Hz tick rate that the physics and thread scheduler run on. This makes it good for measuring performance but much harder to keep synchronized between machines, most likely the implementor decided not to bother as a result. This issue represents a bit of a weak spot in the Roblox API in my opinion.

This presents a very large problem for me… because my framework is reliant on this calculation.

Can you tell me more about the timing requirements?

The process works like this:
A table is shared between the client and the server that is read-only by the client, and can only be modified by the server.
When a player uses an ability, the server puts that down as tick() in the table.

A cooldown GUI is displayed which counts down using the elapsed time, until it hits 0.

you should probably use os.time instead tick is based on timezone

if you have tick ran on the server and on the client there will most likely be a mismatch between the two

if you’re doing non-physics calculations you can make use of os.clock as its high percision (based on HPET) and it’s useful for client only stuff

1 Like

I have had to write a similar solution and I used the following: When an ability is used, both the server and client start a cooldown timer. When the player tries to use the ability again, the client checks the client timer to see if the cooldown is over, if it is, send the ability use message to the server. The server checks against its own timer, if it’s also finished, use the ability. If the player’s ping to the server stays constant, the effect of there being two timers is invisible to the player, but you gain resistance against exploits that just “replay” the use-ability message to the server.
If the ping goes down from when the player uses an ability to when they use it again, they may experience an “eaten input”, to fix that you can queue incoming ability requests for a little while, maybe 1/4 of a second. This is called “Coyote Time” and is used by lots of games to solve the same issue.

I have noticed that MOBAs especially will not show the player an indication that the cooldown has started until the ability actually goes off, this is probably a way of hiding the lag.

nope not true, it’s a tick related issue

One solution to this issue is to use the os.time() function instead of tick(). The os.time() function returns the current time in seconds, which is not affected by lag or frame rate. Here’s an example implementation of a cooldown system using os.time():

local COOLDOWN_TIME = 10
local lastUseTimes = {}

--function to check if an ability can be used based on its cooldown
function canUseAbility(abilityName)
    local now = os.time()
    local lastUseTime = lastUseTimes[abilityName] or 0
    return now - lastUseTime >= COOLDOWN_TIME
end

--function to mark an ability as used and update its last use time
function markAbilityUsed(abilityName)
    lastUseTimes[abilityName] = os.time()
end
1 Like

I wish I could mark two solutions, but os.time fixed it. Thank you everybody for taking the time to assist me!

1 Like

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