• 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.3] EventCallback: Firestorm Event

data\scripts\firestorm.lua:432: attempt to call method 'getTile' (a nil value)
stack traceback:
I get this error can u help ?
I already updated the code so that problem no longer appears, it's my mistake, don't worry.
Likewise, the solution was already published by another user as I mention: Roddet
 
I already updated the code so that problem no longer appears, it's my mistake, don't worry.
Likewise, the solution was already published by another user as I mention: Roddet
ye i diddnt saw it. Thank you, it work now. but after the event end it displayed this error on the console:

Edit: Nvm, i solved with the tipps here, next time i will read more before i write. Thank you guys.
 
Last edited:
callbackId:[]
function: [Scripts Interface]
error [TalkAction with name !firestorm does't have groupType
stack traceback:
[C]: in function 'register'
C:\test\data-otservbr-global/scripts\custom\firestorm.lua:466: in main chunk]
 
Don't seem to be working on canary revscript or I just don't know where to add talkactions..

1694599572833.png
 
Here is the Firestorm Event, the steps to follow below:

Create a new lua file on directory data/scripts/ and name it firestorm.lua then paste the code into the file and that's it!
In the first lines of the script you can find the event settings.

If you don't want to shut down the server and reopen it, just do the command /reload scripts

Added support for slightly outdated versions of TFS 1.3 EventCallback / Container.getItems

CODE
Lua:
--[[

    Credits: Sarah Wesker
    Version: 1.2
    Compat: TFS 1.3
    Create: December 2020

]]--

local config = {
    miscellaneous = {
        name = "FireStorm", -- event name
        talkaction = "!firestorm", -- Talkaction words
        -->> These two functions are compatible with newer versions of TFS Eyes o.o <<-
        canPushPlayers = false, -- Allow players within the event to push each other?
        canMoveItems = false, -- Allow moving items within the event?
        enterOnlyInPzTile = true
    },
    start = {
        time = "20:02:10", -- time to start event each day
        waiting = '30s' -- wait player to enter event, examples: 10s -> 10 seconds | 2m -> 2 minutes
    },
    area = {
        center = Position(3111, 1862, 8), -- center room event
        radius = {
            x = 17, -- radius X
            y = 16 -- radius Y
        }
    },
    players = {
        min = 2, -- min required
        max = 10, -- max players in the evento
        corpseId = 6325, -- corpse id :v
        storage = 7777 -- storage, to remove players from the event in case they get trapped! (it should never happen)
    },
    state = {
        type = 'stoped', -- no edit!
        debug = false -- not use in production
    },
    waves = { -- Wave properties in the event
        interval = 3000, -- event speed
        dificulty = 1, -- default start dificulty
        distanceEffect = CONST_ANI_FIRE, -- rain effect
        impactEffect = CONST_ME_FIREAREA, -- impact effect
        warningEffect = CONST_ME_HITBYFIRE, -- warning effect
        iDWhenWaves = { waves = 5, increase = 0.2 } -- every 5 waves, increase the difficulty 0.2
    },
    rewardBag = 1992, -- Reward bag id
    rewards = { -- In this table, you can add the rewards, as are the example:
        { itemId = 2160, count = 100, chance = 100 },
        { itemId = 7591, count = 100, chance = 70 },
        { itemId = 7590, count = 100, chance = 70 }
    },
    cache = { -- This table should not be modified for anything
        eventIds = {},
        players = {},
        tiles = {},
        tileCount = 0,
        waves = 0,
        dificulty = 0
    }
}

_FSE = {}

function _FSE.isWalkable(x, y, z)
    local tile = Tile(x, y, z)
    if not tile or tile:hasFlag(TILESTATE_FLOORCHANGE) then
        return false
    end

    local ground = tile:getGround()
    if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then
        return false
    end

    local items = tile:getItems()
    for i = 1, tile:getItemCount() do
        local item = items[i]
        local itemType = item:getType()
        if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKSOLID) then
            return false
        end
    end
    return tile
end

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

local function loadTiles()
    config.cache.tiles = {}
    config.cache.tileCount = 0
    for x = config.area.center.x - config.area.radius.x, config.area.center.x + config.area.radius.x do
        for y = config.area.center.y - config.area.radius.y, config.area.center.y + config.area.radius.y do
            local tile = _FSE.isWalkable(x, y, config.area.center.z)
            if tile then
                table.insert(config.cache.tiles, tile)
                config.cache.tileCount = config.cache.tileCount +1
            end
        end
    end
    shuffle(config.cache.tiles)
end

if not Container.getItems then
    function Container.getItems(self, array)
        local array = array or {}
        for slot = 0, self:getCapacity() - 1 do
            local item = self:getItem(slot)
            if item and item:isContainer() then
                array = item:getItems(array)
            elseif item then
                array[#array +1] = item
            end
        end
        return array
    end
end

local function formatTime(seconds)
    if seconds <= 0 then return '0s' end
    local days = math.floor(seconds / 86400)
    seconds = (seconds % 86400)
    local hours = math.floor(seconds / 3600)
    seconds = (seconds % 3600)
    local minutes = math.floor(seconds / 60)
    seconds = (seconds % 60)
    local result = ''
    if days >= 1 then result = string.format("%s%u days", result, days) end
    if hours >= 1 then result = string.format("%s%s%u hours", result, (days > 0 and ' ' or ''), hours) end
    if minutes >= 1 then result = string.format("%s%s%u minutes", result, (hours > 0 and ' ' or ''), minutes) end
    if seconds >= 1 then result = string.format("%s%s%u seconds", result, (minutes > 0 and ' ' or ''), seconds) end
    return result
end

local function getTime(str)
    local seconds = str:match('(%d+)s') or 0
    local minutes = str:match('(%d+)m') or 0
    local hours = str:match('(%d+)h') or 0
    return seconds, minutes, hours
end

function _FSE.debug(message, player)
    local errorMsg = string.format("[%s - Debug] %s", config.miscellaneous.name, message)
    if player then
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, errorMsg)
    end
end

function _FSE.eventSay(message)
    Game.broadcastMessage(string.format("%s says:\n%s", config.miscellaneous.name, message), MESSAGE_EVENT_ADVANCE)
end

function _FSE.sayToPlayers(message)
    for _, playerId in pairs(config.cache.players) do
        local player = Player(playerId)
        if player then
            player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("%s says:\n%s", config.miscellaneous.name, message))
        end
    end
end

function _FSE.removeCachePlayer(player)
    local playerId = player:getId()
    for index, pid in pairs(config.cache.players) do
        if pid == playerId then
            table.remove(config.cache.players, index)
            return true
        end
    end
    return false
end

function _FSE.finish()
    local winner = Player(config.cache.players[1])
    if winner then
        stopEvent(config.cache.eventIds['looping'])
        stopEvent(config.cache.eventIds['warningLooping'])
        winner:getPosition():sendMagicEffect(CONST_ME_TUTORIALSQUARE)
        winner:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations, you are the winner!")
        _FSE.eventSay(string.format("The player %s is the winner!", winner:getName()))
        config.cache.eventIds['winner'] = addEvent(_FSE.sendRewards, 3000)
    else
        _FSE.close()
    end
end

function _FSE.sendRewards()
    local winner = Player(config.cache.players[1])
    if not winner then
        _FSE.close()
        return
    end
    local bag = Game.createItem(config.rewardBag, 1)
    if bag then
        for _, reward in pairs(config.rewards) do
            if reward.chance >= math.random(1, 100) then
                bag:addItem(reward.itemId, reward.count, INDEX_WHEREEVER, FLAG_NOLIMIT)
            end
        end
        local inbox = winner:getInbox()
        if inbox then
            local description, items = "You rewards: ", bag:getItems()
            for _, item in pairs(items) do
                description = string.format("%s%d %s%s", description, item:getCount(), item:getName(), (_ == #items and '.' or ', '))
            end
            inbox:addItemEx(bag, INDEX_WHEREEVER, FLAG_NOLIMIT)
            winner:sendTextMessage(MESSAGE_INFO_DESCR, description..'\nCheck your depot inbox.')
        end
    end
    _FSE.close()
end

function _FSE.killPlayer(player)
    if not _FSE.removeCachePlayer(player) and config.state.debug then
        _FSE.debug(string.format("The player %s could not be removed from the cache, this is weird.", player:getName()))
    end
    local playerPos = player:getPosition()
    player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are burned.")
    _FSE.resetPlayer(player)
    local corpse = Game.createItem(config.players.corpseId, 1, playerPos)
    if corpse then
        corpse:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, string.format("%s ashes.", player:getName()))
    end
end

function _FSE.resetPlayer(player)
    player:setStorageValue(config.players.storage, -1)
    local town = player:getTown()
    if not town then town = Town(1) end
    player:getPosition():sendMagicEffect(CONST_ME_POFF)
    local townPos = town:getTemplePosition()
    player:teleportTo(townPos)
    townPos:sendMagicEffect(CONST_ME_TELEPORT)
end

function _FSE.cleanCorpses()
    for _, tile in pairs(config.cache.tiles) do
        local corpse = tile:getItemById(config.players.corpseId)
        if corpse then
            corpse:remove()
        end
        local creatures = tile:getCreatures()
        for _, creature in pairs(creatures) do
            if creature:isPlayer() then
                _FSE.resetPlayer(creature)
            end
        end
    end
end

function _FSE.checkWaiting()
    if #config.cache.players >= config.players.min then
        _FSE.eventPreparing()
        return
    end
    config.state.type = "stoped"
    _FSE.eventSay("The event has been closed due to lack of participants.")
    _FSE.kickPlayers()
end

function _FSE.kickPlayers()
    for _, playerId in pairs(config.cache.players) do
        local player = Player(playerId)
        if player then
            _FSE.resetPlayer(player)
        end
    end
    config.cache.players = {}
end

function _FSE.kickPlayer(player)
    local index = _FSE.isPlayerExist(player)
    if not index then
        return false
    end
    _FSE.resetPlayer(player)
    table.remove(config.cache.players, index)
    return true
end

function _FSE.isPlayerExist(player)
    local playerId = player:getId()
    for _, pid in pairs(config.cache.players) do
        if playerId == pid then
            return _
        end
    end
    return false
end

function _FSE.joinPlayer(player)
    table.insert(config.cache.players, player:getId())
    player:setStorageValue(config.players.storage, 1)
    player:getPosition():sendMagicEffect(CONST_ME_POFF)
    player:teleportTo(config.area.center)
    config.area.center:sendMagicEffect(CONST_ME_TELEPORT)
    if #config.cache.players >= config.players.max then
        local waitingEventId = config.cache.eventIds['waiting']
        if waitingEventId then
            stopEvent(waitingEventId)
            config.cache.eventIds['waiting'] = nil
        end
        _FSE.eventPreparing()
    end
    return true
end

function _FSE.eventPreparing()
    _FSE.eventSay("We are ready to start, get ready...")
    config.state.type = "preparing"
    config.cache.eventIds['warningLooping'] = addEvent(_FSE.eventWaveWarningLooping, 6000)
end


function _FSE.eventWaveWarningLooping()
    if config.cache.dificulty == 0 then
        config.state.type = "started"
        config.cache.dificulty = config.waves.dificulty
        if config.state.debug then
            _FSE.debug(string.format("load default dificulty: %d", config.cache.dificulty))
        end
    elseif #config.cache.players == 1 then
        _FSE.finish()
        return
    end
    local affectedTileCount = math.min(config.cache.tileCount, (config.cache.tileCount / 6) * config.cache.dificulty)
    local affectedTileList = {}
    local start = os.mtime()
    while affectedTileCount > 0 do
        if os.mtime() - start > 20 then
            break
        end
        local rNumber = math.random(1, config.cache.tileCount)
        if not table.contains(affectedTileList, rNumber) then
            table.insert(affectedTileList, rNumber)
            affectedTileCount = affectedTileCount -1
        end
    end
    for _, tNumber in pairs(affectedTileList) do
        local tile = config.cache.tiles[tNumber]
        tile:getPosition():sendMagicEffect(config.waves.warningEffect)
    end
    local interval = math.max(500, config.waves.interval / config.cache.dificulty)
    config.cache.eventIds['looping'] = addEvent(_FSE.eventWaveLooping, interval, affectedTileList, interval)
end

function _FSE.eventWaveLooping(affectedTileList, interval)
    for _, tNumber in pairs(affectedTileList) do
        local tile = config.cache.tiles[tNumber]
        local position = tile:getPosition()
        local fromPos = Position(position.x -5, position.y -5, position.z)
        fromPos:sendDistanceEffect(position, config.waves.distanceEffect)
        position:sendMagicEffect(config.waves.impactEffect)
        local creatures = tile:getCreatures()
        for _, creature in pairs(creatures) do
            if creature:isPlayer() then
                _FSE.killPlayer(creature)
                if #config.cache.players == 1 then
                    _FSE.finish()
                    return
                end
            end
        end
    end
    -- increase wave count and dificulty
    if config.cache.waves > 0 and (config.cache.waves % config.waves.iDWhenWaves.waves == 0) then
        config.cache.dificulty = config.cache.dificulty + config.waves.iDWhenWaves.increase
        _FSE.sayToPlayers("The difficulty has increased!")
    end
    config.cache.waves = config.cache.waves +1
    config.cache.eventIds['warningLooping'] = addEvent(_FSE.eventWaveWarningLooping, interval)
end

function _FSE.init()
    if config.cache.tileCount == 0 then
        loadTiles()
        if config.state.debug then
            _FSE.debug(string.format("loaded %d tiles.", config.cache.tileCount))
        end
    end
    _FSE.cleanCorpses()
    local s, m ,h = getTime(config.start.waiting)
    local tseconds = s+(m*60)+(h*60*60)
    _FSE.eventSay(string.format("The event will start in %s, use the command %s to enter.", formatTime(tseconds), config.miscellaneous.talkaction))
    config.cache.eventIds['waiting'] = addEvent(_FSE.checkWaiting, tseconds * 1000)
    config.state.type = "open"
    return true
end

function _FSE.close()
    for _, eventId in pairs(config.cache.eventIds) do
        stopEvent(eventId)
    end
    config.cache.waves = 0
    config.cache.dificulty = 0
    config.cache.eventIds = {}
    _FSE.kickPlayers()
    config.state.type = "close"
end

local global = GlobalEvent("FireStorm")
global.onTime = _FSE.init
global:time(config.start.time)
global:register()

local talk = TalkAction(config.miscellaneous.talkaction)
function talk.onSay(player, words, param)
    if player:getGroup():getAccess() then
        if param == "open" then
            if table.contains({'open', 'started', 'preparing'}, config.state.type) then
                player:sendCancelMessage("The event is now open.")
            else
                _FSE.init()
            end
            return false
        elseif param == "close" then
            if table.contains({'close', 'stoped'}, config.state.type) then
                player:sendCancelMessage("The event is already closed.")
            else
                _FSE.close()
            end
            return false
        end
    end
    if config.miscellaneous.enterOnlyInPzTile then
        if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then
            player:sendCancelMessage("You can enter only in PZ.")
            return false
        end
    end
    if table.contains({'back', 'exit'}, param:lower()) then
        if table.contains({'preparing', 'started'}, config.state.type) then
            player:sendCancelMessage("Sorry, but the event already started.")
        elseif not _FSE.kickPlayer(player) then
            player:sendCancelMessage("Sorry, but you are not at the event.")
        else
            player:sendCancelMessage("You have left the event.")
        end
        return false
    end
    if _FSE.isPlayerExist(player) then
        player:sendCancelMessage("Sorry, you're already inside the event.")
        return false
    end
    if config.state.type == "open" then
        if not _FSE.joinPlayer(player) then
            player:sendCancelMessage("Sorry, there are no more spaces.")
        end
    elseif table.contains({'close', 'stoped'}, config.state.type) then
        player:sendCancelMessage("Sorry, but the event is closed.")
    else
        player:sendCancelMessage("Sorry, but the event is running.")
    end
    return false
end

talk:separator(" ")
talk:register()

local cEvent = CreatureEvent("FireStorm")
function cEvent.onLogin(player)
    if player:getStorageValue(config.players.storage) == 1 then
        _FSE.resetPlayer(player)
    end
    return true
end
cEvent:register()

local cEvent = CreatureEvent("FireStormLogout")
function cEvent.onLogout(player)
    if player:getStorageValue(config.players.storage) == 1 then
        player:sendCancelMessage("You cannot logout, because you are at the event.")
        return false
    end
    return true
end
cEvent:register()


if EventCallback then
    local ec = EventCallback
    function ec.onMoveCreature(player, creature, fromPosition, toPosition)
        if not player:getGroup():getAccess() then
            if not config.miscellaneous.canPushPlayers then
                if player:getStorageValue(config.players.storage) == 1 then
                    player:sendCancelMessage("It is not allowed to move players in the event.")
                    return false
                end
            end
        end
        return true
    end
    ec:register(-1)

    local ec = EventCallback
    function ec.onMoveItem(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
        if not player:getGroup():getAccess() then
            if not config.miscellaneous.canMoveItems then
                if toPosition.x ~= CONTAINER_POSITION or fromPosition.x ~= CONTAINER_POSITION then
                    if player:getStorageValue(config.players.storage) == 1 then
                        player:sendCancelMessage("It is not allowed to move items in the event.")
                        return false
                    end
                end
            end
        end
        return true
    end
    ec:register(-1)

elseif config.state.debug then
    _FSE.debug("Your current version does not support EventCallback functionalities.")
end

Commands for admins:
!firestorm open
!firestorm close

Commands for players:
!firestorm
!firestorm
exit

Note: I use the word !firestorm because you can change the command in the config table:
View attachment 53051

If you manage to find an error, then let me know, to fix it, but please pay attention:
It is only for TFS version 1.3 with Revscript and EventCallback system

Does not work for me ;/ Latest Otx
 
Lua:
if config.miscellaneous.enterOnlyInPzTile then
        if not player:getPosition():getTile():hasFlag(TILESTATE_PROTECTIONZONE) then
            player:sendCancelMessage("You can enter only in PZ.")
            return false
        end
    end

I have problem with this on TFS 1.3 this is solution:


Lua:
   if config.miscellaneous.enterOnlyInPzTile then
    local tile = Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE)
        if not tile then
            player:sendCancelMessage("You can enter only in PZ.")
            return false
        end
    end
I changed these lines, I don't get an error in the console, but when it's time for the event to start it simply stops everything. I use TFS 1.5, can you help me?
 
Lua:
Lua Script Error: [GlobalEvent Interface]
data/globalevents/scripts/firestorm_event.lua:onThink
data/globalevents/scripts/firestorm_event.lua:8: attempt to index a nil value
stack traceback:
        [C]: in function '__index'
        data/globalevents/scripts/firestorm_event.lua:8: in function <data/globalevents/scripts/firestorm_event.lua:1>
[Error - GlobalEvents::think] Failed to execute event: firestorm_event

I added the code to my server and i getting this error in console, can someone help me, please?
 
I added the code to my server and i getting this error in console, can someone help me, please?
This is Revscripts, buddy, not just your typical data/globalevent/scripts... It's a local directory, specifically data/scripts. Revscripts means, take a look at this link, and you'll understand it better.
 
This is Revscripts, buddy, not just your typical data/globalevent/scripts... It's a local directory, specifically data/scripts. Revscripts means, take a look at this link, and you'll understand it better.
I just noticed i pasted a old error that i when i was trying to put it another script in data/globalevents...; already cleaned that cause i couldn't make it work.

I'll take a look to the link, also when i put de Revscripts in data/scripts i have this error when I execute "!firestorm open"

Lua:
Lua Script Error: [Scripts Interface]
D:\Escritorio\Basureron't\Pruebas del canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:callback
... canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:249: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
        [C]: at 0x7ff7402320d0
        [C]: in function 'pairs'
        ... canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:249: in function 'cleanCorpses'
        ... canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:387: in function 'init'
        ... canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:419: in function <... canaot\Canas Ote\Toxic Tibia\data\scripts\firestorm.lua:413>

Also sorry if i'm being annoying or something, i'm kindda new in this, :)
 
Back
Top