Atlasloot should be just hardcoding the itemids, then showing a tooltip when you ask for it, thus caching the item. If it's showing textures than it may be pre-caching missing items on load or a timer or such.
To close the unanswered question left in the thread, AtlasLoot is at the core a giant list of itemIDs associated with loot tables. GetItemIcon is used to the textures, other information like tooltips is grabbed if possible with GetItemInfo, if that doesn't work placeholder explanatory text is displayed.
My addon sends the dropped loot over acecomm to everyone with the same addon. They then get a popup to need/greed/pass on all the loot. I send hte items via itemlink not itemid. In the client side opopup you can see the item icon, name and tooltip on mouseover. But i still get reports that some people have no texture/name/tooltip for the item that dropped (They see only the questionmark icon i guess).
So I am wondering how is this possible if I send the item link in the acecomm message? Aparently itemlinks in an acecomm message does not make the client cache the item locally. From my experience it only works in messages the client user can actually see (like in raid chat). So far I could avoid this bug by linking all drops in raid chat before making the window pop up client side. This is obviously not an ideal fix to the problem.
I know i can force the item into the local cache by using GameTooltip:SetHyperlink. But this method doesn't halt execution and wait for the item to get loaded. So most likely GetItemInfo will still return only nils if used a few lua lines later.
Here is the code i use client side:
local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
-- force the item into the cache
if name == nil then
GameTooltip:SetHyperlink(itemLink)
name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
end
So i suppose i have to add an event listener for when i receive the data back from the server? How would i proceed about doing that?
I'm not sure the server fires any event when it validates an itemlink (actually I'm fairly positive, it doesn't). What you can probably do is implement a repeating timer, after you set your hyperlink, that will fire a GetItemInfo(link) call every X seconds and cancel itself when you get a significant (non nil) value returned. Note that this is still "dangerous" if used to query links for items that the server hasn't "seen" since the last restart, but since you are only going to be using it for items that have indeed dropped, you should be fine.
If you are sending the entire itemlink, extract the name from the itemlink and use that if GetItemInfo() doesn't give you a name. Use GetItemIcon() to get the icon since it will work even if the item isn't in the local cache. When the person mouses over for the tooltip it should still work since it will request the info from the server and update itself when it gets it.
I think i found a solution and will test it in the next raid. Basically i call CacheItem if GetItemInfo returns nil. This function creates a tooltip and an event listener for when the tooltip is received. When the Tooltip is received GetItemInfo is called again and the frame is updated at the affected slot.
function FLS:CacheItem(itemLink,slot)
local ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function() FLS:OnTooltipSetItem(itemLink,slot) end)
ttframe:SetHyperlink(itemLink)
end
function FLS:OnTooltipSetItem(itemLink,slot)
if GetItemInfo(itemLink) then
local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
currentloot[slot].item = name
currentloot[slot].quality = quality
currentloot[slot].texture = texture
self:UpdateSlaveFrameSlot(slot)
end
end
Frames are never garbage collected. The correct thing to do is to create a tooltip before any caching is done, and hide it. When caching, show the tooltip, set the hyperlink, do your thing. Then, re-hide it.
do
local ttframe
function FLS:CacheItem(itemLink,slot)
if not ttframe then
ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
end
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function() FLS:OnTooltipSetItem(itemLink,slot) end)
ttframe:SetHyperlink(itemLink)
end
end
^ If you want to wait until the function is called before making the tooltip frame, do this.
do
local ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
function FLS:CacheItem(itemLink,slot)
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function() FLS:OnTooltipSetItem(itemLink,slot) end)
ttframe:SetHyperlink(itemLink)
end
end
^ If you want, you can also do this.
But, yeah, it's very bad mojo to make a throw away frame that never gets thrown away.
I am aware that this creates many frames that will never be garbage collected. But my addon displays all the drops at the same time, so it will have to collect multiple items at the same time. I am not sure how SetHyperlink will behave when I call it multiple times for the same Tooltip-Frame on different items in quick succession. Will it send a response for every item, or only the latest one?
local ttframes = {}
function FLS:CacheItem(itemLink,slot)
if not ttframes[slot] then
ttframes[slot] = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
end
local ttframe = ttframes[slot]
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function() FLS:OnTooltipSetItem(itemLink,slot) end)
ttframe:SetHyperlink(itemLink)
end
I am aware that this creates many frames that will never be garbage collected. But my addon displays all the drops at the same time, so it will have to collect multiple items at the same time. I am not sure how SetHyperlink will behave when I call it multiple times for the same Tooltip-Frame on different items in quick succession. Will it send a response for every item, or only the latest one?
you could always use a coroutine, have a function generate the tooltip and then yield, OnTooltipSetItem could resume the function ensuring that the item data is in the local cache and then your code can process it.
you'd probably have issues if you were pulling a lot of uncached item data though as it's going to depend on server load. but at least you'd never get disconnected from it.
easiest to do what the others have mentioned though, just send the itemlink and have the user pull the name and texture from that string and give them a tooltip when they mouseover the texture (so they cache the actual item and can see its stats if they want)
Halting execution to receive tooltip, don't know if its a good idea. It would fetch the item data one by one adding up to a huge delay when displaying multiple items. The solution i use now is using like a maximum of 5 tooltip frames (don't think any boss drops more then 5 epic items) that all get re-used.
Using a single frame is sufficient. In Ackis Recipe List, we parse thousands of items with the data-miner using a single tooltip. Using that approach, the longest perceived client freeze is roughly three to five seconds if we scan the entire database of recipes. Doing otherwise is creating a memory leak.
local ttframe
function FLS:CacheItem(itemLink,slot)
if not ttframe then
ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function(self) FLS:OnTooltipSetItem(self) end)
ttframe.slots = {}
ttframe.itemLinks = {}
ttframe:SetHyperlink(itemLink)
elseif not next(ttframe.slots) then
ttframe:SetHyperlink(itemLink)
end
tinsert(ttframe.slots, slot)
tinsert(ttframe.itemLinks, itemLink)
end
function FLS:OnTooltipSetItem(tootlip)
local itemLink, slot = tremove(tooltip.itemLinks), tremove(tooltip.slots)
if GetItemInfo(itemLink) then
local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
currentloot[slot].item = name
currentloot[slot].quality = quality
currentloot[slot].texture = texture
self:UpdateSlaveFrameSlot(slot)
end
local tsize = #ttframe.itemLinks
if tsize > 0 then
ttframe:SetHyperlink(ttframe.itemLinks[tsize])
end
end
Don't think your code is entirely correct. The first item that gets querried is at the start of the table, but if you receive the tooltip from the server you take the last entry from the tables, which did not get polled yet. But then you continue polling items always from the end of the table, so from then on it would be correct. I changed it so it kindo works like FIFO
local ttframe
function FLS:CacheItem(itemLink,slot)
if not ttframe then
ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function(self) FLS:OnTooltipSetItem(self) end)
ttframe.slots = {}
ttframe.itemLinks = {}
ttframe:SetHyperlink(itemLink)
elseif not next(ttframe.slots) then
ttframe:SetHyperlink(itemLink)
end
tinsert(ttframe.slots, slot)
tinsert(ttframe.itemLinks, itemLink)
end
function FLS:OnTooltipSetItem(tootlip)
local itemLink = table.remove(tooltip.itemLinks,1)
local slot = table.remove(tooltip.slots,1)
if GetItemInfo(itemLink) then
local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
currentloot[slot].item = name
currentloot[slot].quality = quality
currentloot[slot].texture = texture
self:UpdateSlaveFrameSlot(slot)
end
if next(ttframe.itemlinks) then
ttframe:SetHyperlink(next(ttframe.itemlinks))
end
end
This of corse will only work if OnTooltipSetItem is only triggered when the tooltip is finally received from the server and not by some intermediate responses. Thus this version seems saver to me:
local ttframe
function FLS:CacheItem(itemLink,slot)
if not ttframe then
ttframe = CreateFrame("GameTooltip", nil, nil, "GameTooltipTemplate")
ttframe:SetOwner(UIParent, "ANCHOR_NONE")
ttframe:SetScript('OnTooltipSetItem', function(self) FLS:OnTooltipSetItem(self) end)
ttframe.slots = {}
ttframe.itemLinks = {}
ttframe:SetHyperlink(itemLink)
elseif not next(ttframe.slots) then
ttframe:SetHyperlink(itemLink)
end
tinsert(ttframe.slots, slot)
tinsert(ttframe.itemLinks, itemLink)
end
function FLS:OnTooltipSetItem(tootlip)
local itemName, itemLink = tooltip:GetItem()
if GetItemInfo(itemLink) then
local i = table.findElement(tootlip.itemLinks, itemLink)
if i then
local slot = tootlip.slots[i]
local name, link, quality, iLevel, reqLevel, type, subType, maxStack, equipSlot, texture = GetItemInfo(itemLink)
currentloot[slot].item = name
currentloot[slot].quality = quality
currentloot[slot].texture = texture
self:UpdateSlaveFrameSlot(slot)
table.remove(tootlip.itemLinks, i)
table.remove(tootlip.slots, i)
if next(ttframe.itemlinks) then
ttframe:SetHyperlink(next(ttframe.itemlinks))
end
end
end
end
Obviously table.findElement is a selfdefined function that finds the index of an element.
Yeah, got the order bit a bit wrong :P.
As for this
This of corse will only work if OnTooltipSetItem is only triggered when the tooltip is finally received from the server and not by some intermediate responses. Thus this version seems saver to me:
What kind of intermediate response? Unless I'm mistaken WoW wont touch your custom tooltip. (unless I'm missing something which I seem to be doing alot lately)
As far as i know that event can be triggered more then once when you use SetHyperlink. I am pretty sure that for example Patterns that craft an item trigger it twice - once for the pattern and once for the item they craft.
So if you were to request the tooltip for a LWing recipe and it triggered two responses, you would end up removing an entry from the table that you actually didnt get the response for yet.
If you are sending the entire itemlink, extract the name from the itemlink and use that if GetItemInfo() doesn't give you a name. Use GetItemIcon() to get the icon since it will work even if the item isn't in the local cache. When the person mouses over for the tooltip it should still work since it will request the info from the server and update itself when it gets it.
Is there any reason this will not work for you? If so then tell me so I can write up some correct tooltip code because everything else posted has been perty bad with doing unnecessary stuff and creating unneeded closures on every call.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
To close the unanswered question left in the thread, AtlasLoot is at the core a giant list of itemIDs associated with loot tables. GetItemIcon is used to the textures, other information like tooltips is grabbed if possible with GetItemInfo, if that doesn't work placeholder explanatory text is displayed.
My addon sends the dropped loot over acecomm to everyone with the same addon. They then get a popup to need/greed/pass on all the loot. I send hte items via itemlink not itemid. In the client side opopup you can see the item icon, name and tooltip on mouseover. But i still get reports that some people have no texture/name/tooltip for the item that dropped (They see only the questionmark icon i guess).
So I am wondering how is this possible if I send the item link in the acecomm message? Aparently itemlinks in an acecomm message does not make the client cache the item locally. From my experience it only works in messages the client user can actually see (like in raid chat). So far I could avoid this bug by linking all drops in raid chat before making the window pop up client side. This is obviously not an ideal fix to the problem.
I know i can force the item into the local cache by using GameTooltip:SetHyperlink. But this method doesn't halt execution and wait for the item to get loaded. So most likely GetItemInfo will still return only nils if used a few lua lines later.
Here is the code i use client side:
So i suppose i have to add an event listener for when i receive the data back from the server? How would i proceed about doing that?
Do not create a new tooltip frame for every call to CacheItem()!
And reads whispers for a certain format.
^ If you want, you can also do this.
But, yeah, it's very bad mojo to make a throw away frame that never gets thrown away.
But i guess i can change the function to this?
you could always use a coroutine, have a function generate the tooltip and then yield, OnTooltipSetItem could resume the function ensuring that the item data is in the local cache and then your code can process it.
you'd probably have issues if you were pulling a lot of uncached item data though as it's going to depend on server load. but at least you'd never get disconnected from it.
easiest to do what the others have mentioned though, just send the itemlink and have the user pull the name and texture from that string and give them a tooltip when they mouseover the texture (so they cache the actual item and can see its stats if they want)
This of corse will only work if OnTooltipSetItem is only triggered when the tooltip is finally received from the server and not by some intermediate responses. Thus this version seems saver to me:
Obviously table.findElement is a selfdefined function that finds the index of an element.
As for this
What kind of intermediate response? Unless I'm mistaken WoW wont touch your custom tooltip. (unless I'm missing something which I seem to be doing alot lately)
So if you were to request the tooltip for a LWing recipe and it triggered two responses, you would end up removing an entry from the table that you actually didnt get the response for yet.
Is there any reason this will not work for you? If so then tell me so I can write up some correct tooltip code because everything else posted has been perty bad with doing unnecessary stuff and creating unneeded closures on every call.