• 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,595
Solutions
3
Reaction score
941
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:
[email protected]/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

[email protected]/movements/movements.xml
XML:
<!-- Rune Match event-->
<movevent event="StepOut" actionid="1337" script="match.lua"/>

[email protected]/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

[email protected]/actions/actions.xml
XML:
<!-- Rune Match event -->
<action uniqueid="1337" script="match.lua" />

[email protected]/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 [email protected]/events/events.xml
XML:
<event class="Player" method="onMoveItem" enabled="1" />

edit player [email protected]/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:

strutZ

Australian OT Member {AKA Beastn}
Joined
Nov 16, 2014
Messages
1,380
Solutions
7
Reaction score
537
ahh this is cool as thanks!
 

imkingran

Learning everyday.
Premium User
Joined
Jan 15, 2014
Messages
1,318
Solutions
35
Reaction score
431
Good Job! That looks like a fun one. :D
 

Progenosis

Member
Joined
Sep 6, 2011
Messages
129
Reaction score
18
Can't find this addres: enable player [email protected]/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?
 

Scorpvm

Member
Joined
Feb 23, 2010
Messages
434
Solutions
3
Reaction score
15
Location
Spain
@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>
 
OP
OP
hodleo

hodleo

Formerly cbrm -Crypto enthusiast, Retired scripter
Staff member
Global Moderator
Joined
Jan 6, 2009
Messages
6,595
Solutions
3
Reaction score
941
Location
Caribbean Sea
@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
 

Infernum

Senator
Joined
Feb 14, 2015
Messages
5,631
Solutions
559
Reaction score
3,846
@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
[email protected]/movements/scripts/match.lua
 

Efren

Lua Scripter
Joined
Apr 18, 2010
Messages
81
Reaction score
4
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!
 
OP
OP
hodleo

hodleo

Formerly cbrm -Crypto enthusiast, Retired scripter
Staff member
Global Moderator
Joined
Jan 6, 2009
Messages
6,595
Solutions
3
Reaction score
941
Location
Caribbean Sea
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.
 

undead mage

Active Member
Joined
Mar 25, 2012
Messages
364
Solutions
2
Reaction score
46
Location
Amsterdam
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
 

Fortera Global

Intermediate OT User
Joined
Nov 20, 2015
Messages
1,157
Solutions
2
Reaction score
110
The actions, uniqueID are corrects ?
4 actions on tiles where Players stay for play?
 
OP
OP
hodleo

hodleo

Formerly cbrm -Crypto enthusiast, Retired scripter
Staff member
Global Moderator
Joined
Jan 6, 2009
Messages
6,595
Solutions
3
Reaction score
941
Location
Caribbean Sea
Top