• 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.X+ Help with this script: Treasure Action.

Udun

Well-Known Member
Joined
Jan 5, 2012
Messages
199
Solutions
2
Reaction score
69
Hi guys! I'm having problems with this script. TFS 1.2, Tibia 10.98
Should do this:
The player use a shovel (or any item of your election) and you can make a hole in any ground (grass, desert, snow etc.) could be a custom "hole" item, and you can hide items inside like a depot, then after a X time the hole will decay to the gound that was before and the char can back later and take the buried stuff, it means, all the grounds of the server could be a potential depot or a place to hide things (lootbags, weapons, corpses, gp, etc) without need to go to the dp

Code:
function onUse(cid, item, fromPosition, itemEx, toPosition)
  if isInArray({4526}, itemEx.itemid) then -- object ground ID that triggers the action
    local player = Player(cid)
    local storageKey = "holeStorage_" .. itemEx.uid
    if player:getStorageValue(storageKey) ~= 1 then
      -- create a new container and put it inside the hole
      local container = player:getPlayerSlotItem(2594) -- container ID
      if not container then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "There's not enough room for more items.")
        return true
      end
      for i = 1, container:getCapacity() do
        container:addItem(10) -- add the ammount if slots inside the container
      end
      doTransformItem(itemEx.uid, 8249) -- hole ID
      doTileAddItem(fromPosition, container:getId(), 1, container) -- puts container inside the hole
      player:setStorageValue(storageKey, container:getId()) -- storage ID of cointainer in the container chatacter system
      addEvent(function()
        -- Recover the objects in the container and eliminate it
        local holeContainer = Container(player:getStorageValue(storageKey))
        if holeContainer then
          for _, item in ipairs(holeContainer:getItems()) do
            player:addItemEx(item, false) -- Add the objects to the character
          end
          holeContainer:remove()
        end
        -- Return the hole to a normal gound item and eliminates the storage
        doTransformItem(itemEx.uid, 4526) -- original ground ID
        local holeItems = getTileItemsByType(fromPosition, ITEM_TYPE_HOLE)
        for i = 1, #holeItems do
          local item = holeItems[i]
          doSendMagicEffect(item:getPosition(), CONST_ME_POFF) -- effect when hole dissapear
          item:remove()
        end
        player:setStorageValue(storageKey, -1)
      end, 10000) -- Milisenconds "10000 = 10 seconds to the hole gets closed
      return true
    else
      player:sendTextMessage(MESSAGE_STATUS_SMALL, "You already have created a hole here. Use the shovel again to recover your objects.")
    end
  end
  return true
end

Thanks alot!
 
Last edited:
Solution
-----UPDATE------ 5/2/2025.

Working with revscripts, TFS 1.4

After being away from posting this for 681 days, I FINALLY!!!! have the script working and I want to share it with the community here. I think it is a very cool concept; a way to make the game more interactive. With that said, it's not perfect though, and I'll detail the challenges below.


I've been spending an insane number of hours trying to make this work.

How it Works:
You use a custom item (like a custom shovel or a custom item of your choice) on a ground item ID (you can choose and customize it too). This creates a hole in that tile (a custom item that must be an unmovable container). I guess you could use existing items, but I highly recommend creating...
Hi guys! I'm having problems with this script. TFS 1.2, Tibia 10.98
Should do this:
The player use a shovel (or any item of your election) and you can make a hole in any ground (grass, desert, snow etc.) could be a custom "hole" item, and you can hide items inside like a depot, then after a X time the hole will decay to the gound that was before and the char can back later and take the buried stuff, it means, all the grounds of the server could be a potential depot or a place to hide things (lootbags, weapons, corpses, gp, etc) without need to go to the dp
Ok, there are couple things wrong with your script.

I'll start with the major issue with your addEvent - it will cause your server to crash, because you are passing a player and item references, while these could have been already removed from the game.

Here, corrected version
LUA:
    local playerId = player:getId()
    addEvent(function()
        local player = Player(playerId)
        if not player then
            return
        end

        -- Recover the objects in the container and remove it
        local holeContainer = Container(player:getStorageValue(storageKey))
        if holeContainer then
            for _, item in ipairs(holeContainer:getItems()) do
                -- You need to find the tile again (using position?) because the item might have been removed
                -- player:addItemEx(item, false) -- Add the objects to the character
            end
            holeContainer:remove()
        end

        -- FIXME doTransformItem(itemEx.uid, 4526) -- original ground ID4

        local holeItems = getTileItemsByType(fromPosition, ITEM_TYPE_HOLE)
        for i = 1, #holeItems do
            local item = holeItems[i]
            item:getPosition():sendMagixEffect(CONST_ME_POFF) -- effect when hole dissapear
            item:remove()
        end

        player:setStorageValue(storageKey, -1)
    end, 10 * 1000)

Second of all, by default, you can only use numerical storage keys, while you are trying to check a string one.
Third of all, As Bjarne Stroustrup suggests himself, you should first handle the corner cases.
This way you can get rid of the nesting, which makes your code very hard to read.

A cool simple watch

Here, slightly refactored version to get you going.
LUA:
function onUse(cid, item, fromPosition, itemEx, toPosition)
    if not table.contains({ 4526 }, itemEx.itemid) then -- object ground ID that triggers the action
        return true
    end

    local player = Player(cid)

    local storageKey = "holeStorage_" .. itemEx.uid
    if player:getStorageValue(storageKey) == 1 then
        player:sendTextMessage(MESSAGE_STATUS_SMALL,
            "You already have created a hole here. Use the shovel again to recover your objects.")
        return true
    end

    -- create a new container and put it inside the hole
    local container = player:getPlayerSlotItem(2594) -- container ID
    if not container then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "There's not enough room for more items.")
        return true
    end

    -- add the ammount if slots inside the container
    for _ = 1, container:getCapacity() do
        container:addItem(10)
    end

    itemEx:transform(8249)
    player:setStorageValue(storageKey, container:getId())

    -- Are you are trying to fit the container inside the same container?
    -- doTileAddItem(fromPosition, container:getId(), 1, container) -- puts container inside the hole

    local playerId = player:getId()
    addEvent(function()
        local player = Player(playerId)
        if not player then
            return
        end

        -- Recover the objects in the container and remove it
        local holeContainer = Container(player:getStorageValue(storageKey))
        if holeContainer then
            for _, item in ipairs(holeContainer:getItems()) do
                -- You need to find the tile again because the item might have been removed
                -- player:addItemEx(item, false) -- Add the objects to the character
            end
            holeContainer:remove()
        end

        -- FIXME doTransformItem(itemEx.uid, 4526) -- original ground ID

        local holeItems = getTileItemsByType(fromPosition, ITEM_TYPE_HOLE)
        for i = 1, #holeItems do
            local item = holeItems[i]
            item:getPosition():sendMagixEffect(CONST_ME_POFF) -- effect when hole dissapear
            item:remove()
        end

        player:setStorageValue(storageKey, -1)
    end, 10 * 1000)

    return true
end
This obviously is not working, but will be a good start.

To actually achieve your goal of hiding items in tiles, you will need to create a table of positions (keys) and containers (values), to which you'll be moving the items.
Then, upon user action, you'll need to check the table and either create the container, populate it with your items, or move the items out of the container, remove it and clear the key.

Good luck
 
Last edited:
Ok, there are couple things wrong with your script.

I'll start with the major issue with your addEvent - it will cause your server to crash, because you are passing a player and item references, while these could have been already removed from the game.

Here, corrected version
LUA:
    local playerId = player:getId()
    addEvent(function()
        local player = Player(playerId)
        if not player then
            return
        end

        -- Recover the objects in the container and remove it
        local holeContainer = Container(player:getStorageValue(storageKey))
        if holeContainer then
            for _, item in ipairs(holeContainer:getItems()) do
                player:addItemEx(item, false) -- Add the objects to the character
            end
            holeContainer:remove()
        end

        -- FIXME doTransformItem(itemEx.uid, 4526) -- original ground ID
        -- You need to find the tile again (using position?) because the item might have been removed

        local holeItems = getTileItemsByType(fromPosition, ITEM_TYPE_HOLE)
        for i = 1, #holeItems do
            local item = holeItems[i]
            item:getPosition():sendMagixEffect(CONST_ME_POFF) -- effect when hole dissapear
            item:remove()
        end

        player:setStorageValue(storageKey, -1)
    end, 10 * 1000)

Second of all, by default, you can only use numerical storage keys, while you are trying to check a string one.
Third of all, As Bjarne Stroustrup suggests himself, you should first handle the corner cases.
This way you can get rid of the nesting, which makes your code very hard to read.

A cool simple watch

Here, slightly refactored version to get you going.
LUA:
function onUse(cid, item, fromPosition, itemEx, toPosition)
    if not table.contains({ 4526 }, itemEx.itemid) then -- object ground ID that triggers the action
        return true
    end

    local player = Player(cid)

    local storageKey = "holeStorage_" .. itemEx.uid
    if player:getStorageValue(storageKey) == 1 then
        player:sendTextMessage(MESSAGE_STATUS_SMALL,
            "You already have created a hole here. Use the shovel again to recover your objects.")
        return true
    end

    -- create a new container and put it inside the hole
    local container = player:getPlayerSlotItem(2594) -- container ID
    if not container then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "There's not enough room for more items.")
        return true
    end

    -- add the ammount if slots inside the container
    for _ = 1, container:getCapacity() do
        container:addItem(10)
    end

    itemEx:transform(8249)
    player:setStorageValue(storageKey, container:getId())

    -- Are you are trying to fit the container inside the same container?
    -- doTileAddItem(fromPosition, container:getId(), 1, container) -- puts container inside the hole

    local playerId = player:getId()
    addEvent(function()
        local player = Player(playerId)
        if not player then
            return
        end

        -- Recover the objects in the container and remove it
        local holeContainer = Container(player:getStorageValue(storageKey))
        if holeContainer then
            for _, item in ipairs(holeContainer:getItems()) do
                player:addItemEx(item, false) -- Add the objects to the character
            end
            holeContainer:remove()
        end

        -- FIXME doTransformItem(itemEx.uid, 4526) -- original ground ID
        -- You need to find the tile again because the item might have been removed

        local holeItems = getTileItemsByType(fromPosition, ITEM_TYPE_HOLE)
        for i = 1, #holeItems do
            local item = holeItems[i]
            item:getPosition():sendMagixEffect(CONST_ME_POFF) -- effect when hole dissapear
            item:remove()
        end

        player:setStorageValue(storageKey, -1)
    end, 10 * 1000)

    return true
end
This obviously is not working, but will be a good start.

To actually achieve your goal of hiding items in tiles, you will need to create a table of positions (keys) and containers (values), to which you'll be moving the items.
Then, upon user action, you'll need to check the table and either create the container, populate it with your items, or move the items out of the container, remove it and clear the key.

Good luck
Thanks alot for your answer. I think this system would be very cool to implement in general on the ot community. I've been chasing it for long time. Sadly I'm very noob in coding. Can't make it work.
 
-----UPDATE------ 5/2/2025.

Working with revscripts, TFS 1.4

After being away from posting this for 681 days, I FINALLY!!!! have the script working and I want to share it with the community here. I think it is a very cool concept; a way to make the game more interactive. With that said, it's not perfect though, and I'll detail the challenges below.


I've been spending an insane number of hours trying to make this work.

How it Works:
You use a custom item (like a custom shovel or a custom item of your choice) on a ground item ID (you can choose and customize it too). This creates a hole in that tile (a custom item that must be an unmovable container). I guess you could use existing items, but I highly recommend creating custom items to avoid interfering with rl items.

So:
  1. Use the custom shovel on the defined tile.
  2. This creates the custom hole container.
  3. You open the hole and put your items inside.
  4. The hole disappears in 30 seconds.
---
  1. Repeat the cycle: you open the hole in the same tile, open it, and recover your items.
  • The items are stored in the database, so you can recover them even after the server shuts down.
Limitations:
To avoid abuse, once you open a hole, you are blocked for 2 minutes from creating another new one in a different coordinate. However, you can open a hole if it already has items inside before that time. So, technically, you can only access the holes that have items stored without a time limit.

Unsolved Problems:
I have not been able to store "containers" within the "hole container." I mean, if you put a backpack, bag, chest, box, etc., in the hole with items inside, only the main item will be stored, but the content inside the container will be deleted. For example, if you store a backpack of burst arrows, the hole closes, and you open it again, you'll see the backpack, but all the arrows or any item inside will be deleted. It would be amazing to add that to the system.
Alternatively, I thought of a solution for this, without success: simply "limiting" some items in the hole. So, I tried to prevent all containers (backpacks, bags, boxes) from being placed in the hole, but I couldn't do it. I tried using actions, creaturescripts, editing login.lua, etc., but to no avail. (If someone can make it work and complete this, it would be amazing (maybe with an item limit like the depot system).)

So, go to data/scripts and create a lua file with the name: treasure_system.lua

Inside paste this:

LUA:
-- treasure_system.lua By Udun - For Otland

-------------------------------------------------------------
-- CONFIG
-------------------------------------------------------------
local SHOVEL_ID = YourCustomItem      --Custom shovel or tool choosen to open the hole
local HOLE_ID = YourCustomItem        -- Hole item, must be configured as container
local DECAY_TIME = 30        -- Decay Time in seconds
local HOLE_COOLDOWN_STORAGE = 12345  -- storage key for cooldown
local HOLE_COOLDOWN = 2 * 60         -- cooldown 2 minutes (120 seconds)

--You can choose any tile id you want
local GROUND_IDS = {
    4526, 4527, 4528, 4529, 4530, 4531, 4532, 4533, 4534, 4535,
    4536, 4537, 4538, 4539, 4540, 4541, 9043, 9044, 9045, 9046,
    9047, 9048, 9049, 9050, 9051, 9052, 9053, 9054, 9055, 9056,
    9057, 9058, 231, 9021, 9022, 9023, 9024, 9025, 4566, 4570,
    4571, 4572, 4573, 4574, 4575, 4576, 4577, 4578, 4579, 8594,
    8315, 8316, 8317, 8318, 8319, 8319, 8320, 8321, 8322, 6580,
    6581, 6582, 6583, 6584, 6585, 6586, 6587, 6588, 6589, 6590,
    6591, 6592, 6593, 6594, 6595, 6596, 6597, 6598, 6599, 6600,
    6601, 6602, 6603, 6604, 6605, 6606, 6607, 6608, 3263, 3264,
    3265, 194, 836, 280, 351, 352, 353, 354, 355, 368, 103, 104,
    106, 108, 109
}

-------------------------------------------------------------
-- UTILITIES AND DATABASE
-------------------------------------------------------------
local function getPositionKey(position)
    return string.format("%d|%d|%d", position.x, position.y, position.z)
end

local function saveItems(position, container)
    local key = getPositionKey(position)
    db.query("DELETE FROM ground_storage WHERE position = " .. db.escapeString(key))
    for i = 0, container:getSize() - 1 do
        local item = container:getItem(i)
        if item then
            db.query(string.format(
                "INSERT INTO ground_storage (position, item_id, count) VALUES (%s, %d, %d)",
                db.escapeString(key), item:getId(), item:getCount()
            ))
        end
    end
end

local function loadItems(position, container)
    local key = getPositionKey(position)
    local resultId = db.storeQuery("SELECT item_id, count FROM ground_storage WHERE position = " .. db.escapeString(key))
    if resultId then
        repeat
            local itemId = result.getNumber(resultId, "item_id")
            local count = result.getNumber(resultId, "count")
            container:addItem(itemId, count)
        until not result.next(resultId)
        result.free(resultId)
    end
end

local function decayHole(position)
    local tile = Tile(position)
    if tile then
        local hole = tile:getItemById(HOLE_ID)
        if hole then
            saveItems(position, hole)
            hole:remove()
        end
    end
end

-------------------------------------------------------------
-- SHOVEL ACTION
-------------------------------------------------------------
local shovel = Action()
function shovel.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item:getId() ~= SHOVEL_ID then return false end

    local targetPos = target and target:getPosition() or toPosition
    if not targetPos then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Dig in a better place.")
        return true
    end

    local tile = Tile(targetPos)
    if not tile then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You can't dig here.") -- invalid location
        return true
    end

    local ground = tile:getGround()
    if not ground or not table.contains(GROUND_IDS, ground:getId()) then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You can't dig here.")
        return true
    end

    if tile:getItemById(HOLE_ID) then
        player:sendTextMessage(MESSAGE_INFO_DESCR, "There's a hole already.")
        return true
    end

    -- Verify if database has items stored on this coordinates.
    local key = getPositionKey(targetPos)
    local hasStoredItems = false
    local resultId = db.storeQuery("SELECT item_id FROM ground_storage WHERE position = " .. db.escapeString(key))
    if resultId then
        hasStoredItems = true
        result.free(resultId)
    end

    -- If there are NOT items stored, cooldown is activated
    if not hasStoredItems then
        local nextHoleTime = player:getStorageValue(HOLE_COOLDOWN_STORAGE)
        if nextHoleTime > os.time() then
            local remaining = nextHoleTime - os.time()
            player:sendTextMessage(MESSAGE_INFO_DESCR, "You must wait " .. remaining .. " before digging another hole.")
            return true
        end
    end

    local depot = Game.createItem(HOLE_ID, 1, targetPos)
    if depot then
        loadItems(targetPos, depot)
        addEvent(decayHole, DECAY_TIME * 1000, targetPos)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "You have dig a hole.")
        -- Cooldown update only if stored items are not recovered
        if not hasStoredItems then
            player:setStorageValue(HOLE_COOLDOWN_STORAGE, os.time() + HOLE_COOLDOWN)
        end
    else
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Error digging a hole.")
    end

    return true
end

shovel:id(SHOVEL_ID)
shovel:register()

In items.xml make sure your hole item looks like this:
Code:
    <item id="YOUR CUSTOM ITEM" article="a" name="hole">
        <attribute key="containerSize" value="25" />
        <attribute key="duration" value="30" />
        <attribute key="showduration" value="1" />
    </item>

Database table:
Go to your mysql, select your database, the go to SQL and insert this code:
Code:
CREATE TABLE IF NOT EXISTS ground_storage ( position VARCHAR(50) NOT NULL, item_id INT NOT NULL, count INT NOT NULL DEFAULT 1, attributes LONGBLOB NOT NULL, PRIMARY KEY (position, item_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Then continue, and it's Done!

I hope I can solve the problems the system has in the future. If I can, I'll post it here. Of course, if someone wants to contribute to finishing a better version of the system, they are more than welcome!

I hope this is useful.
Udun.

UPDATE:
Last part of the systmem solved.

Go to:
data/scripts/eventcallbacks/player/
in the player folder create a lua file called:
0_block_container_move.lua

Inside paste this:


LUA:
--By Udun - For Otland
local ec = EventCallback

ec.onMoveItem = function(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    -- Mensaje de depuración (opcional)
    print("[block_container_move] onMoveItem llamado. ItemID: " .. item:getId())

  -- Restricted items table, you can restrict any item, in this case are containers because they are not handled by the system.
    local restrictedItems = {
        [1988] = true, [1998] = true, [1999] = true, [2000] = true, [2001] = true,
        [2002] = true, [2003] = true, [2004] = true, [2365] = true, [3940] = true,
        [3960] = true, [2640] = true, [5801] = true, [5926] = true, [5949] = true,
        [7342] = true, [9774] = true, [10518] = true, [10519] = true, [10521] = true,
        [10522] = true, [11119] = true, [11241] = true, [11243] = true, [11244] = true,
        [11263] = true, [15645] = true, [15646] = true, [16007] = true, [18393] = true,
        [18394] = true, [21475] = true, [22696] = true, [23666] = true, [23816] = true,
        [24740] = true, [26181] = true, [1987] = true, [1991] = true, [1992] = true,
        [1993] = true, [1994] = true, [1995] = true, [1996] = true, [1997] = true,
        [2330] = true, [3939] = true, [5927] = true, [5950] = true, [6497] = true,
        [6570] = true, [6571] = true, [7343] = true, [5787] = true, [7737] = true,
        [7738] = true, [7739] = true, [9075] = true, [9076] = true, [9077] = true,
        [9108] = true, [9775] = true, [10070] = true, [10520] = true, [11242] = true,
        [11773] = true, [11774] = true, [12654] = true, [13224] = true, [13537] = true,
        [14330] = true, [14903] = true, [23574] = true, [23663] = true, [23782] = true,
        [24249] = true, [24250] = true, [25419] = true, [26338] = true, [2595] = true,
        [2596] = true, [7955] = true, [1981] = true, [1714] = true, [1715] = true,
        [1716] = true, [1717] = true, [1724] = true, [1725] = true, [1726] = true,
        [1727] = true, [1738] = true, [1739] = true, [1740] = true, [1741] = true,
        [1746] = true, [1747] = true, [1727] = true, [1748] = true, [1749] = true,
        [1750] = true, [1751] = true, [1752] = true, [1753] = true, [1770] = true,
        [1774] = true, [1990] = true, [1989] = true, [6085] = true, [6104] = true,
        [6506] = true, [6507] = true, [6508] = true, [6509] = true, [6010] = true
    }
    local targetContainerId = 29392  -- Container ID in this case the hole

    -- Verify if target destiny exist, if its a container and march with the target container.
    if toCylinder and toCylinder:isContainer() and toCylinder:getId() == targetContainerId then
        --If the item is moving and is one on the restricted ones:
        if restrictedItems[item:getId()] then
            print("[block_container_move] Forbidden Item Detected. Blocked Movement.")
            -- If the source is a player, broadcast message.
            if self and self:isPlayer() then
                self:sendTextMessage(MESSAGE_STATUS_WARNING, "You can't store this object here.")
            end
            -- Block the movement: the item will NOT move.
            return false
        end
    end
    return true
end

ec:register("block_container_move")
 
Last edited:
Solution
Wouldn't it be what you were looking for about the shovel? I think I did something like that, but I'm not sure... You can take a look.
Note: I don't have the shovel IDs, I just used it as an example. You can check it out, share your thoughts, and adapt it to your needs.

 
Wouldn't it be what you were looking for about the shovel? I think I did something like that, but I'm not sure... You can take a look.
Note: I don't have the shovel IDs, I just used it as an example. You can check it out, share your thoughts, and adapt it to your needs.

Thanks for sharing! Sadly, no, the system is quite different. But I'm glad to announce I finally managed to limit the use of containers in the hole. I'll share the script above.
 
Last edited:

Similar threads

Back
Top