• 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 Switch function :)

Codex NG

Recurrent Flamer
Joined
Jul 24, 2015
Messages
2,994
Solutions
12
Reaction score
1,657
I should just have 1 thread for all my experimental code, but then how would you find anything heh

Anyone who has ever programmed in c,c++, php, java, javascript etc.. knows how powerful a switch statement can be and since lua doesn't seem to have one and we have to resort to countless if / else statements I thought it would be nice to create one.

So I created a mixed table with different types of indexes and values.
Code:
    local case = {
        ['a'] = function() return 2 end,
        ['b'] = 2,
        ['c'] = {1,2,4},
        name = "codex",
        [10] = ' this is from index 10 ',
        5 * 6,
        [45] = 20 + 7,
        {5, 23, 27},
        ["two words"] = 'hey you'
    }

Then I created a function which would take 1 argument called switch just as it is seen in the languages mentioned above.
Code:
    function switch(x)
        if type(case[x]) == "function" then
            return case[x]()
        elseif type(case[x]) == "table" then
            return unpack(case[x])
        else
            return case[x]
        end
    end

Then to test it I used a generic for loop with pairs()
Code:
   for k, v in pairs(case) do
        print("index "..k, switch(k) )
   end

And this is the results
Code:
index 1    5    23    27
index 2    30
index a    2
index c    1    2    4
index b    2
index 45    27
index two words    hey you
index 10     this is from index 10
index name    codex

Using this switch function should make decision making a lot easier :)
 
In theory, using this the switch function inside of an npc script
Code:
NpcSystem.parseParameters(npcHandler)
local talkState = {}

function onCreatureAppear(cid)                npcHandler:onCreatureAppear(cid)            end
function onCreatureDisappear(cid)            npcHandler:onCreatureDisappear(cid)            end
function onCreatureSay(cid, type, msg)            npcHandler:onCreatureSay(cid, type, msg)        end
function onThink()                    npcHandler:onThink()

-- vocations to be promoted to
local promotions = {
    5, -- master sorcerer
    6, -- elder druid
    7, -- royal paladin
    8  -- elite knight
}
-- cost of the promotion
local cost = 20000
-- level required to be promoted
local levelReq = 20
-- amount of days required to buy a promotion
local premDays = 5


function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end

    local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid
    -- talkUser, cid & everything outside of callback is global to case
    local case = {
        ['promo'] = function() say('Do you want to be promoted in your vocation for '.. cost ..' gold?', cid) talkState[talkUser] = 1 end,
        ['yes'] =     function()
                        if talkState[talkUser] == 1 then
                            if getPlayerPremiumDays(cid) >= premDays then
                                if getPlayerLevel(cid) >= levelReq then
                                    if promotions[getPlayerVocation(cid)] ~= nil then
                                        if(doPlayerRemoveMoney(cid, cost)) then
                                            setPlayerPromotionLevel(promotions[getPlayerVocation(cid)])
                                            selfSay('Congratulations! You are now promoted.', cid)
                                        else
                                            selfSay('Sorry, you don\'t have enough gold.', cid)
                                        end
                                    else
                                        selfSay('You have already been promoted.', cid)
                                    end
                                else
                                    selfSay('Sorry, you need to be level '.. levelReq ..' to be promoted.', cid)
                                end
                            else
                                selfSay('Sorry, you need to have '.. premDays ..' or more premium days on your account to buy a promotion.', cid)
                        end
                        talkState[talkUser] = 0   
                    end,
        ['no'] =    function()
                        if talkState[talkUser] == 1 then
                            talkState[talkUser] = 0
                            selfSay('Allright then. Come back when you are ready.', cid)
                        end
                    end
    }
   
    local function switch(x)
        if type(case[x]) == "function" then
            return case[x]()
        elseif type(case[x]) == "table" then
            return unpack(case[x])
        else
            return case[x]
        end
    end
   
    switch( msg )
    return true
end

npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())

Should work the same as this script
Code:
NpcSystem.parseParameters(npcHandler)
local talkState = {}

function onCreatureAppear(cid)                npcHandler:onCreatureAppear(cid)            end
function onCreatureDisappear(cid)            npcHandler:onCreatureDisappear(cid)            end
function onCreatureSay(cid, type, msg)            npcHandler:onCreatureSay(cid, type, msg)        end
function onThink()                    npcHandler:onThink()

-- vocations to be promoted to
local promotions = {
    5, -- master sorcerer
    6, -- elder druid
    7, -- royal paladin
    8  -- elite knight
}
-- cost of the promotion
local cost = 20000
-- level required to be promoted
local levelReq = 20
-- amount of days required to buy a promotion
local premDays = 5

function creatureSayCallback(cid, type, msg)
    if(not npcHandler:isFocused(cid)) then
        return false
    end

    local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid

    if(msgcontains(msg, 'promo') or msgcontains(msg, 'promotion')) then
        selfSay('Do you want to be promoted in your vocation for '.. cost ..' gold?', cid)
        talkState[talkUser] = 1
    elseif(msgcontains(msg, 'yes') and talkState[talkUser] == 1) then
        if getPlayerPremiumDays(cid) >= premDays then
            if getPlayerLevel(cid) >= levelReq then
                if promotions[getPlayerVocation(cid)] ~= nil then
                    if(doPlayerRemoveMoney(cid, cost)) then
                        setPlayerPromotionLevel(promotions[getPlayerVocation(cid)])
                        selfSay('Congratulations! You are now promoted.', cid)
                    else
                        selfSay('Sorry, you don\'t have enough gold.', cid)
                    end
                else
                    selfSay('You have already been promoted.', cid)
                end
            else
                selfSay('Sorry, you need to be level '.. levelReq ..' to be promoted.', cid)
            end
        else
            selfSay('Sorry, you need to have '.. premDays ..' or more premium days on your account to buy a promotion.', cid)
        end
        talkState[talkUser] = 0
    elseif(msgcontains(msg, 'no') and talkState[talkUser] == 1) then
        talkState[talkUser] = 0
        selfSay('Allright then. Come back when you are ready.', cid)
    end
    return true
end

npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
 
Switch statements (on languages that support it) are only better than if/else statements when you have a lot of clauses. That is because the switch creates a hash map with the address it should jump to for each expression, instead of the if/else flow control, that is, to jump to each clause at run time in order and evaluate its expression to decide whether to jump to the next if or not.

You can see that in this byte code generated by LuaJIT with five if/else clauses checking if 'v' is a specific number:

Code:
0001    KSHORT   0   4
0002    ISNEN    0   0      ; 0
0003    JMP      1 => 0008
0004    GGET     1   0      ; "print"
0005    KSTR     2   1      ; "v is zero"
0006    CALL     1   1   2
0007    JMP      1 => 0031
0008 => ISNEN    0   1      ; 1
0009    JMP      1 => 0014
0010    GGET     1   0      ; "print"
0011    KSTR     2   2      ; "v is one"
0012    CALL     1   1   2
0013    JMP      1 => 0031
0014 => ISNEN    0   2      ; 2
0015    JMP      1 => 0020
0016    GGET     1   0      ; "print"
0017    KSTR     2   3      ; "v is two"
0018    CALL     1   1   2
0019    JMP      1 => 0031
0020 => ISNEN    0   3      ; 3
0021    JMP      1 => 0026
0022    GGET     1   0      ; "print"
0023    KSTR     2   4      ; "v is three"
0024    CALL     1   1   2
0025    JMP      1 => 0031
0026 => ISNEN    0   4      ; 4
0027    JMP      1 => 0031
0028    GGET     1   0      ; "print"
0029    KSTR     2   5      ; "v is four"
0030    CALL     1   1   2
0031 => RET0     0   1

However the switch statement, as mentioned, works by creating a hash map and then jumping to it in run time, making a Lua table to map each expression to a function, we get the following byte code:

Code:
0001    GGET     0   0      ; "print"
0002    KSTR     1   1      ; "v is zero"
0003    CALL     0   1   2
0004    RET0     0   1

-- BYTECODE -- a.lua:19-19
0001    GGET     0   0      ; "print"
0002    KSTR     1   1      ; "v is one"
0003    CALL     0   1   2
0004    RET0     0   1

-- BYTECODE -- a.lua:20-20
0001    GGET     0   0      ; "print"
0002    KSTR     1   1      ; "v is two"
0003    CALL     0   1   2
0004    RET0     0   1

-- BYTECODE -- a.lua:21-21
0001    GGET     0   0      ; "print"
0002    KSTR     1   1      ; "v is three"
0003    CALL     0   1   2
0004    RET0     0   1

-- BYTECODE -- a.lua:22-22
0001    GGET     0   0      ; "print"
0002    KSTR     1   1      ; "v is four"
0003    CALL     0   1   2
0004    RET0     0   1

-- BYTECODE -- a.lua:0-48
0001    KSHORT   0   4
0002    TNEW     1 4099
0003    FNEW     2   0      ; a.lua:18
0004    TSETB    2   1   0
0005    FNEW     2   1      ; a.lua:19
0006    TSETB    2   1   1
0007    FNEW     2   2      ; a.lua:20
0008    TSETB    2   1   2
0009    FNEW     2   3      ; a.lua:21
0010    TSETB    2   1   3
0011    FNEW     2   4      ; a.lua:22
0012    TSETB    2   1   4
0013    TGETV    2   1   0
0014    CALL     2   1   1
0015    RET0     0   1

In Lua you have the additional overhead of creating each function and setting it to a table, which made the over all byte code slightly longer, however it could be more efficient during run time, especially if you only create this table once and reuse it, because then you are only executing 2 operations to retrieve and call the function from the table (ignoring the function call overhead, of course)

Conclusion, use a table with functions if you're reusing the same table over and over or if you can't predict which if/else statements are probably true, because if the first if statement is true 90% of the time, 9 in 10 times the code flow will be optimal with no switch overhead.

Hope you find this as interesting!
 
Back
Top