• 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 How to access ”item” object across events?

ohman

Member
Joined
Oct 24, 2008
Messages
289
Reaction score
5
Location
Sweden
Hey!

How do I transfer the object “item” between an onUse action that contains “item” and a modalWindow event?

I need to have the ability to remove the used item in the event.

Is it possible? Thanks 😊
 

In the 4th.or 5th post merge I do what you're trying to do.

They use the item, then choose a reward with modal window, and then it removes the item that was used.
 

In the 4th.or 5th post merge I do what you're trying to do.

They use the item, then choose a reward with modal window, and then it removes the item that was used.
I think it is easy to abuse and cause crash. You are checking not usedItem but its not gonna work, if you for example throw that usedItem into into lava/bin after using the item it will still hold userdata so that not usedItem won't be true. Test it to be sure if it is gonna crash or just throw some Lua error.
 
I think it is easy to abuse and cause crash. You are checking not usedItem but its not gonna work, if you for example throw that usedItem into into lava/bin after using the item it will still hold userdata so that not usedItem won't be true. Test it to be sure if it is gonna crash or just throw some Lua error.
yo, I was thinking something similar, just trade the item and accept before using it will cause some problem imo
 
I think it is easy to abuse and cause crash. You are checking not usedItem but its not gonna work, if you for example throw that usedItem into into lava/bin after using the item it will still hold userdata so that not usedItem won't be true. Test it to be sure if it is gonna crash or just throw some Lua error.
yo, I was thinking something similar, just trade the item and accept before using it will cause some problem imo
View attachment bandicam 2024-04-29 20-31-41-284.mp4
View attachment bandicam 2024-04-29 20-33-52-912.mp4
 
I think trash bin is a container so the item still exists. Trading is also moving the item not recreating it. Try with lava.

Edit: Nvm.
This is weird behavior tho. How is the item returning something with getTopParent if the item doesn't exist anymore?
View attachment bandicam 2024-04-29 21-27-18-895.mp4

Not sure what to tell you. xD
It seems to work fine and as intended.

-- Edit
Ah, the script in the thread wasn't updated.

changed
Lua:
if not usedItem then
to
Lua:
if not usedItem or not Item(usedItem.uid) then
 
Last edited:
In fact, I was curious, and I tried it...
The first attempts by pure chance the server did not crash, and my expression was wow xd
Then I kept trying, and POFF the server broke

I notice that if you first move the object to a tile and then delete it with lava or the trash, it seems that the object still maintains a reference and that is why it is not deleted, are we witnessing a memory leak? 😲

~If you delete the item directly from inventory, the crash occurs, which should always happen...~

By the way, the correct way to store an item in a list is to add a custom attribute and then use the onMoveItem event to identify if you move the item and then clear the table.

EDIT: It's strange because now it happens completely randomly....
 
Last edited:

In the 4th.or 5th post merge I do what you're trying to do.

They use the item, then choose a reward with modal window, and then it removes the item that was used.
Your code causes undefined behaviour, you cant store userdata like that. It may be deleted or not, if it is, it doesn't necessarily mean it will crash, its ub.
Thus you see unpredictable behaviour. This is also why checking a pointer validity doesn't work.
The pointer is "valid" in terms of being not nullish, it may not be valid in terms of a valid object (not freed memory) at that memory address.

So essentially what you are doing here when an item is deleted, you are dereferencing some random memory address which could have something else there or nothing, either way it will crash or not, that's why it's called undefined behaviour and that is also why .uid "seems to work" - it doesn't, it probably returns different numbers each time.
 
Last edited:
Anyway, here I leave a modification of the script that is much safer and should not cause errors in 99.9% of the cases

Lua:
local rewardBagItemId = 1950

local config = {
    modalWindow = {
        id = 1001, -- needs to be unique number?
        title = "Reward Bag",
        message = "Choose a reward from the list!",
        eventText = "ModalWindow_Reward_Bag",
        buttons = {
            {text = "Choose", defaultEnterButton = true},
            {text = "Cancel", defaultEscapeButton = true},
        }
    },
    rewards = {
        {itemId = 2160, amount = 1},
        {itemId = 2160, amount = 3}, -- add as many as you want.
        {itemId = 2160, amount = 10}
    }
}

-- END OF CONFIG
local usedItems = {
--    [playerId] = item
}

local function getItemNameString(item)
    return item:getNameDescription(item:getSubType(), true)
end

local function createRewardBagWindow(playerId)
    local player = Player(playerId)
    if not player then
        return
    end

    player:unregisterEvent(config.modalWindow.eventText)
    
    player:registerEvent(config.modalWindow.eventText)
  
    local modalWindow = ModalWindow(config.modalWindow.id, config.modalWindow.title, config.modalWindow.message)
  
    for id, button in ipairs(config.modalWindow.buttons) do
        modalWindow:addButton(id, button.text)
        if button.defaultEscapeButton then
            modalWindow:setDefaultEscapeButton(id)
        elseif button.defaultEnterButton then
            modalWindow:setDefaultEnterButton(id)
        end
    end      
    for id, v in ipairs(config.rewards) do
        local item = Game.createItem(v.itemId, v.amount)
        local text = getItemNameString(item)
        modalWindow:addChoice(id, text)
    end
  
    modalWindow:hasPriority()
    modalWindow:sendToPlayer(player)
end

local action = Action()

function action.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local _player = Player(item:getTopParent())
    if not _player then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag must be in your inventory to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
        return true
    end
    item:setCustomAttribute("onUse_chooseItemReward", true)
    usedItems[player:getId()] = item
    createRewardBagWindow(player:getId())
    return true
end

action:id(rewardBagItemId)
action:register()

local creatureevent = CreatureEvent(config.modalWindow.eventText)

function creatureevent.onModalWindow(player, modalWindowId, buttonId, choiceId)
    player:unregisterEvent(config.modalWindow.eventText)
  
    if modalWindowId == config.modalWindow.id then
        local buttonChoice = config.modalWindow.buttons[buttonId].text
        local playerId = player:getId()
      
        if buttonChoice == "Choose" then
            local usedItem = usedItems[playerId]
            if not usedItem or not Item(usedItem.uid) then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag no longer exists.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            local _player = Player(usedItem:getTopParent())
            if not _player or _player:getId() ~= playerId then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag must be in your inventory to be used.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            local item = Game.createItem(config.rewards[choiceId].itemId, config.rewards[choiceId].amount)
            if not player:addItemEx(item, false) then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "You do not have the room or capacity to receive this item.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            player:say("You received " .. getItemNameString(item), TALKTYPE_MONSTER_SAY, nil, player)
            player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
            usedItem:remove(1)
            usedItems[playerId] = nil
        else
            -- "Cancel" button
            usedItems[playerId] = nil
            return true
        end
      
    end
    return true
end
creatureevent:register()

local event = EventCallback

function event.onMoveItem(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    if item:getCustomAttribute("onUse_chooseItemReward") then
        item:removeCustomAttribute("onUse_chooseItemReward")
        usedItems[player:getId()] = nil
    end
    return RETURNVALUE_NOERROR
end

event:register()
 
Anyway, here I leave a modification of the script that is much safer and should not cause errors in 99.9% of the cases

Lua:
local rewardBagItemId = 1950

local config = {
    modalWindow = {
        id = 1001, -- needs to be unique number?
        title = "Reward Bag",
        message = "Choose a reward from the list!",
        eventText = "ModalWindow_Reward_Bag",
        buttons = {
            {text = "Choose", defaultEnterButton = true},
            {text = "Cancel", defaultEscapeButton = true},
        }
    },
    rewards = {
        {itemId = 2160, amount = 1},
        {itemId = 2160, amount = 3}, -- add as many as you want.
        {itemId = 2160, amount = 10}
    }
}

-- END OF CONFIG
local usedItems = {
--    [playerId] = item
}

local function getItemNameString(item)
    return item:getNameDescription(item:getSubType(), true)
end

local function createRewardBagWindow(playerId)
    local player = Player(playerId)
    if not player then
        return
    end

    player:unregisterEvent(config.modalWindow.eventText)
  
    player:registerEvent(config.modalWindow.eventText)
 
    local modalWindow = ModalWindow(config.modalWindow.id, config.modalWindow.title, config.modalWindow.message)
 
    for id, button in ipairs(config.modalWindow.buttons) do
        modalWindow:addButton(id, button.text)
        if button.defaultEscapeButton then
            modalWindow:setDefaultEscapeButton(id)
        elseif button.defaultEnterButton then
            modalWindow:setDefaultEnterButton(id)
        end
    end    
    for id, v in ipairs(config.rewards) do
        local item = Game.createItem(v.itemId, v.amount)
        local text = getItemNameString(item)
        modalWindow:addChoice(id, text)
    end
 
    modalWindow:hasPriority()
    modalWindow:sendToPlayer(player)
end

local action = Action()

function action.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local _player = Player(item:getTopParent())
    if not _player then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag must be in your inventory to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
        return true
    end
    item:setCustomAttribute("onUse_chooseItemReward", true)
    usedItems[player:getId()] = item
    createRewardBagWindow(player:getId())
    return true
end

action:id(rewardBagItemId)
action:register()

local creatureevent = CreatureEvent(config.modalWindow.eventText)

function creatureevent.onModalWindow(player, modalWindowId, buttonId, choiceId)
    player:unregisterEvent(config.modalWindow.eventText)
 
    if modalWindowId == config.modalWindow.id then
        local buttonChoice = config.modalWindow.buttons[buttonId].text
        local playerId = player:getId()
    
        if buttonChoice == "Choose" then
            local usedItem = usedItems[playerId]
            if not usedItem or not Item(usedItem.uid) then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag no longer exists.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            local _player = Player(usedItem:getTopParent())
            if not _player or _player:getId() ~= playerId then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "Reward bag must be in your inventory to be used.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            local item = Game.createItem(config.rewards[choiceId].itemId, config.rewards[choiceId].amount)
            if not player:addItemEx(item, false) then
                player:sendTextMessage(MESSAGE_STATUS_SMALL, "You do not have the room or capacity to receive this item.")
                player:getPosition():sendMagicEffect(CONST_ME_POFF, player)
                usedItems[playerId] = nil
                return true
            end
            player:say("You received " .. getItemNameString(item), TALKTYPE_MONSTER_SAY, nil, player)
            player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
            usedItem:remove(1)
            usedItems[playerId] = nil
        else
            -- "Cancel" button
            usedItems[playerId] = nil
            return true
        end
    
    end
    return true
end
creatureevent:register()

local event = EventCallback

function event.onMoveItem(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    if item:getCustomAttribute("onUse_chooseItemReward") then
        item:removeCustomAttribute("onUse_chooseItemReward")
        usedItems[player:getId()] = nil
    end
    return RETURNVALUE_NOERROR
end

event:register()
Same issue, don't store the item in table. Store item id, then get item by that id from player and validate it and then remove.
 
Same issue, don't store the item in table. Store item id, then get item by that id from player and validate it and then remove.
That's just removing a random item instead of the item that was used, tho?
 
Last edited:
What's the difference in Tibia? ItemType is ItemType, what does it matter if its in slot 2 of 4 or in bp 1 or bp 5?
Player experience.
They click item in backpack 5, you expect that is the item that will be consumed.

Scripting wise, 2 items with same itemid but with custom attributes applied.
1 is legendary, 1 is common.

Common is in backpack 5, but legendary item would be consumed in backpack 1.
 
Thank you for all the answers! It was more advanced than I thought.

Unfortunately, it doesn't work with itemid. I am in the process of creating a script for imbuing, where one uses their item on a stone ("shrine"). It is crucial that it is exactly the item that was chosen initially, as it could be really bad if the wrong item gets imbued.

Perhaps there are better ways to create this script? I would appreciate any suggestions :) Thanks again!
 
Back
Top