MoveEvent Snake Event TFS 1.3 ~ Revscript ~ EventCallback

Sarah Wesker

ค∂vαηcε รүηтαx ❤
Support Team
Joined
Mar 16, 2017
Messages
918
Solutions
90
Reaction score
902
Location
London
GitHub
MillhioreBT
Hello everyone, I hope you are well
Today I want to share with you an event that I had planned to do for a long time, I know that @Snavy released his version but for a change I will also launch mine

First of all I will show you a simple example of what it looks like:

Installation: data/scripts/snakeevent.lua

Lua:
-- SnakeEvent version de Sarah Wesker de otland.net
-- Version: 1.0
-- Fecha de creacion: Martes, 13 de Abril del 2021
-- Requerimientos del servidor:
-- *-> Engine TFS 1.3
-- *-> Revscript
-- *-> EventCallback

if not SnakeEvent or SnakeEvent.state == "stoped" then
    SnakeEvent = {
        talkAction = "/snake",
        misc = {
            exitPosition = Position(3191, 1809, 7),
            minPlayers = 2,
            maxPlayers = 10,
            timeEventStart = "16:24:10"
        },
        area = {
            fromPos = Position(3093, 1898, 8),
            toPos = Position(3133, 1925, 8)
        },
        foods = {
            { itemId = 2674, count = 1, chance = 5 },
            { itemId = 2685, count = 2, chance = 3 },
            { itemId = 2680, count = 3, chance = 2 },
            { itemId = 8844, count = 5, chance = 1 }
        },
        teleport = {
            itemId = 1387,
            actionId = 9998,
            timeDisappear = 120, -- seconds
            position = Position(3188, 1804, 7)
        },
        reward = {
            bagId = 1991,
            items = {
                { itemId = 2160, count = 100, chance = 100 },
                { itemId = 2160, count = 100, chance = 100 }
            }
        },
        state = "stoped", -- not modify
        debug = false
    }

    function SnakeEvent.checkCollision(player, tile)
        local playerGuid = player:getGuid()
        for _, info in pairs(SnakeEvent.foods) do
            local food = tile:getItemById(info.itemId)
            if food then
                SnakeEvent.cache.foods[playerGuid] = SnakeEvent.cache.foods[playerGuid] and SnakeEvent.cache.foods[playerGuid] +info.count or info.count
                local position = player:getPosition()
                position:sendMagicEffect(CONST_ME_MAGIC_RED)
                player:say(string.format("+%d", info.count), TALKTYPE_MONSTER_SAY, false, player, position)
                food:remove()
                break
            end
        end

        for _, creature in pairs(tile:getCreatures()) do
            if creature:isPlayer() then
                local food = SnakeEvent.cache.foods[playerGuid]
                local foodOther = SnakeEvent.cache.foods[creature:getGuid()]
                if food > foodOther then
                    return SnakeEvent.kill(player, creature)
                elseif food < foodOther then
                    return SnakeEvent.kill(creature, player)
                elseif math.random(1, 100) >= 50 then
                    return SnakeEvent.kill(player, creature)
                end
                return SnakeEvent.kill(creature, player)
            else
                local other = Player(creature:getMaxHealth())
                if other then
                    return SnakeEvent.kill(other, player)
                end
            end
        end
    end

    function SnakeEvent.kill(player, target)
        SnakeEvent.leave(target)
        if #SnakeEvent.cache.players == 1 then
            local winner = Player(SnakeEvent.cache.players[1])
            if winner then
                SnakeEvent.win(winner)
                SnakeEvent.leave(winner)
                Game.broadcastMessage(string.format("[SnakeEvent] %s is the winner.", winner:getName()))
            end
            return true
        end
        if SnakeEvent.debug then
            print(string.format("[SnakeEvent/Debug] Player %s kill to %s", player:getName(), target:getName()))
        end
        return false
    end

    function SnakeEvent.win(winner)
        local bag = Game.createItem(SnakeEvent.reward.bagId, 1)
        if bag then
            for _, it in pairs(SnakeEvent.reward.items) do
                if it.chance >= math.random(1, 100) then
                    bag:addItem(it.itemId, it.count)
                end
            end
            winner:getInbox():addItemEx(bag, INDEX_WHERETHER, FLAG_NOLIMIT)
            winner:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You reward: %s.\nCheck your inbox!", bag:getContentDescription()))
        end
        if SnakeEvent.debug then
            print(string.format("[SnakeEvent/Debug] %s is the winner.", winner:getName()))
        end
    end

    function SnakeEvent.step(playerGuid)
        local player = Player(playerGuid)
        if not player then
            return
        end
        local position = player:getPosition()
        local destPos = Position(position.x, position.y, position.z)
        local direction = player:getDirection()
        destPos:getNextPosition(direction, 1)
        local wormHoleDir
        if destPos.x < SnakeEvent.area.fromPos.x then
            destPos.x = SnakeEvent.area.toPos.x
            wormHoleDir = DIRECTION_WEST
        elseif destPos.x > SnakeEvent.area.toPos.x then
            destPos.x = SnakeEvent.area.fromPos.x
            wormHoleDir = DIRECTION_EAST
        elseif destPos.y < SnakeEvent.area.fromPos.y then
            destPos.y = SnakeEvent.area.toPos.y
            wormHoleDir = DIRECTION_NORTH
        elseif destPos.y > SnakeEvent.area.toPos.y then
            destPos.y = SnakeEvent.area.fromPos.y
            wormHoleDir = DIRECTION_SOUTH
        end
        local tile = Tile(destPos)
        if tile and tile:isWalkable() then
            if SnakeEvent.checkCollision(player, tile) then
                return SnakeEvent.stop()
            end
            player:teleportTo(destPos, true)
            if wormHoleDir then
                player:setDirection(wormHoleDir)
            end
            local food = SnakeEvent.cache.foods[playerGuid]
            if food > 0 then
                local size = #SnakeEvent.cache.tails[playerGuid]
                local tiles = #SnakeEvent.cache.tiles[playerGuid]
                table.insert(SnakeEvent.cache.tiles[playerGuid], 1, position)
                if size > 0 then
                    for index = 1, size do
                        local copy = SnakeEvent.cache.tails[playerGuid][index]
                        if copy then
                            local toPos = SnakeEvent.cache.tiles[playerGuid][index]
                            if toPos then
                                local currPos = copy:getPosition()
                                copy:teleportTo(toPos, true)
                                for _, target in pairs(copy:getTargetList()) do
                                    copy:removeTarget(target)
                                end
                                if currPos:getDistance(toPos) > 1 then
                                    if currPos.x ~= toPos.x then
                                        copy:setDirection(currPos.x < toPos.x and DIRECTION_WEST or DIRECTION_EAST)
                                    elseif currPos.y ~= toPos.y then
                                        copy:setDirection(currPos.y < toPos.y and DIRECTION_NORTH or DIRECTION_SOUTH)
                                    end
                                end
                            end
                        end
                    end
                end
                if size < food then
                    local createPos = SnakeEvent.cache.tiles[playerGuid][tiles] or position
                    local tail = Game.createMonster("SnakeEvent Tail Ex", createPos)
                    if tail then
                        tail:setMaxHealth(player:getGuid())
                        tail:setOutfit(player:getOutfit())
                        tail:changeSpeed(player:getSpeed())
                        table.insert(SnakeEvent.cache.tails[playerGuid], tail)
                    end
                end
                if tiles > food then
                    if SnakeEvent.debug then
                        SnakeEvent.cache.tiles[playerGuid][tiles]:sendMagicEffect(CONST_ME_POFF)
                    end
                    table.remove(SnakeEvent.cache.tiles[playerGuid], tiles)
                end
            end
        end
    end

    function SnakeEvent.startAutoWalking()
        for _, playerGuid in pairs(SnakeEvent.cache.players) do
            if SnakeEvent.step(playerGuid) then
                return
            end
        end
        SnakeEvent.cache.events[4] = addEvent(SnakeEvent.startAutoWalking, 200)
    end

    function SnakeEvent.createFood()
        local position = Position(
            math.random(SnakeEvent.area.fromPos.x, SnakeEvent.area.toPos.x),
            math.random(SnakeEvent.area.fromPos.y, SnakeEvent.area.toPos.y),
            SnakeEvent.area.fromPos.z)
        local tile = Tile(position)
        if not tile then
            return false
        end
        for index = #SnakeEvent.foods, 1, -1 do
            local info = SnakeEvent.foods[index]
            if info.chance >= math.random(1, 100) then
                if not tile:getItemById(info.itemId) then
                    local food = Game.createItem(info.itemId, 1, position)
                    if food then
                        food:setCustomAttribute("SnakeEvent", info.count)
                        addEvent(function (pos, itemId)
                            local tile = Tile(pos)
                            if tile then
                                local food = tile:getItemById(itemId)
                                if food then
                                    food:remove()
                                    pos:sendMagicEffect(CONST_ME_POFF)
                                end
                            end
                        end, 1000 * 60, position, info.itemId)
                        position:sendMagicEffect(CONST_ME_TELEPORT)
                        return true
                    end
                    if SnakeEvent.debug then
                        print("[SnakeEvent/Debug] a food could not be generated.")
                    end
                end
            end
        end
        return false
    end

    function SnakeEvent.stop(force)
        if force then
            for _, playerGuid in pairs(SnakeEvent.cache.players) do
                local player = Player(playerGuid)
                if player then
                    SnakeEvent.leave(player)
                end
            end
            SnakeEvent.removeTeleport()
        end
        stopEvent(SnakeEvent.cache.events[1]) -- Stop main loop
        stopEvent(SnakeEvent.cache.events[2]) -- Stop teleport Event
        stopEvent(SnakeEvent.cache.events[3]) -- Stop warning messages
        stopEvent(SnakeEvent.cache.events[4]) -- Stop autoWalking
        SnakeEvent.state = "stoped"
        SnakeEvent.clear()
        if SnakeEvent.debug then
            print("[SnakeEvent/Debug] stop game!")
        end
        return true
    end

    function SnakeEvent.loop()
        SnakeEvent.createFood()
        SnakeEvent.cache.events[1] = addEvent(SnakeEvent.loop, 1000)
        return true
    end

    local monster = {}
    monster.description = "a snake tail ex"
    monster.experience = 0
    monster.outfit = {
        lookTypeEx = 7910
    }

    monster.health = 1
    monster.maxHealth = monster.health
    monster.race = "undead"
    monster.corpse = 0
    monster.speed = 350
    monster.maxSummons = 0

    monster.flags = {
        healthHidden = true,
        summonable = false,
        attackable = false,
        hostile = false,
        convinceable = false,
        illusionable = false,
        canPushItems = false,
        pushable = false,
        canPushCreatures = false,
        isBlockable = false,
        canWalkOnEnergy = false,
        canWalkOnFire = false,
        canWalkOnPoison = false
    }

    Game.createMonsterType("SnakeEvent Tail Ex"):register(monster)

    function SnakeEvent.start()
        local players = #SnakeEvent.cache.players
        if players < SnakeEvent.misc.minPlayers then
            Game.broadcastMessage("[SnakeEvent] The event could not start due to lack of participants.")
            for _, playerGuid in pairs(SnakeEvent.cache.players) do
                local player = Player(playerGuid)
                if player then
                    SnakeEvent.leave(player)
                end
            end
            SnakeEvent.state = "stoped"
            return false
        end
        if SnakeEvent.cache.events[2] then
            stopEvent(SnakeEvent.cache.events[2])
        end
        SnakeEvent.removeTeleport()
        SnakeEvent.state = "running"
        SnakeEvent.startAutoWalking()
        if SnakeEvent.debug then
            print("[SnakeEvent/Debug] event start!")
        end
        return SnakeEvent.loop()
    end

    local function getTime(s)
        return string.format("%.2d:%.2d", math.floor(s/60%60), math.floor(s%60))
    end

    function SnakeEvent.warnings(seconds)
        if seconds <= 0 then
            return SnakeEvent.start()
        end
        if SnakeEvent.state ~= "full" then
            Game.broadcastMessage(string.format("[SnakeEvent] Go to the teleport in the temple to enter the event.\nThe event starts at: %s", getTime(seconds)))
        else
            Game.broadcastMessage("[SnakeEvent] We are about to begin...")
        end
        local discount = math.ceil(math.max(5, seconds/5))
        SnakeEvent.cache.events[3] = addEvent(SnakeEvent.warnings, discount * 1000, seconds -discount)
    end

    function SnakeEvent.isExist(player)
        local playerGuid = player:getGuid()
        for index, guid in pairs(SnakeEvent.cache.players) do
            if playerGuid == guid then
                return index
            end
        end
        return false
    end

    function SnakeEvent.enter(player)
        if #SnakeEvent.cache.players >= SnakeEvent.misc.maxPlayers then
            return false
        end
        local playerGuid = player:getGuid()
        if SnakeEvent.isExist(player) then
            if SnakeEvent.debug then
                print(string.format("[SnakeEvent/Debug] Player %s already into event!", player:getName()))
            end
            return false
        end
        table.insert(SnakeEvent.cache.players, playerGuid)
        SnakeEvent.cache.tiles[playerGuid] = {}
        SnakeEvent.cache.tails[playerGuid] = {}
        SnakeEvent.cache.foods[playerGuid] = 0
        local position = Position(
            math.random(SnakeEvent.area.fromPos.x, SnakeEvent.area.toPos.x),
            math.random(SnakeEvent.area.fromPos.y, SnakeEvent.area.toPos.y),
            SnakeEvent.area.fromPos.z)
        player:teleportTo(position)
        player:setMovementBlocked(true)
        position:sendMagicEffect(CONST_ME_TELEPORT)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "~ Welcomen to SnakeEvent ~\nYou can change direction with Control+Arrows")
        if #SnakeEvent.cache.players >= SnakeEvent.misc.maxPlayers then
            Game.broadcastMessage("[SnakeEvent] The event is at full, we will start soon...")
            SnakeEvent.state = "full"
            stopEvent(SnakeEvent.cache.events[3])
            SnakeEvent.warnings(5)
        end
        if SnakeEvent.debug then
            print(string.format("[SnakeEvent/Debug] Player %s into event!", player:getName()))
        end
        return true
    end

    function SnakeEvent.leave(player)
        local playerGuid = player:getGuid()
        for _, copy in pairs(SnakeEvent.cache.tails[playerGuid]) do
            copy:remove()
        end
        local index = SnakeEvent.isExist(player)
        if not index then
            if SnakeEvent.debug then
                print(string.format("[SnakeEvent/Debug] Player %s not found!", player:getName()))
            end
            return false
        end

        table.remove(SnakeEvent.cache.players, index)
        player:teleportTo(SnakeEvent.misc.exitPosition)
        player:setMovementBlocked(false)
        SnakeEvent.misc.exitPosition:sendMagicEffect(CONST_ME_TELEPORT)
        if SnakeEvent.debug then
            print(string.format("[SnakeEvent/Debug] Player %s leave success!", player:getName()))
        end
        return true
    end

    function SnakeEvent.removeTeleport()
        local tile = Tile(SnakeEvent.teleport.position)
        if tile then
            local teleport = tile:getItemById(SnakeEvent.teleport.itemId)
            if teleport then
                teleport:remove()
                SnakeEvent.teleport.position:sendMagicEffect(CONST_ME_POFF)
            end
        end
    end

    function SnakeEvent.createTeleport()
        local teleport = Game.createItem(SnakeEvent.teleport.itemId, 1, SnakeEvent.teleport.position)
        if teleport then
            teleport:setAttribute(ITEM_ATTRIBUTE_ACTIONID, SnakeEvent.teleport.actionId)
            SnakeEvent.warnings(SnakeEvent.teleport.timeDisappear)
            SnakeEvent.cache.events[2] = addEvent(SnakeEvent.removeTeleport, SnakeEvent.teleport.timeDisappear * 1000)
        end
    end

    function SnakeEvent.clear()
        SnakeEvent.cache = {
            foods = {},
            tails = {},
            tiles = {},
            players = {},
            events = {}
        }
    end
end

local teleportEvent = MoveEvent()

function teleportEvent.onStepIn(creature, item, pos, fromPosition)
    if creature:isPlayer() then
        if not SnakeEvent.enter(creature) then
            creature:teleportTo(fromPosition)
            creature:sendCancelMessage("There is not enough space.")
        end
    end
    return true
end

teleportEvent:aid(SnakeEvent.teleport.actionId)
teleportEvent:register()

local snakeTalk = TalkAction(SnakeEvent.talkAction)

function snakeTalk.onSay(player, words, param)
    local split = param:split(",")
    if split[1] == "run" then
        if SnakeEvent.state ~= "stoped" then
            player:sendCancelMessage("The event is running right now.")
            return false
        end
        SnakeEvent.clear()
        SnakeEvent.state = "waiting"
        SnakeEvent.createTeleport()
        return false
    elseif split[1] == "close" then
        if SnakeEvent.state ~= "stoped" then
            SnakeEvent.stop(true)
            return false
        end
        player:sendCancelMessage("The event is already stoped.")
    end
    return false
end

snakeTalk:access(true)
snakeTalk:separator(" ")
snakeTalk:register()

local snakeEvent = GlobalEvent("SnakeEventEventGlobal")

function snakeEvent.onTime(interval)
    SnakeEvent.clear()
    SnakeEvent.state = "waiting"
    SnakeEvent.createTeleport()
    return true
end

snakeEvent:time(SnakeEvent.misc.timeEventStart)
snakeEvent:register()

local directionOffset = {
    [DIRECTION_EAST] = DIRECTION_WEST,
    [DIRECTION_WEST] = DIRECTION_EAST,
    [DIRECTION_NORTH] = DIRECTION_SOUTH,
    [DIRECTION_SOUTH] = DIRECTION_NORTH
}

local ec = EventCallback

function ec.onTurn(player, direction)
    if SnakeEvent.state == "stoped" or not SnakeEvent.isExist(player) then
        return true
    end
    local currDirection = player:getDirection()
    if currDirection == direction then
        return false
    elseif directionOffset[currDirection] == direction then
        return false
    end
    return true
end

ec:register(-1)

local ec = EventCallback

function ec.onMoveItem(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    if SnakeEvent.state == "stoped" or not SnakeEvent.isExist(player) then
        return true
    end

    if not player:getGroup():getAccess() then
        if toPosition.x ~= CONTAINER_POSITION or fromPosition.x ~= CONTAINER_POSITION then
            player:sendCancelMessage("It is not allowed to move items in the event.")
            return false
        end
    end
    return true
end

ec:register(-1)

Config area:
The area should be square and have an extra line for the wormholes
wormholes, are those that allow us to enter from one side and exit from the other as if it were a curved and closed world in itself
Example:
The green line represents the coordinates that you must configure in the event area, and the blue lines are walkable tiles to be able to use wormholes, since it is designed to be work like that, yes or yes.
1618439845328.png

TalkAction:
  • /snake run
  • /snake close

Note:
This event is not affected by recharges, if you want to reload the event must be closed and stopped before of the reload.

Do not forget to leave your like so that they inspire me to create more great things :D
 

miguelshta

Member
Joined
Mar 21, 2009
Messages
295
Solutions
1
Reaction score
10
Location
Toronto, Canada
C++:
\data\scripts\snake.lua:478: attempt to call method 'access' (a nil value)
stack traceback:
        [C]: in function 'access'
 
OP
Sarah Wesker

Sarah Wesker

ค∂vαηcε รүηтαx ❤
Support Team
Joined
Mar 16, 2017
Messages
918
Solutions
90
Reaction score
902
Location
London
GitHub
MillhioreBT

jeanmarcanzzoni

New Member
Joined
Oct 21, 2020
Messages
7
Reaction score
1
I located your event, with no errors on the console, but when the event starts after the waiting room, I have this error on the console.
 
Last edited:
OP
Sarah Wesker

Sarah Wesker

ค∂vαηcε รүηтαx ❤
Support Team
Joined
Mar 16, 2017
Messages
918
Solutions
90
Reaction score
902
Location
London
GitHub
MillhioreBT
Oh nice :D

The event started normally now but i still have one last problem.
When I use the arrows to choose the directions he does not follow the command, he is always going north as in the video. Do you have any idea what it might be?
View attachment 59132
Use: Ctrl + Arrrows
 

zbizu

Legendary OT User
Joined
Nov 22, 2010
Messages
2,995
Solutions
17
Reaction score
2,113
Location
Poland
GitHub
Zbizu
A great idea would be mirroring the tiles at the edges so the player would see what is on the other side of the edge (a copy of player would walk behind the edge to display where he's about to pop out) and hide player names to avoid sickness from crossing edges (in case of implementing this)
 

Codinablack

Dreamer
Content Editor
Premium User
Joined
Dec 26, 2013
Messages
1,313
Solutions
4
Reaction score
448
Ahah! I love how you pwned that guy! It was awesome, bot or friend, doesn't matter, you saw the opportunity and barely got it! It was awesome! Thanks for the share
 

5lave Ots

Active Member
Joined
Oct 2, 2017
Messages
243
Solutions
1
Reaction score
38
Location
Ankrahmun
*when 2 players hit each others (collision) the game freezes
*also when the second char trying to enter it gets freeze condition but it stucks on the enterance teleport without entering game
**and when the last player looses , he didnt get rewarded and returns that error
Lua:
Lua Script Error: [Main Interface]
in a timer event called from:
(Unknown scriptfile)
D:\EVORIAA\data\scripts\snake.lua:103: attempt to call method 'getInbox' (a nil value)
stack traceback:
        [C]: in function 'getInbox'
        D:\EVORIAA\data\scripts\snake.lua:103: in function 'win'
        D:\EVORIAA\data\scripts\snake.lua:83: in function 'checkCollision'
        D:\EVORIAA\data\scripts\snake.lua:136: in function 'step'
        D:\EVORIAA\data\scripts\snake.lua:192: in function <D:\EVORIAA\data\scripts\snake.lua:190>
tibia 8.6 tfs 1.3 nekiros
 
Last edited:

Sriago

New Member
Joined
Feb 24, 2021
Messages
6
Reaction score
0
i did it but still same problem can you help me?

Lua Script Error: [Scripts Interface]
C:\Users\User\Desktop\NovoServer\otserv - Baiak\data\scripts\snakeevent.lua
...NovoUniServer\otserv - Baiak\data\scripts\snakeevent.lua:478: attempt to call method 'access' (a nil value)
stack traceback:
[C]: in function 'access'
...NovoServer\otserv - Baiak\data\scripts\snakeevent.lua:478: in main chunk
snakeevent.lua [error]
 

Evil Puncker

prolonged absenteeism
TFS Developer
Joined
May 30, 2009
Messages
7,752
Solutions
186
Reaction score
3,548
*when 2 players hit each others (collision) the game freezes
*also when the second char trying to enter it gets freeze condition but it stucks on the enterance teleport without entering game
**and when the last player looses , he didnt get rewarded and returns that error
Lua:
Lua Script Error: [Main Interface]
in a timer event called from:
(Unknown scriptfile)
D:\EVORIAA\data\scripts\snake.lua:103: attempt to call method 'getInbox' (a nil value)
stack traceback:
        [C]: in function 'getInbox'
        D:\EVORIAA\data\scripts\snake.lua:103: in function 'win'
        D:\EVORIAA\data\scripts\snake.lua:83: in function 'checkCollision'
        D:\EVORIAA\data\scripts\snake.lua:136: in function 'step'
        D:\EVORIAA\data\scripts\snake.lua:192: in function <D:\EVORIAA\data\scripts\snake.lua:190>
tibia 8.6 tfs 1.3 nekiros
8.6 doesn't have inbox, that is why you have this error
 

Codinablack

Dreamer
Content Editor
Premium User
Joined
Dec 26, 2013
Messages
1,313
Solutions
4
Reaction score
448
The solution would be to have the item added somewhere else, like in the database for depot items or something
 
Top