• 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+ Lua remove item charges

roriscrave

Advanced OT User
Joined
Dec 7, 2011
Messages
1,188
Solutions
34
Reaction score
200
Hello, I made this code where the player uses a rune that has 300 charges. Each charge will give him 1 crystal coin.
But there is a bug, when there are 2 runes in the backpack.
When I use rune 2, the charge is being deducted from rune 1 (I think it's because it comes first in the backpack).
how do I get discounted from the rune the player used and not the first one in the backpack?
1609217764776.png
Lua:
function removeCharges (cid, itemID)
    local player = Player(cid)
    if player then
        local checkItem = player:getItemById(itemID, true)
        if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
            local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
            if currentCharges >= 1 then
                checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
                player:addItem(2160, 1)
                player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
                addEvent(removeCharges, 1500, player:getId(), itemID)
            end
        end
    end
    return true
end
   
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        removeCharges(player:getId(), item.itemid)
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Start Charge Rune.")
    end
    return true
end
 
Solution
Now bby!
action script
Lua:
evDecayChargesList = {}

function removeCharges(playerId, index)
    local player = Player(playerId)
    if not player then
        return false
    end
    local removeItem = evDecayChargesList[index]
    if not removeItem then
        return false
    end
    local item = removeItem.item
    if item:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = item:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            local newCharges = currentCharges -1
            item:setAttribute(ITEM_ATTRIBUTE_CHARGES, newCharges)
            player:addItem(2160, 1)
            if newCharges > 0 then
                removeItem.evId = addEvent(removeCharges, 1500, playerId...
You're currently checking for an item on the player instead of checking the item that the player is using.

It's a small difference, but you can see why it's problematic at this point.

You'd need to pass the item into the function instead of calling the function and attempting to find the item.
 
You're currently checking for an item on the player instead of checking the item that the player is using.

It's a small difference, but you can see why it's problematic at this point.

You'd need to pass the item into the function instead of calling the function and attempting to find the item.
but what i need to pass? item.uid?
 
but what i need to pass? item.uid?
Try this?

Lua:
local function removeCharges(cid, item)
    local player = Player(cid)
    if player then
        local checkItem = item
        if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
            local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
            if currentCharges >= 1 then
                checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
                player:addItem(2160, 1)
                --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
                --addEvent(removeCharges, 1500, player:getId(), itemID)
            else
                return false
            end
        end
    end
    return true
end

function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player:getId(), item.uid) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end

-- edit

and if above doesn't work.. try this.. xD
Lua:
function removeCharges (cid, item)
    local player = Player(cid)
    if player then
        local checkItem = Item(item)
        if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
            local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
            if currentCharges >= 1 then
                checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
                player:addItem(2160, 1)
                --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
                --addEvent(removeCharges, 1500, player:getId(), itemID)
            else
                return false
            end
        end
    end
    return true
end
   
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player:getId(), item:getId()) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
 
Try this?

Lua:
local function removeCharges(cid, item)
    local player = Player(cid)
    if player then
        local checkItem = item
        if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
            local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
            if currentCharges >= 1 then
                checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
                player:addItem(2160, 1)
                --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
                --addEvent(removeCharges, 1500, player:getId(), itemID)
            else
                return false
            end
        end
    end
    return true
end

function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player:getId(), item.uid) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end

-- edit

and if above doesn't work.. try this.. xD
Lua:
function removeCharges (cid, item)
    local player = Player(cid)
    if player then
        local checkItem = Item(item)
        if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
            local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
            if currentCharges >= 1 then
                checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
                player:addItem(2160, 1)
                --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
                --addEvent(removeCharges, 1500, player:getId(), itemID)
            else
                return false
            end
        end
    end
    return true
end
  
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player:getId(), item:getId()) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
either failed bro, the first one:
Code:
data/actions/scripts/wandSkills.lua:5: attempt to index local 'checkItem' (a number value)

and second:
Code:
data/actions/scripts/wandSkills.lua:5: attempt to index local 'checkItem' (a nil value)
 
either failed bro, the first one:
Code:
data/actions/scripts/wandSkills.lua:5: attempt to index local 'checkItem' (a number value)

and second:
Code:
data/actions/scripts/wandSkills.lua:5: attempt to index local 'checkItem' (a nil value)
I don't have a server setup, and I've never passed an item to a function before... :/

I remember dealing with this before and coming up to this same stumbling block..

from memory.. item uid changes constantly, and therefore you need to be able to find the item again like you were doing initially.. but when it's inside a player inventory it's very hard to find the exact item again, if they have multiple of them.

Someone else is going to have to come to the rescue here, I think.
I have no idea how to keep track of a specific item over time.

Like, for instance..
Imagine having an item that has 500 charges, and every 2 seconds that item will have 1 charge removed automatically.
Everytime a player uses the item, it gains back 1 charge, up to a maximum of 500.

In this scenario, you should be able to move that item around on the floor / put it into inventory / et cetera and because you're calling and passing the item information directly into the function you can keep track of it wherever it goes.. and remove the charges.

But I think it's impossible to do that within the scope of tfs as it currently stands.. because the object's uid is constantly changing..? idk

--

on topic, I'm not sure how to pass the item information into an external function

Hopefully someone else smarter then me comes along. xD
 
@Xikini

Your script was almost correct I think. But item:getId() returns the real ID of item, like 2160 for gold coin.

@roriscrave

Try the second script from @Xikini, but change this line:
Code:
if removeCharges(player:getId(), item:getId()) then

To:
Code:
if removeCharges(player:getId(), item.uid) then

#Edit
Full script:
Lua:
function removeCharges (player, checkItem)
    if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
            player:addItem(2160, 1)
            --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
            --addEvent(removeCharges, 1500, player:getId(), itemID)
        else
            return false
        end
    end
    return true
end
   
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player, Item(item.uid)) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
 
I don't have a server setup, and I've never passed an item to a function before... :/

I remember dealing with this before and coming up to this same stumbling block..

from memory.. item uid changes constantly, and therefore you need to be able to find the item again like you were doing initially.. but when it's inside a player inventory it's very hard to find the exact item again, if they have multiple of them.

Someone else is going to have to come to the rescue here, I think.
I have no idea how to keep track of a specific item over time.

Like, for instance..
Imagine having an item that has 500 charges, and every 2 seconds that item will have 1 charge removed automatically.
Everytime a player uses the item, it gains back 1 charge, up to a maximum of 500.

In this scenario, you should be able to move that item around on the floor / put it into inventory / et cetera and because you're calling and passing the item information directly into the function you can keep track of it wherever it goes.. and remove the charges.

But I think it's impossible to do that within the scope of tfs as it currently stands.. because the object's uid is constantly changing..? idk

--

on topic, I'm not sure how to pass the item information into an external function

Hopefully someone else smarter then me comes along. xD

Xikini

I haven't looked at the whole thread (I'm just looking for useful examples of OT Lua for a different project), but your description here caught my eye ...

Could the game be creating an instance of a "static" object when it's placed in a backpack, put on a tile, or something similar?
(not sure of Lua terminology, but this would mean there's a unique "master" item "owned" by the game engine that's being used as a template, and a copy is made when it's used).

In that case there are two (perhaps three) unique ids, but in this case the interesting ones will be the references to the instances in the player's backpack (if I understood the thread correctly).
 
Last edited:
Xikini

I haven't looked at the whole thread (I'm just looking for useful examples of OT Lua for a different project), but your description here caught my eye ...

Could the game be creating an instance of a "static" object when it's placed in a backpack, put on a tile, or something similar?
(not sure of Lua terminology, but this would mean there's a unique "master" item "owned" by the game engine that's being used as a template, and a copy is made when it's used).

In that case there are two (perhaps three) unique ids, but in this case the interesting ones will be the references to the instances in the player's backpack (if I understood the thread correctly).
I really don't know enough to answer. xD

I just know that if you print(item.uid) into console, you'll get a different result each time.
 
I really don't know enough to answer. xD

I just know that if you print(item.uid) into console, you'll get a different result each time.
I wish I could test this, but I don't have OT installed so it's impossible.

Would you like to run some tests as a joint effort? I can suggest some things to do, but you'd have to run them.

I think the only pre-reqs would be:
  • Have a container (e.g. a backpack)
  • Be able to add/remove some items that have this characteristic into/from the container, plus at least one other type of item
  • Be able to
    • Print information about every item in the container (i.e. step through them in the container's preferred order)
    • Select items from the container with both the "get()" that returns the global itemid and/or reference, and using "item.uid" (which I expect is returning the instance's unique id, not the global one)

BTW what I think is happening is that if the container is asked for an item using the item type's global id (which looks to be the index into a game-engine-managed global table), it returns the first instance of the item type in the container, where "first" is defined by the native sort order of the container, and might seem random to a Lua program.

We can track this by adding and removing stuff to/from the container and printing its entire contents in between and/or trying different method calls to select specific items.

Unless the game is coded to deliberately hide the instance ids of everything in a container (which seems unlikely :) it shouldn't take long to figure out what's happening.

The upside is that if we find a stable instance id, even if the API doesn't support exactly what the OP wants via a Lua script attached to the instances, we should be able to achieve the desired result via a "Storage" containing a table keyed by the instance Id (or perhaps some proxy if it's a weird data type).

FYI Java has containers for which the sort order is explicitly specified as being undefined and not necessarily stable. It's easy to deal with of course, but it can surprise an unwary coder :)
An example where it might change if when something is added and there's no space left: the container will be expanded, and in the process may move things around to distribute used/empty slots for efficient future changes (adds/deletes).
 
@Xikini

Your script was almost correct I think. But item:getId() returns the real ID of item, like 2160 for gold coin.

@roriscrave

Try the second script from @Xikini, but change this line:
Code:
if removeCharges(player:getId(), item:getId()) then

To:
Code:
if removeCharges(player:getId(), item.uid) then

#Edit
Full script:
Lua:
function removeCharges (player, checkItem)
    if checkItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = checkItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            checkItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, (currentCharges - 1))
            player:addItem(2160, 1)
            --player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
            --addEvent(removeCharges, 1500, player:getId(), itemID)
        else
            return false
        end
    end
    return true
end
  
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        if removeCharges(player, Item(item.uid)) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
why are u removing addEvent inside removeCharges function? i need this add event repeat...
with uncomment addEvent i get error:

Code:
Lua Script Error: [Action Interface]
data/actions/scripts/wandSkills.lua:onUse
LuaScriptInterface::luaAddEvent(). Arguments #3 and #4 are unsafe
stack traceback:
        [C]: in function 'addEvent'
        data/actions/scripts/wandSkills.lua:9: in function 'removeCharges'
        data/actions/scripts/wandSkills.lua:19: in function <data/actions/scripts/wandSkills.lua:17>

Lua Script Error: [Main Interface]
in a timer event called from:
(Unknown scriptfile)
data/actions/scripts/wandSkills.lua:3: attempt to index local 'checkItem' (a number value)
stack traceback:
        [C]: in function '__index'
        data/actions/scripts/wandSkills.lua:3: in function <data/actions/scripts/wandSkills.lua:1>
 
You can try saving the position of the item and then find it ;)
You can also save the article in a local or global table, and then work on it, directly with the object and not with the uniqueid, since the uniqueid is temporary.
This works perfectly, in case the object was moved, it would continue to be removed.

Lua:
local removeItems = {}

function removeCharges(playerId, index)
    local player = Player(playerId)
    if not player then
        return false
    end
    local removeItem = removeItems[index]
    if not removeItem then
        return false
    end
    if removeItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = removeItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            local newCharges = currentCharges -1
            removeItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, newCharges)
            player:addItem(2160, 1)
            if newCharges > 0 then
                addEvent(removeCharges, 1500, playerId, index)
            else
                removeItem = nil
            end
        end
    end
    return true
end

function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        table.insert(removeItems, target)
        if removeCharges(player:getId(), #removeItems) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
GIF 29-12-2020 05-04-35 p. m..gif
work!
the only problem is if you do /reload action this process stops

Note: if you can guarantee that these items will be alive during this process, you can use this code, otherwise better not use it, it could cause a crash of your server if you do not know how to deal with this kind of code
 
Last edited:
You can try saving the position of the item and then find it ;)
You can also save the article in a local or global table, and then work on it, directly with the object and not with the uniqueid, since the uniqueid is temporary.
This works perfectly, in case the object was moved, it would continue to be removed.

Lua:
local removeItems = {}

function removeCharges(playerId, index)
    local player = Player(playerId)
    if not player then
        return false
    end
    local removeItem = removeItems[index]
    if not removeItem then
        return false
    end
    if removeItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = removeItem:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            local newCharges = currentCharges -1
            removeItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, newCharges)
            player:addItem(2160, 1)
            if newCharges > 0 then
                addEvent(removeCharges, 1500, playerId, index)
            else
                removeItem = nil
            end
        end
    end
    return true
end

function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        table.insert(removeItems, target)
        if removeCharges(player:getId(), #removeItems) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end
View attachment 53161
work!
the only problem is if you do /reload action this process stops

Note: if you can guarantee that these items will be alive during this process, you can use this code, otherwise better not use it, it could cause a crash of your server if you do not know how to deal with this kind of code
here i get this error:

Lua:
Lua Script Error: [Action Interface]
data/actions/scripts/wandMagic.lua:onUse
data/actions/scripts/wandMagic.lua:12: attempt to call method 'hasAttribute' (a nil value)
stack traceback:
        [C]: in function 'hasAttribute'
        data/actions/scripts/wandMagic.lua:12: in function 'removeCharges'
        data/actions/scripts/wandMagic.lua:31: in function <data/actions/scripts/wandMagic.lua:28>
 
here i get this error:

Lua:
Lua Script Error: [Action Interface]
data/actions/scripts/wandMagic.lua:onUse
data/actions/scripts/wandMagic.lua:12: attempt to call method 'hasAttribute' (a nil value)
stack traceback:
        [C]: in function 'hasAttribute'
        data/actions/scripts/wandMagic.lua:12: in function 'removeCharges'
        data/actions/scripts/wandMagic.lua:31: in function <data/actions/scripts/wandMagic.lua:28>
you are probably hitting the tile or creature and not an item as such, fix to:
Lua:
if item.itemid == 7415 and target and target:isItem() then
here:
1609288427408.png
 
you are probably hitting the tile or creature and not an item as such, fix to:
Lua:
if item.itemid == 7415 and target and target:isItem() then
here:
View attachment 53172
but when it go into addEvent, it doesn't run, because it return false:

Lua:
print(removeItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES))
    if removeItem:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then

it print false, but my item have 100 charges left, probable because removeItem is a table, and not a item

edit:
only changed it:
Lua:
table.insert(removeItems, target)

to it:
Code:
table.insert(removeItems, item)

edit2: have one bug in this code.
if i drop the item on the floor, the addEvent keep running
 
Last edited:
Code:
table.insert(removeItems, item)
I understand, what happens is that you want to apply this effect to the item used, in my script I tried it with use with target 👍

is there any way for the server to crash for this? or with this code?
if for some reason you delete this item before the event ends all its cycles, then yes
possible ways to remove it:
1) with a script
2) Throwing the item to a destructive tile, for example, garbage, water, lava, acid ect...

The first option is impossible to bypass, any administrator with access to run lua code, can remove it and cause the block on purpose.

To avoid the second option, you should create some code that is in charge of not letting you throw these objects at the aforementioned tiles, or if you do, eliminate the event that eliminates the charges with stopEvent() bla bla bla....
 
I understand, what happens is that you want to apply this effect to the item used, in my script I tried it with use with target 👍


if for some reason you delete this item before the event ends all its cycles, then yes
possible ways to remove it:
1) with a script
2) Throwing the item to a destructive tile, for example, garbage, water, lava, acid ect...
i edited above, can u look too? if player drop item, addevent keep runing :(
 
You have EventCallback system? if you have i can help you now!
is that I am lazy to explain the process of where you must add the code and all that, with EventCallback system I just tell you paste this in scripts and now XD
 
Now bby!
action script
Lua:
evDecayChargesList = {}

function removeCharges(playerId, index)
    local player = Player(playerId)
    if not player then
        return false
    end
    local removeItem = evDecayChargesList[index]
    if not removeItem then
        return false
    end
    local item = removeItem.item
    if item:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
        local currentCharges = item:getAttribute(ITEM_ATTRIBUTE_CHARGES)
        if currentCharges >= 1 then
            local newCharges = currentCharges -1
            item:setAttribute(ITEM_ATTRIBUTE_CHARGES, newCharges)
            player:addItem(2160, 1)
            if newCharges > 0 then
                removeItem.evId = addEvent(removeCharges, 1500, playerId, index)
            else
                removeItem = nil
            end
        end
    end
    return true
end
   
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item.itemid == 7415 then
        table.insert(evDecayChargesList, { item = item })
        local index = #removeItems
        item:setCustomAttribute("evDecayCharges", index)
        if removeCharges(player:getId(), index) then
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You gained 1 bar.")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This item has no more charges.")
        end
    end
    return true
end

events/player.lua
Lua:
function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    local index = item:getCustomAttribute("evDecayCharges")
    if index then
        if toPosition.x ~= CONTAINER_POSITION then
            stopEvent(evDecayChargesList[index].evId)
        elseif toCylinder and toCylinder:isItem() then
            local topParent = toCylinder:getTopParent()
            if topParent and topParent:isItem() then
                stopEvent(evDecayChargesList[index].evId)
            end
        end
    end
    return true
end
 
Solution
Back
Top