Thread Tools Display Modes
11/18/14, 10:08 AM   #1
brekal
Guest
Posts: n/a
FindFirstEmptySlotInBag not updating

Hey guys it's me again ;-)

I have some issues with the function FindFirstEmptySlotInBag(BAG_BACKPACK).

I want to move items from the bank into my inventory but it always just moves one item.
I dumped the values of the variables and found out that the Destination SlotID (slotId_Bag) always stays the same.
So I think that this is the issue - but unfortunately I cannot image why it is not updating because I call it in every loop of the for-loop so it should update correctly.

Lua Code:
  1. function LootManager.GetIngredientsFromBank()
  2.     local name
  3.     local item
  4.     local slotId
  5.     local count
  6.     local bagId = BAG_BANK
  7.     local slots = GetBagSize(bagId)
  8.     local slotId_Bag
  9.     local bag_Dest = BAG_BACKPACK
  10.  
  11.     for i=0, slots - 1 do
  12.         slotId = i
  13.         name = zo_strformat(SI_UNIT_NAME, GetItemName(bagId, slotId))
  14.         item, count = GetItemInfo(bagId, slotId)
  15.         slotId_Bag = FindFirstEmptySlotInBag(bag_Dest)
  16.  
  17.         if name == LootManager.savedVars.ingredient1 then
  18.             LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  19.         end
  20.  
  21.         if name == LootManager.savedVars.ingredient2 then  
  22.             LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  23.         end
  24.        
  25.         if name == LootManager.savedVars.ingredient3 then
  26.             LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  27.         end
  28.  
  29.     end
  30.  
  31. end
  32.  
  33. ------------------------------------------------------------------------------------------
  34. ------------------------------------------------------------------------------------------
  35.  
  36. function LootManager.MoveItem(srcBag, srcSlot, destBag, destSlot, quantity)
  37.     ClearCursor()
  38.     zo_callLater(function()
  39.         if CallSecureProtected("PickupInventoryItem", srcBag, srcSlot, quantity) then
  40.             CallSecureProtected("PlaceInInventory",destBag, destSlot)
  41.         end
  42.     end, 500)  
  43.     ClearCursor()  
  44.     d("Moved: " .. zo_strformat(SI_UNIT_NAME, GetItemName(srcBag, srcSlot)) .. " x " .. quantity .. " to inventory")
  45.     d("SrcBag: " .. srcBag .. " SrcSlot: " .. srcSlot .. " DestBag: " .. destBag .. " DestSlot: " .. destSlot .. " Quantity: " .. quantity)
  46. end

Do you see any error here?

Thanks in advance
  Reply With Quote
11/18/14, 11:03 AM   #2
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
That's probably by design. The items are not actually moved immediately, your commands are carried out after your handler exits.

Some time ago I wrote my own function for finding an empty slot, which allows me to start the search after the previous one:
Lua Code:
  1. local function findEmptySlotInBag(bagId, prevIndex, lastIndex)
  2.     local slotIndex = prevIndex or -1
  3.     while slotIndex < lastIndex do
  4.         slotIndex = slotIndex + 1
  5.         if GetItemType(bagId, slotIndex) == ITEMTYPE_NONE then
  6.             return slotIndex
  7.         end
  8.     end
  9.     return nil
  10. end
  11.  
  12. -- usage
  13. local numSlots = GetBagSize(BAG_BANK)
  14. local emptyIndex = findEmptySlotInBag(BAG_BANK, nil, numSlots - 1)
  15. while emptyIndex do
  16.    -- we have an empty slot in bank
  17.    ... put something in there
  18.     emptyIndex = findEmptySlotInBag(BAG_BANK, emptyIndex, numSlots - 1)
  19. end
  20. -- bank full
  Reply With Quote
11/18/14, 12:29 PM   #3
brekal
Guest
Posts: n/a
Thanks for your reply.

I already thought about making my own Slotfinder but i hoped that there was some working built-in function ....
  Reply With Quote
11/18/14, 09:15 PM   #4
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
I'm pretty sure the keys in the slots table matches the slotIndices. So you could loop through the slots and check to see if each key exists in the table.

Lua Code:
  1. local inventory = PLAYER_INVENTORY.inventories[INVENTORY_BACKPACK].slots
  2. local bagSize = GetBagSize(BAG_BACKPACK)
  3.    
  4. for i = 0, bagSize-1 do
  5.    if not inventory[i] then
  6.       -- do whatever inventory[i] is an empty slot
  7.    end
  8. end

Or you could make it into a function and each time you find an empty slot throw it into a table, then return the table & you'll have all of the empty slots to do whatever you want with them.

Although merlights answer is probably better for what your doing, just giving some options.
  Reply With Quote
11/18/14, 10:05 PM   #5
Garkin
 
Garkin's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 832
This issue is because of 500ms delay in you function LootManager.MoveItems. All calls of the FindFirstEmptySlotInBag within this 500ms will return still the same slot. If you remove zo_callLater from the function, it should work correctly.

And I think it will be better if you use RequestMoveItem function instead of PickupInventoryItem and PlaceInInventory functions.
  Reply With Quote
11/19/14, 05:59 AM   #6
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by Garkin View Post
This issue is because of 500ms delay in you function LootManager.MoveItems. All calls of the FindFirstEmptySlotInBag within this 500ms will return still the same slot. If you remove zo_callLater from the function, it should work correctly.
They might have changed it, but I don't think so. I did the same thing some time ago (the addon in question still has APIVersion 100008), and FindFirstEmptySlotInBag kept returning the same value over and over.

Similar thing happens if you handle EVENT_LOOT_UPDATED. Functions GetLootMoney/GetNumLootItems will keep returning the values applicable when the handler was called, even after you call LootMoney/LootItemById inside the handler. After the handler exits, items are actually moved, and a new event generated with updated state.

Originally Posted by Garkin View Post
And I think it will be better if you use RequestMoveItem function instead of PickupInventoryItem and PlaceInInventory functions.
Good point, this one was added recently. And has a better name - request indicates the action is not carried out immediately.
  Reply With Quote
11/19/14, 10:31 PM   #7
Sasky
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 231
Originally Posted by merlight View Post
They might have changed it, but I don't think so. I did the same thing some time ago (the addon in question still has APIVersion 100008), and FindFirstEmptySlotInBag kept returning the same value over and over.
Might be a base issue, but Garkin was looking at this in brekal's code:

Lua Code:
  1. function LootManager.MoveItem(srcBag, srcSlot, destBag, destSlot, quantity)
  2.     --...
  3.     zo_callLater(function()
  4.         --Actual move in here, delayed 500ms
  5.     end, 500)  
  6.     --...
  7. end
This introduces a delay for actually executing the move, and iterating through the list (hopefully) finishes in <500ms so they're all looking at the same slot.

What is the purpose of the delay? If you're trying to not flood the server (which could result in a forced logout), it doesn't work as written. zo_callLater is non-blocking, so it will exit the main function and continue in the loop without waiting at all. You'll end up with all the actual move attempts ~500ms later still close enough to flip a message rate limit.
  Reply With Quote
11/20/14, 03:14 AM   #8
brekal
Guest
Posts: n/a
thanks for your replies.

First I tried out circonians suggestion - but unfortunately it didn't work.

So I tried his second suggestion - to go through the inventory and save all free slots in an extra table.

When I then move the item I iterate through the table of free stlots. I had there some issues too. Because when I started at the first element sometimes it did not get the right destSlots. So I decided to start at the last element and this works fine.

I know that my solution is not the smartest but at least it is working for my purposes

Here ist the actual code:

Lua Code:
  1. function LootManager.GetIngredientsFromBank()
  2.     if LootManager.savedVars.getBankIngredients ~= true then
  3.         return
  4.     end
  5.    
  6.     local name
  7.     local item
  8.     local slotId
  9.     local count
  10.     local bagId = BAG_BANK
  11.     local slots = GetBagSize(bagId)
  12.     local bag_Dest = BAG_BACKPACK
  13.    
  14.     LootManager.FindEmptySlotInBag()   
  15.     local slotId_Bag = LootManager.emptySlotsInBag[1]
  16.    
  17.     for i=0, slots - 1 do
  18.         slotId = i
  19.         name = zo_strformat(SI_UNIT_NAME, GetItemName(bagId, slotId))
  20.         item, count = GetItemInfo(bagId, slotId)
  21.  
  22.         if slotId_Bag ~= nil then
  23.             if name == LootManager.savedVars.ingredient1 then
  24.                 LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  25.                 slotId_Bag = LootManager.emptySlotsInBag[table.getn(LootManager.emptySlotsInBag)-1]
  26.             end
  27.  
  28.             if name == LootManager.savedVars.ingredient2 then  
  29.                 LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  30.                 slotId_Bag = LootManager.emptySlotsInBag[table.getn(LootManager.emptySlotsInBag)-2]
  31.             end
  32.        
  33.             if name == LootManager.savedVars.ingredient3 then
  34.                 LootManager.MoveItem(bagId, slotId, bag_Dest, slotId_Bag, count)
  35.                 slotId_Bag = LootManager.emptySlotsInBag[table.getn(LootManager.emptySlotsInBag)-3]
  36.             end
  37.         else
  38.             d("Fehler: Slot-ID ist nil")
  39.             break
  40.         end
  41.     end
  42.    
  43.     LootManager.emptySlotsInBag = {}
  44. end
  45.  
  46. ------------------------------------------------------------------------------------------
  47. ------------------------------------------------------------------------------------------
  48.  
  49. function LootManager.MoveItem(srcBag, srcSlot, destBag, destSlot, quantity)
  50.     ClearCursor()
  51.         if CallSecureProtected("PickupInventoryItem", srcBag, srcSlot, quantity) then
  52.             CallSecureProtected("PlaceInInventory",destBag, destSlot)
  53.         end
  54.     ClearCursor()  
  55. --  d("Moved: " .. zo_strformat(SI_UNIT_NAME, GetItemName(srcBag, srcSlot)) .. " x " .. quantity .. " to inventory")
  56. --  d("SrcBag: " .. srcBag .. " SrcSlot: " .. srcSlot .. " DestBag: " .. destBag .. " DestSlot: " .. destSlot .. " Quantity: " .. quantity)
  57. end
  58.  
  59. ------------------------------------------------------------------------------------------
  60. ------------------------------------------------------------------------------------------
  61.  
  62. function LootManager.FindEmptySlotInBag()
  63.     local bagSize = GetBagSize(BAG_BACKPACK)
  64.     local name
  65.    
  66.     for i = 0, bagSize - 1 do
  67.         name = zo_strformat(SI_UNIT_NAME, GetItemName(BAG_BACKPACK, i))
  68.         if name == "" then
  69.             table.insert(LootManager.emptySlotsInBag, i)   
  70.         end
  71.     end
  72.  
  73. end


I also do not understand why this delay should cause the problem because I only delay the movement of the item, not the FindFirstEmptySlotInBag-function. The interesting thing about this is, that dumping the values (as I do in line 44 and 45 of my original post) shows the correct item name and srcSlot - the only thing that stays the same is the destSlot.

I did not find any article on esoui-wiki about this "RequestMoveItem" function that Garkin suggested - do you have more infos about it?
  Reply With Quote
11/20/14, 10:05 AM   #9
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Adding delay is unnecessary, the move won't happen until your handler exists anyway.

Haven't really tested it, but I'm pretty sure the client doesn't do any network communication directly from API functions. Think about it - if you called RequestMoveItem and the client would immediately send the request to the server, waited for the response and then returned the result to you, the latency would be horrid, and your add-on would completely block all other add-ons for the duration of the request (by all other add-ons I mean the whole UI, health bars and such). API functions absolutely have to put network requests on a queue.

Then I have some design notes on your free slots caching:
1) You don't know in advance how many free slots you're going to need. Thus finding all of them before you start is a bit wasteful. The function I posted earlier looks for one free slot, you call it when you need one. This is a somewhat generic principle: don't search and cache everything you might need later, search and cache things you actually need.
2) Check whether a slot is empty using something other than GetItemName (comparing strings will always be slower than comparing numbers). I used GetItemType for that.
edit: 3) you probably wanted slotId_Bag = table.remove(LootManager.emptySlotsInBag); your code doesn't really take items from the cache, it uses the same 1-4 slots over and over.

From ESOUIDocumentationP5.txt (you can find it on official forums in PTS section)

* RequestMoveItem *protected* (*integer* _sourceBag_, *integer* _sourceSlot_, *integer* _destBag_, *integer* _destSlot_, *integer* _stackCount_)

Last edited by merlight : 11/20/14 at 10:12 AM.
  Reply With Quote
11/20/14, 01:47 PM   #10
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
Originally Posted by brekal View Post
thanks for your replies.

First I tried out circonians suggestion - but unfortunately it didn't work.

So I tried his second suggestion - to go through the inventory and save all free slots in an extra table.

When I then move the item I iterate through the table of free stlots. I had there some issues too. Because when I started at the first element sometimes it did not get the right destSlots. So I decided to start at the last element and this works fine.
You said the code did not work for you. I'm not sure if you mean there was another problem later after you used it or not, but that code does find the free slots, I tested it.

Also as merlight stated it is wasteful to loop through and find ALL of the free slots available if you do not need them all. As I stated above, merlights answer is definitely better. I was only trying to give some options on ways you can find free slots, without copying his answer because I could not think of a better one and figured who knows sometime it may be useful to find all of the free slots for you or someone else reading this later. Sorry if I did not make my reply clear enough.
  Reply With Quote
11/20/14, 02:04 PM   #11
circonian
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 613
Originally Posted by merlight View Post
2) Check whether a slot is empty using something other than GetItemName (comparing strings will always be slower than comparing numbers). I used GetItemType for that.
merlight, this is strictly a curiosity question to see if there is something I'm missing or do not understand, it is probably a negligible difference.

But, Is there a reason you don't just check the inventory slots table instead of calling GetItemType and comparing it to see if its ITEMTYPE_NONE?
Wouldn't that be even faster?
Lua Code:
  1. local function findEmptySlotInBag(bagId, prevIndex, lastIndex)
  2.    local slotIndex = prevIndex or -1
  3.    while slotIndex < lastIndex do
  4.       slotIndex = slotIndex + 1
  5.       -- this
  6.       local iInventory = PLAYER_INVENTORY.bagToInventoryType[bagId]
  7.       if not PLAYER_INVENTORY.inventories[iInventory].slots[slotIndex] then
  8.       -- instead of this
  9.       --if GetItemType(bagId, slotIndex) == ITEMTYPE_NONE then
  10.          return slotIndex
  11.       end
  12.    end
  13.    return nil
  14. end
  Reply With Quote
11/20/14, 03:29 PM   #12
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by circonian View Post
But, Is there a reason you don't just check the inventory slots table instead of calling GetItemType and comparing it to see if its ITEMTYPE_NONE?
Wouldn't that be even faster?

No particular reason. GetItemType is what I learned first, is easy to remember and also shorter, dunno about speed Although if you wanted to use pre-cached slot data, you should probably use SHARED_INVENTORY:GenerateSingleSlotData. That's what PLAYER_INVENTORY.inventories[iInventory].slots are initialized with:
Lua Code:
  1. -- ingame/inventory/inventory.lua
  2. inventory.slots[slotIndex] = SHARED_INVENTORY:GenerateSingleSlotData(bagId, slotIndex)
  Reply With Quote
10/28/15, 05:50 PM   #13
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
A very old bump for 1st thank you for this feedback

I personnally wrote this :

Lua Code:
  1. -- Our bagcache, because game don't have it in realtime
  2.     local tinyBagCache = {
  3.         [BAG_BACKPACK] = {},
  4.         [BAG_BANK] = {},
  5.     }
  6.    
  7.     -- Thanks Merlight & circonian, FindFirstEmptySlotInBag don't refresh in realtime.
  8.     local function FindEmptySlotInBag(bagId)
  9.         for slotIndex = 0, (GetBagSize(bagId) - 1) do
  10.             if not SHARED_INVENTORY.bagCache[bagId][slotIndex] and not tinyBagCache[bagId][slotIndex] then
  11.                 tinyBagCache[bagId][slotIndex] = true
  12.                 return slotIndex
  13.             end
  14.         end
  15.         return nil
  16.     end


But this function is not the only one to do not update in real time, a second one, more recent got the same problem : GetItemLinkStacks(itemLink)


My solution is :


Lua Code:
  1. -- Our bagcache for qty, because game don't have it in realtime
  2.     local qtyBagCache = {}
  3.  
  4.     -- this function returns more info than desired, but it's for the exemple.
  5.     local function StackInfoInBag(bagToCheck, slotIdFrom, bagIdFrom, itemLink)
  6.         local stackCountBackpack, stackCountBank
  7.        
  8.         if not qtyBagCache[itemLink] then
  9.             stackCountBackpack, stackCountBank = GetItemLinkStacks(itemLink) -- Not updated in realtime
  10.             qtyBagCache[itemLink] = {}
  11.             qtyBagCache[itemLink][BAG_BACKPACK] = stackCountBackpack
  12.             qtyBagCache[itemLink][BAG_BANK] = stackCountBank
  13.         else
  14.             stackCountBackpack = qtyBagCache[itemLink][BAG_BACKPACK]
  15.             stackCountBank = qtyBagCache[itemLink][BAG_BANK]
  16.         end
  17.        
  18.         d("--------------")
  19.         d(stackCountBackpack, stackCountBank)
  20.         d("---")
  21.         d(GetItemLinkStacks(itemLink))
  22.         d("--------------")
  23.        
  24.         local stackSize, maxStack = GetSlotStackSize(bagIdFrom, slotIdFrom)
  25.         if bagToCheck == BAG_BACKPACK then
  26.             return stackCountBackpack >= maxStack, stackSize, maxStack, stackCountBackpack, stackCountBank
  27.         elseif bagToCheck == BAG_BANK then
  28.             return stackCountBank >= maxStack, stackSize, maxStack, stackCountBackpack, stackCountBank
  29.         end
  30.     end


An exemple :

Code:
[00:38:18] --------------
[00:38:18] 672 -- backpackqty with mycache func
[00:38:18] 500 -- bankqty with mycache func
[00:38:18] ---
[00:38:18] 672 -- backpackqty with ZOS func
[00:38:18] 500 -- bankqty with ZOS func
[00:38:18] --------------
[00:38:18] BMR a déplacé 72x [Toile du Vide] en banque <----- my addon moved 72x from bag to bank
[00:38:18] --------------
[00:38:18] 600 -- backpackqty with mycache func
[00:38:18] 572 -- bankqty with mycache func
[00:38:18] ---
[00:38:18] 672 -- backpackqty with ZOS func -- Wrong
[00:38:18] 500 -- bankqty with ZOS func -- Wrong
[00:38:18] --------------
[00:38:18] BMR a déplacé 28x [Toile du Vide] en banque <----- my addon moved 28x from bag to bank (from another stack, approx 1ms after 1st move
[00:38:18] --------------
[00:38:18] 572 -- backpackqty with mycache func
[00:38:18] 600 -- bankqty with mycache func
[00:38:18] ---
[00:38:18] 672 -- backpackqty with ZOS func -- Wrong
[00:38:18] 500 -- bankqty with ZOS func -- Wrong
[00:38:18] --------------
  Reply With Quote

ESOUI » Developer Discussions » Lua/XML Help » FindFirstEmptySlotInBag not updating


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off