Scripted timer

From Mod Wiki

(Difference between revisions)
Jump to: navigation, search
Revision as of 07:27, 23 July 2008 (edit)
Barin (Talk | contribs)

← Previous diff
Revision as of 07:53, 23 July 2008 (edit) (undo)
Don Reba (Talk | contribs)
(Author - linked to Team:Dez0wave)
Next diff →
Line 349: Line 349:
== Author == == Author ==
-'''dez0wave'''+[[Team:Dez0wave]]
[[Category:Articles]] [[Category:Articles]]

Revision as of 07:53, 23 July 2008

There are situations when you would like to perform an action, when certain amount of time expire. For instance, say you are working on a task in which you have to occupy certain position for a period of 15 minutes, or you want to spawn more npcs/mutants after each 24 hours, or you want to implement a blowout script which has 3 phases - each phase remain different amount of time. To do this in a convenient way, you can implement a timer. In this article I would like to show you a simple timer class. It defines two different types of timer, both timers counts backward starting from a given value (expiration_time):

silent timer - its main purpose is to perform previously defined action (when the timer expire)

counterclockwise timer - same as silent timer, but with text presentation on hud (useful for debugging purposes)

Contents

Useful variables

First let's define some variables with default values, i.e. time after the timer expires (def_expiration_time), type of timer (def_timer_type) and function which will be called when timer expires (def_action):

local def_expiration_time = {h = 0, m = 5, s = 0} -- default time - 5 minutes
local def_timer_type = "counterclockwise" -- default type of timer
local def_action = "no_action" -- default function called when timer expires

Timer class constructor

Class constructor is called when you create a new timer object. With arguments passed to constructor we can choose timer type, timer expiration time and action. Variable timer_expired indicates whether the timer expired or not. Function time_global() returns a number of seconds passed since you start the game. Function script_name() returns name of the current script (without extension), so if you named your script my_timer.script then function script_name() returns my_timer. Variable action holds name (string) of the function which will be called when the timer expired. There are two ways you can call functions in Stalker:

- using name of the script where the function you want to call is defined, like this:

my_script_name.my_function_name(arguments)

- or you can use global variable (array) _G

_G["my_script_name"]["my_function_name"](arguments)

We will define the action function in the same script as timer class, so we will use script_name() function, but of course you can use different script for action functions only. Variable timer_wnd holds a pointer to hud window where you can see the text presentation of counterclockwise timer (upper right corner). We will use the window called hud_timer_text, defined in config\ui\ui_custom_msgs.xml (you can define your own "window" there if you want, for example change the font of the timer text etc)

class "Timer"
 
function Timer:__init(timer_type, expiration_time, action)
   if timer_type ~= "counterclockwise" and
      timer_type ~= "silent"
   then
      timer_type = def_timer_type
   end
 
   self.timer_type = timer_type
   self.expiration_time = self:normalize_expiration_time(expiration_time)
   self.activation_time = time_global()
   self.timer_expired = false
   self.timer_wnd = nil
 
   if not action or not _G[script_name()][action] then
      self.action = def_action
   else
      self.action = action
   end
 
   if self.timer_type ~= "silent" then
      get_hud():AddCustomStatic("hud_timer_text", true)
      self.timer_wnd = get_hud():GetCustomStatic("hud_timer_text"):wnd()
   end
end

For variable expiration_time we will use a string data type, so we can pass the time in a convinient way. Here is the format we will use: hours:minutes:seconds, for instance:

expiration_time = "15:34:34"

Function normalize_expiration_time() parse the expiration_time we passed as a constructor argument and returns the number of seconds. We will use 1000 multiplier and ... math backgrounds ;)

- 1 minute = 60 seconds

- 1 hour = 60 minutes

- 1 hour = 60 * 60 = 3600 seconds

so to convert expiration_time to seconds (because function time_global() is using seconds) we will use this formula:

local multiplier = 1000
 
local hours = expiration_time_hours * 60 * 60 * multiplier
local minutes = expiration_time_minutes * 60 * multiplier
local seconds = expiration_time_seconds * multiplier
 
-- for instance:
local expiration_time = "10:15:02"
 
local hours = 10 * 60 * 60 * 1000 -- 36 000 000 seconds
local minutes = 15 * 60 * 1000 -- 900 000 seconds
local seconds = 2 * 1000 -- 2000 seconds
 
local total_num_of_secs = hours + minutes + seconds -- 36 902 000 seconds
function Timer:normalize_expiration_time(exp_time)
   if exp_time == nil then
      exp_time = ""
   end
 
   local t = {}
   for word in string.gmatch(exp_time, "[%d]+") do
      table.insert(t, word)
   end
 
   local hours = tonumber(t[1] or def_expiration_time.h) * 3600000
   local minutes = tonumber(t[2] or def_expiration_time.m) * 60000
   local seconds = tonumber(t[3] or def_expiration_time.s) * 1000
 
   return hours + minutes + seconds
end

Destructor

Destructor is used to release any resources allocated by the object. Since our timer does not consume much resources, we can use an empty destructor:

function Timer:__finalize()
end

or if you want, you can help a bit lua interpreter releasing the variables on your own:

function Timer:__finalize()
   self.timer_type = nil
   self.activation_time = nil
   self.expiration_time = nil
   self.timer_wnd = nil
   self.action = nil
   self.timer_expired = nil
end

Update method (the core of our timer)

Update method should be instantly called when we create our timer object. Its main purpose is to count the time and if expiration_time expire - perform an action (call a function with certain action)

function Timer:Update()
   if self.timer_expired then
      return
   end
 
   local new_time = self.expiration_time - (time_global() - self.activation_time)
   if new_time <= 0 then
      self.timer_expired = true
      new_time = 0
   end
 
   -- if we are using the counterclockwise timer
   if self.timer_type ~= "silent" then
      -- convert seconds to: hours, minutes and seconds
      local hours = math.floor(new_time / 3600000)
      local minutes = math.floor(new_time / 60000 - hours * 60)
      local seconds = math.floor(new_time / 1000 - hours * 3600 - minutes * 60)
 
      -- update hud window (6 digit display)
      self.timer_wnd:SetTextST(self:TimeToString(hours) .. ":" .. self:TimeToString(minutes) .. ":" .. self:TimeToString(seconds))
   end
 
   if self.timer_expired then
      -- timer expired:
      self:Expired()
   end
end
 
function Timer:TimeToString(t)
   if t >= 10 then
      return tostring(t)
   else
      return "0" .. tostring(t)
   end
end

When the time expires

We perform the action using special function:

function Timer:Expired()
   -- if we are using counterclockwise timer then remove hud display
   if self.timer_type ~= "silent" then
      get_hud():RemoveCustomStatic("hud_timer_text")
   end
 
   -- perform action
   _G[script_name()][self.action]()
end

Creating timer object and using its methods

Let's create a local variable to hold our timer object. Function get_timer() will be our "handle" to timer object.

local pTimer = nil
 
function get_timer(timer_type, expiration_time, action)
   if pTimer == nil then
      -- create timer object
      pTimer = Timer(timer_type, expiration_time, action)
   end
   return pTimer
end

If you want to use more then one timer at the same time - create an array of objects (timers):

local my_timers = {}
 
local timer_type, expiration_time = nil, nil
local h, m, s = nil, nil, nil
 
-- create 10 timers
for index = 1, 10 do
   if index % 2 == 0 then
      -- 5x silent timers
      timer_type = "silent"
   else
      -- 5x counterclockwise timers
      timer_type = "counterclockwise"
   end
 
   -- just an exaple:
   h = tostring(math.random(100) % 24)
   m = tostring(math.random(100) % 60)
   s = tostring(math.random(100) % 60)
   expiration_time = h .. ":" .. m .. ":" .. s
 
   table.insert(my_timers, Timer(timer_type, expiration_time, "no_action"))
end
 
-- and let's call an update method for each created timer
for index, timer in pairs(my_timers) do
   timer:Update()
end

Action when timer expires

As a default action we used no_action function. Now it's time to define it, we will do that in the same script where we implemented our timer class:

function no_action()
   -- default action
end

Obviously, the empty action function is not very useful, however it is only a default function, in case we forgot to pass an action function to timer constructor. What action function should contain depends on the purpose of timer. For instance:

-- in the same script where timer class is defined, add:
local spawn_vectors = 
{
   {pos = vector():set(1, 2, 3), lvid = 123, gvid = 456},
   {pos = vector():set(4, 5, 6), lvid = 789, gvid = 1011},
   {pos = vector():set(7, 8, 9), lvid = 1213, gvid = 1415},
   {pos = vector():set(8, 7, 6), lvid = 1617, gvid = 1819},
   {pos = vector():set(5, 4, 3), lvid = 2021, gvid = 2223},
}
 
-- action for our timer
function spawn_mutants()
   local spawn_data = nil
   for index = 1, 5 do
      spawn_data = spawn_vectors[math.random(1, #spawn_vectors)]
      alife():create("bloodsucker_strong", spawn_data.pos, spawn_data.lvid, spawn_data.gvid)
 
      spawn_data = spawn_vectors[math.random(1, #spawn_vectors)]
      alife():create("tushkano_normal", spawn_data.pos, spawn_data.lvid, spawn_data.gvid)
   end
end
 
-- create and update timer, for example in bind_stalker.script:
local my_timer = nil
 
function actor_binder:update(delta)
...
   if my_timer == nil then
      -- timer class is defined in timer.script
      my_timer = timer.Timer("silent", "0:30:0", "spawn_mutants")
   else
      -- update timer, if it expires then spawn_mutants() function
      -- will be called and after that, my_timer will be suspended
      my_timer:Update()
   end
...
end

As you can see the timer will be suspended when the expiration_time expires and then the action function will be called. We can also add a method to resume our timer:

function Timer:isSuspended()
   return self.timer_expired
end
 
function Timer:Resume()
   if self.timer_type ~= "silent" then
      get_hud():AddCustomStatic("hud_timer_text", true)
      self.timer_wnd = get_hud():GetCustomStatic("hud_timer_text"):wnd()
   end
   self.activation_time = time_global()
   self.timer_expired = false
end

and resume the timer when it expired:

function actor_binder:update(delta)
...
   if my_timer == nil then
      my_timer = timer.Timer("silent", "0:30:0", "spawn_mutants")
   elseif my_timer:isSuspended() then
      my_timer:Resume()
   else
      my_timer:Update()
   end
...
end

We can also stop the timer (suspend it) if the certain conditions are meet

function Timer:Suspend()
   self.timer_expired = true
end

for instance:

function actor_binder:update(delta)
...
   if my_timer == nil then
      my_timer = timer.Timer("silent", "0:30:0", "spawn_mutants")
   else
      if not xr_conditions.is_day() then
         if my_timer:isSuspended() then
            my_timer:Resume()
         end
         -- update only during nights
         my_timer:Update()
      elseif not my_time:isSuspended() then
         -- suspend if not suspended yet
         my_timer:Suspend()
      end
   end
...
end

Author

Team:Dez0wave

Personal tools