Inheritance
From Mod Wiki
Revision as of 11:52, 26 July 2008 (edit) Barin (Talk | contribs) (New page: == 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 enab...) ← Previous diff |
Current revision (12:00, 26 July 2008) (edit) (undo) Don Reba (Talk | contribs) m (→HudTimer class - {{Clear}} after Image) |
||
Line 257: | Line 257: | ||
=== HudTimer class === | === HudTimer class === | ||
- | [[Image:Hud_timer.jpg|left|thumb|hud_timer]] [[Image:Hud_timer_text.jpg|left|thumb|hud_timer_text]] | + | [[Image:Hud_timer.jpg|left|thumb|hud_timer]] [[Image:Hud_timer_text.jpg|left|thumb|hud_timer_text]] {{Clear}} |
'''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''': | '''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''': |
Current revision
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
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