Inheritance

From Mod Wiki

Jump to: navigation, search

Contents

Lua and OOP

Lua is not an object oriented language, it doesn't provide class system. However you can simulate classes in Lua using metatables. Luckily for us developers already enabled class system so we can use classes in our scripts. In this article I would like to show you how to use inheritance. I assume you are familiar with Lua and object oriented programming. Let's start by defining some useful functions for debuging purposes:

function ToString(arg)
   local t = type(arg)
   if t == "string" then
      return arg
   elseif t == "number" or t == "boolean" then
      return tostring(arg)
   elseif t == "nil" then
      return "<nil>"
   elseif t == "table" then
      return "<table>"
   else
      return "<user_data>"
   end
end
 
function debug_print(...)	
   local args = {...}
   if args[1] ~= nil then
      local con, str = get_console(), ""
      for k, v in pairs(args) do
         str = str .. ToString(v) .. " "
         if string.len(str) >= 190 then
            con:execute("load *** " .. str)
            str = ""
         end
      end
      if str ~= "" then
         con:execute("load *** " .. str)
      end
   end
end

Base class

class "BaseClass"
 
function BaseClass:__init(a, b)
   self.a = a
   self.b = b
   debug_print("BaseClass init")
end
 
function BaseClass:__finalize()
   self.a = nil
   self.b = nil
   debug_print("BaseClass finalize")
end
 
function BaseClass:Print()
   debug_print("BaseClass a =", self.a)
   debug_print("BaseClass b =", self.b)
end

As you can see our BaseClass has two variables (fields) and one method (function). Now let's create a new object and call function Print:

local bc = BaseClass(5, 20)
-- output: BaseClass init
 
bc:Print()
-- output: BaseClass a = 5
-- output: BaseClass b = 20
 
bc = nil
-- output: BaseClass finalize

First we created a new object of BaseClass, the constructor of that class was called, then we called function Print and we destroyed our object by assigning a nil value to variable bc thus the destructor of BaseClass was called. Now let's create a new class which derives from BaseClass.

Derived class

class "DerivedClass" (BaseClass)
 
function DerivedClass:__init(a, b, c) super(a, b)
   self.c = c
   debug_print("DerivedClass init")
end
 
function DerivedClass:__finalize()
   self.c = nil
   debug_print("DerivedClass finalize")
end
 
function DerivedClass:Print()
   BaseClass.Print(self)
   debug_print("DerivedClass c =", self.c)
end

Right after the name of our new class, we have to write (in parentheses) name of the base class we are deriving from (BaseClass in this case). In the constructor definition we call a constructor of the base class. Inside function Print we call function Print from the base class. As an argument we have to provide a "pointer" to derived class (self). If we create a new object from derived class then the constructor of the base class will be called first and then constructor of the derived class. When the object of derived class is destroyed then its destructor is called first and then destructor of the base class (if there are no more objects derived from that class).

local dc = DerivedClass(5, 20, 100)
-- output: BaseClass init
-- output: DerivedClass init
 
dc:Print()
-- output: BaseClass a = 5
-- output: BaseClass b = 20
-- output: DerivedClass c = 100
 
dc = nil
-- output: DerivedClass finalize
-- output: BaseClass finalize

When a class is inherited then all the functions and variables are inherited:

function DerivedClass:Print()
   --BaseClass.Print(self)
   debug_print("DerivedClass a =", self.a)
   debug_print("DerivedClass b =", self.b)
   debug_print("DerivedClass c =", self.c)
end
 
local dc = DerivedClass(5, 20, 100)
-- output: BaseClass init
-- output: DerivedClass init
 
dc:Print()
-- output: DerivedClass a = 5
-- output: DerivedClass b = 20
-- output: DerivedClass c = 100
 
dc = nil
-- output: DerivedClass finalize
-- output: BaseClass finalize

Practical example

This example is based on Scripted timer article. This time the Timer class defines only silent timer. HudTimer class derives from Timer class and defines the timer with text displayed on hud.

Timer class

local def_expiration_time = {h = 0, m = 5, s = 0}
 
class "Timer"
 
function Timer:__init(expiration_time, action)
   self.expiration_time = self:normalize_expiration_time(expiration_time)
   self.activation_time = time_global()
   self.current_time = nil
   self.scriptName, self.functionName = this.get_action(action)
   self.timer_expired = false
end
 
function Timer:__finalize()
   self.scriptName = nil
   self.functionName = nil
   self.timer_expired = nil
   self.current_time = nil
   self.activation_time = nil
   self.expiration_time = nil
end
 
function Timer:Update()
   if self.timer_expired then
      return
   end
 
   self.current_time = self.expiration_time - (time_global() - self.activation_time)	
   if self.current_time <= 0 then
      self.current_time = 0
      self:Expired()
   end
end
 
function Timer:Expired()
   self.timer_expired = true
   _G[self.scriptName][self.functionName]()
end
 
function Timer:isSuspended()
   return self.timer_expired
end
 
function Timer:Suspend()
   self.timer_expired = true
end
 
function Timer:Resume()
   self.activation_time = time_global()
   self.timer_expired = false
end
 
function Timer:Resume(expiration_time)
   self.expiration_time = self:normalize_expiration_time(expiration_time)
   self.activation_time = time_global()
   self.timer_expired = false
end
 
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
 
function no_action()
end
 
function get_action(str)
   if not str then
      return script_name(), "no_action"
   end
 
   local scriptName, functionName = nil, nil
   local dot = string.find(str, "%.")
 
   if dot then
      scriptName = string.sub(str, 0, dot - 1)
      functionName = string.sub(str, dot + 1)
   end
 
   if not scriptName then
      scriptName = script_name()
   end
 
   if not functionName then
      functionName = "no_action"
   end
 
   if not _G[scriptName] then
      scriptName = script_name()
   end
 
   if not _G[scriptName][functionName] then
      return scriptName, "no_action"
   end
 
   return scriptName, functionName
end

HudTimer class

hud_timer
hud_timer
hud_timer_text
hud_timer_text

HudTimer class only display the text on the hud while base class (Timer) is responsible for counting time. However Timer class can be used separately for silent timers. As for the display you can use hud_timer or hud_timer_text:

local def_display = "hud_timer" -- "hud_timer_text"
 
class "HudTimer" (Timer)
 
function HudTimer:__init(expiration_time, action, display_type) super(expiration_time, action)
   if not display_type then
      self.display_type = def_display
   else
      self.display_type = display_type
   end
 
   get_hud():AddCustomStatic(self.display_type, true)
   self.timer_wnd = get_hud():GetCustomStatic(self.display_type):wnd()
end
 
function HudTimer:__finalize()
   self.display_type = nil
   self.timer_wnd = nil
end
 
function HudTimer:Update()
   Timer.Update(self)
 
   local hours = math.floor(self.current_time / 3600000)
   local minutes = math.floor(self.current_time / 60000 - hours * 60)
   local seconds = math.floor(self.current_time / 1000 - hours * 3600 - minutes * 60)
   self.timer_wnd:SetTextST(self:TimeToString(hours) .. ":" .. self:TimeToString(minutes) .. ":" .. self:TimeToString(seconds))
 
   if self.timer_expired then
      get_hud():RemoveCustomStatic(self.display_type)
   end
end
 
function HudTimer:isSuspended()
   return self.timer_expired
end
 
function HudTimer:Suspend()
   self.timer_expired = true
end
 
function HudTimer:Resume()
   Timer.Resume(self)
   get_hud():AddCustomStatic(self.display_type, true)
   self.timer_wnd = get_hud():GetCustomStatic(self.display_type):wnd()
end
 
function HudTimer:Resume(expiration_time)
   Timer.Resume(self, expiration_time)
   get_hud():AddCustomStatic(self.display_type, true)
   self.timer_wnd = get_hud():GetCustomStatic(self.display_type):wnd()
end
 
function HudTimer:TimeToString(t)
   if t >= 10 then
      return tostring(t)
   else
      return "0" .. tostring(t)
   end
end

Author

Team:Dez0wave

Personal tools