• 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!

[TFS 1.2] Rune Match event

hodleo

Formerly cbrm -Crypto enthusiast, Retired scripter
Staff member
Global Moderator
Joined
Jan 6, 2009
Messages
6,598
Solutions
3
Reaction score
955
Location
Caribbean Sea
Rune Match!
by cbrm

written with Sublime Text 3 on TFS 1.2, 10.95
powered by 1337 technology
ScUftjB.gif
Cr5Oho8.gif
R8ngQFB.gif
lcnNkNu.gif
OEvbVzN.gif


2~4 multiplayer event
not recommended for players with poor memory
based on the traditional Concentration game



Goal: Player must compete with other ones by searching pairs of runes in the board. Using look (Shift+Left Click) on the blank runes, the player will reveal the rune there, and then must find its pair to make a match and score points.

Map Layout:
2nl2i4n.png


33-q0Ga.png

Setup:
add@data/lib/core/position.lua

Lua:
function Position:isInRange(fromPosition, toPosition)
    return (self.x >= fromPosition.x and self.y >= fromPosition.y and self.z >= fromPosition.z and self.x <= toPosition.x and self.y <= toPosition.y and self.z <= toPosition.z)
end

add@data/movements/movements.xml
XML:
<!-- Rune Match event-->
<movevent event="StepOut" actionid="1337" script="match.lua"/>

create@data/movements/scripts/match.lua
Lua:
--[[
    ////////////////////////
        Rune Match event  
         written by cbrm
           for TFS 1.2
           @otland.net
    \\\\\\\\\\\\\\\\\\\\\\\\
]]--

MATCH = {
    -- do not touch --
    POS = {},
    LAST = {},
    COUNT = {},
    SCORE = {},
    SHOWN = {},
    EXHAUST = {},
    EVENT = {},
    SIZE = {7, 8}, 
    DELAY = 1,
    HIDDEN = 2260,
    TIMER = 0,
    DURATION = 15,
    ------------------
    BOARD = {
        from = Position(32393, 32191, 7),
        to = Position(32399, 32198, 7)
    },
    PLAYERS = {
        from = {
            [1] = Position(32395, 32189, 7),
            [2] = Position(32396, 32189, 7),
            [3] = Position(32397, 32189, 7),
            [4] = Position(32398, 32189, 7)
        },
        to = {
            [1] = Position(32392, 32193, 7),
            [2] = Position(32400, 32193, 7),
            [3] = Position(32392, 32196, 7),
            [4] = Position(32400, 32196, 7)
        }     
    },
    EXIT = Position(32400, 32204, 7)
}

for rune = 2261, 2316 do
    table.insert(MATCH.SHOWN, rune)
end

pause = coroutine.yield
function doSleep(co)
    if coroutine.status(co) ~= 'dead' then
        local _, delay = coroutine.resume(co)
        addEvent(doSleep, delay, co)
    end
end

function enableSleep(f)
    if type(f) == 'function' then
        local co = coroutine.create(f)
        doSleep(co)
    end
end

function getValue(v)
    return v or 0
end

function table.getIndex(t, v)
    for index, value in ipairs(t) do
        if v == value then
            return index
        end
    end
end

function table.shuffle(t)
    local j
    for i = #t, 2, -1 do
        j = math.random(i)
        t[i], t[j] = t[j], t[i]
    end
end

function isMatchRune(id)
    return isInArray(MATCH.SHOWN, id) or (id == MATCH.HIDDEN)
end

function hideRune(p) 
    local v = Tile(p):getTopVisibleThing(false)
    return isMatchRune(v:getId()) and v:transform(MATCH.HIDDEN), p:sendMagicEffect(10)
end

function controlMatchRune(cid, id)
    if getValue(MATCH.LAST[cid]) == id then
        p = MATCH.POS[cid]
        hideRune(p)
        MATCH.POS[cid] = 0
        MATCH.LAST[cid] = 0
        MATCH.COUNT[cid] = 0
        doCreatureSay(cid, "TIMEOUT", TALKTYPE_MONSTER_SAY, false, nil, p)
    end
end

function showRune(cid, rune)
    local p, u = rune:getPosition(), rune:getAttribute(ITEM_ATTRIBUTE_CHARGES)
    rune:transform(u)
    p:sendMagicEffect(10)
    local v = getValue(MATCH.LAST[cid])
    if v > 0 then
        return v == u
    end
    MATCH.LAST[cid] = u
    MATCH.POS[cid] = p
    MATCH.EVENT[cid] = addEvent(controlMatchRune, 4200, cid, u)
    return nil
end

function cleanBoard()
    for x = MATCH.BOARD.from.x, MATCH.BOARD.to.x do
        for y = MATCH.BOARD.from.y, MATCH.BOARD.to.y do 
            for _, item in ipairs(Tile(Position(x, y, MATCH.BOARD.from.z)):getItems()) do
                item:remove()
            end
        end
    end
end

function endMatch()
    local h = {}
    for _, pos in ipairs(MATCH.PLAYERS.to) do
        local player = Tile(pos):getTopCreature()
        if player ~= nil then
            local cid = player:getId()
            table.insert(h, {player:getName(), getValue(MATCH.SCORE[cid])})
            MATCH.SCORE[cid] = nil
            player:teleportTo(MATCH.EXIT)
        end
    end
    table.sort(h, function(a, b) return a[2] > b[2] end)
    local str = "Rune Match event finished!"
    for _, highscore in ipairs(h) do
        str = str .. "\n" .. highscore[1] .. " scored " .. math.floor((highscore[2]/(MATCH.SIZE[1]*MATCH.SIZE[2]))*100) .. "%"
    end
    Game.broadcastMessage(str, MESSAGE_EVENT_ADVANCE)
    cleanBoard()
    stopEvent(MATCH.TIMER)
end

function newBoard()
    cleanBoard()
    table.shuffle(MATCH.SHOWN)
    MATCH.TIMER = addEvent(endMatch, MATCH.DURATION*60*1000)
    local i, k, l, x, y = 0, {}, MATCH.SIZE[1]*MATCH.SIZE[2], MATCH.BOARD.from.x, MATCH.BOARD.from.y
    for index = 1, (MATCH.SIZE[1]*MATCH.SIZE[2])/2 do     
        table.insert(k, MATCH.SHOWN[index])
        table.insert(k, MATCH.SHOWN[index])
    end
    while i < l do             
        v, p = k[math.random(#k)], Position(x, y, MATCH.BOARD.from.z)     
        p:sendMagicEffect(10)
        table.remove(k, table.getIndex(k, v))     
        r = Item(doCreateItemEx(MATCH.HIDDEN, 1))
        r:setActionId(31337)
        r:setAttribute(ITEM_ATTRIBUTE_CHARGES, v)
        doTileAddItemEx(p, r:getUniqueId())
        i = i + 1
        if x == MATCH.BOARD.to.x then
            y = y + 1         
            x = MATCH.BOARD.from.x
        else
            x = x + 1
        end
    end
end

function isBoardEmpty()
    for x = MATCH.BOARD.from.x, MATCH.BOARD.to.x do
        for y = MATCH.BOARD.from.y, MATCH.BOARD.to.y do
            local v = Tile(Position(x, y, MATCH.BOARD.from.z)):getTopVisibleThing(false)
            if (v ~= nil) and isMatchRune(v:getId()) then
                return false
            end
        end
    end
    return true
end

function onStepOut(creature, item, position, fromPosition)
    if not position:isInRange(MATCH.BOARD.from, MATCH.BOARD.to) and (MATCH.SCORE[creature:getId()] ~= nil) then
        creature:teleportTo(fromPosition)
    end
    return true
end

add@data/actions/actions.xml
XML:
<!-- Rune Match event -->
<action uniqueid="1337" script="match.lua" />

create@data/actions/scripts/match.lua
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    for _, pos in ipairs(MATCH.PLAYERS.to) do
        local v = Tile(pos):getTopCreature()
        if v ~= nil then
            return player:say("There are still players on the game!", TALKTYPE_MONSTER_SAY, false, nil, fromPosition)
        end
    end

    local players = {}
    for _, pos in ipairs(MATCH.PLAYERS.from) do
        local v = Tile(pos):getTopCreature()
        if (v ~= nil) and v:isPlayer() then
            table.insert(players, v)
        end
    end

    if #players > 4 then
        return player:say("Limit is 4 players!", TALKTYPE_MONSTER_SAY, false, nil, fromPosition)
    end

    if #players < 2 then
        return player:say("At least 2 players are needed!", TALKTYPE_MONSTER_SAY, false, nil, fromPosition)
    end

    newBoard()
    for i, eventPlayer in ipairs(players) do
        local pid = eventPlayer:getId()
        eventPlayer:teleportTo(MATCH.PLAYERS.to[i])
        MATCH.POS[pid] = 0
        MATCH.LAST[pid] = 0
        MATCH.COUNT[pid] = 0
        MATCH.SCORE[pid] = 0
        MATCH.EXHAUST[pid] = 0
    end
    return item:transform(item:getId() == 1945 and 1946 or 1945)
end

enable player method@data/events/events.xml
XML:
<event class="Player" method="onMoveItem" enabled="1" />

edit player methods@data/events/scripts/player.lua
After
Lua:
function Player:onMoveItem(item, count, fromPosition, toPosition)
Add
Lua:
if fromPosition:isInRange(MATCH.BOARD.from, MATCH.BOARD.to) then
    self:sendCancelMessage('Sorry, not possible.')
    return false
end

if toPosition:isInRange(MATCH.BOARD.from, MATCH.BOARD.to) then
    self:sendCancelMessage('Sorry, not possible.')
    return false
end

After
Lua:
function Player:onLook(thing, position, distance)
Add
Lua:
if thing:isItem() and (thing.actionid == 31337) then
    local pid = self:getId()
    if MATCH.SCORE[pid] == nil then
        return false
    end

    if thing:getId() ~= MATCH.HIDDEN then
        return false
    end

    if getValue(MATCH.EXHAUST[pid]) >= os.time(t) then
        self:say("Wait...", TALKTYPE_MONSTER_SAY, false, self)
        return false
    end

    local a = MATCH.COUNT[pid]
    if a == 2 then
        return false
    end

    MATCH.EXHAUST[pid] = os.time(t) + 1
    MATCH.COUNT[pid] = a + 1
    self:getPosition():sendDistanceEffect(position, 31)
    local v, p = showRune(pid, thing), MATCH.POS[pid]
    enableSleep(function()
        if v ~= nil then
            pause(500)
            MATCH.POS[pid] = 0
            MATCH.LAST[pid] = 0
            MATCH.COUNT[pid] = 0      
            if v then
                for _, pos in ipairs({p, position}) do
                    local b = Tile(pos):getTopVisibleThing(false)
                    if isMatchRune(b:getId()) then
                        b:remove()
                        pos:sendMagicEffect(10)
                    end
                end
                self:say("MATCH!", TALKTYPE_MONSTER_SAY, false, nil, p)
                self:say("MATCH!", TALKTYPE_MONSTER_SAY, false, nil, position)
                p:sendDistanceEffect(self:getPosition(), 36)
                position:sendDistanceEffect(self:getPosition(), 36)
                local u = getValue(MATCH.SCORE[pid])
                MATCH.SCORE[pid] = u + 2
                local k = (getValue(MATCH.SCORE[pid])/(MATCH.SIZE[1]*MATCH.SIZE[2]))*100
                self:say(math.floor(k) .. "%", TALKTYPE_MONSTER_SAY)
                self:sendCancelMessage(getValue(MATCH.SCORE[pid])/2 .. "/".. (MATCH.SIZE[1]*MATCH.SIZE[2])/2 .." runes unlocked")
                if isBoardEmpty() then
                    endMatch()
                end
            else
                hideRune(p)
                hideRune(position)
                self:say("FAIL!", TALKTYPE_MONSTER_SAY, false, nil, p)
                self:say("FAIL!", TALKTYPE_MONSTER_SAY, false, nil, position)
                stopEvent(MATCH.EVENT[pid])
            end
        end
    end)
    return false
end

Lever used is 1945/1946
Set UniqueID 1337 to lever


Optional settings:
Add reward to player with top score, only if his score is higher than 2nd in rank
:
Before
Lua:
Game.broadcastMessage(str, MESSAGE_EVENT_ADVANCE)
Add
Lua:
if h[1][2] > h[2][2] then
    local topPlayer = Player(h[1][1])
    topPlayer:addItem(2152, 10)
    topPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You won cash!")
end

Show # of matches scored instead of %
Change
Lua:
local k = (getValue(MATCH.SCORE[pid])/(MATCH.SIZE[1]*MATCH.SIZE[2]))*100
self:say(math.floor(k) .. "%", TALKTYPE_MONSTER_SAY)
For
Lua:
self:say(getValue(MATCH.SCORE[pid])/2 .. " matches", TALKTYPE_MONSTER_SAY)
 
Last edited:
ahh this is cool as thanks!
 
Good Job! That looks like a fun one. :D
 
Can't find this addres: enable player method@data/events/events.xml
and stove related to "method" on my server

:/

Edit: Maybe 'cause I'm running TFS 0.3.6
How can I make this event work?
 
Can't find this addres: enable player method@data/events/events.xml
and stove related to "method" on my server

:/

Edit: Maybe 'cause I'm running TFS 0.3.6
How can I make this event work?
Upgrade to TFS 1.2 :D
 
@cbrm
Lua Script Error: [Action Interface]
data/actions/scripts/add/match.lua:eek:nUse
data/actions/scripts/add/match.lua:2: attempt to index global 'MATCH' (a nil value)
stack traceback:
[C]: in function '__index'
data/actions/scripts/add/match.lua:2: in function <data/actions/scripts/add/match.lua:1>
 
@cbrm
Lua Script Error: [Action Interface]
data/actions/scripts/add/match.lua:eek:nUse
data/actions/scripts/add/match.lua:2: attempt to index global 'MATCH' (a nil value)
stack traceback:
[C]: in function '__index'
data/actions/scripts/add/match.lua:2: in function <data/actions/scripts/add/match.lua:1>
> TFS 1.2
>>shared lua environment
 
@cbrm
Lua Script Error: [Action Interface]
data/actions/scripts/add/match.lua:eek:nUse
data/actions/scripts/add/match.lua:2: attempt to index global 'MATCH' (a nil value)
stack traceback:
[C]: in function '__index'
data/actions/scripts/add/match.lua:2: in function <data/actions/scripts/add/match.lua:1>
you didn't add the movement script
create@data/movements/scripts/match.lua
 
hey @cbrm

I tested your script and everything is smooth! the only thing is that if someone clicks the lever outside the FROM positions and there is no one inside the FROM positions the lever still works.

I fixed this by modifying (In the actions/scripts/math.lua - line 21):
this:
Code:
if #players == 1 then

to this:
Code:
if #players == 1 or #players == 0 then

btw, I love this script Im using it on my server now :)
http://tnt-ot.com/
Thanks for sharing!
 
Now the problem is.. runes never reveal they are always blank runes.
https://imgur.com/Qi4fSf3
I guess you are doing something wrong or missed a step of the installation.

hey @cbrm

I tested your script and everything is smooth! the only thing is that if someone clicks the lever outside the FROM positions and there is no one inside the FROM positions the lever still works.

I fixed this by modifying (In the actions/scripts/math.lua - line 21):
this:
Code:
if #players == 1 then

to this:
Code:
if #players == 1 or #players == 0 then

btw, I love this script Im using it on my server now :)
http://tnt-ot.com/
Thanks for sharing!
Sure, it'll be a bug depending of the layout of the map, since in mine the lever cannot be accessed from outside the positions.
But I updated that line anyway, thanks for the review too.
 
I guess you are doing something wrong or missed a step of the installation.


Sure, it'll be a bug depending of the layout of the map, since in mine the lever cannot be accessed from outside the positions.
But I updated that line anyway, thanks for the review too.
Just tested it. It seems like everything is working perfect!
Is there a time limit? So that people won't be able to just exit their char there and block the game untill ss
 
The actions, uniqueID are corrects ?
4 actions on tiles where Players stay for play?
 
Back
Top