From b3a74b773ffb961bf09232b5ae4028cadf57c54a Mon Sep 17 00:00:00 2001 From: Anonomit Date: Sat, 19 Mar 2022 20:35:32 -0400 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .pkgmeta | 9 + CHANGELOG.html | 3 + Config.lua | 176 ++ ItemCache.lua | 1585 +++++++++++++ ItemCache.toc | 21 + ItemCache.xml | 4 + ItemCache_Mainline.toc | 21 + ItemCache_TBC.toc | 21 + ItemCache_Vanilla.toc | 21 + Locale/enUs.lua | 10 + embeds.xml | 26 + libs/AceAddon-3.0/AceAddon-3.0.lua | 653 ++++++ libs/AceAddon-3.0/AceAddon-3.0.xml | 4 + libs/AceComm-3.0/AceComm-3.0.lua | 305 +++ libs/AceComm-3.0/AceComm-3.0.xml | 5 + libs/AceComm-3.0/ChatThrottleLib.lua | 534 +++++ libs/AceConfig-3.0/AceConfig-3.0.lua | 58 + libs/AceConfig-3.0/AceConfig-3.0.xml | 8 + .../AceConfigCmd-3.0/AceConfigCmd-3.0.lua | 794 +++++++ .../AceConfigCmd-3.0/AceConfigCmd-3.0.xml | 4 + .../AceConfigDialog-3.0.lua | 2019 +++++++++++++++++ .../AceConfigDialog-3.0.xml | 4 + .../AceConfigRegistry-3.0.lua | 371 +++ .../AceConfigRegistry-3.0.xml | 4 + libs/AceConsole-3.0/AceConsole-3.0.lua | 250 ++ libs/AceConsole-3.0/AceConsole-3.0.xml | 4 + libs/AceDB-3.0/AceDB-3.0.lua | 744 ++++++ libs/AceDB-3.0/AceDB-3.0.xml | 4 + libs/AceDBOptions-3.0/AceDBOptions-3.0.lua | 460 ++++ libs/AceDBOptions-3.0/AceDBOptions-3.0.xml | 4 + libs/AceEvent-3.0/AceEvent-3.0.lua | 126 + libs/AceEvent-3.0/AceEvent-3.0.xml | 4 + libs/AceGUI-3.0/AceGUI-3.0.lua | 1026 +++++++++ libs/AceGUI-3.0/AceGUI-3.0.xml | 28 + .../AceGUIContainer-BlizOptionsGroup.lua | 138 ++ .../widgets/AceGUIContainer-DropDownGroup.lua | 157 ++ .../widgets/AceGUIContainer-Frame.lua | 318 +++ .../widgets/AceGUIContainer-InlineGroup.lua | 103 + .../widgets/AceGUIContainer-ScrollFrame.lua | 215 ++ .../widgets/AceGUIContainer-SimpleGroup.lua | 69 + .../widgets/AceGUIContainer-TabGroup.lua | 349 +++ .../widgets/AceGUIContainer-TreeGroup.lua | 715 ++++++ .../widgets/AceGUIContainer-Window.lua | 336 +++ .../widgets/AceGUIWidget-Button.lua | 103 + .../widgets/AceGUIWidget-CheckBox.lua | 296 +++ .../widgets/AceGUIWidget-ColorPicker.lua | 190 ++ .../widgets/AceGUIWidget-DropDown-Items.lua | 471 ++++ .../widgets/AceGUIWidget-DropDown.lua | 737 ++++++ .../widgets/AceGUIWidget-EditBox.lua | 263 +++ .../widgets/AceGUIWidget-Heading.lua | 78 + libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua | 140 ++ .../widgets/AceGUIWidget-InteractiveLabel.lua | 94 + .../widgets/AceGUIWidget-Keybinding.lua | 249 ++ .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua | 179 ++ .../widgets/AceGUIWidget-MultiLineEditBox.lua | 366 +++ .../widgets/AceGUIWidget-Slider.lua | 284 +++ libs/AceHook-3.0/AceHook-3.0.lua | 511 +++++ libs/AceHook-3.0/AceHook-3.0.xml | 4 + libs/AceLocale-3.0/AceLocale-3.0.lua | 137 ++ libs/AceLocale-3.0/AceLocale-3.0.xml | 4 + libs/AceSerializer-3.0/AceSerializer-3.0.lua | 287 +++ libs/AceSerializer-3.0/AceSerializer-3.0.xml | 4 + libs/AceTimer-3.0/AceTimer-3.0.lua | 278 +++ libs/AceTimer-3.0/AceTimer-3.0.xml | 4 + .../CallbackHandler-1.0.lua | 212 ++ .../CallbackHandler-1.0.xml | 4 + libs/LibStub/LibStub.lua | 30 + libs/SemVer/MIT-LICENSE.txt | 20 + libs/SemVer/SemVer.lua | 190 ++ libs/SemVer/SemVer.xml | 4 + locales.xml | 5 + 72 files changed, 16856 insertions(+) create mode 100644 .gitattributes create mode 100644 .pkgmeta create mode 100644 CHANGELOG.html create mode 100644 Config.lua create mode 100644 ItemCache.lua create mode 100644 ItemCache.toc create mode 100644 ItemCache.xml create mode 100644 ItemCache_Mainline.toc create mode 100644 ItemCache_TBC.toc create mode 100644 ItemCache_Vanilla.toc create mode 100644 Locale/enUs.lua create mode 100644 embeds.xml create mode 100644 libs/AceAddon-3.0/AceAddon-3.0.lua create mode 100644 libs/AceAddon-3.0/AceAddon-3.0.xml create mode 100644 libs/AceComm-3.0/AceComm-3.0.lua create mode 100644 libs/AceComm-3.0/AceComm-3.0.xml create mode 100644 libs/AceComm-3.0/ChatThrottleLib.lua create mode 100644 libs/AceConfig-3.0/AceConfig-3.0.lua create mode 100644 libs/AceConfig-3.0/AceConfig-3.0.xml create mode 100644 libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua create mode 100644 libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml create mode 100644 libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua create mode 100644 libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml create mode 100644 libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua create mode 100644 libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml create mode 100644 libs/AceConsole-3.0/AceConsole-3.0.lua create mode 100644 libs/AceConsole-3.0/AceConsole-3.0.xml create mode 100644 libs/AceDB-3.0/AceDB-3.0.lua create mode 100644 libs/AceDB-3.0/AceDB-3.0.xml create mode 100644 libs/AceDBOptions-3.0/AceDBOptions-3.0.lua create mode 100644 libs/AceDBOptions-3.0/AceDBOptions-3.0.xml create mode 100644 libs/AceEvent-3.0/AceEvent-3.0.lua create mode 100644 libs/AceEvent-3.0/AceEvent-3.0.xml create mode 100644 libs/AceGUI-3.0/AceGUI-3.0.lua create mode 100644 libs/AceGUI-3.0/AceGUI-3.0.xml create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Heading.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-MultiLineEditBox.lua create mode 100644 libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua create mode 100644 libs/AceHook-3.0/AceHook-3.0.lua create mode 100644 libs/AceHook-3.0/AceHook-3.0.xml create mode 100644 libs/AceLocale-3.0/AceLocale-3.0.lua create mode 100644 libs/AceLocale-3.0/AceLocale-3.0.xml create mode 100644 libs/AceSerializer-3.0/AceSerializer-3.0.lua create mode 100644 libs/AceSerializer-3.0/AceSerializer-3.0.xml create mode 100644 libs/AceTimer-3.0/AceTimer-3.0.lua create mode 100644 libs/AceTimer-3.0/AceTimer-3.0.xml create mode 100644 libs/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 libs/CallbackHandler-1.0/CallbackHandler-1.0.xml create mode 100644 libs/LibStub/LibStub.lua create mode 100644 libs/SemVer/MIT-LICENSE.txt create mode 100644 libs/SemVer/SemVer.lua create mode 100644 libs/SemVer/SemVer.xml create mode 100644 locales.xml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.pkgmeta b/.pkgmeta new file mode 100644 index 0000000..1416f6f --- /dev/null +++ b/.pkgmeta @@ -0,0 +1,9 @@ +package-as: ItemCache +enable-nolib-creation: no + +ignore: + - README.md + +manual-changelog: + filename: CHANGELOG.html + markup-type: html diff --git a/CHANGELOG.html b/CHANGELOG.html new file mode 100644 index 0000000..b718dc2 --- /dev/null +++ b/CHANGELOG.html @@ -0,0 +1,3 @@ +

1.0.0

+

Initial Release

+

 

\ No newline at end of file diff --git a/Config.lua b/Config.lua new file mode 100644 index 0000000..134e3a2 --- /dev/null +++ b/Config.lua @@ -0,0 +1,176 @@ + +local ADDON_NAME, Data = ... + + +local buildMajor = tonumber(GetBuildInfo():match"^(%d+)%.") +if buildMajor == 2 then + Data.WOW_VERSION = "BCC" +elseif buildMajor == 1 then + Data.WOW_VERSION = "Classic" +end + +function Data:IsBCC() + return Data.WOW_VERSION == "BCC" +end +function Data:IsClassic() + return Data.WOW_VERSION == "Classic" +end + + +Data.CHAT_COMMAND = ADDON_NAME:lower() + +-- How spread out options are in interface options +local OPTIONS_DIVIDER_HEIGHT = 3 + + + +function Data:MakeDefaultOptions() + return { + profile = { + + Debug = { + enabled = true, + menu = false, + }, + }, + global = { + + UsePersistentStorage = true, + + }, + } +end + + + +local function GetOptionTableHelpers(Options, Addon) + local defaultInc = 1000 + local order = 1000 + + + local function GetOption(key1, ...) + if key1 == "global" then + return Addon:GetGlobalOption(...) + else + return Addon:GetOption(key1, ...) + end + end + local function SetOption(val, key1, ...) + if key1 == "global" then + return Addon:SetGlobalOption(val, ...) + else + return Addon:SetOption(val, key1, ...) + end + end + + local GUI = {} + + function GUI:GetOrder() + return order + end + function GUI:SetOrder(newOrder) + order = newOrder + end + function GUI:Order(inc) + self:SetOrder(self:GetOrder() + (inc or defaultInc)) + return self:GetOrder() + end + + function GUI:CreateEntry(key, name, desc, widgetType, order) + key = widgetType .. "_" .. (key or "") + Options.args[key] = {name = name, desc = desc, type = widgetType, order = order or self:Order()} + return Options.args[key] + end + + function GUI:CreateHeader(name) + local option = self:CreateEntry(self:Order(), name, nil, "header", self:Order(0)) + return option + end + + function GUI:CreateDescription(desc, fontSize) + local option = self:CreateEntry(self:Order(), desc, nil, "description", self:Order(0)) + option.fontSize = fontSize or "large" + return option + end + function GUI:CreateDivider(count) + for i = 1, count or 3 do + self:CreateDescription("", "small") + end + end + function GUI:CreateNewline() + return self:CreateDivider(1) + end + + function GUI:CreateToggle(keys, name, desc, disabled) + if type(keys) ~= "table" then keys = {keys} end + local option = self:CreateEntry(table.concat(keys, "."), name, desc, "toggle") + option.disabled = disabled + option.set = function(info, val) SetOption(val, unpack(keys)) end + option.get = function(info) return GetOption(unpack(keys)) end + return option + end + function GUI:CreateRange(keys, name, desc, min, max, step, disabled) + if type(keys) ~= "table" then keys = {keys} end + local option = self:CreateEntry(table.concat(keys, "."), name, desc, "range") + option.disabled = disabled + option.min = min + option.max = max + option.step = step + option.set = function(info, val) SetOption(val, unpack(keys)) end + option.get = function(info) return GetOption(unpack(keys)) end + return option + end + function GUI:CreateInput(keys, name, desc, multiline, disabled) + if type(keys) ~= "table" then keys = {keys} end + local option = self:CreateEntry(table.concat(keys, "."), name, desc, "input") + option.multiline = multiline + option.disabled = disabled + option.set = function(info, val) SetOption(val, unpack(keys)) end + option.get = function(info) return GetOption(unpack(keys)) end + return option + end + function GUI:CreateExecute(key, name, desc, func) + local option = self:CreateEntry(key, name, desc, "execute") + option.func = func + return option + end + + return GUI +end + + +function Data:RefreshOptionsTable(title, Addon, L) + Addon.Options[title] = Addon.Options[title] or {} + Options = Addon.Options[title] + wipe(Options) + Options.name = title + Options.type = "group" + Options.args = {} + + local GUI = GetOptionTableHelpers(Options, Addon) + + GUI:CreateNewline() + GUI:CreateToggle({"global", "UsePersistentStorage"}, L["Use Persistent Storage"], L["If enabled, cache will be stored on logout. This may slightly increase loading time.|n|nIf disabled, cache will be rebuilt during each session. This will result in dramatically more cache misses."]) + + + return Options +end + + + + +function Data:MakeDebugOptionsTable(title, Addon, L) + local Options = { + name = title, + type = "group", + args = {} + } + local GUI = GetOptionTableHelpers(Options, Addon) + + GUI:CreateToggle({"Debug", "enabled"}, "Enabled") + + return Options +end + + + diff --git a/ItemCache.lua b/ItemCache.lua new file mode 100644 index 0000000..fb0013c --- /dev/null +++ b/ItemCache.lua @@ -0,0 +1,1585 @@ + + +local ADDON_NAME = "ItemCache" +local HOST_ADDON_NAME, Data = ... +local IsStandalone = ADDON_NAME == HOST_ADDON_NAME + +local MAJOR, MINOR = ADDON_NAME, 0 +local ItemCache, oldMinor = LibStub:NewLibrary(MAJOR, MINOR) +if not ItemCache and not IsStandalone then + return +end + +local Addon = {} +local L +local AceConfig +local AceConfigDialog +local AceConfigRegistry +local AceDB +local AceDBOptions +local SemVer + +if IsStandalone then + Addon = LibStub("AceAddon-3.0"):NewAddon(ADDON_NAME, "AceConsole-3.0") or {} + ItemCacheAddon = Addon + L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) + + AceConfig = LibStub"AceConfig-3.0" + AceConfigDialog = LibStub"AceConfigDialog-3.0" + AceConfigRegistry = LibStub"AceConfigRegistry-3.0" + AceDB = LibStub"AceDB-3.0" + AceDBOptions = LibStub"AceDBOptions-3.0" + + SemVer = LibStub"SemVer" +end + + + + + +--[[ + +ItemCache:Item | ItemCache:Get + ItemCache:Item(id[, suffix]) + ItemCache:Item("item:12345") + ItemCache:Item(item) + +ItemCache:DoesItemExistByID + ItemCache:DoesItemExistByID(id) + +ItemCache:FormatSearchText + ItemCache:FormatSearchText(text) + +ItemCache:Cache + ItemCache:Cache(itemsList) + +ItemCache:OnCache + ItemCache:OnCache(itemsList, func, ...) + func(itemsList, ...) + +ItemCache:GetItemInfo + ItemCache:GetItemInfo(id) + ItemCache:GetItemInfo("item:12345") + ItemCache:GetItemInfo(item) + +ItemCache:Filter + ItemCache:Filter(func) + func(item, id, suffix) + +--]] + + + +local assert = assert +local type = type +local next = next +local ipairs = ipairs +local pairs = pairs +local getmetatable = getmetatable +local setmetatable = setmetatable +local tonumber = tonumber + +local strsplit = strsplit +local wipe = wipe +local GetMouseFocus = GetMouseFocus +local DoesItemExistByID = C_Item.DoesItemExistByID +local GetItemInfo = GetItemInfo +local UnitExists = UnitExists +local UnitClass = UnitClass + +local strmatch = string.match +local strfind = string.find +local strgmatch = string.gmatch +local strgsub = string.gsub +local strformat = string.format +local tblinsert = table.insert +local tblremove = table.remove +local floor = math.floor + + + +function Addon:GetDB() + return self.db +end +function Addon:GetDefaultDB() + return self.dbDefault +end +function Addon:GetProfile() + return self:GetDB().profile +end +function Addon:GetDefaultProfile() + return self:GetDefaultDB().profile +end +function Addon:GetGlobal() + return self:GetDB().global +end +function Addon:GetDefaultGlobal() + return self:GetDefaultDB().global +end +local function GetOption(self, db, ...) + local val = db + for _, key in ipairs{...} do + val = val[key] + end + return val +end +function Addon:GetOption(...) + return GetOption(self, self:GetProfile(), ...) +end +function Addon:GetDefaultOption(...) + return GetOption(self, self:GetDefaultProfile(), ...) +end +function Addon:GetGlobalOption(...) + return GetOption(self, self:GetGlobal(), ...) +end +function Addon:GetDefaultGlobalOption(...) + return GetOption(self, self:GetDefaultGlobal(), ...) +end +local function SetOption(self, db, val, ...) + local keys = {...} + local lastKey = tblremove(keys, #keys) + local tbl = db + for _, key in ipairs(keys) do + tbl = tbl[key] + end + tbl[lastKey] = val +end +function Addon:SetOption(val, ...) + return SetOption(self, self:GetProfile(), val, ...) +end +function Addon:ResetOption(...) + return self:SetOption(val, self:GetDefaultOption(...)) +end +function Addon:SetGlobalOption(val, ...) + return SetOption(self, self:GetGlobal(), val, ...) +end +function Addon:ResetGlobalOption(...) + return self:SetOption(val, self:GetDefaultGlobalOption(...)) +end + + +local UNUSABLE_EQUIPMENT = {} + +local CLASS_MAP_TO_ID = {} +for i = 1, GetNumClasses() do + local name, file, id = GetClassInfo(i) + if name then + local maleName, femaleName = LOCALIZED_CLASS_NAMES_MALE[file], LOCALIZED_CLASS_NAMES_FEMALE[file] + for _, v in ipairs{name, file, id, maleName, femaleName} do + CLASS_MAP_TO_ID[v] = id + end + end +end + +do + -- these are constants, not normal translations + -- they are not open to interpretation, and are required whether the library is run standalone or embedded + -- this is why they are not in a separate locale file + local localeSubTypes = { + ["ptBR"] = { + ["Totems"] = "Totens", + ["Librams"] = "Incunábulos", + ["Thrown"] = "Bestas", + ["Idols"] = "Ídolos", + ["Crossbows"] = "Arremesso", + ["Plate"] = "Placas", + ["One-Handed Maces"] = "Maças de Uma Mão", + ["Polearms"] = "Armas de Haste", + ["One-Handed Axes"] = "Machados de Uma Mão", + ["Shields"] = "Escudos", + ["Daggers"] = "Adagas", + ["Mail"] = "Malha", + ["Bows"] = "Arcos", + ["Two-Handed Swords"] = "Espadas de Duas Mãos", + ["Staves"] = "Báculos", + ["Leather"] = "Couro", + ["One-Handed Swords"] = "Espadas de Uma Mão", + ["Guns"] = "Armas de Fogo", + ["Fist Weapons"] = "Armas de punho", + ["Cloth"] = "Tecido", + ["Wands"] = "Varinhas", + ["Two-Handed Maces"] = "Maças de Duas Mãos", + ["Two-Handed Axes"] = "Machados de Duas Mãos", + }, + ["ruRU"] = { + ["Totems"] = "Тотемы", + ["Librams"] = "Манускрипты", + ["Thrown"] = "Арбалеты", + ["Idols"] = "Идолы", + ["Crossbows"] = "Метательное оружие", + ["Plate"] = "Латы", + ["One-Handed Maces"] = "Одноручное ударное оружие", + ["Polearms"] = "Древковое оружие", + ["One-Handed Axes"] = "Одноручные топоры", + ["Shields"] = "Щиты", + ["Daggers"] = "Кинжалы", + ["Mail"] = "Кольчуга", + ["Bows"] = "Луки", + ["Two-Handed Swords"] = "Двуручные мечи", + ["Staves"] = "Посохи", + ["Leather"] = "Кожа", + ["One-Handed Swords"] = "Одноручные мечи", + ["Guns"] = "Ружья", + ["Fist Weapons"] = "Кистевое оружие", + ["Cloth"] = "Ткань", + ["Wands"] = "Жезлы", + ["Two-Handed Maces"] = "Двуручное ударное оружие", + ["Two-Handed Axes"] = "Двуручные топоры", + }, + ["frFR"] = { + ["Totems"] = "Totems", + ["Librams"] = "Librams", + ["Thrown"] = "Arbalètes", + ["Idols"] = "Idoles", + ["Crossbows"] = "Armes de jet", + ["Plate"] = "Plaques", + ["One-Handed Maces"] = "Masses à une main", + ["Polearms"] = "Armes d'hast", + ["Two-Handed Maces"] = "Masses à deux mains", + ["Shields"] = "Boucliers", + ["Bows"] = "Arcs", + ["Cloth"] = "Tissu", + ["Daggers"] = "Dagues", + ["Two-Handed Swords"] = "Epées à deux mains", + ["Staves"] = "Bâtons", + ["Leather"] = "Cuir", + ["One-Handed Swords"] = "Epées à une main", + ["Guns"] = "Fusils", + ["Fist Weapons"] = "Armes de pugilat", + ["Mail"] = "Mailles", + ["Wands"] = "Baguettes", + ["One-Handed Axes"] = "Haches à une main", + ["Two-Handed Axes"] = "Haches à deux mains", + }, + ["koKR"] = { + ["Totems"] = "토템", + ["Librams"] = "성서", + ["Thrown"] = "석궁류", + ["Idols"] = "우상", + ["Crossbows"] = "투척 무기류", + ["Plate"] = "판금", + ["One-Handed Maces"] = "한손 둔기류", + ["Polearms"] = "장창류", + ["Two-Handed Maces"] = "양손 둔기류", + ["Shields"] = "방패", + ["Bows"] = "활류", + ["Cloth"] = "천", + ["Daggers"] = "단검류", + ["Two-Handed Swords"] = "양손 도검류", + ["Staves"] = "지팡이류", + ["Leather"] = "가죽", + ["One-Handed Swords"] = "한손 도검류", + ["Guns"] = "총기류", + ["Fist Weapons"] = "장착 무기류", + ["Mail"] = "사슬", + ["Wands"] = "마법봉류", + ["One-Handed Axes"] = "한손 도끼류", + ["Two-Handed Axes"] = "양손 도끼류", + }, + ["esMX"] = { + ["Totems"] = "Tótems", + ["Librams"] = "Tratados", + ["Thrown"] = "Ballestas", + ["Idols"] = "Ídolos", + ["Crossbows"] = "Armas arrojadizas", + ["Plate"] = "Placas", + ["One-Handed Maces"] = "Mazas de una mano", + ["Polearms"] = "Armas de asta", + ["Two-Handed Maces"] = "Mazas de dos manos", + ["Shields"] = "Escudos", + ["Bows"] = "Arcos", + ["Cloth"] = "Tela", + ["Daggers"] = "Dagas", + ["Two-Handed Swords"] = "Espadas de dos manos", + ["Staves"] = "Bastones", + ["Leather"] = "Cuero", + ["One-Handed Swords"] = "Espadas de una mano", + ["Guns"] = "Armas de fuego", + ["Fist Weapons"] = "Armas de puño", + ["Mail"] = "Malla", + ["Wands"] = "Varitas", + ["One-Handed Axes"] = "Hachas de una mano", + ["Two-Handed Axes"] = "Hachas de dos manos", + }, + ["enUS"] = { + ["Totems"] = "Totems", + ["Librams"] = "Librams", + ["Thrown"] = "Crossbows", + ["Idols"] = "Idols", + ["Crossbows"] = "Thrown", + ["Plate"] = "Plate", + ["One-Handed Maces"] = "One-Handed Maces", + ["Polearms"] = "Polearms", + ["Two-Handed Maces"] = "Two-Handed Maces", + ["Shields"] = "Shields", + ["Bows"] = "Bows", + ["Cloth"] = "Cloth", + ["Daggers"] = "Daggers", + ["Two-Handed Swords"] = "Two-Handed Swords", + ["Staves"] = "Staves", + ["Leather"] = "Leather", + ["One-Handed Swords"] = "One-Handed Swords", + ["Guns"] = "Guns", + ["Fist Weapons"] = "Fist Weapons", + ["Mail"] = "Mail", + ["Wands"] = "Wands", + ["One-Handed Axes"] = "One-Handed Axes", + ["Two-Handed Axes"] = "Two-Handed Axes", + }, + ["zhCN"] = { + ["Totems"] = "图腾", + ["Librams"] = "圣契", + ["Thrown"] = "弩", + ["Idols"] = "神像", + ["Crossbows"] = "投掷武器", + ["Plate"] = "板甲", + ["One-Handed Maces"] = "单手锤", + ["Polearms"] = "长柄武器", + ["Two-Handed Maces"] = "双手锤", + ["Shields"] = "盾牌", + ["One-Handed Axes"] = "单手斧", + ["Bows"] = "弓", + ["Daggers"] = "匕首", + ["Two-Handed Swords"] = "双手剑", + ["Staves"] = "法杖", + ["Leather"] = "皮甲", + ["One-Handed Swords"] = "单手剑", + ["Guns"] = "枪械", + ["Fist Weapons"] = "拳套", + ["Cloth"] = "布甲", + ["Wands"] = "魔杖", + ["Mail"] = "锁甲", + ["Two-Handed Axes"] = "双手斧", + }, + ["deDE"] = { + ["Totems"] = "Totems", + ["Librams"] = "Buchbände", + ["Thrown"] = "Armbrüste", + ["Idols"] = "Götzen", + ["Crossbows"] = "Wurfwaffen", + ["Plate"] = "Platte", + ["One-Handed Maces"] = "Einhandstreitkolben", + ["Polearms"] = "Stangenwaffen", + ["Two-Handed Maces"] = "Zweihandstreitkolben", + ["Shields"] = "Schilde", + ["Bows"] = "Bogen", + ["Cloth"] = "Stoff", + ["Daggers"] = "Dolche", + ["Two-Handed Swords"] = "Zweihandschwerter", + ["Staves"] = "Stäbe", + ["Leather"] = "Leder", + ["One-Handed Swords"] = "Einhandschwerter", + ["Guns"] = "Schusswaffen", + ["Fist Weapons"] = "Faustwaffen", + ["Mail"] = "Schwere Rüstung", + ["Wands"] = "Zauberstäbe", + ["One-Handed Axes"] = "Einhandäxte", + ["Two-Handed Axes"] = "Zweihandäxte", + }, + ["zhTW"] = { + ["Totems"] = "圖騰", + ["Librams"] = "聖契", + ["Thrown"] = "弩", + ["Idols"] = "塑像", + ["Crossbows"] = "投擲武器", + ["Plate"] = "鎧甲", + ["One-Handed Maces"] = "單手錘", + ["Polearms"] = "長柄武器", + ["Mail"] = "鎖甲", + ["Shields"] = "盾牌", + ["One-Handed Axes"] = "單手斧", + ["Daggers"] = "匕首", + ["Bows"] = "弓", + ["Two-Handed Swords"] = "雙手劍", + ["Staves"] = "法杖", + ["Leather"] = "皮甲", + ["One-Handed Swords"] = "單手劍", + ["Guns"] = "槍械", + ["Fist Weapons"] = "拳套", + ["Cloth"] = "布甲", + ["Wands"] = "魔杖", + ["Two-Handed Maces"] = "雙手錘", + ["Two-Handed Axes"] = "雙手斧", + }, + ["esES"] = { + ["Totems"] = "Tótems", + ["Librams"] = "Tratados", + ["Thrown"] = "Ballestas", + ["Idols"] = "Ídolos", + ["Crossbows"] = "Armas arrojadizas", + ["Plate"] = "Placas", + ["One-Handed Maces"] = "Mazas de una mano", + ["Polearms"] = "Armas de asta", + ["One-Handed Axes"] = "Hachas de una mano", + ["Shields"] = "Escudos", + ["Daggers"] = "Dagas", + ["Mail"] = "Malla", + ["Bows"] = "Arcos", + ["Two-Handed Swords"] = "Espadas de dos manos", + ["Staves"] = "Bastones", + ["Leather"] = "Cuero", + ["One-Handed Swords"] = "Espadas de una mano", + ["Guns"] = "Armas de fuego", + ["Fist Weapons"] = "Armas de puño", + ["Cloth"] = "Tela", + ["Wands"] = "Varitas", + ["Two-Handed Maces"] = "Mazas de dos manos", + ["Two-Handed Axes"] = "Hachas de dos manos", + }, + } + local translate = localeSubTypes[GetLocale()] + if not translate then translate = localeSubTypes["enUS"] end + + local localeArmorTypes = {MISCELLANEOUS} + for _, subType in ipairs{"Cloth", "Leather", "Mail", "Plate", "Shields", "Librams", "Idols", "Totems"} do + tblinsert(localeArmorTypes, translate[subType]) + end + + local usableArmor = {} + usableArmor[CLASS_MAP_TO_ID.WARRIOR] = {[translate["Leather"]] = true, [translate["Mail"]] = true, [translate["Plate"]] = true, [translate["Shields"]] = true} + usableArmor[CLASS_MAP_TO_ID.ROGUE] = {[translate["Leather"]] = true} + usableArmor[CLASS_MAP_TO_ID.MAGE] = {} + usableArmor[CLASS_MAP_TO_ID.PRIEST] = {} + usableArmor[CLASS_MAP_TO_ID.WARLOCK] = {} + usableArmor[CLASS_MAP_TO_ID.HUNTER] = {[translate["Leather"]] = true, [translate["Mail"]] = true} + usableArmor[CLASS_MAP_TO_ID.DRUID] = {[translate["Leather"]] = true, [translate["Idols"]] = true} + usableArmor[CLASS_MAP_TO_ID.SHAMAN] = {[translate["Leather"]] = true, [translate["Mail"]] = true, [translate["Shields"]] = true, [translate["Totems"]] = true} + usableArmor[CLASS_MAP_TO_ID.PALADIN] = {[translate["Leather"]] = true, [translate["Mail"]] = true, [translate["Plate"]] = true, [translate["Shields"]] = true, [translate["Librams"]] = true} + + for _, usableArmorTypes in pairs(usableArmor) do + usableArmorTypes[MISCELLANEOUS] = true + usableArmorTypes[translate["Cloth"]] = true + end + for class in pairs(usableArmor) do + UNUSABLE_EQUIPMENT[class] = { + [ARMOR] = {}, + [WEAPON] = {}, + } + for _, armorType in ipairs(localeArmorTypes) do + UNUSABLE_EQUIPMENT[class][ARMOR][armorType] = not usableArmor[class][armorType] + end + end + + local function SetClassWeapons(class, ...) + local class = CLASS_MAP_TO_ID[class] + for _, weapon in ipairs{...} do + UNUSABLE_EQUIPMENT[class][WEAPON][translate[weapon]] = nil + end + end + + local localeWeaponTypes = {} + for _, subType in ipairs{"Two-Handed Axes", "One-Handed Axes", "Two-Handed Swords", "One-Handed Swords", + "Two-Handed Maces", "One-Handed Maces", "Polearms", "Staves", "Daggers", + "Fist Weapons", "Bows", "Crossbows", "Guns", "Thrown", "Wands"} do + tblinsert(localeWeaponTypes, translate[subType]) + end + + for class in pairs(usableArmor) do + for _, weapon in ipairs(localeWeaponTypes) do + UNUSABLE_EQUIPMENT[class][WEAPON][weapon] = true + end + end + + SetClassWeapons("DRUID", "Two-Handed Maces", "One-Handed Maces", "Staves", "Daggers", "Fist Weapons") + SetClassWeapons("HUNTER", "Two-Handed Axes", "One-Handed Axes", "Two-Handed Swords", "One-Handed Swords", + "Polearms", "Staves", "Daggers", "Fist Weapons", "Bows", + "Crossbows", "Guns", "Thrown") + SetClassWeapons("MAGE", "One-Handed Swords", "Staves", "Daggers", "Wands") + SetClassWeapons("PALADIN", "Two-Handed Axes", "One-Handed Axes", "Two-Handed Swords", "One-Handed Swords", + "Two-Handed Maces", "One-Handed Maces", "Polearms") + SetClassWeapons("PRIEST", "One-Handed Maces", "Staves", "Daggers", "Wands") + SetClassWeapons("ROGUE", "One-Handed Swords", "One-Handed Maces", "Daggers", "Fist Weapons", + "Bows", "Crossbows", "Guns", "Thrown") + SetClassWeapons("SHAMAN", "Two-Handed Axes", "One-Handed Axes", "Two-Handed Maces", "One-Handed Maces", + "Staves", "Daggers", "Fist Weapons") + SetClassWeapons("WARLOCK", "One-Handed Swords", "Staves", "Daggers", "Wands") + SetClassWeapons("WARRIOR", "Two-Handed Axes", "One-Handed Axes", "Two-Handed Swords", "One-Handed Swords", + "Two-Handed Maces", "One-Handed Maces", "Polearms", "Staves", "Daggers", "Fist Weapons", + "Bows", "Crossbows", "Guns", "Thrown") +end + + + + + +local ItemDB = {} +local Item = {} + +local privates = setmetatable({}, {__mode = "k"}) +local function private(obj) + return privates[obj] +end +local function setPrivate(obj, p) + privates[obj] = p + return private(obj) +end + + +local function Round(num, decimalPlaces) + local mult = 10^(tonumber(decimalPlaces) or 0) + return floor(tonumber(num) * mult + 0.5) / mult +end + + + +Queue = {} +local queueMeta = { + __index = Queue, + __tostring = function(self) return "Queue" end, +} +local function MakeQueue(_, vals) + local queue = vals or {} + setPrivate(queue, {head = 1, tail = #queue + 1}) + setmetatable(queue, queueMeta) + return queue +end +setmetatable(Queue, {__call = MakeQueue}) +function Queue:len() + local private = private(self) + return private.tail - private.head +end +function Queue:isEmpty() + return self:len() <= 0 +end +function Queue:hasValues() + return self:len() > 0 +end +function Queue:add(v) + local private = private(self) + self[private.tail] = v + private.tail = private.tail + 1 + return self +end +function Queue:peek() + return self[private(self).head] +end +function Queue:pop() + local private = private(self) + local v = self:peek() + self[private.head] = nil + private.head = private.head + 1 + return v +end +function Queue:empty() + local private = private(self) + private.head, private.tail = 1, 1 + wipe(self) + return self +end + + + +local retrieveModes = { + cache = {Retrieve = function(self) return self:Cache() end, IsRetrieved = function(self) return self:IsCached() end}, + load = {Retrieve = function(self) return self:Load() end, IsRetrieved = function(self) return self:IsLoaded() end}, +} + +local CallbackController = {} +local callbackControllerMeta = { + __index = CallbackController, + __tostring = function(self) return "CallbackController" end, +} +local function MakeCallbackController(_, items, retrieveMode, callback, ...) + local callbackController = {...} + local queue = {} + for id, suffixItems in pairs(items) do + for suffix, item in pairs(suffixItems) do + tblinsert(queue, item) + end + end + setPrivate(callbackController, { + items = items, + queue = Queue(queue), + Retrieve = retrieveModes[retrieveMode].Retrieve, + IsRetrieved = retrieveModes[retrieveMode].IsRetrieved, + callback = callback, + itemsRemaining = #queue, + max = #queue, + speed = 0, + suspended = false, + cancelled = false, + }) + return setmetatable(callbackController, callbackControllerMeta) +end +setmetatable(CallbackController, {__call = MakeCallbackController}) +function CallbackController:GetSize() + return private(self).max +end +function CallbackController:GetRemaining() + return private(self).itemsRemaining +end +function CallbackController:GetProgress(decimalPlaces) + local private = private(self) + return Round(private.max == 0 and 100 or (private.max - private.itemsRemaining) * 100/private.max, decimalPlaces or 2) +end +function CallbackController:IsComplete() + return private(self).itemsRemaining == 0 +end +function CallbackController:GetSpeed() + return private(self).speed +end +function CallbackController:SetSpeed(speed) + private(self).speed = speed + return self +end +function CallbackController:IsSuspended() + return private(self).suspended +end +function CallbackController:Suspend() + private(self).suspended = true + return self +end +function CallbackController:Resume() + private(self).suspended = false + return self +end +function CallbackController:Cancel() + if not self:IsComplete() and not self.cancelled then + private(self).cancelled = true + ItemDB:UnregisterCallbackController(self) + end + return self +end +function CallbackController:IsCancelled() + return private(self).cancelled +end + + +-- debug +function CallbackController:GetQueue() + return private(self).queue +end +function CallbackController:GetItems() + return private(self).items +end + + + +local storage + +local matchMeta = {} +local itemMeta = { + __index = Item, + __newindex = function(self, k, v) error("Item cannot be modified") end, + __metatable = matchMeta, + __eq = function(item1, item2) return item1:GetID() == item2:GetID() and item1:GetSuffix() == item2:GetSuffix() end, + __lt = function(item1, item2) return (item1:GetName() or "") < (item2:GetName() or "") end, + __le = function(item1, item2) return (item1:GetName() or "") <= (item2:GetName() or "") end, + __tostring = function(self) return "Item " .. self:GetID() end, +} + +function ItemCache:DoesItemExistByID(id) + ItemDB:Init() + if not DoesItemExistByID(id) then + return false + end + if storage[id] then + for suffix, itemPrivate in pairs(storage[id]) do + if suffix ~= 0 then + return true + elseif itemPrivate.dne then + return false + end + end + end + return true +end + +function IsItem(item) + return type(item) == "table" and getmetatable(item) == matchMeta +end + +local function MakeItem(id, suffix) + ItemDB:Init() + if suffix == 0 then + suffix = nil + end + local facade = {id, suffix} -- this table could be used to rebuild the Item without a metatable (like after being stored in savedvars). it is NOT protected from editing, and the user can change it if they'd like + local item -- this table is protected from editing, and contains the actual id and suffix used by Item + if storage[id] and storage[id][suffix or 0] then + item = storage[id][suffix or 0] + else + item = {id = id, suffix = suffix} + end + setPrivate(facade, item) + return setmetatable(facade, itemMeta) +end + +function ItemCache:FormatSearchText(text) + return text:gsub("%W", ""):lower() +end + +local function InterpretItem(arg, suffix) + if IsItem(arg) then + return arg:GetID(), arg:GetSuffix() + end + local argType = type(arg) + if argType == "table" and (arg.id or #arg > 0) then + return arg.id or arg[1], arg.suffix or arg[2] + elseif argType == "string" then -- try to decipher itemlink + local id = tonumber(arg) + if id then + return id + end + local id, suffix = strmatch(arg, "^.-item:(%d-):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([%-%d]*):") + id, suffix = tonumber(id or ""), tonumber(suffix or "") + if id then + return id, suffix + end + return nil -- this can happen if GetItemInfo is passed an item name or any random string + elseif argType == "number" then -- must be an itemID + return arg, suffix + else + return nil + end +end + +function ItemCache:Filter(...) + return ItemDB:Filter(...) +end + +function ItemCache:All() + return ItemDB:Filter(function() return true end) +end + +function ItemCache:GetItemInfo(...) + return ItemCache:Item(...):GetInfo() +end + +function ItemCache:Item(arg, suffix) + if IsItem(arg) then + return arg + else + local id, suffix = InterpretItem(arg, suffix) + assert(id, "Bad Item format.") + return ItemDB:Get(id, suffix) + end + return nil +end +ItemCache.Get = ItemCache.Item +setmetatable(ItemCache, {__call = ItemCache.Item}) + + + + + + +function ItemDB:Check(id, suffix) + if not self.cache[id] then + return nil + end + return self.cache[id][suffix or 0] +end +function ItemDB:Get(id, suffix) + if not self.cache[id] then + self.cache[id] = {} + end + local item = self.cache[id][suffix or 0] + if not item then + item = MakeItem(id, suffix) + self.cache[id][suffix or 0] = item + end + return item +end + +-- check if an item is in storage +function ItemDB:IsStored(item) + ItemDB:Init() + local id, suffix = item:GetIDSuffix() + return storage[id] and storage[id][suffix or 0] and true or false +end + +-- save an item into storage +function ItemDB:Store(item) + ItemDB:Init() + if not storage[item:GetID()] then + if not item:GetSuffix() then + storage[item:GetID()] = {[0] = private(item)} + end + elseif item:GetSuffix() and not storage[item:GetID()][item:GetSuffix()] then -- only store suffix items if the suffix matters + local noSuffixItem = self:Get(item:GetID()) + if noSuffixItem and item:GetName() ~= noSuffixItem:GetName() then + storage[item:GetID()][item:GetSuffix()] = private(item) + end + end +end + +function ItemDB:Filter(func) + local list = {} + for id, items in pairs(self.cache) do + for suffix, item in pairs(items) do + if self:IsStored(item) and func(item, id, suffix) then + tblinsert(list, item) + end + end + end + return list +end + +function ItemDB:GetItemInfoPacked(...) + self.GetItemInfoHook = false + local info = {GetItemInfo(...)} + self.GetItemInfoHook = true + return info +end + +function ItemDB:AddQueryItems(callbackController) + tblinsert(self.queryCallbacks, callbackController) +end + +function ItemDB:AddLoadCallback(callbackController) + tblinsert(self.loadCallbacks, callbackController) +end + + +function ItemDB:RunLoadCallbacks(item) + local id, suffix = item:GetID(), item:GetSuffix() or 0 + for i = #self.loadCallbacks, 1, -1 do + local private = private(self.loadCallbacks[i]) + local items = private.items + if items[id] and items[id][suffix] then + items[id][suffix] = nil + if not next(items[id]) then + items[id] = nil + end + private.itemsRemaining = private.itemsRemaining - 1 + if private.itemsRemaining == 0 then + if private.callback then + private.callback(unpack(private)) + end + tblremove(self.loadCallbacks, i) + end + end + end +end + +function ItemDB:RegisterCallbackController(callbackController) + tblinsert(self.queryCallbacks, callbackController) + tblinsert(self.loadCallbacks, callbackController) + private(callbackController).registered = true +end + +function ItemDB:UnregisterCallbackController(callbackController) + for _, tbl in ipairs{self.queryCallbacks, self.loadCallbacks} do + for i, v in ipairs(tbl) do + if v == callbackController then + tblremove(tbl, i) + end + end + end + private(callbackController).registered = false +end + + + + +function ItemDB:InitTooltipScanner() + self.tooltipScanner = ItemCachetooltipScanner + if not self.tooltipScanner then + self.tooltipScanner = CreateFrame("GameTooltip", "ItemCachetooltipScanner", UIParent, "GameTooltipTemplate") + self.tooltipScanner:Hide() + + self.tooltipScanner.ClassesAllowed = format("^%s$" , ITEM_CLASSES_ALLOWED:gsub("%d+%$", ""):gsub("%%s", "(.+)")) + self.tooltipScanner.SkillRequired = format("^%s$" , ITEM_MIN_SKILL:gsub("%d+%$", ""):gsub("%%s ", "([%%a%%s]+) "):gsub("%(%%d%)", "%%((%%d+)%%)")) + self.tooltipScanner.Unique = format("^(%s)$", ITEM_UNIQUE:gsub("%d+%$", "")) + self.tooltipScanner.StartsQuest = format("^(%s)$", ITEM_STARTS_QUEST:gsub("%d+%$", "")) + end +end + +function ItemDB:InitQueryCallbacks() + self.QueryFrame = CreateFrame("Frame", nil, UIItem) + self.QueryFrame:SetPoint("TOPLEFT", UIItem, "TOPLEFT", 0, 0) + self.QueryFrame:SetSize(0, 0) + self.QueryFrame:Show() + + self.QueryFrame:SetScript("OnUpdate", function() + if #self.queryCallbacks == 0 then return end + for i = #self.queryCallbacks, 1, -1 do + local private = private(self.queryCallbacks[i]) + local queue = private.queue + local yieldThreshold = private.speed + if private.itemsRemaining == 0 then + queue:empty() + tblremove(self.queryCallbacks, i) + elseif not private.suspended then + local Retrieve = private.Retrieve + local IsRetrieved = private.IsRetrieved + while queue:hasValues() do + if yieldThreshold <= 0 then + break + end + local item = queue:pop() + if not IsItem(item) then + local id, suffix = InterpretItem(item) + if not self:Check(id, suffix) then + yieldThreshold = yieldThreshold - 1 + end + item = ItemCache:Item(id, suffix) + end + if IsRetrieved(item) then + self:RunLoadCallbacks(item) + else + Retrieve(item) + if IsRetrieved(item) then + self:RunLoadCallbacks(item) + else + yieldThreshold = yieldThreshold - 1 + if item:Exists() then + queue:add(item) + end + end + end + end + if not queue:hasValues() then + tblremove(self.queryCallbacks, i) + end + end + end + end) +end + +function ItemDB:InitItemInfoListener() + self.ItemInfoListenerFrame = CreateFrame("Frame", nil, UIItem) + self.ItemInfoListenerFrame:SetPoint("TOPLEFT", UIItem, "TOPLEFT", 0, 0) + self.ItemInfoListenerFrame:SetSize(0, 0) + self.ItemInfoListenerFrame:Show() + + self.ItemInfoListenerFrame:RegisterEvent"GET_ITEM_INFO_RECEIVED" + self.ItemInfoListenerFrame:SetScript("OnEvent", function(_, event, id, success) + self:Get(id) + for suffix in pairs(self.cache[id]) do + local item = self:Get(id, suffix) + if not item:IsCached() then + if success then + item:Cache() + + else + private(item).dne = true + ItemDB:Store(item) + if not self.loadAttempts[id] then + self.loadAttempts[id] = 0 + end + self.loadAttempts[id] = self.loadAttempts[id] + 1 + if self.loadAttempts[id] >= self.MAX_LOAD_ATTEMPTS then + private(item).dne = true + else + item:Load() + end + end + end + end + end) +end + +function ItemDB:InitChatListener() + self.ChatListenerFrame = CreateFrame("Frame", nil, UIItem) + self.ChatListenerFrame:SetPoint("TOPLEFT", UIItem, "TOPLEFT", 0, 0) + self.ChatListenerFrame:SetSize(0, 0) + self.ChatListenerFrame:Show() + + for _, channel in ipairs{"CHAT_MSG_CHANNEL", "CHAT_MSG_ADDON", "CHAT_MSG_BN_WHISPER", "CHAT_MSG_EMOTE", + "CHAT_MSG_GUILD", "CHAT_MSG_LOOT", "CHAT_MSG_OFFICER", "CHAT_MSG_OPENING", + "CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER", "CHAT_MSG_RAID", + "CHAT_MSG_RAID_LEADER", "CHAT_MSG_RAID_WARNING", "CHAT_MSG_SAY", + "CHAT_MSG_TEXT_EMOTE", "CHAT_MSG_TRADESKILLS", "CHAT_MSG_WHISPER", + "CHAT_MSG_YELL"} do + self.ChatListenerFrame:RegisterEvent(channel) + end + self.ChatListenerFrame:SetScript("OnEvent", function(_, event, message) + for itemString in strgmatch(message, "item[%-?%d:]+") do + local id, suffix = InterpretItem(itemString) + if id then + local item = self:Get(id, suffix) + if item:Exists() then + item:Cache() + end + end + end + end) +end + +function ItemDB:InitMouseoverHook() + self.mouseoverHook = true + GameTooltip:HookScript("OnTooltipSetItem", function(tooltip) + if not self.mouseoverHook then return end + + local name, link = tooltip:GetItem() + if not link then return end + local itemString = strmatch(link, "item[%-?%d:]+") + + local id, suffix = InterpretItem(itemString) + if id and id ~= 0 then + local item = self:Get(id, suffix) + if item:Exists() then + item:Cache() + end + elseif TradeSkillFrame and TradeSkillFrame:IsVisible() then + if GetMouseFocus():GetName() == "TradeSkillSkillIcon" then + local id, suffix = InterpretItem(GetTradeSkillItemLink(TradeSkillFrame.selectedSkill)) + if id then + local item = self:Get(id, suffix) + if item:Exists() then + item:Cache() + end + end + else + for i = 1, 8 do + if GetMouseFocus():GetName() == "TradeSkillReagent"..i then + local id, suffix = InterpretItem(GetTradeSkillReagentItemLink(TradeSkillFrame.selectedSkill, i)) + if id then + local item = self:Get(id, suffix) + if item:Exists() then + item:Cache() + end + end + break + end + end + end + end + end) +end + +function ItemDB:InitGetItemInfoHook() + self.GetItemInfoHook = true + hooksecurefunc("GetItemInfo", function(...) + if not self.GetItemInfoHook then return end + local id, suffix = InterpretItem(...) + if id then + local item = self:Get(id, suffix) + if item then + item:Cache() + end + end + end) +end + +function ItemDB:Destructor() + self.QueryFrame : SetScript("OnUpdate") + self.ItemInfoListenerFrame : SetScript("OnEvent") + self.ChatListenerFrame : SetScript("OnEvent") + + self.GetItemInfoHook = false + self.tooltipHook = false + self.mouseoverHook = false + + wipe(self.cache) + wipe(self.loadCallbacks) + wipe(self.queryCallbacks) +end + +function ItemDB:Init() + if self.initialized then return end + local version, build = GetBuildInfo() + + if not ItemCacheStorage then + ItemCacheStorage = {} + end + local db = ItemCacheStorage + + if not db._BUILD or db._BUILD < build then + wipe(db) + db._BUILD = build + end + if IsStandalone and not Addon:GetGlobalOption"UsePersistentStorage" then + ItemCacheStorage = nil + end + + local locale = GetLocale() + if not db[locale] then + db[locale] = {} + end + storage = db[locale] + + self.cache = {} + self.loadCallbacks = {} + self.queryCallbacks = {} + self.loadAttempts = {} + self.MAX_LOAD_ATTEMPTS = 10 + + self:InitTooltipScanner() + self:InitQueryCallbacks() + self:InitItemInfoListener() + self:InitChatListener() + self:InitGetItemInfoHook() + self:InitMouseoverHook() + + self.initialized = true + + -- Bring all stored items into cache + for id, items in pairs(storage) do + if type(id) == "number" then + for suffix, item in pairs(items) do + self:Get(id, suffix) + end + end + end + + -- Shut down if a newer version is found + local timer = C_Timer.NewTicker(1, function() + local _, minor = LibStub:GetLibrary(ADDON_NAME) + if minor > MINOR then + timer:Cancel() + self:Destructor() + end + end) +end + + +local function Items_OnCacheOrLoad(items, self, retrieveMode, func, ...) + local itemsToLoad = {} + local IsRetrieved = retrieveModes[retrieveMode].IsRetrieved + for _, item in pairs(items) do + local id, suffix = InterpretItem(item) + assert(id, "Bad Item format.") + local loadItem = true + if not ItemCache:DoesItemExistByID(id) then + loadItem = false + end + if loadItem and ItemDB:Check(id, suffix) then + item = ItemDB:Get(id, suffix) + if IsRetrieved(item) then + loadItem = false + end + end + if loadItem then + if not itemsToLoad[id] then + itemsToLoad[id] = {} + end + itemsToLoad[id][suffix or 0] = ItemDB:Check(id, suffix) and ItemDB:Get(id, suffix) or item + end + end + if next(itemsToLoad) then + local callbackController + if self then + callbackController = CallbackController(itemsToLoad, retrieveMode, func, self, ...) + else + callbackController = CallbackController(itemsToLoad, retrieveMode) + end + ItemDB:RegisterCallbackController(callbackController) + return callbackController + else + if self then + func(self, ...) + return CallbackController({}, retrieveMode, func, self, ...) + else + return CallbackController({}, retrieveMode) + end + end +end + + +function ItemCache:OnCache(items, ...) + return Items_OnCacheOrLoad(items, items, "cache", ...) +end +function ItemCache:OnLoad(items, ...) + return Items_OnCacheOrLoad(items, items, "load", ...) +end +function ItemCache:Cache(items) + return Items_OnCacheOrLoad(items, nil, "cache") +end +function ItemCache:Load(items) + return Items_OnCacheOrLoad(items, nil, "load") +end + +function Item:OnCache(...) + return Items_OnCacheOrLoad({self}, self, "cache", ...) +end +function Item:OnLoad(...) + return Items_OnCacheOrLoad({self}, self, "load", ...) +end + + + +function Item:GetID() + return private(self).id +end +Item.GetId = Item.GetID +function Item:GetSuffix() + return private(self).suffix +end +function Item:GetIDSuffix() + return self:GetID(), self:GetSuffix() +end +Item.GetIdSuffix = Item.GetIDSuffix + + +function Item:GetString() + return format("item:%d::::::%d:::::::::::", self:GetID(), self:GetSuffix() or "") +end + +function Item:Exists() + return ItemCache:DoesItemExistByID(self:GetID()) +end +function Item:IsLoaded() + return GetItemInfo(self:GetID()) ~= nil +end +function Item:Load() + self:IsLoaded() +end + +function Item:IsCached() + return private(self).info ~= nil +end +function Item:Cache() + if not self:IsCached() then + self:GetInfo() + end + return self +end + +function Item:GetInfo() + if self:Exists() then + if not private(self).info then + local info = ItemDB:GetItemInfoPacked(self:GetString()) + if #info == 0 then + return + end + private(self).dne = nil + info[2] = strgsub(info[2], "(item:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:)([^:]*):", "%1:") + private(self).info = info + -- local name, link, quality, _, _, itemType, itemSubType, _, equipLoc, texture, sellPrice, _, _, bindType = unpack(info) + private(self).searchName = ItemCache:FormatSearchText(info[1]) + + ItemDB.tooltipScanner:SetOwner(UIParent, "ANCHOR_NONE") + ItemDB.tooltipScanner:SetHyperlink("item:" .. self:GetID()) + for i = 1, ItemDB.tooltipScanner:NumLines() do + local line = _G[ItemDB.tooltipScanner:GetName() .. "TextLeft" .. i] + if line and line:GetText() then + if strmatch(line:GetText(), ItemDB.tooltipScanner.Unique) then + private(self).unique = true + + elseif strmatch(line:GetText(), ItemDB.tooltipScanner.StartsQuest) then + private(self).startsQuest = true + + else + local skill, level = strmatch(line:GetText(), ItemDB.tooltipScanner.SkillRequired) + if skill then + private(self).skillRequired = skill + private(self).skillLevelRequired = tonumber(level) + else + local classesAllowedText = strmatch(line:GetText(), ItemDB.tooltipScanner.ClassesAllowed) + if classesAllowedText then + local classesAllowed = {} + for _, names in ipairs{LOCALIZED_CLASS_NAMES_MALE, LOCALIZED_CLASS_NAMES_FEMALE} do + for file, name in pairs(names) do + if strmatch(classesAllowedText, name) then + classesAllowed[CLASS_MAP_TO_ID[file]] = true + end + end + end + private(self).classesAllowed = classesAllowed + end + end + end + end + end + ItemDB.tooltipScanner:Hide() + ItemDB:Store(self) + ItemDB:RunLoadCallbacks(self) + end + end + local info = private(self).info + if info then + return info[1], strgsub(info[2], "(item:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*)([^:]*)(:.*)", "%1" .. UnitLevel"player" .. "%3"), select(3, unpack(info)) + end + return nil +end +function Item:GetInfoPacked() + return {self:GetInfo()} +end + + + +function Item:IsUsableBy(classOrUnit) + if not self:IsCached() then return nil end + local id = CLASS_MAP_TO_ID[classOrUnit] + if not id and UnitExists(classOrUnit) then + local className, classFile, classID = UnitClass(classOrUnit) + id = classID + end + local classesAllowed = private(self).classesAllowed + if classesAllowed then + return classesAllowed[id] or false + end + local itemType, itemSubType = self:GetTypeSubType() + if itemType and itemSubType then + if UNUSABLE_EQUIPMENT[id][itemType] and UNUSABLE_EQUIPMENT[id][itemType][itemSubType] then + return false + end + end + return true +end +function Item:GetSkillRequired() + if not self:IsCached() then return nil end + local skill, level = private(self).skillRequired, private(self).skillLevelRequired + if not skill then + skill = false + end + return skill, level +end + +function Item:RequiresSkill(skill, level) + local skillRequired, levelRequired = self:GetSkillRequired() + if skillRequired == nil then return nil end + return (not skill or skill == skillRequired) and (not level or level <= levelRequired) and true or false +end + +function Item:RequiresCooking(...) return self:RequiresSkill(PROFESSIONS_COOKING, ...) end +function Item:RequiresFirstAid(...) return self:RequiresSkill(PROFESSIONS_FIRST_AID, ...) end +function Item:RequiresFishing(...) return self:RequiresSkill(PROFESSIONS_FISHING, ...) end + +-- these GlobalStrings are not appearing ingame (yet?) + +-- function Item:RequiresAlchemy(...) return self:RequiresSkill(CHARACTER_PROFESSION_ALCHEMY, ...) end +-- function Item:RequiresBlacksmithing(...) return self:RequiresSkill(CHARACTER_PROFESSION_BLACKSMITHING, ...) end +-- function Item:RequiresEnchanting(...) return self:RequiresSkill(CHARACTER_PROFESSION_ENCHANTING, ...) end +-- function Item:RequiresEngineering(...) return self:RequiresSkill(CHARACTER_PROFESSION_ENGINEERING, ...) end +-- function Item:RequiresHerbalism(...) return self:RequiresSkill(CHARACTER_PROFESSION_HERBALISM, ...) end +-- function Item:RequiresInscription(...) return self:RequiresSkill(CHARACTER_PROFESSION_INSCRIPTION, ...) end +-- function Item:RequiresJewelcrafting(...) return self:RequiresSkill(CHARACTER_PROFESSION_JEWELCRAFTING, ...) end +-- function Item:RequiresLeatherworking(...) return self:RequiresSkill(CHARACTER_PROFESSION_LEATHERWORKING, ...) end +-- function Item:RequiresMining(...) return self:RequiresSkill(CHARACTER_PROFESSION_MINING, ...) end +-- function Item:RequiresPoisons(...) return self:RequiresSkill(MINIMAP_TRACKING_VENDOR_POISON, ...) end +-- function Item:RequiresRiding(...) return self:RequiresSkill("Riding", ...) end +-- function Item:RequiresTailoring(...) return self:RequiresSkill(CHARACTER_PROFESSION_TAILORING, ...) end + + +function Item:IsUnique() + if not self:IsCached() then return nil end + return private(self).unique or false +end +function Item:StartsQuest() + if not self:IsCached() then return nil end + return private(self).startsQuest or false +end +function Item:GetSearchName() + return private(self).searchName +end + +function Item:Matches(text) + local id = tonumber(text) + if id then + return self:GetID() == id + end + local searchName = private(self).searchName + if searchName then + return strfind(searchName, text) + end + return nil +end + +function Item:GetName() + return self:GetInfoPacked()[1] +end +function Item:GetLink() + return self:GetInfoPacked()[2] +end +function Item:GetTexture() + return self:GetInfoPacked()[10] +end +function Item:GetNameLinkTexture() + local name, link, _, _, _, _, _, _, _, texture = self:GetInfo() + return name, link, texture +end + +function Item:GetType() + return self:GetInfoPacked()[6] +end +function Item:GetSubType() + return self:GetInfoPacked()[7] +end +function Item:GetTypeSubType() + local _, _, _, _, _, itemType, itemSubType = self:GetInfo() + return itemType, itemSubType +end + +function Item:GetQuality() + return self:GetInfoPacked()[3] +end + +function Item:GetSellPrice() + return self:GetInfoPacked()[11] +end +Item.GetVendorPrice = Item.GetSellPrice +Item.GetPrice = Item.GetSellPrice +Item.GetValue = Item.GetSellPrice + +function Item:GetBindType() + return self:GetInfoPacked()[14] +end +function Item:DoesNotBind() return self:GetBindType() == LE_ITEM_BIND_NONE end +function Item:CanBind() return self:GetBindType() ~= LE_ITEM_BIND_NONE end +function Item:IsBindOnPickup() return self:GetBindType() == LE_ITEM_BIND_ON_ACQUIRE end +function Item:IsBindOnEquip() return self:GetBindType() == LE_ITEM_BIND_ON_EQUIP end +function Item:IsBindOnUse() return self:GetBindType() == LE_ITEM_BIND_ON_USE end +Item.Binds = Item.CanBind +Item.IsBoP = Item.IsBindOnPickup +Item.IsBoE = Item.IsBindOnEquip +Item.IsBoU = Item.IsBindOnUse + + +function Item:GetEquipLocation() + return self:GetInfoPacked()[9] +end +function Item:IsEquippable() return self:GetEquipLocation() ~= "" and self:GetEquipLocation() ~= "INVTYPE_NON_EQUIP" end +function Item:IsHelm() return self:GetEquipLocation() == "INVTYPE_HEAD" end +function Item:IsNecklace() return self:GetEquipLocation() == "INVTYPE_NECK" end +function Item:IsShoulder() return self:GetEquipLocation() == "INVTYPE_SHOULDER" end +function Item:IsShirt() return self:GetEquipLocation() == "INVTYPE_BODY" end +function Item:IsTabard() return self:GetEquipLocation() == "INVTYPE_TABARD" end +function Item:IsChest() return self:GetEquipLocation() == "INVTYPE_CHEST" or self:GetEquipLocation() == "INVTYPE_ROBE" end +function Item:IsBelt() return self:GetEquipLocation() == "INVTYPE_WAIST" end +function Item:IsPants() return self:GetEquipLocation() == "INVTYPE_LEGS" end +function Item:IsBoots() return self:GetEquipLocation() == "INVTYPE_FEET" end +function Item:IsBracers() return self:GetEquipLocation() == "INVTYPE_WRIST" end +function Item:IsGloves() return self:GetEquipLocation() == "INVTYPE_HAND" end +function Item:IsRing() return self:GetEquipLocation() == "INVTYPE_FINGER" end +function Item:IsTrinket() return self:GetEquipLocation() == "INVTYPE_TRINKET" end +function Item:IsCloak() return self:GetEquipLocation() == "INVTYPE_CLOAK" end + +function Item:IsQuiver() return self:GetEquipLocation() == "INVTYPE_QUIVER" end +function Item:IsNormalBag() return self:GetEquipLocation() == "INVTYPE_BAG" end + +function Item:IsRelic() return self:GetEquipLocation() == "INVTYPE_RELIC" end +function Item:IsShield() return self:GetEquipLocation() == "INVTYPE_SHIELD" end +function Item:IsHoldable() return self:GetEquipLocation() == "INVTYPE_HOLDABLE" end + +function Item:IsOneHandWeapon() return self:GetEquipLocation() == "INVTYPE_WEAPON" end +function Item:IsTwoHandWeapon() return self:GetEquipLocation() == "INVTYPE_2HWEAPON" end +function Item:IsMainHandWeapon() return self:GetEquipLocation() == "INVTYPE_WEAPONMAINHAND" end +function Item:IsOffHandWeapon() return self:GetEquipLocation() == "INVTYPE_WEAPONOFFHAND" end + +function Item:IsGunOrBow() return self:GetEquipLocation() == "INVTYPE_RANGED" end +function Item:IsThrownWeapon() return self:GetEquipLocation() == "INVTYPE_THROWN" end + +function Item:IsBag() return self:IsNormalBag() or self:IsQuiver() end +function Item:IsOffHand() return self:IsRelic() or self:IsShield() or self:IsHoldable() or self:IsOffHandWeapon() end +function Item:IsRangedWeapon() return self:IsGunOrBow() or self:IsThrownWeapon() end +function Item:IsMeleeWeapon() return self:IsOneHandWeapon() or self:IsTwoHandWeapon() or self:IsMainHandWeapon() or self:IsOffHandWeapon() end +function Item:IsWeapon() return self:IsMeleeWeapon() or self:IsRangedWeapon() end + +Item.IsHat = Item.IsHelm +Item.IsNeck = Item.IsNecklace +Item.IsRobe = Item.IsChest +Item.IsLegs = Item.IsPants +Item.IsShoes = Item.IsBoots + + + + + + + + +function Addon:OnChatCommand(input) + self:OpenConfig(ADDON_NAME, true) +end + +function Addon:OpenConfig(category, expandSection) + InterfaceAddOnsList_Update() + InterfaceOptionsFrame_OpenToCategory(category) + + if expandSection then + -- Expand config if it's collapsed + local i = 1 + while _G["InterfaceOptionsFrameAddOnsButton"..i] do + local frame = _G["InterfaceOptionsFrameAddOnsButton"..i] + if frame.element then + if frame.element.name == ADDON_NAME then + if frame.element.hasChildren and frame.element.collapsed then + if _G["InterfaceOptionsFrameAddOnsButton"..i.."Toggle"] and _G["InterfaceOptionsFrameAddOnsButton"..i.."Toggle"].Click then + _G["InterfaceOptionsFrameAddOnsButton"..i.."Toggle"]:Click() + break + end + end + break + end + end + i = i + 1 + end + end +end +function Addon:MakeDefaultFunc(category) + return function() + self:GetDB():ResetProfile() + self:InitDB() + self:Printf(L["Profile reset to default."]) + AceConfigRegistry:NotifyChange(category) + end +end +function Addon:CreateOptionsCategory(categoryName, options) + local category = ADDON_NAME + if categoryName then + category = ("%s.%s"):format(category, categoryName) + end + AceConfig:RegisterOptionsTable(category, options) + local Panel = AceConfigDialog:AddToBlizOptions(category, categoryName, categoryName and ADDON_NAME or nil) + Panel.default = self:MakeDefaultFunc(category) + return Panel +end + +function Addon:RefreshOptions() + Data:RefreshOptionsTable(ADDON_NAME, self, L) + + AceConfigRegistry:NotifyChange(ADDON_NAME) +end + +function Addon:CreateOptions() + self.Options = {} + + self:CreateOptionsCategory(nil, Data:RefreshOptionsTable(ADDON_NAME, self, L)) + + if self:GetOption("Debug", "menu") then + self:CreateOptionsCategory("Debug" , Data:MakeDebugOptionsTable("Debug", self, L)) + end +end + +function Addon:InitDB() + local configVersion = SemVer(self:GetOption"_VERSION" or tostring(self.Version)) + if configVersion < self.Version then + -- Update data schema here + end + self:SetOption(tostring(self.Version), "_VERSION") +end + + +function Addon:OnInitialize() + self.Version = SemVer(GetAddOnMetadata(ADDON_NAME, "Version")) + self.db = AceDB:New(("%sDB"):format(ADDON_NAME) , Data:MakeDefaultOptions(), true) + self.dbDefault = AceDB:New(("%sDB_Default"):format(ADDON_NAME), Data:MakeDefaultOptions(), true) + + self:RegisterChatCommand(Data.CHAT_COMMAND, "OnChatCommand", true) + + ItemDB:Init() +end + +function Addon:OnEnable() + self:InitDB() + self:GetDB().RegisterCallback(self, "OnProfileChanged", "InitDB") + self:GetDB().RegisterCallback(self, "OnProfileCopied" , "InitDB") + self:GetDB().RegisterCallback(self, "OnProfileReset" , "InitDB") + + self:CreateOptions() +end + +function Addon:OnDisable() +end + + + + +if not IsStandalone then + local frame = CreateFrame"Frame" + frame:RegisterEvent"ADDON_LOADED" + frame:SetScript("OnEvent", function(_, event, name) + if name == HOST_ADDON_NAME then + ItemDB:Init() + end + end) +end + + + + + diff --git a/ItemCache.toc b/ItemCache.toc new file mode 100644 index 0000000..553db44 --- /dev/null +++ b/ItemCache.toc @@ -0,0 +1,21 @@ +## Interface: 20503 +## Version: 0.0.0 +## Author: Anonomit + +## Title: ItemCache +## Notes: Caches item data returned by GetItemInfo() and more. Can be run standalone to store data between sessions, or can be embedded as a library. + +## SavedVariables: ItemCacheDB, ItemCacheStorage + +## X-Embeds: LibStub, Ace3 + + +embeds.xml + +Config.lua + +locales.xml + +ItemCache.lua + + diff --git a/ItemCache.xml b/ItemCache.xml new file mode 100644 index 0000000..9f54f9f --- /dev/null +++ b/ItemCache.xml @@ -0,0 +1,4 @@ + +