• 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!
  • 2026 staff recruitment is open! Check it out and consider applying!

Problem with item detection in module

poncex

Member
Joined
Nov 11, 2012
Messages
66
Reaction score
10
Hi everyone, I'm finishing my module but I'm having a problem detecting if the player has the necessary items to be able to craft. At the moment I've only been able to achieve this with the g_game.getContainers function.

but the items are only reflected in the module if the backpack with the items is open, if the backpack is closed or you open another backpack where the items needed to craft are not, the module shows as if the player does not have the items (when he does have them)

I haven't been able to find the solution even with chatgpt, and with opcode I've been able to make everything else work except for that specific function of the module.

I'll leave a gif showing the problem, thank you very much in advance.
 

Attachments

  • 2025-10-21-23-12-36.gif
    2025-10-21-23-12-36.gif
    5.2 MB · Views: 31 · VirusTotal
If your in the lua tibia client it sounds like your just looking for the g_game.findPlayerItem function (or something close to it)
The main API between the cpp and lua is via sharing objects, checkout luainterface.h for a high level understanding of what's accesible.
Best luck!
 
Let me give you an idea. Perform item recognition on the server, sending only the item ID and quantity to the client. On the client, compile a list of the item ID information received from the server.
ex:
LUA:
local SPELL_OPCODE = 205

local spellsUpgrade = {
    [spellID] = {
        {id = 2148, count = 100},
        {id = 2152, count = 1}, 
        {id = 7618, count = 2}, 
    },
    [2] = {
        {id = 2152, count = 10},
    },
    [3] = {
        {id = 9971, count = 3}, 
        {id = 2160, count = 1}, 
    },
}

function onExtendedOpcode(player, opcode, buffer)
    if opcode ~= SPELL_OPCODE then return end

    local msg = NetworkMessage()
    msg:setBuffer(buffer)
    local spellId = msg:getU16()

    local items = spellsUpgrade[spellId]
    if not items then
        return
    end

    local response = NetworkMessage()
    response:addU16(spellId)
    response:addU8(#items)

    for _, item in ipairs(items) do
        local playerCount = player:getItemCount(item.id)
        local diff = item.count - playerCount
        if diff < 0 then diff = 0 end
        response:addU16(item.id)       -- itemid 
        response:addU16(item.count)    -- count
        response:addU16(playerCount)   -- check count player items
        response:addU16(diff)          -- missing count items
    end

    player:sendExtendedOpcode(SPELL_OPCODE, response:getBuffer())
end
 
Perform item recognition on the server, sending only the item ID and quantity to the client. On the client, compile a list of the item ID information received from the server.
@poncex
Chucky code processes single spell information request, so each time player clicks on spell in OTC panel, you have to send opcode to load given spell items info and then update/redraw OTC panel to show numbers received from server.
It's a great optimization for system, where every upgrade requires different items (IDs). Instead of counting 100+ kinds of items - on server side -every time player opens window, it counts only items required by given. It's great for big upgrade systems (saves a lot of server CPU), but pretty complicated to implement in OTC code, if you don't have experience with asynchronous logic.

On your .gif, it looks like your upgrades require 4 kinds of items (red, blue, green and yellow). In this case, you could load these 4 items count from server just once, when player opens panel. It looks like NPC sends opcode to open that panel in OTC, you can add to given opcode count of 4 kinds of items required counted using player:getItemCount(itemid) on server side.
You should not use this kind of code for too many items (I would say 10+ would be problematic on big OTS). Every player:getItemCount(itemid) iterates over all players items just to count items with given ID.

There is also possibility to mix both solutions into one. Send all items counts, when player opens panel in OTC and count all kinds of items at once. It's used by Market system in TFS. It's in C++, it iterates over player items just once and count all kinds of items making std::map of them "ID -> count":

Anyway, some servers prefer to count items only in open BPs. It saves a lot of server CPU and if you tell players how it works, they will get used to it.
 
Let me give you an idea. Perform item recognition on the server, sending only the item ID and quantity to the client. On the client, compile a list of the item ID information received from the server.
ex:
LUA:
local SPELL_OPCODE = 205

local spellsUpgrade = {
    [spellID] = {
        {id = 2148, count = 100},
        {id = 2152, count = 1},
        {id = 7618, count = 2},
    },
    [2] = {
        {id = 2152, count = 10},
    },
    [3] = {
        {id = 9971, count = 3},
        {id = 2160, count = 1},
    },
}

function onExtendedOpcode(player, opcode, buffer)
    if opcode ~= SPELL_OPCODE then return end

    local msg = NetworkMessage()
    msg:setBuffer(buffer)
    local spellId = msg:getU16()

    local items = spellsUpgrade[spellId]
    if not items then
        return
    end

    local response = NetworkMessage()
    response:addU16(spellId)
    response:addU8(#items)

    for _, item in ipairs(items) do
        local playerCount = player:getItemCount(item.id)
        local diff = item.count - playerCount
        if diff < 0 then diff = 0 end
        response:addU16(item.id)       -- itemid
        response:addU16(item.count)    -- count
        response:addU16(playerCount)   -- check count player items
        response:addU16(diff)          -- missing count items
    end

    player:sendExtendedOpcode(SPELL_OPCODE, response:getBuffer())
end

Hi @chucky91 , thanks for responding. In fact, the code I use is similar to the one you posted.
LUA:
local SPELLUPGRADE_OPCODE = 20

local recipes = {

    -- Healing
    ["Mass Healing"] = { maxLevel = 10, storage = 49998, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {2,6} },
    ["Heal Party"] = { maxLevel = 10, storage = 49999, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {2,6} },
    ["Divine Healing"] = { maxLevel = 10, storage = 50000, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {3,7} },
    ["Light Healing"] = { maxLevel = 10, storage = 50001, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {1,2,3,5,6,7} },
    ["Intense Healing"] = { maxLevel = 10, storage = 50002, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,3,5,6,7} },
    ["Ultimate Healing"] = { maxLevel = 10, storage = 50003, requiredItems = { {id = 6547, count = 20}, {id = 6549, count = 10} }, vocations = {1,2,5,6} },
    ["Heal Friend"] = { maxLevel = 10, storage = 50004, requiredItems = { {id = 6547, count = 15}, {id = 6549, count = 8} }, vocations = {2,6} },

    -- Attack
    ["Ice Strike"] = { maxLevel = 10, storage = 50005, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,5,6} },
    ["Flame Strike"] = { maxLevel = 10, storage = 50006, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,5,6} },
    ["Energy Beam"] = { maxLevel = 10, storage = 50007, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,5} },
    ["Energy Strike"] = { maxLevel = 10, storage = 50008, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,5,6} },
    ["Terra Strike"] = { maxLevel = 10, storage = 50009, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,5,6} },
    ["Berserk"] = { maxLevel = 10, storage = 50010, requiredItems = { {id = 6547, count = 1}, {id = 6549, count = 1}, {id = 6550, count = 1}, {id = 6551, count = 1} }, vocations = {4,8} },
    ["Death Strike"] = { maxLevel = 10, storage = 50011, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,2,5,6} },
    ["Divine Caldera"] = { maxLevel = 10, storage = 50012, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {3,7} },
    ["Divine Missile"] = { maxLevel = 10, storage = 50013, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {3,7} },
    ["Energy Wave"] = { maxLevel = 10, storage = 50014, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,5} },
    ["Eternal Winter"] = { maxLevel = 10, storage = 50015, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {2,6} },
    ["Ethereal Spear"] = { maxLevel = 10, storage = 50016, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {3,7} },
    ["Fierce Berserk"] = { maxLevel = 10, storage = 50017, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {4,8} },
    ["Groundshaker"] = { maxLevel = 10, storage = 50018, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {4,8} },
    ["Hells Core"] = { maxLevel = 10, storage = 50019, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,5} },
    ["Ice Wave"] = { maxLevel = 10, storage = 50020, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {2,6} },
    ["Rage of the Skies"] = { maxLevel = 10, storage = 50021, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {1,5} },
    ["Terra Wave"] = { maxLevel = 10, storage = 50022, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {2,6} },
    ["Whirlwind Throw"] = { maxLevel = 10, storage = 50023, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {4,8} },
    ["Wrath of Nature"] = { maxLevel = 10, storage = 50024, requiredItems = { {id = 6547, count = 10}, {id = 6549, count = 5} }, vocations = {2,6} },

    -- Support
    ["Haste"] = { maxLevel = 10, storage = 50027, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {1,2,3,4,5,6,7,8} },
    ["Avalanche"] = { maxLevel = 10, storage = 50031, requiredItems = { {id = 6547, count = 5}, {id = 6549, count = 2} }, vocations = {2,6} }

}


function onExtendedOpcode(player, opcode, buffer)
    if opcode ~= SPELLUPGRADE_OPCODE then return false end

    local data = json.decode(buffer)
    if not data then return true end

    if not data.spell then
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error: spell inválida.")
        return true
    end

    local spellName = data.spell
    local spellData = spells[spellName]
    if not spellData then
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error: spell desconocida.")
        return true
    end


    for _, item in ipairs(spellData.requiredItems) do
        if player:getItemCount(item.id) < item.count then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No tienes suficientes " .. ItemType(item.id):getName() .. ".")
            return true
        end
    end

    for _, item in ipairs(spellData.requiredItems) do
        player:removeItem(item.id, item.count)
    end

    player:setStorageValue(spellData.storage, level + 1)
    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "La spell " .. spellName .. " ha subido al nivel " .. (level + 1) .. "!")


    return true
end

So far everything is working fine, in fact it has a function that detects whether the player has the necessary items or not, but this only works on the server.

@Gesior.pl
Thank you very much for responding. Honestly, I don't have much intention of using that many items for crafting; in fact, 4 is more than enough for me.

Now, as for how it's currently detecting items, it seems a little strange and even incomplete. That's mainly why I wanted to see if the server could detect the number of items, etc., from the server. Although this way isn't so bad either, since it at least fulfills the main function of showing the items the player has versus what the system requires to be able to craft.

Even so, I'll try the answer you gave me and see how it goes.

and thanks to both of you for the help :D
 
To open the module through the npc I use the

LUA:
function openUpgradeSpellModule()
    spelllistButton:setOn(true)
    spelllistWindow:show()
    spelllistWindow:raise()
    spelllistWindow:focus()
    updateSpellInformation()
end

which in turn is declared in a file in libs on my server, and for the module to detect if the player has the necessary amount of items to upgrade, I use the function

Code:
local owned = 0
      for _, container in pairs(g_game.getContainers()) do
        for _, cItem in pairs(container:getItems()) do
          if cItem:getId() == item.id then
            owned = owned + cItem:getCount()
          end
        end
      end
        
        countLabel:setText(string.format("%d / %d", owned, item.count))
        countLabel:setColor(owned < item.count and "red" or "green")
        countLabel:show()



And here is the complete function where it updates both spells, icon, item and quantity of items (both what the player has and what he requests to upgrade through local countIds):

Code:
function updateSpellInformation(widget) 
  if not widget then return end 
  local spell = widget:getId() if not spell then return end 
  local info = SpellInfo[SpelllistProfile] and SpellInfo[SpelllistProfile][spell] 
  if not info then return end 
  
  local iconWidget = spelllistWindow:getChildById('spellIcon') 
  if iconWidget then 
    local iconId = tonumber(info.icon) 
    if not iconId and SpellIcons[info.icon] then 
      iconId = SpellIcons[info.icon][1] 
    end 

    if not iconId then iconId = 1 end 
    
    iconWidget:setImageSource(SpelllistSettings[SpelllistProfile].iconFile) 
    iconWidget:setImageClip(Spells.getImageClip(iconId, SpelllistProfile)) 
    iconWidget:setImageSize(tosize('34 34')) 
    iconWidget:show() 
  end 
  
    local spellData = spells[spell] 
    local itemIds = {"item1", "item2", "item3", "item4"} 
    local countIds = {"count1", "count2", "count3", "count4"}  
    local lineIds = {"linea1", "linea2", "linea3", "linea4", "linea5", "linea6"}
    
    for _, id in ipairs(itemIds) do 
      local uiItem = spelllistWindow:getChildById(id) 
      if uiItem then uiItem:hide() end 
    end 
    
    for _, id in ipairs(countIds) do 
      local label = spelllistWindow:getChildById(id) 
      if label then label:hide() end 
    end 
    
    for _, id in ipairs(lineIds) do 
      local line = spelllistWindow:getChildById(id) 
      if line then line:hide() line:setBackgroundColor("#000000") 
      end 
    end 
    
    for _, id in ipairs(flatIds) do 
      local flat = spelllistWindow:getChildById(id) 
      if flat then flat:hide() flat:setBackgroundColor("#000000") 
      end 
    end 
    
    if not spellData then return end 
    
    local player = g_game.getLocalPlayer()

  for i, item in ipairs(spellData.requiredItems) do
    local uiItem = spelllistWindow:getChildById(itemIds[i])
    local countLabel = spelllistWindow:getChildById(countIds[i])
    if uiItem and countLabel then
      uiItem:setItemId(item.id)
      uiItem:setVirtual(true)
      uiItem:show()

      local owned = 0
      for _, container in pairs(g_game.getContainers()) do
        for _, cItem in pairs(container:getItems()) do
          if cItem:getId() == item.id then
            owned = owned + cItem:getCount()
          end
        end
      end
        
        countLabel:setText(string.format("%d / %d", owned, item.count)) 
        countLabel:setColor(owned < item.count and "red" or "green") 
        countLabel:show()
                
        local linesToShow = {}
        if i == 1 then linesToShow = {lineIds[1]} 
        elseif i == 2 then linesToShow = {lineIds[2]} 
        elseif i == 3 then linesToShow = {lineIds[3], lineIds[4]} 
        elseif i == 4 then linesToShow = {lineIds[5], lineIds[6]} 
        end 
        
        for _, lineId in ipairs(linesToShow) do 
          local line = spelllistWindow:getChildById(lineId) 
          if line then line:setBackgroundColor(color) line:show() 
          end 
        end 
      end 
    end 
  end
 
To open the module through the npc I use the

LUA:
function openUpgradeSpellModule()
    spelllistButton:setOn(true)
    spelllistWindow:show()
    spelllistWindow:raise()
    spelllistWindow:focus()
    updateSpellInformation()
end

which in turn is declared in a file in libs on my server, and for the module to detect if the player has the necessary amount of items to upgrade, I use the function

Code:
local owned = 0
      for _, container in pairs(g_game.getContainers()) do
        for _, cItem in pairs(container:getItems()) do
          if cItem:getId() == item.id then
            owned = owned + cItem:getCount()
          end
        end
      end
      
        countLabel:setText(string.format("%d / %d", owned, item.count))
        countLabel:setColor(owned < item.count and "red" or "green")
        countLabel:show()



And here is the complete function where it updates both spells, icon, item and quantity of items (both what the player has and what he requests to upgrade through local countIds):

Code:
function updateSpellInformation(widget)
  if not widget then return end
  local spell = widget:getId() if not spell then return end
  local info = SpellInfo[SpelllistProfile] and SpellInfo[SpelllistProfile][spell]
  if not info then return end
 
  local iconWidget = spelllistWindow:getChildById('spellIcon')
  if iconWidget then
    local iconId = tonumber(info.icon)
    if not iconId and SpellIcons[info.icon] then
      iconId = SpellIcons[info.icon][1]
    end

    if not iconId then iconId = 1 end
  
    iconWidget:setImageSource(SpelllistSettings[SpelllistProfile].iconFile)
    iconWidget:setImageClip(Spells.getImageClip(iconId, SpelllistProfile))
    iconWidget:setImageSize(tosize('34 34'))
    iconWidget:show()
  end
 
    local spellData = spells[spell]
    local itemIds = {"item1", "item2", "item3", "item4"}
    local countIds = {"count1", "count2", "count3", "count4"}
    local lineIds = {"linea1", "linea2", "linea3", "linea4", "linea5", "linea6"}
  
    for _, id in ipairs(itemIds) do
      local uiItem = spelllistWindow:getChildById(id)
      if uiItem then uiItem:hide() end
    end
  
    for _, id in ipairs(countIds) do
      local label = spelllistWindow:getChildById(id)
      if label then label:hide() end
    end
  
    for _, id in ipairs(lineIds) do
      local line = spelllistWindow:getChildById(id)
      if line then line:hide() line:setBackgroundColor("#000000")
      end
    end
  
    for _, id in ipairs(flatIds) do
      local flat = spelllistWindow:getChildById(id)
      if flat then flat:hide() flat:setBackgroundColor("#000000")
      end
    end
  
    if not spellData then return end
  
    local player = g_game.getLocalPlayer()

  for i, item in ipairs(spellData.requiredItems) do
    local uiItem = spelllistWindow:getChildById(itemIds[i])
    local countLabel = spelllistWindow:getChildById(countIds[i])
    if uiItem and countLabel then
      uiItem:setItemId(item.id)
      uiItem:setVirtual(true)
      uiItem:show()

      local owned = 0
      for _, container in pairs(g_game.getContainers()) do
        for _, cItem in pairs(container:getItems()) do
          if cItem:getId() == item.id then
            owned = owned + cItem:getCount()
          end
        end
      end
      
        countLabel:setText(string.format("%d / %d", owned, item.count))
        countLabel:setColor(owned < item.count and "red" or "green")
        countLabel:show()
              
        local linesToShow = {}
        if i == 1 then linesToShow = {lineIds[1]}
        elseif i == 2 then linesToShow = {lineIds[2]}
        elseif i == 3 then linesToShow = {lineIds[3], lineIds[4]}
        elseif i == 4 then linesToShow = {lineIds[5], lineIds[6]}
        end
      
        for _, lineId in ipairs(linesToShow) do
          local line = spelllistWindow:getChildById(lineId)
          if line then line:setBackgroundColor(color) line:show()
          end
        end
      end
    end
  end
It seems to me that you're using local client recognition instead of the one coming from the server.
g_game.getContainers()

Also, be careful not to send too many bytes, so as not to overwhelm. if gets too large, it recommended to make server-side requests for item authentication and client-side requests for module aesthetics.
If you send only spellData{current update status}

Your error is that you are using g_game.getContainers()
instead of spellData.requiredItems for exemple.
 
It seems to me that you're using local client recognition instead of the one coming from the server.
g_game.getContainers()

Also, be careful not to send too many bytes, so as not to overwhelm. if gets too large, it recommended to make server-side requests for item authentication and client-side requests for module aesthetics.
If you send only spellData{current update status}

Your error is that you are using g_game.getContainers()
instead of spellData.requiredItems for exemple.
Of course, in fact, as I said at the beginning, that was the function I was using from the client. This is because, as a Sun user, I'm just learning how to work with opcodes. I still don't know how to send data or display elements through opcode. That's why I had to use the client function (also to know if the module was working, opened properly, etc.). That's why I'm trying to find a way to detect if the player has the items and reflect it in the module through opcode. Because I understand that's the best way.
 
Exemple how u can see this data sending, with key/value exemple:
LUA:
g_game.sendExtendedOpcode(SPELLUPGRADE_OPCODE, json.encode({
    spell = "Mass Healing",
    maxLevel = 10,
    storage = 49998,
    requiredItems = {
        {id = 6547, count = 5},
        {id = 6549, count = 2}
    },
    vocations = {2, 6}
}))

how sent and received in json this data format:
JSON:
{

  "spell": "Mass Healing",
  "maxLevel": 10,
  "storage": 49998,
  "requiredItems": [
    { "id": 6547, "count": 5 },
    { "id": 6549, "count": 2 }
  ],
  "vocations": [2, 6]
}
but sending key and value is heavier than sending the position of the values, but you would have to have more control over the size of the exact positions.

light data:
LUA:
local data = {
  "Mass Healing",          -- [1] spellName
  10,                      -- [2] maxLevel
  49998,                   -- [3] storage
  {{6547, 5}, {6549, 2}},  -- [4] requiredItems (two pairs)
  {2, 6}                   -- [5] vocations
}
g_game.sendExtendedOpcode(SPELLUPGRADE_OPCODE, json.encode(data))
sent and received:
Code:
[
  "Mass Healing",
  10,
  49998,
  [[6547,5],[6549,2]],
  [2,6]
]
this is an easy basic with opcode or protocol that uses bytes in pre-fixed positions!
 
Back
Top