diff --git a/imports/timer/shared.lua b/imports/timer/shared.lua new file mode 100644 index 0000000..74dcdf9 --- /dev/null +++ b/imports/timer/shared.lua @@ -0,0 +1,155 @@ +---@class TimerPrivateProps +---@field initialTime number the initial duration of the timer. +---@field onEnd? fun() cb function triggered when the timer finishes +---@field async? boolean wether the timer should run asynchronously or not +---@field startTime number the gametimer stamp of when the timer starts. changes when paused and played +---@field triggerOnEnd boolean set in the forceEnd method using the optional param. wether or not the onEnd function is triggered when force ending the timer early +---@field currentTimeLeft number current timer length +---@field paused boolean the pause state of the timer + +---@class OxTimer : OxClass +---@field private TimerPrivateProps +---@field constructor fun(self: self, time: number, onEnd: fun(), async: boolean) +---@field start fun(self: self, async?: boolean) starts the timer +---@field forceEnd fun(self: self, triggerOnEnd: boolean) end timer early and optionally trigger the onEnd function still +---@field isPaused fun(self: self): boolean returns wether the timer is paused or not +---@field pause fun(self: self) pauses the timer until play method is called +---@field play fun(self: self) resumes the timer if paused +---@field getTimeLeft fun(self: self, format?: 'ms' | 's' | 'm' | 'h'): number | table returns the time left on the timer with the specified format rounded to 2 decimal places (miliseconds, seconds, minutes, hours). returns a table of all if not specified. +local timer = lib.class('OxTimer') + +function timer:constructor(time, onEnd, async) + assert(type(time) == "number" and time > 0, "Time must be a positive number") + assert(onEnd == nil or type(onEnd) == "function", "onEnd must be a function or nil") + assert(type(async) == "boolean" or async == nil, "async must be a boolean or nil") + + self.private.initialTime = time + self.private.currentTimeLeft = time + self.private.startTime = 0 + self.private.paused = false + self.private.onEnd = onEnd + self.private.triggerOnEnd = true + + self:start(async) +end + +function timer:start(async) + if self.private.startTime > 0 then return end + + self.private.startTime = GetGameTimer() + + local function tick(instance) + while true do + while instance.private.paused do + Wait(0) + end + + if instance:getTimeLeft('ms') <= 0 then + break + end + + Wait(0) + end + end + + if async then + Citizen.CreateThreadNow(function() + tick(self) + self:onEnd() + end) + else + tick(self) + self:onEnd() + end +end + +function timer:onEnd() + if self:getTimeLeft('ms') > 0 then return end + + if self.private.triggerOnEnd and self.private.onEnd then + self.private:onEnd() + end +end + +function timer:forceEnd(triggerOnEnd) + if self:getTimeLeft('ms') <= 0 then return end + self.private.triggerOnEnd = triggerOnEnd + self.private.paused = false + self.private.currentTimeLeft = 0 + Wait(0) +end + +function timer:pause() + if self.private.paused then return end + self.private.currentTimeLeft = self:getTimeLeft('ms') --[[@as number]] + self.private.paused = true +end + +function timer:play() + if not self.private.paused then return end + self.private.startTime = GetGameTimer() + self.private.paused = false +end + +function timer:isPaused() + return self.private.paused +end + +function timer:restart() + self:forceEnd(false) + Wait(0) + self.private.currentTimeLeft = self.private.initialTime + self.private.startTime = 0 + self:start() +end + +function timer:getTimeLeft(format) + local ms = self.private.currentTimeLeft - (GetGameTimer() - self.private.startTime) + + local roundedfloat = function(value) + return tonumber(string.format('%.2f', value)) + end + + if format == 'ms' then + return roundedfloat(ms) + end + + local s = ms / 1000 + + if format == 's' then + return roundedfloat(s) + end + + local m = s / 60 + + if format == 'm' then + return roundedfloat(m) + end + + local h = m / 60 + + if format == 'h' then + return roundedfloat(h) + end + + return { + ms = roundedfloat(ms), + s = roundedfloat(s), + m = roundedfloat(m), + h = roundedfloat(h) + } +end + +lib.timer = { + async = function(time, onEnd) + return timer:new(time, onEnd, true) + end +} + +setmetatable(lib.timer, { + __call = function(t, time, onEnd) + return timer:new(time, onEnd) + end +}) + +return lib.timer \ No newline at end of file