• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[Lua] Using metamethods in practice. Introduce to OOP in lua.

tarjei

Necronian Engineer
Joined
May 25, 2008
Messages
505
Reaction score
126
Location
Poland
Hello. I have never seen tutorial about metamethods, or how to use it in practice.
Ofcourse if you are more familiar with scripting you can easily read tutorials over internet.
But still I think that using metamethods is something extremely usefull.
So what metamethod is? Its an action made on table. There are few kinds of metamethods, in this tutorial I will use __add metamethod.
But I won't give you a theory, you can read about it in inthernet, lets make some practice.

This is some sample code:
Code:
local position = getPlayerPosition(cid)
local position2 = {x = 123, y =455, z= 7}

I hope that obvious is result of action

Code:
local summary = position + position2

It will give us error in console.
But yet this is possible to add 2 arrays using .add methamethod!

Code:
local mt = {} --metatable that will hold our metametods.
mt.__add = function(a,b) --We tell what to do when arithmetical operation occours
    return setmetatable({x = a.x + b.x, y= a.y+b.y,z = a.z+b.z},mt) --it will return new position, with metatable attached.
end

function Position:new(x,y,z)
  local obj = {x = x, y=y,z=z }
  return setmetatable(obj,mt)
end

So basicly when we use code like that, it will allow to add arrays that contains indexes x,y,z. If array wont have them (or one of them) it will return error.
Now lets try this.

Code:
local pos1 = Position:new(30,30,8)
local pos2 = Position:new(12,56,1)

local pos3 = pos1 + pos2

print(pos3.x,pos3.y,pos3.z)
Result of print should be : 42, 86, 9

Conculsion:
Key to everything is setmetatable function. It allows us to make wierd nice things with regular lua tables. I don't know if given examples won't give any errors when you want test it in your engine, but I think that you can easily fix it if you are a litlle skilled in lua.
 
Last edited:
I got exited writing tutorial about this. I know it might look a bit abstract at first, but in a matter of fact its dead easy when you catch it.
This thread will bring some more information about metametods and how to use it in practice.

So here we go.. Sets. I redirect you to its definition if you dont know what it is, read this before you start reading code:
Code:
http://en.wikipedia.org/wiki/Set_(mathematics)

I want to write basics operations for sets, that is union(addition), substitue, and intersection(common part)

Needed metametods:
Code:
local mt = {} -- metatable
mt.__add = function(a,b) end -- when + operator appears(adding arrays)
mt.__sub = function(a,b) end --when - operator appears(subtracking arrays)
mt.__mul = function(a,b) end --when * operator appears(multiplying arrays)

At this very point I think I should explain more clearly how this concept would work.
If we set meta table to any defined array using function setmetatable depending on what metametod we defined
actions will be taken. Aritmetic operations has two 2 arguments (a + b) / (a -b) /(a*b) and so on thats why we define function(a,b) end
with 2 parameters a and b that are given to this function during aritmethical operations.

Back to subsets; when we talk about addition of subsets, we want as result c such subset that contains all elements of a and b, so sample definition may look like this.

Code:
mt.__add = function(a,b)
    for k, v in pairs(b) do -- for each element of array b we do
         if not(isInArray(a,v)) then
            table.insert(a,v)
		 end
    end
   return a
end

What about when we want to subtraction of sets?

Code:
mt.__sub = function(a,b) 
	local ret = {}
	local insert = false
    for k, v in pairs(a) do -- for each element of array a we do
	  insert = true
	  if(type(v) == "number") then
		   for j, p in pairs(b) do
			 if p == v then
				insert = false
				break
			 end
		   end
		   if insert then
			table.insert(ret,v)
		   end
	   end
    end
    return setmetatable(ret,mt)
end

And best one intersection (sometimes called multiplication of sets)

Code:
mt.__mul = function(a,b) 
   local ret = {} -- Our new array with same values in a and b
    for k, v in pairs(b) do -- for each element of array b we do
         if isInArray(a,v) then
            table.insert(ret,v);
		 end
    end
    return setmetatable(ret,mt) -- we want this new array to have a set properties too.
end

Ok now we just need a set:

Code:
Set = {}
mt.__index = Set -- this metametod occour when we what to index our array
function Set:new(...)
  local obj = {}
  obj.numbers = arg --all arguments given as dots
  return setmetatable(obj,mt) --setting mt as obj metatable
end

function Set:printElements()
  local ret = ""
   for k, v in pairs(self)
    if type(v) == "number" then
     ret = ret ..v..","
    end
   end
  print( ret)
end


Now you can test this your self using code:
Code:
local set1 = Set:new(1,5,7,9,10)
local set2 = Set:new(1,3,7,8,12,11,15)
local set3 = Set:new(1,5,7,2)
set1:printElements()
set2:printElements()
set3:printElements()
local set4 = (set1 - set2)*set3
set4:printElements()

Enjoy.
 
Last edited:
Snake you could add mine code in there? It will be nice base of knowledge about metamethods then ^^
 
Snake you could add mine code in there? It will be nice base of knowledge about metamethods then ^^
Sure, there is code with examples of usage:

  • /data/lib/060-thread.lua
    Lua:
    Thread = {}
    Thread.__index = Thread
    
    
    function Thread:new()
    	local thread =
    	{
    		id 		= 0,
    		cid		= 0,
    		
    		delay 		= 0,
    		callback	= nil
    	}
    	setmetatable(thread, Thread)
    	self.__index = self
    	return thread
    end
    
    
    function Thread:start(...)
    	if (not self.callback) then
    		error('[Thread:start] Could not start thread.')
    	end
    	
    	self.id = addEvent(self:work(), self.delay, ...)
    end
    
    
    function Thread:stop()
    	if (self.id <= 0) then
    		error('[Thread:stop] Could not stop thread.')
    	end
    	stopEvent(self.id)
    end
    
    
    function Thread:work(...)
    	return function(...) 
    		if (self.cid > 0 and not isCreature(self.cid)) then
    			self:stop()
    		else
    			self.callback(...)
    		end
    	end
    end
  • Example:
    Lua:
    local event 	= Thread:new()
    event.delay 	= 5 * 1000
    event.callback 	= function(line1, line2)
    	print(line1, line2)
    end
    event:start("first word", "second word")
    -- event:stop()

TheForgottenServer Threading | View and comment!​
 
This is very useful. It's quite easy to set up in C API, too. OOP should be used in scripts, because ot scripting is about objects:

Code:
local item = Item.new{id=1234, count=50}
print(item.id, item.uid, item.actionid, item.count)

local player = Player.new(cid)
print(player.hp, player.mana, player.name, player:itemCount(item.id))

local tile = Map(123, 455, 7)
print(tile.top, tile[2])
tile:push(item)
 
It uses Snakes thread :D

Lua:
function FuncQueue:new()
	local obj = {}
	obj.funcs = {}
	obj.currentId = 0
	
	obj.isPaused = false;
	obj.runningId = 0
	self.__index = self
	
	setmetatable(obj, self)
	return obj
end

function FuncQueue:addFunction( func,delay, ...)
	self.funcs[self.currentId + 1] = Thread:new()
	self.funcs[self.currentId + 1]:setCallback(func,delay,...)
	self.currentId = self.currentId + 1
end

function FuncQueue:run()
	return function()
	self.runningId = self.runningId + 1
	print(self.runningId.." ID")
	if self.funcs[self.runningId] == nil or self.isPaused then
		return 
	end
	local func, arg = nil, nil
	
	
	self.funcs[self.runningId]:work()()
	local nextcallback = self.funcs[self.runningId + 1]
	if nextcallback == nil then
	 return 
	end
	addEvent(self:run(), nextcallback.callback.delay)
	end
end
function FuncQueue:start()
	self:run()()
end
function FuncQueue:restart()
	self.runningId = 0
	self:start()
end
function FuncQueue:pause()
	self.isPaused = true
end
function FuncQueue:continue()
	self.isPaused = false
	self:start()
end

Lua:
	local function broadcast(text) doBroadcastMessage(text, MESSAGE_STATUS_WARNING) end
	local BroadcastQueue = FuncQueue:new()
	for i = 0, 10 do
		BroadcastQueue:addFunction(broadcast,2*i*60*1000, "TEXT HERE"..i)
	end	
       BroadcastQueue:start()
Basicly it makes a queue of functions we want to be called with some specific delays and in order.

- - - Updated - - -

And here a position library, it might serve someone for handling positions (it will make definately scripts more readable xD )


Lua:
Position = {}
function Position.isValid(arr)
	return type(arr.x) == "number" and type(arr.y) == "number"  and type(arr.z) == "number"
end
function Position.print(arr)
	print("Position representation", unpack(arr))
end
function Position:new(x,y,z)
	local obj = {}
	if type(x) == "table" and Position.isValid(x) then
	  obj.pos = x --we passed position as one argument
	else
		
	  obj.pos = {x = x, y = y, z = z}
	 
	end
	
	self.__index = self
	
	self.__add = function(a,b)
		if Position.isValid(a()) and Position.isValid(b()) then
			local result = {x = a().x +b().x, y = a().y + b().y, z = a().z }
			return self:new(result)
		end
		return error("You are attemping to add something else than positions objects")
	end
	self.__sub = function(a,b)
		if a == nil then return error("A is nil") end
		if b == nil then return error("B is nil") end
		if Position.isValid(a()) and Position.isValid(b()) then
			local result = {x = a().x -b().x, y = a().y - b().y, z = a().z }
			return self:new(result)
		end
		return error("You are attemping to subtrack something else than positions objects")	
	end
	self.__eq = function(a, b)
		self.print(a)
		self.print(b)
		if Position.isValid(a()) and Position.isValid(b()) then
			local result =  (a().x ==b().x and a().y == b().y and b().z == a().z )
			return result
		end
		return error("You are attemping to compare something else than positions objects")	
	end
	self.__call = function(s)
		--if we call object like function it will return its position array.
		return s.pos
	end
	return setmetatable(obj,self)
end
function Position:tell()
   return self.pos.x, self.pos.y, self.pos.z
end
 
Last edited:
Back
Top