Fenris_Wolf Posted March 3, 2018 Share Posted March 3, 2018 As you (probably) already know, PZ allows us to define timed events using OnEveryTenMinutes, EveryHours, and EveryDays. As handy as they are, sometimes we want a more refined system. Maybe we want a event to trigger in 15 minutes, and possibly repeat itself a few times then stop. Maybe we want something to happen every 60 seconds in game. Well I got bored this morning and cooked up such a system... (this would go in the lua/shared folder) Spoiler Timers = { ActiveTimers = {}, } Timers.add = function(delay, repeats, callback, args) local hours = getGameTime():getWorldAgeHours() local t = { delay = delay, callback = callback, args = args, repeats = repeats, trigger = hours + (delay/60), } t.id = ZombRand(100000) .."-".. t.trigger Timers.ActiveTimers[t.id] = t return t end Timers.remove = function(timer) if type(timer) == "table" then -- not a proper timer table if not timer.id then return end timer = timer.id end Timers.ActiveTimers[timer] = nil end Timers.check = function() local current = getGameTime():getWorldAgeHours() for id, timer in pairs(Timers.ActiveTimers) do if timer.trigger <= current then if not timer.repeats or timer.repeats == 0 then Timers.ActiveTimers[id] = nil elseif type(timer.repeats) == "number" then timer.repeats = timer.repeats - 1 end timer.trigger = current + (timer.delay/60) timer.callback(timer, timer.args) end end end First we create our Timers table: Timers = { ActiveTimers = {}, } This will hold our functions, and the sub-table ActiveTimers will hold our actual timer queue. Now we need a handy function to add timers to our table: Timers.add = function(delay, repeats, callback, args) local hours = getGameTime():getWorldAgeHours() local t = { delay = delay, callback = callback, args = args, repeats = repeats, trigger = hours + (delay/60), } t.id = ZombRand(100000) .."-".. t.trigger Timers.ActiveTimers[t.id] = t return t end This function takes 4 arguments: delay = the number of minutes to wait before triggering our timer. This should be a integer value greater then 0. repeats = the number of times to repeat the timer, after the first triggering. If repeats is 0, nil or false, it will only fire once. If true then it will endlessly fire. A value of 2 will trigger a total of 3 times (the initial trigger, then 2 repeats) callback = the function to call when this timer is triggered. It should accept 2 arguments: the timer itself, and whatever value is in the 'args' variable args = passed to your callback function. This can be anything, although you should be careful not to pass variables that may have expired (ie: a IsoPlayer object on a server, in case that player disconnects before the timer is triggered) Its going to be up to you to make sure your callback function properly verifies anything you pass to it as args. Notice we use getGameTime():getWorldAgeHours() to help define when this timer should trigger (see the t.trigger variable in the above function). This will return a float value such as 125.25, which would mean 125 hours and 15 minutes into the game (125.50 is 125h and 30min). Since our 'minutes' are a percent value of 1 (15 minutes = .25) we use delay/60 Now we need a function to remove our timers just in case we need to cancel early: Timers.remove = function(timer) if type(timer) == "table" then -- not a proper timer table if not timer.id then return end timer = timer.id end Timers.ActiveTimers[timer] = nil end The timer argument can be one of 2 things: the actual timer table returned by Timers.add(), or the timer id. Now those 2 important functions are out of the way, time for our function that actually checks if a timer should be triggered: Timers.check = function() local current = getGameTime():getWorldAgeHours() for id, timer in pairs(Timers.ActiveTimers) do if timer.trigger <= current then if not timer.repeats or timer.repeats == 0 then Timers.ActiveTimers[id] = nil elseif type(timer.repeats) == "number" then timer.repeats = timer.repeats - 1 end timer.trigger = current + (timer.delay/60) timer.callback(timer, timer.args) end end end basically we loop through our Timers.ActiveTimers table, and if the timer.trigger is less then the current world age hours, its time to call the callback function! Notice if the timer.repeats is a number (and not 0) it lowers the repeat by 1. It also sets up the next timer.trigger value. Pretty easy stuff. Now we need to be able to call the Timers.check() function on a interval, using PZ's events system. There's 2 events we can use, each with benefits and drawbacks: Events.OnTick.Add(Timers.check) This method using OnTick will give you the most accurate timers and allows you to setup timers with a delay of 1 minute. Its drawback is that its called ALOT (multiple times per 1 second of game time), so if your planning on having alot of active timers at once, performance may suffer and you should probably use this instead: Events.EveryTenMinutes.Add(Timers.check) The drawback here is timers are effectively only triggered every ten minutes. So now for a example of a timer added client side (they can be added server side as well) local callback = function(timer, args) if not timer.repeats or timer.repeats == 0 then args:Say("Last Call!") else args:Say("Timer Triggered!") end end Events.OnGameStart.Add(function() Timers.add(15, 3, callback, getSpecificPlayer(0)) end) On logging into the game, this will make our player say "Timer Triggered!" every 15 minutes, 3 times, then 15 minutes later say "Last Call!" Please note these timers are NOT saved on exiting (or server shutdown), I'll leave that part to you creative people to figure out a method of doing so. This is after all, a tutorial on timers, not saving data ATPHHe, Ʀocky, Svarog and 2 others 4 1 Link to comment Share on other sites More sharing options...
Fenris_Wolf Posted March 4, 2018 Author Share Posted March 4, 2018 17 hours ago, Fenris_Wolf said: Now we need to be able to call the Timers.check() function on a interval, using PZ's events system. There's 2 events we can use, each with benefits and drawbacks This bugged me, why can't we have the best of both worlds? A timer system that is accurate and wont degrade performance if a lot of timers are active? Well.... local lastMinute = nil Events.OnTick.Add(function() local minute = getGameTime():getMinutes() if minute ~= lastMinute then Timers.check() lastMinute = minute end end) Now we can use the OnTick event, and only call the Timers.check() function if the minute has actually changed, instead of multiple times per game second. ATPHHe, Nebula, Svarog and 1 other 4 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now