feat(lua): Item follows retail

Util/Item.lua is now retail Blizzard_ObjectAPI/Mainline/Item.lua in
retail's own text, and its listener is retail's AsyncCallbackSystem.lua
with the item and quest listeners. There is no spell listener: every
spell is client-side data here, so there is nothing to wait for. The one
other difference is that GetItemQualityColor reads ITEM_QUALITY_COLORS,
the same entry retail's ColorManager returns.

What follows retail now:
- Load callbacks get (id, success).
- An item callback also runs when the load fails, with success nil.
  TradeSkillLink's two callbacks now stop on a failure, because
  re-rendering would request the missing item again.
- C_Item.RequestLoadItemDataByID runs once per item, not once per
  callback.
- New methods: GetStackCount, ContinueWithCancelOnRecordLoad and
  IsRecordDataCached. ContinueOnItemLoad's usage errors name the bad
  itemID or link, and GetItemGUID returns the GUID an item was made from.

C_Item.GetItemIDForItemInfo is new in the DLL for ItemMixin:GetItemID's
fallback. It uses the resolver that GetItemInfoInstant uses.

Verified in game: Item:CreateFromItemID(19019) calls back with
(19019, 1), and C_Item.GetItemIDForItemInfo("item:6948") returns 6948.
This commit is contained in:
Brues
2026-10-30 21:12:22 -05:00
parent 6cee968a8a
commit 474da1a5b5
6 changed files with 330 additions and 244 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ on a frame timer" goal landed as the request/event pair below:
cache (or synchronously if it was already loaded).
Together those replace the polyfill's 180-second polling frame
(`ItemEventListener` in `Util/ItemUtil.lua:254-372`) — addons can now
(`ItemEventListener`, now in `Util/AsyncCallbackSystem.lua`) — addons can now
fire `RequestLoadItemDataByID(id)` and listen for
`ITEM_DATA_LOAD_RESULT` instead.
@@ -41,6 +41,7 @@ Util\ColorUtil.lua
Util\ItemLocation.lua
Util\PlayerLocation.lua
Util\EventUtil.lua
Util\AsyncCallbackSystem.lua
Util\Item.lua
Util\AddOnCompat.lua
Util\FunctionUtil.lua
@@ -0,0 +1,116 @@
-- Backport of retail's Blizzard_ObjectAPI/Mainline/AsyncCallbackSystem.lua.
--
-- Differences from the retail source:
-- - No ASYNC_SPELL type and no SpellEventListener. This client keeps every
-- spell in its own data files, so there is no spell data to wait for and
-- no C_Spell.RequestLoadSpellData / SPELL_DATA_LOAD_RESULT.
--[[
Queries some data retrieval API (specifically where the data may not be currently available) and when it becomes available
calls a user-supplied function. The callback can be canceled if necessary (e.g. the frame that would use the data becomes
hidden before the data arrives).
The API is managed so that arbitrary query functions cannot be executed.
--]]
AsyncCallbackAPIType = {
ASYNC_QUEST = 1,
ASYNC_ITEM = 2,
}
local permittedAPI =
{
[AsyncCallbackAPIType.ASYNC_QUEST] = { event = "QUEST_DATA_LOAD_RESULT", accessor = C_QuestLog.RequestLoadQuestByID },
[AsyncCallbackAPIType.ASYNC_ITEM] = { event = "ITEM_DATA_LOAD_RESULT", accessor = C_Item.RequestLoadItemDataByID },
};
AsyncCallbackSystemMixin = {};
function AsyncCallbackSystemMixin:Init(apiType)
self.callbacks = {};
-- API Type should be set up from key value pairs before OnLoad.
self.api = permittedAPI[apiType];
self:SetScript("OnEvent",
function(self, event, ...)
if event == self.api.event then
local id, success = ...;
if success or apiType == AsyncCallbackAPIType.ASYNC_ITEM then
self:FireCallbacks(id, success);
else
self:ClearCallbacks(id);
end
end
end
);
self:RegisterEvent(self.api.event);
end
local CANCELED_SENTINEL = -1;
function AsyncCallbackSystemMixin:AddCallback(id, callbackFunction)
local callbacks = self:GetOrCreateCallbacks(id);
table.insert(callbacks, callbackFunction);
local needsAccessorCall = #callbacks == 1;
if needsAccessorCall then
self.api.accessor(id);
end
return #callbacks, callbacks;
end
function AsyncCallbackSystemMixin:AddCancelableCallback(id, callbackFunction)
-- NOTE: If the data is currently availble then the callback will be executed and callbacks cleared, so there will be nothing to cancel.
local index, callbacks = self:AddCallback(id, callbackFunction);
return function()
if #callbacks > 0 and callbacks[index] ~= CANCELED_SENTINEL then
callbacks[index] = CANCELED_SENTINEL;
return true;
end
return false;
end;
end
function AsyncCallbackSystemMixin:FireCallbacks(id, success)
local callbacks = self:GetCallbacks(id);
if callbacks then
self:ClearCallbacks(id);
for i, callback in ipairs(callbacks) do
if callback ~= CANCELED_SENTINEL then
xpcall(callback, CallErrorHandler, id, success);
end
end
-- The cancel functions have a reference to this table, so ensure that it's cleared out.
for i = #callbacks, 1, -1 do
callbacks[i] = nil;
end
end
end
function AsyncCallbackSystemMixin:ClearCallbacks(id)
self.callbacks[id] = nil;
end
function AsyncCallbackSystemMixin:GetCallbacks(id)
return self.callbacks[id];
end
function AsyncCallbackSystemMixin:GetOrCreateCallbacks(id)
local callbacks = self.callbacks[id];
if not callbacks then
callbacks = {};
self.callbacks[id] = callbacks;
end
return callbacks;
end
local function CreateListener(apiType)
local listener = Mixin(CreateFrame("Frame"), AsyncCallbackSystemMixin);
listener:Init(apiType);
return listener;
end
ItemEventListener = CreateListener(AsyncCallbackAPIType.ASYNC_ITEM);
QuestEventListener = CreateListener(AsyncCallbackAPIType.ASYNC_QUEST);
+178 -239
View File
@@ -1,70 +1,55 @@
-- Backport of 3.3.5 FrameXML/ItemUtil.lua to vanilla 1.12 / Lua 5.0.
-- Backport of retail's Blizzard_ObjectAPI/Mainline/Item.lua.
--
-- Implementation differences from the 3.3.5 source:
-- - Many C_Item.* calls the modern code expects are not exposed by
-- ClassicAPI.dll. We shim the missing ones at the top using the
-- stock 1.12 globals (GetItemInfo, GetInventoryItemLink,
-- GetContainerItemLink/Info). DLL-provided functions are left alone
-- via `or` guards so we never clobber a real implementation.
-- - Lua 5.0 has no select(); calls that previously did
-- `select(N, GetItemInfo(...))` use named locals to peel off the
-- return values instead.
-- - CallErrorHandler isn't in 1.12; we route through geterrorhandler().
-- - In 1.12 OnEvent scripts, event name + payload are exposed via the
-- `event`, `arg1`, `arg2`... globals rather than as function args.
-- - 1.12 has no concept of an item GUID and no public item-lock API,
-- so those return nil / no-op.
-- Differences from the retail source:
-- - GetItemQualityColor reads ITEM_QUALITY_COLORS directly; retail's
-- ColorManager.GetColorDataForItemQuality returns the same entry.
----------------------------------------------------------------------
Item = {}
ItemMixin = {}
local ItemEventListener
Item = {};
ItemMixin = {};
--[[static]] function Item:CreateFromItemLocation(itemLocation)
if type(itemLocation) ~= "table" or type(itemLocation.HasAnyLocation) ~= "function" or not itemLocation:HasAnyLocation() then
error("Usage: Item:CreateFromItemLocation(notEmptyItemLocation)", 2)
end
local item = CreateFromMixins(ItemMixin)
item:SetItemLocation(itemLocation)
return item
if type(itemLocation) ~= "table" or type(itemLocation.HasAnyLocation) ~= "function" or not itemLocation:HasAnyLocation() then
error("Usage: Item:CreateFromItemLocation(notEmptyItemLocation)", 2);
end
local item = CreateFromMixins(ItemMixin);
item:SetItemLocation(itemLocation);
return item;
end
--[[static]] function Item:CreateFromBagAndSlot(bagID, slotIndex)
if type(bagID) ~= "number" or type(slotIndex) ~= "number" then
error("Usage: Item:CreateFromBagAndSlot(bagID, slotIndex)", 2)
end
local item = CreateFromMixins(ItemMixin)
item:SetItemLocation(ItemLocation:CreateFromBagAndSlot(bagID, slotIndex))
return item
if type(bagID) ~= "number" or type(slotIndex) ~= "number" then
error("Usage: Item:CreateFromBagAndSlot(bagID, slotIndex)", 2);
end
local item = CreateFromMixins(ItemMixin);
item:SetItemLocation(ItemLocation:CreateFromBagAndSlot(bagID, slotIndex));
return item;
end
--[[static]] function Item:CreateFromEquipmentSlot(equipmentSlotIndex)
if type(equipmentSlotIndex) ~= "number" then
error("Usage: Item:CreateFromEquipmentSlot(equipmentSlotIndex)", 2)
end
local item = CreateFromMixins(ItemMixin)
item:SetItemLocation(ItemLocation:CreateFromEquipmentSlot(equipmentSlotIndex))
return item
if type(equipmentSlotIndex) ~= "number" then
error("Usage: Item:CreateFromEquipmentSlot(equipmentSlotIndex)", 2);
end
local item = CreateFromMixins(ItemMixin);
item:SetItemLocation(ItemLocation:CreateFromEquipmentSlot(equipmentSlotIndex));
return item;
end
--[[static]] function Item:CreateFromItemLink(itemLink)
if type(itemLink) ~= "string" then
error("Usage: Item:CreateFromItemLink(itemLinkString)", 2)
end
local item = CreateFromMixins(ItemMixin)
item:SetItemLink(itemLink)
return item
if type(itemLink) ~= "string" then
error("Usage: Item:CreateFromItemLink(itemLinkString)", 2);
end
local item = CreateFromMixins(ItemMixin);
item:SetItemLink(itemLink);
return item;
end
--[[static]] function Item:CreateFromItemID(itemID)
if type(itemID) ~= "number" then
error("Usage: Item:CreateFromItemID(itemID)", 2)
end
local item = CreateFromMixins(ItemMixin)
item:SetItemID(itemID)
return item
if type(itemID) ~= "number" then
error("Usage: Item:CreateFromItemID(itemID)", 2);
end
local item = CreateFromMixins(ItemMixin);
item:SetItemID(itemID);
return item;
end
--[[static]] function Item:CreateFromItemGUID(itemGUID)
@@ -103,18 +88,18 @@ function ItemMixin:Matches(item)
end
function ItemMixin:SetItemLocation(itemLocation)
self:Clear()
self.itemLocation = itemLocation
self:Clear();
self.itemLocation = itemLocation;
end
function ItemMixin:SetItemLink(itemLink)
self:Clear()
self.itemLink = itemLink
self:Clear();
self.itemLink = itemLink;
end
function ItemMixin:SetItemID(itemID)
self:Clear()
self.itemID = itemID
self:Clear();
self.itemID = itemID;
end
function ItemMixin:SetItemGUID(itemGUID)
@@ -133,129 +118,149 @@ function ItemMixin:GetItemLocation()
return nil;
end
function ItemMixin:GetItemGUID()
if self.itemGUID then
return self.itemGUID;
end
if self.itemLocation then
return C_Item.GetItemGUID(self.itemLocation);
end
return nil;
end
function ItemMixin:HasItemLocation()
return self.itemLocation ~= nil
return self.itemLocation ~= nil;
end
function ItemMixin:Clear()
self.itemLocation = nil
self.itemLink = nil
self.itemID = nil
self.itemGUID = nil
self.itemLocation = nil;
self.itemLink = nil;
self.itemID = nil;
self.itemGUID = nil;
end
function ItemMixin:IsItemEmpty()
if self:GetStaticBackingItem() then
return not C_Item.DoesItemExistByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return not C_Item.DoesItemExistByID(self:GetStaticBackingItem());
end
return not self:IsItemInPlayersControl()
return not self:IsItemInPlayersControl();
end
function ItemMixin:GetStaticBackingItem()
return self.itemLink or self.itemID
return self.itemLink or self.itemID;
end
function ItemMixin:IsItemInPlayersControl()
local itemLocation = self:GetItemLocation()
return itemLocation and C_Item.DoesItemExist(itemLocation)
local itemLocation = self:GetItemLocation();
return itemLocation and C_Item.DoesItemExist(itemLocation);
end
-- Item API
function ItemMixin:GetItemID()
if self:GetStaticBackingItem() then
return (C_Item.GetItemInfoInstant(self:GetStaticBackingItem()))
end
local backingItem = self:GetStaticBackingItem();
if backingItem then
return C_Item.GetItemInfoInstant(backingItem) or C_Item.GetItemIDForItemInfo(backingItem);
end
if not self:IsItemEmpty() then
return C_Item.GetItemID(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetItemID(self:GetItemLocation());
end
return nil;
end
function ItemMixin:IsItemLocked()
return self:IsItemInPlayersControl() and C_Item.IsLocked(self:GetItemLocation())
return self:IsItemInPlayersControl() and C_Item.IsLocked(self:GetItemLocation());
end
function ItemMixin:LockItem()
if self:IsItemInPlayersControl() then
C_Item.LockItem(self:GetItemLocation())
end
if self:IsItemInPlayersControl() then
C_Item.LockItem(self:GetItemLocation());
end
end
function ItemMixin:UnlockItem()
if self:IsItemInPlayersControl() then
C_Item.UnlockItem(self:GetItemLocation())
end
if self:IsItemInPlayersControl() then
C_Item.UnlockItem(self:GetItemLocation());
end
end
function ItemMixin:GetItemIcon() -- requires item data to be loaded
if self:GetStaticBackingItem() then
return C_Item.GetItemIconByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return C_Item.GetItemIconByID(self:GetStaticBackingItem());
end
if not self:IsItemEmpty() then
return C_Item.GetItemIcon(self:GetItemLocation())
end
if not self:IsItemEmpty() then
return C_Item.GetItemIcon(self:GetItemLocation());
end
end
function ItemMixin:GetItemName() -- requires item data to be loaded
if self:GetStaticBackingItem() then
return C_Item.GetItemNameByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return C_Item.GetItemNameByID(self:GetStaticBackingItem());
end
if not self:IsItemEmpty() then
return C_Item.GetItemName(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetItemName(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetItemLink() -- requires item data to be loaded
if self.itemLink then
return self.itemLink
end
if self.itemLink then
return self.itemLink;
end
if self.itemID then
return (select(2, C_Item.GetItemInfo(self.itemID)));
end
if self.itemID then
return (select(2, C_Item.GetItemInfo(self.itemID)));
end
if not self:IsItemEmpty() then
return C_Item.GetItemLink(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetItemLink(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetItemQuality() -- requires item data to be loaded
if self:GetStaticBackingItem() then
return C_Item.GetItemQualityByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return C_Item.GetItemQualityByID(self:GetStaticBackingItem());
end
if not self:IsItemEmpty() then
return C_Item.GetItemQuality(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetItemQuality(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetStackCount() -- requires item data to be loaded
if not self:IsItemEmpty() then
return C_Item.GetStackCount(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetCurrentItemLevel() -- requires item data to be loaded
if self:GetStaticBackingItem() then
return (C_Item.GetDetailedItemLevelInfo(self:GetStaticBackingItem()))
end
if self:GetStaticBackingItem() then
return (C_Item.GetDetailedItemLevelInfo(self:GetStaticBackingItem()));
end
if not self:IsItemEmpty() then
return C_Item.GetCurrentItemLevel(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetCurrentItemLevel(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetItemQualityColor() -- requires item data to be loaded
local itemQuality = self:GetItemQuality()
return ITEM_QUALITY_COLORS[itemQuality] -- may be nil if item data isn't loaded
local itemQuality = self:GetItemQuality();
return ITEM_QUALITY_COLORS[itemQuality]; -- may be nil if item data isn't loaded
end
function ItemMixin:GetItemQualityColorRGB() -- requires item data to be loaded
local colorTbl = self:GetItemQualityColor()
return colorTbl.color:GetRGB()
local colorTbl = self:GetItemQualityColor();
return colorTbl.color:GetRGB();
end
function ItemMixin:GetItemMaxStackSize() -- requires item data to be loaded
@@ -275,141 +280,75 @@ function ItemMixin:IsStackable() -- requires item data to be loaded
end
function ItemMixin:GetInventoryType()
if self:GetStaticBackingItem() then
return C_Item.GetItemInventoryTypeByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return C_Item.GetItemInventoryTypeByID(self:GetStaticBackingItem());
end
if not self:IsItemEmpty() then
return C_Item.GetItemInventoryType(self:GetItemLocation())
end
return nil
end
function ItemMixin:GetItemGUID()
if self:GetStaticBackingItem() then
return nil
end
if not self:IsItemEmpty() then
return C_Item.GetItemGUID(self:GetItemLocation())
end
return nil
if not self:IsItemEmpty() then
return C_Item.GetItemInventoryType(self:GetItemLocation());
end
return nil;
end
function ItemMixin:GetInventoryTypeName()
if not self:IsItemEmpty() then
return select(4, C_Item.GetItemInfoInstant(self:GetItemID()));
end
if not self:IsItemEmpty() then
return select(4, C_Item.GetItemInfoInstant(self:GetItemID()));
end
end
function ItemMixin:IsItemDataCached()
if self:GetStaticBackingItem() then
return C_Item.IsItemDataCachedByID(self:GetStaticBackingItem())
end
if self:GetStaticBackingItem() then
return C_Item.IsItemDataCachedByID(self:GetStaticBackingItem());
end
if not self:IsItemEmpty() then
return C_Item.IsItemDataCached(self:GetItemLocation())
end
return true
if not self:IsItemEmpty() then
return C_Item.IsItemDataCached(self:GetItemLocation());
end
return true;
end
function ItemMixin:IsDataEvictable()
return true
-- Item data could be evicted from the cache
return true;
end
-- Default error handler routing: stock 1.12 has no CallErrorHandler.
local function ItemUtilErrorHandler(err)
local h = geterrorhandler and geterrorhandler()
if h then return h(err) end
function ItemMixin:ValidateForContinueOnItemLoad(methodName, callbackFunction)
if type(callbackFunction) ~= "function" then
error(("Usage: NonEmptyItem:%s(callbackFunction): invalid callbackFunction"):format(methodName), 3);
end
if self:IsItemEmpty() then
if self.itemLink then
error(("Usage: NonEmptyItem:%s(callbackFunction) invalid itemLink: <%s>"):format(methodName, self.itemLink), 3);
elseif self.itemID then
error(("Usage: NonEmptyItem:%s(callbackFunction) invalid itemID: <%d>"):format(methodName, self.itemID), 3);
end
error(("Usage: NonEmptyItem:%s(callbackFunction): invalid item"):format(methodName), 3);
else
if not self:GetItemID() then
error(("Usage: NonEmptyItem:%s(callbackFunction): item appeared non-empty, but had invalid itemID. itemID: <%s>, itemLink: <%s>"):format(methodName, tostring(self.itemID), tostring(self.itemLink)), 3);
end
end
end
-- Add a callback to be executed when item data is loaded, if the item data is already loaded then execute it immediately
function ItemMixin:ContinueOnItemLoad(callbackFunction)
if type(callbackFunction) ~= "function" or self:IsItemEmpty() then
error("Usage: NonEmptyItem:ContinueOnLoad(callbackFunction)", 2)
end
ItemEventListener:AddCallback(self:GetItemID(), callbackFunction)
self:ValidateForContinueOnItemLoad("ContinueOnItemLoad", callbackFunction);
ItemEventListener:AddCallback(self:GetItemID(), callbackFunction);
end
-- Same as ContinueOnItemLoad, except it returns a function that when called will cancel the continue
function ItemMixin:ContinueWithCancelOnItemLoad(callbackFunction)
if type(callbackFunction) ~= "function" or self:IsItemEmpty() then
error("Usage: NonEmptyItem:ContinueWithCancelOnItemLoad(callbackFunction)", 2)
end
return ItemEventListener:AddCancelableCallback(self:GetItemID(), callbackFunction)
self:ValidateForContinueOnItemLoad("ContinueWithCancelOnItemLoad", callbackFunction);
return ItemEventListener:AddCancelableCallback(self:GetItemID(), callbackFunction);
end
--[ Item Event Listener ]
ItemEventListener = CreateFrame("Frame")
ItemEventListener.callbacks = {}
-- 1.12 OnEvent receives no function args; event name and payload live in
-- the `event`, `arg1`, ... globals.
ItemEventListener:SetScript("OnEvent", function()
if event == "ITEM_DATA_LOAD_RESULT" then
local itemID, success = arg1, arg2
if success then
ItemEventListener:FireCallbacks(itemID)
else
ItemEventListener:ClearCallbacks(itemID)
end
end
end)
ItemEventListener:RegisterEvent("ITEM_DATA_LOAD_RESULT")
local CANCELED_SENTINEL = -1
function ItemEventListener:AddCallback(itemID, callbackFunction)
local callbacks = self:GetOrCreateCallbacks(itemID)
table.insert(callbacks, callbackFunction)
C_Item.RequestLoadItemDataByID(itemID)
-- Generic aliases for use with ContinuableContainer
function ItemMixin:ContinueWithCancelOnRecordLoad(callbackFunction)
return self:ContinueWithCancelOnItemLoad(callbackFunction);
end
function ItemEventListener:AddCancelableCallback(itemID, callbackFunction)
local callbacks = self:GetOrCreateCallbacks(itemID)
table.insert(callbacks, callbackFunction)
C_Item.RequestLoadItemDataByID(itemID)
local index = table.getn(callbacks)
return function()
if table.getn(callbacks) > 0 and callbacks[index] ~= CANCELED_SENTINEL then
callbacks[index] = CANCELED_SENTINEL
return true
end
return false
end
end
function ItemEventListener:FireCallbacks(itemID)
local callbacks = self:GetCallbacks(itemID)
if callbacks then
self:ClearCallbacks(itemID)
for i, callback in ipairs(callbacks) do
if callback ~= CANCELED_SENTINEL then
xpcall(callback, ItemUtilErrorHandler)
end
end
for i = table.getn(callbacks), 1, -1 do
callbacks[i] = nil
end
end
end
function ItemEventListener:ClearCallbacks(itemID)
self.callbacks[itemID] = nil
end
function ItemEventListener:GetCallbacks(itemID)
return self.callbacks[itemID]
end
function ItemEventListener:GetOrCreateCallbacks(itemID)
local callbacks = self.callbacks[itemID]
if not callbacks then
callbacks = {}
self.callbacks[itemID] = callbacks
end
return callbacks
function ItemMixin:IsRecordDataCached()
return self:IsItemDataCached();
end
@@ -182,8 +182,14 @@ local function ResolveSubclasses(list, onLoaded)
if subType and subType ~= "" then
r.subclass = subType;
elseif onLoaded then
-- Not cached — re-derive when the item loads.
Item:CreateFromItemID(r.createdItem):ContinueOnItemLoad(onLoaded);
-- Not cached — re-derive when the item loads. The callback
-- also runs when the load fails; re-deriving then would ask
-- for the item again.
Item:CreateFromItemID(r.createdItem):ContinueOnItemLoad(function(_, success)
if success then
onLoaded();
end
end);
end
end
if r.subclass and not present[r.subclass] then
@@ -526,8 +532,8 @@ local function CreateFrame_TradeSkillLink()
local function itemInfo(id)
local item = Item:CreateFromItemID(id);
if not item:IsItemDataCached() then
item:ContinueOnItemLoad(function()
if self:IsShown() and self.selectedSpellID == entry.spellID then
item:ContinueOnItemLoad(function(_, success)
if success and self:IsShown() and self.selectedSpellID == entry.spellID then
self:RenderDetail(entry);
end
end);
+24
View File
@@ -120,6 +120,28 @@ static int __fastcall Script_GetItemInfoInstant(void *L) {
return 7;
}
// `C_Item.GetItemIDForItemInfo(itemInfo)` — the itemID alone, from the same
// resolver `GetItemInfoInstant` echoes: an itemID, an `"item:N"` string or an
// item link. ItemMixin:GetItemID falls back to it when GetItemInfoInstant
// returns nothing. Returns nothing for input that names no item.
static const Game::Doc::Field kGetItemIDForItemInfoArgs[] = {
Game::Doc::Req("itemInfo", "ItemInfo"),
};
static const Game::Doc::Field kGetItemIDForItemInfoRets[] = {
Game::Doc::Req("itemID", "number"),
};
static const Game::Doc::Function kGetItemIDForItemInfo{
"Returns the item ID of an item ID, item string or item link.", kGetItemIDForItemInfoArgs,
kGetItemIDForItemInfoRets};
static int __fastcall Script_GetItemIDForItemInfo(void *L) {
const int itemID = Item::Arg::ResolveItemID(L, 1);
if (itemID <= 0)
return 0;
Game::Lua::PushNumber(L, static_cast<double>(itemID));
return 1;
}
// Pushes the icon path for `itemID` and returns 1, or returns 0 (= nil
// to Lua) if the item isn't cached, the displayInfoID is missing, or
// the icon record's path slot is empty. Shared by all three
@@ -315,6 +337,8 @@ static int __fastcall Script_C_Item_GetItemInfo(void *L) {
static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_Item", "GetItemInfo", &Script_C_Item_GetItemInfo);
Game::Lua::RegisterTableFunction("C_Item", "GetItemInfoInstant", &Script_GetItemInfoInstant);
Game::Lua::RegisterTableFunction("C_Item", "GetItemIDForItemInfo",
&Script_GetItemIDForItemInfo, &kGetItemIDForItemInfo);
Game::Lua::RegisterGlobalFunction("GetItemIcon", &Script_GetItemIcon);
Game::Lua::RegisterTableFunction("C_Item", "GetItemIcon", &Script_C_Item_GetItemIcon);
Game::Lua::RegisterTableFunction("C_Item", "GetItemIconByID", &Script_C_Item_GetItemIconByID);