Last active
January 18, 2023 00:41
-
-
Save scottcreynolds/150e643a4932732128b3d2bbc8ec1cbc to your computer and use it in GitHub Desktop.
Ulduar Addon Patches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Auctionator: Auctionator/Source_Classic/Tabs/Selling/Mixins/BagItemSelected.lua | |
DataStore_Containers: DataStore_Containers/DataStore_Containers.lua | |
ItemRack: ItemRack/ItemRack.lua |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AuctionatorBagItemSelectedMixin = CreateFromMixins(AuctionatorBagItemMixin) | |
local seenBag, seenSlot | |
function AuctionatorBagItemSelectedMixin:OnClick(button) | |
if not self:ProcessCursor() then | |
AuctionatorBagItemMixin.OnClick(self, button) | |
end | |
end | |
function AuctionatorBagItemSelectedMixin:OnReceiveDrag() | |
self:ProcessCursor() | |
end | |
function AuctionatorBagItemSelectedMixin:ProcessCursor() | |
local location = C_Cursor.GetCursorItem() | |
ClearCursor() | |
if not location then | |
Auctionator.Debug.Message("nothing on cursor") | |
return false | |
end | |
-- Case when picking up a key from your keyring, WoW doesn't always give a | |
-- valid item location for the cursor, causing an error unless we either: | |
-- 1. Ignore it | |
-- 2. Replace the location with one that is valid based on a hook on bag | |
-- clicks. | |
-- We use 2. | |
if not location:HasAnyLocation() then | |
Auctionator.Debug.Message("AuctionatorBagItemSelected", "recovering") | |
location = ItemLocation:CreateFromBagAndSlot(seenBag, seenSlot) | |
end | |
if not C_Item.DoesItemExist(location) then | |
Auctionator.Debug.Message("AuctionatorBagItemSelected", "not exists") | |
return false | |
end | |
local itemInfo = Auctionator.Utilities.ItemInfoFromLocation(location) | |
itemInfo.count = Auctionator.Selling.GetItemCount(location) | |
if not Auctionator.EventBus:IsSourceRegistered(self) then | |
Auctionator.EventBus:RegisterSource(self, "AuctionatorBagItemSelectedMixin") | |
end | |
if itemInfo.auctionable then | |
Auctionator.Debug.Message("AuctionatorBagItemSelected", "auctionable") | |
Auctionator.EventBus:Fire(self, Auctionator.Selling.Events.BagItemClicked, itemInfo) | |
return true | |
end | |
Auctionator.Debug.Message("AuctionatorBagItemSelected", "err") | |
UIErrorsFrame:AddMessage(ERR_AUCTION_BOUND_ITEM, 1.0, 0.1, 0.1, 1.0) | |
return false | |
end | |
-- Record clicks on bag items so that we can make keyring items being picked up | |
-- and placed in the Selling tab work. | |
hooksecurefunc(C_Container, "PickupContainerItem", function(bag, slot) | |
seenBag = bag | |
seenSlot = slot | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ *** DataStore_Containers *** | |
Written by : Thaoky, EU-Marécages de Zangar | |
June 21st, 2009 | |
This modules takes care of scanning & storing player bags, bank, & guild banks | |
Extended services: | |
- guild communication: at logon, sends guild bank tab info (last visit) to guildmates | |
- triggers events to manage transfers of guild bank tabs | |
--]] | |
if not DataStore then return end | |
local addonName = "DataStore_Containers" | |
_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceComm-3.0", "AceSerializer-3.0") | |
local addon = _G[addonName] | |
local THIS_ACCOUNT = "Default" | |
local commPrefix = "DS_Cont" -- let's keep it a bit shorter than the addon name, this goes on a comm channel, a byte is a byte ffs :p | |
local MAIN_BANK_SLOTS = 100 -- bag id of the 28 main bank slots | |
local guildMembers = {} -- hash table containing guild member info (tab timestamps) | |
-- Message types | |
local MSG_SEND_BANK_TIMESTAMPS = 1 -- broacast at login | |
local MSG_BANK_TIMESTAMPS_REPLY = 2 -- reply to someone else's login | |
local MSG_BANKTAB_REQUEST = 3 -- request bank tab data .. | |
local MSG_BANKTAB_REQUEST_ACK = 4 -- .. ack the request, tell the requester to wait | |
local MSG_BANKTAB_REQUEST_REJECTED = 5 -- .. refuse the request | |
local MSG_BANKTAB_TRANSFER = 6 -- .. or send the data | |
local AddonDB_Defaults = { | |
global = { | |
Guilds = { | |
['*'] = { -- ["Account.Realm.Name"] | |
money = nil, | |
faction = nil, | |
Tabs = { | |
['*'] = { -- tabID = table index [1] to [6] | |
name = nil, | |
icon = nil, | |
visitedBy = "", | |
ClientTime = 0, -- since epoch | |
ClientDate = nil, | |
ClientHour = nil, | |
ClientMinute = nil, | |
ServerHour = nil, | |
ServerMinute = nil, | |
ids = {}, | |
links = {}, | |
counts = {} | |
} | |
}, | |
} | |
}, | |
Characters = { | |
['*'] = { -- ["Account.Realm.Name"] | |
lastUpdate = nil, | |
numBagSlots = 0, | |
numFreeBagSlots = 0, | |
numBankSlots = 0, | |
numFreeBankSlots = 0, | |
Containers = { | |
['*'] = { -- Containers["Bag0"] | |
icon = nil, -- Containers's texture | |
link = nil, -- Containers's itemlink | |
size = 0, | |
freeslots = 0, | |
bagtype = 0, | |
ids = {}, | |
links = {}, | |
counts = {}, | |
cooldowns = {} | |
} | |
} | |
} | |
} | |
} | |
} | |
local function GetDBVersion() | |
return addon.db.global.Version or 0 | |
end | |
local function SetDBVersion(version) | |
addon.db.global.Version = version | |
end | |
local DBUpdaters = { | |
-- Table of functions, each one updates to its index's version | |
-- ex: [3] = the function that upgrades from v2 to v3 | |
[1] = function(self) | |
local function CopyTable(src, dest) | |
for k, v in pairs (src) do | |
if type(v) == "table" then | |
dest[k] = {} | |
CopyTable(v, dest[k]) | |
else | |
dest[k] = v | |
end | |
end | |
end | |
end, | |
} | |
local function UpdateDB() | |
local version = GetDBVersion() | |
for i = (version+1), #DBUpdaters do -- start from latest version +1 to the very last | |
DBUpdaters[i]() | |
SetDBVersion(i) | |
end | |
DBUpdaters = nil | |
GetDBVersion = nil | |
SetDBVersion = nil | |
end | |
-- *** Utility functions *** | |
local bAnd = bit.band | |
local bOr = bit.bor | |
local bXOr = bit.bxor | |
local function TestBit(value, pos) | |
local mask = 2^pos | |
if bAnd(value, mask) == mask then | |
return true | |
end | |
end | |
local function GetThisGuild() | |
local key = DataStore:GetThisGuildKey() | |
return key and addon.db.global.Guilds[key] | |
end | |
local function GetBankTimestamps(guild) | |
-- returns a | delimited string containing the list of alts in the same guild | |
guild = guild or GetGuildInfo("player") | |
if not guild then return end | |
local thisGuild = GetThisGuild() | |
if not thisGuild then return end | |
local out = {} | |
for tabID, tab in pairs(thisGuild.Tabs) do | |
if tab.name then | |
table.insert(out, format("%d:%s:%d:%d:%d", tabID, tab.name, tab.ClientTime, tab.ServerHour, tab.ServerMinute)) | |
end | |
end | |
return table.concat(out, "|") | |
end | |
local function SaveBankTimestamps(sender, timestamps) | |
if not timestamps or strlen(timestamps) == 0 then return end -- sender has no tabs | |
guildMembers[sender] = guildMembers[sender] or {} | |
wipe(guildMembers[sender]) | |
for _, v in pairs( { strsplit("|", timestamps) }) do | |
local id, name, clientTime, serverHour, serverMinute = strsplit(":", v) | |
-- ex: guildMembers["Thaoky"]["RaidFood"] = { clientTime = 123, serverHour = ... } | |
guildMembers[sender][name] = {} | |
local tab = guildMembers[sender][name] | |
tab.id = tonumber(id) | |
tab.clientTime = tonumber(clientTime) | |
tab.serverHour = tonumber(serverHour) | |
tab.serverMinute = tonumber(serverMinute) | |
end | |
addon:SendMessage("DATASTORE_GUILD_BANKTABS_UPDATED", sender) | |
end | |
local function GuildBroadcast(messageType, ...) | |
local serializedData = addon:Serialize(messageType, ...) | |
addon:SendCommMessage(commPrefix, serializedData, "GUILD") | |
end | |
local function GuildWhisper(player, messageType, ...) | |
if DataStore:IsGuildMemberOnline(player) then | |
local serializedData = addon:Serialize(messageType, ...) | |
addon:SendCommMessage(commPrefix, serializedData, "WHISPER", player) | |
end | |
end | |
local function IsEnchanted(link) | |
if not link then return end | |
if not string.find(link, "item:%d+:0:0:0:0:0:0:%d+:%d+:0:0") then -- 7th is the UniqueID, 8th LinkLevel which are irrelevant | |
-- enchants/jewels store values instead of zeroes in the link, if this string can't be found, there's at least one enchant/jewel | |
return true | |
end | |
end | |
local BAGS = 1 -- All bags, 0 to 11, and keyring ( id -2 ) | |
local BANK = 2 -- 28 main slots | |
local GUILDBANK = 3 -- 98 main slots | |
local ContainerTypes = { | |
[BAGS] = { | |
GetSize = function(self, bagID) | |
return C_Container.GetContainerNumSlots(bagID) | |
end, | |
GetFreeSlots = function(self, bagID) | |
local freeSlots, bagType = C_Container.GetContainerNumFreeSlots(bagID) | |
return freeSlots, bagType | |
end, | |
GetLink = function(self, slotID, bagID) | |
return C_Container.GetContainerItemLink(bagID, slotID) | |
end, | |
GetCount = function(self, slotID, bagID) | |
local _, count = C_Container.GetContainerItemInfo(bagID, slotID) | |
return count | |
end, | |
GetCooldown = function(self, slotID, bagID) | |
local startTime, duration, isEnabled = C_Container.GetContainerItemCooldown(bagID, slotID) | |
return startTime, duration, isEnabled | |
end, | |
}, | |
[BANK] = { | |
GetSize = function(self) | |
return NUM_BANKGENERIC_SLOTS or 28 -- hardcoded in case the constant is not set | |
end, | |
GetFreeSlots = function(self) | |
local freeSlots, bagType = C_Container.GetContainerNumFreeSlots(-1) -- -1 = player bank | |
return freeSlots, bagType | |
end, | |
GetLink = function(self, slotID) | |
-- return GetInventoryItemLink("player", slotID) | |
return C_Container.GetContainerItemLink(-1, slotID) | |
end, | |
GetCount = function(self, slotID) | |
-- return GetInventoryItemCount("player", slotID) | |
return select(2, C_Container.GetContainerItemInfo(-1, slotID)) | |
end, | |
GetCooldown = function(self, slotID) | |
local startTime, duration, isEnabled = GetInventoryItemCooldown("player", slotID) | |
return startTime, duration, isEnabled | |
end, | |
}, | |
[GUILDBANK] = { | |
GetSize = function(self) | |
return MAX_GUILDBANK_SLOTS_PER_TAB or 98 -- hardcoded in case the constant is not set | |
end, | |
GetFreeSlots = function(self) | |
return nil, nil | |
end, | |
GetLink = function(self, slotID, tabID) | |
return GetGuildBankItemLink(tabID, slotID) | |
end, | |
GetCount = function(self, slotID, tabID) | |
local _, count = GetGuildBankItemInfo(tabID, slotID) | |
return count | |
end, | |
GetCooldown = function(self, slotID) | |
return nil | |
end, | |
} | |
} | |
local function GetRemainingCooldown(start) | |
local uptime = GetTime() | |
if start <= uptime + 1 then | |
return start - uptime | |
end | |
return -uptime - ((2 ^ 32) / 1000 - start) | |
end | |
-- *** Scanning functions *** | |
local function ScanContainer(bagID, containerType) | |
local Container = ContainerTypes[containerType] | |
local bag | |
if containerType == GUILDBANK then | |
local thisGuild = GetThisGuild() | |
if not thisGuild then return end | |
bag = thisGuild.Tabs[bagID] -- bag is actually the current tab | |
else | |
bag = addon.ThisCharacter.Containers["Bag" .. bagID] | |
wipe(bag.cooldowns) -- does not exist for a guild bank | |
end | |
wipe(bag.ids) -- clean existing bag data | |
wipe(bag.counts) | |
wipe(bag.links) | |
local link, count | |
local startTime, duration, isEnabled | |
bag.size = Container:GetSize(bagID) | |
bag.freeslots, bag.bagtype = Container:GetFreeSlots(bagID) | |
-- Scan from 1 to bagsize for normal bags or guild bank tabs, but from 40 to 67 for main bank slots | |
-- local baseIndex = (containerType == BANK) and 39 or 0 | |
local baseIndex = 0 | |
local index | |
for slotID = baseIndex + 1, baseIndex + bag.size do | |
index = slotID - baseIndex | |
link = Container:GetLink(slotID, bagID) | |
if link then | |
bag.ids[index] = tonumber(link:match("item:(%d+)")) | |
if IsEnchanted(link) then | |
bag.links[index] = link | |
end | |
count = Container:GetCount(slotID, bagID) | |
if count and count > 1 then | |
bag.counts[index] = count -- only save the count if it's > 1 (to save some space since a count of 1 is extremely redundant) | |
end | |
end | |
startTime, duration, isEnabled = Container:GetCooldown(slotID, bagID) | |
if startTime and startTime > 0 then | |
startTime = time() + GetRemainingCooldown(startTime) | |
bag.cooldowns[index] = format("%s|%s|1", startTime, duration) | |
end | |
end | |
addon.ThisCharacter.lastUpdate = time() | |
addon:SendMessage("DATASTORE_CONTAINER_UPDATED", bagID, containerType) | |
end | |
local function ScanBagSlotsInfo() | |
local char = addon.ThisCharacter | |
local numBagSlots = 0 | |
local numFreeBagSlots = 0 | |
for bagID = 0, NUM_BAG_SLOTS do | |
local bag = char.Containers["Bag" .. bagID] | |
numBagSlots = numBagSlots + bag.size | |
numFreeBagSlots = numFreeBagSlots + bag.freeslots | |
end | |
char.numBagSlots = numBagSlots | |
char.numFreeBagSlots = numFreeBagSlots | |
end | |
local function ScanBankSlotsInfo() | |
local char = addon.ThisCharacter | |
local numBankSlots = NUM_BANKGENERIC_SLOTS | |
local numFreeBankSlots = char.Containers["Bag"..MAIN_BANK_SLOTS].freeslots | |
for bagID = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do -- 5 to 11 | |
local bag = char.Containers["Bag" .. bagID] | |
numBankSlots = numBankSlots + bag.size | |
numFreeBankSlots = numFreeBankSlots + bag.freeslots | |
end | |
char.numBankSlots = numBankSlots | |
char.numFreeBankSlots = numFreeBankSlots | |
end | |
local function ScanGuildBankInfo() | |
-- only the current tab can be updated | |
local thisGuild = GetThisGuild() | |
if not thisGuild then return end | |
local tabID = GetCurrentGuildBankTab() | |
local t = thisGuild.Tabs[tabID] -- t = current tab | |
t.name, t.icon = GetGuildBankTabInfo(tabID) | |
t.visitedBy = UnitName("player") | |
t.ClientTime = time() | |
if GetLocale() == "enUS" then -- adjust this test if there's demand | |
t.ClientDate = date("%m/%d/%Y") | |
else | |
t.ClientDate = date("%d/%m/%Y") | |
end | |
t.ClientHour = tonumber(date("%H")) | |
t.ClientMinute = tonumber(date("%M")) | |
t.ServerHour, t.ServerMinute = GetGameTime() | |
end | |
local function ScanBag(bagID) | |
if bagID < -2 or bagID == -1 then return end | |
local char = addon.ThisCharacter | |
local bag = char.Containers["Bag" .. bagID] | |
if bagID == 0 then -- Bag 0 | |
bag.icon = "Interface\\Buttons\\Button-Backpack-Up" | |
bag.link = nil | |
elseif bagID == -2 then | |
bag.icon = "ICONS\\INV_Misc_Key_04.blp" | |
bag.link = nil | |
else -- Bags 1 through 11 | |
bag.icon = GetInventoryItemTexture("player", C_Container.ContainerIDToInventoryID(bagID)) | |
bag.link = GetInventoryItemLink("player", C_Container.ContainerIDToInventoryID(bagID)) | |
if bag.link then | |
local _, _, rarity = GetItemInfo(bag.link) | |
if rarity then -- in case rarity was known from a previous scan, and GetItemInfo returns nil for some reason .. don't overwrite | |
bag.rarity = rarity | |
end | |
end | |
end | |
ScanContainer(bagID, BAGS) | |
ScanBagSlotsInfo() | |
end | |
-- *** Event Handlers *** | |
local function OnBagUpdate(event, bag) | |
if bag < -2 or bag == -1 then return end | |
if (bag >= 5) and (bag <= 11) and not addon.isBankOpen then | |
return | |
end | |
ScanBag(bag) | |
end | |
local function OnBankFrameClosed() | |
addon.isBankOpen = nil | |
addon:UnregisterEvent("BANKFRAME_CLOSED") | |
addon:UnregisterEvent("PLAYERBANKSLOTS_CHANGED") | |
end | |
local function OnPlayerBankSlotsChanged(event, slotID) | |
-- from top left to bottom right, slotID = 1 to 28for main slots, and 29 to 35 for the additional bags | |
if (slotID >= 29) and (slotID <= 35) then | |
ScanBag(slotID - 24) -- bagID for bank bags goes from 5 to 11, so slotID - 24 | |
else | |
ScanContainer(MAIN_BANK_SLOTS, BANK) | |
ScanBankSlotsInfo() | |
end | |
end | |
local function OnBankFrameOpened() | |
addon.isBankOpen = true | |
for bagID = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do -- 5 to 11 | |
ScanBag(bagID) | |
end | |
ScanContainer(MAIN_BANK_SLOTS, BANK) | |
ScanBankSlotsInfo() | |
addon:RegisterEvent("BANKFRAME_CLOSED", OnBankFrameClosed) | |
addon:RegisterEvent("PLAYERBANKSLOTS_CHANGED", OnPlayerBankSlotsChanged) | |
end | |
local function OnGuildBankFrameClosed() | |
addon:UnregisterEvent("GUILDBANKFRAME_CLOSED") | |
addon:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED") | |
addon:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED") | |
local guildName = GetGuildInfo("player") | |
if guildName then | |
GuildBroadcast(MSG_SEND_BANK_TIMESTAMPS, GetBankTimestamps(guildName)) | |
end | |
end | |
local function OnGuildBankBagSlotsChanged() | |
ScanContainer(GetCurrentGuildBankTab(), GUILDBANK) | |
ScanGuildBankInfo() | |
end | |
local function OnGuildBankFrameOpened() | |
addon:RegisterEvent("GUILDBANKFRAME_CLOSED", OnGuildBankFrameClosed) | |
addon:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED", OnGuildBankBagSlotsChanged) | |
local thisGuild = GetThisGuild() | |
if thisGuild then | |
thisGuild.money = GetGuildBankMoney() | |
thisGuild.faction = UnitFactionGroup("player") | |
end | |
end | |
local function OnAuctionMultiSellStart() | |
-- if a multi sell starts, unregister bag updates. | |
addon:UnregisterEvent("BAG_UPDATE") | |
end | |
local function OnAuctionMultiSellUpdate(event, current, total) | |
if current == total then -- ex: multisell = 8 items, if we're on the 8th, resume bag updates. | |
addon:RegisterEvent("BAG_UPDATE", OnBagUpdate) | |
end | |
end | |
local function OnAuctionHouseClosed() | |
addon:UnregisterEvent("AUCTION_MULTISELL_START") | |
addon:UnregisterEvent("AUCTION_MULTISELL_UPDATE") | |
addon:UnregisterEvent("AUCTION_HOUSE_CLOSED") | |
addon:RegisterEvent("BAG_UPDATE", OnBagUpdate) -- just in case things went wrong | |
end | |
local function OnAuctionHouseShow() | |
-- when going to the AH, listen to multi-sell | |
addon:RegisterEvent("AUCTION_MULTISELL_START", OnAuctionMultiSellStart) | |
addon:RegisterEvent("AUCTION_MULTISELL_UPDATE", OnAuctionMultiSellUpdate) | |
addon:RegisterEvent("AUCTION_HOUSE_CLOSED", OnAuctionHouseClosed) | |
end | |
-- ** Mixins ** | |
local function _GetContainer(character, containerID) | |
-- containerID can be number or string | |
if type(containerID) == "number" then | |
return character.Containers["Bag" .. containerID] | |
end | |
return character.Containers[containerID] | |
end | |
local function _GetContainers(character) | |
return character.Containers | |
end | |
local BagTypeStrings = { | |
[1] = "Quiver", | |
[2] = "Ammo Pouch", | |
[4] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 1), -- "Soul Bag", | |
[8] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 7), -- "Leatherworking Bag", | |
[16] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 8), -- "Inscription Bag", | |
[32] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 2), -- "Herb Bag" | |
[64] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 3), -- "Enchanting Bag", | |
[128] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 4), -- "Engineering Bag", | |
[512] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 5), -- "Gem Bag", | |
[1024] = GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER, 6), -- "Mining Bag", | |
} | |
local function _GetContainerInfo(character, containerID) | |
local bag = _GetContainer(character, containerID) | |
local icon = bag.icon | |
local size = bag.size | |
if containerID == MAIN_BANK_SLOTS then -- main bank slots | |
icon = "Interface\\Icons\\inv_misc_enggizmos_17" | |
end | |
return icon, bag.link, size, bag.freeslots, BagTypeStrings[bag.bagtype] | |
end | |
local function _GetContainerSize(character, containerID) | |
-- containerID can be number or string | |
if type(containerID) == "number" then | |
return character.Containers["Bag" .. containerID].size | |
end | |
return character.Containers[containerID].size | |
end | |
local rarityColors = { | |
[2] = "|cFF1EFF00", | |
[3] = "|cFF0070DD", | |
[4] = "|cFFA335EE" | |
} | |
local function _GetColoredContainerSize(character, containerID) | |
local bag = _GetContainer(character, containerID) | |
local size = _GetContainerSize(character, containerID) | |
if bag.rarity and rarityColors[bag.rarity] then | |
return format("%s%s", rarityColors[bag.rarity], size) | |
end | |
return format("%s%s", "|cFFFFFFFF", size) | |
end | |
local function _GetSlotInfo(bag, slotID) | |
assert(type(bag) == "table") -- this is the pointer to a bag table, obtained through addon:GetContainer() | |
assert(type(slotID) == "number") | |
local link = bag.links[slotID] | |
-- return itemID, itemLink, itemCount | |
return bag.ids[slotID], link, bag.counts[slotID] or 1 | |
end | |
local function _GetContainerCooldownInfo(bag, slotID) | |
assert(type(bag) == "table") -- this is the pointer to a bag table, obtained through addon:GetContainer() | |
assert(type(slotID) == "number") | |
local cd = bag.cooldowns[slotID] | |
if cd then | |
-- "389165.957|259200|1", | |
local startTime, duration, isEnabled = strsplit("|", bag.cooldowns[slotID]) | |
local remaining = duration - (GetTime() - startTime) | |
if remaining > 0 then -- valid cd ? return it | |
return tonumber(startTime), tonumber(duration), tonumber(isEnabled) | |
end | |
-- cooldown expired ? clean it from the db | |
bag.cooldowns[slotID] = nil | |
end | |
end | |
local function _GetContainerItemCount(character, searchedID) | |
local bagCount = 0 | |
local bankCount = 0 | |
local id | |
for containerName, container in pairs(character.Containers) do | |
for slotID = 1, container.size do | |
id = container.ids[slotID] | |
if (id) and (id == searchedID) then | |
local itemCount = container.counts[slotID] or 1 | |
if (containerName == "Bag"..MAIN_BANK_SLOTS) then | |
bankCount = bankCount + itemCount | |
elseif (containerName == "Bag-2") then | |
bagCount = bagCount + itemCount | |
else | |
local bagNum = tonumber(string.sub(containerName, 4)) | |
if (bagNum >= 0) and (bagNum <= 4) then | |
bagCount = bagCount + itemCount | |
else | |
bankCount = bankCount + itemCount | |
end | |
end | |
end | |
end | |
end | |
return bagCount, bankCount | |
end | |
local function _GetNumBagSlots(character) | |
return character.numBagSlots | |
end | |
local function _GetNumFreeBagSlots(character) | |
return character.numFreeBagSlots | |
end | |
local function _GetNumBankSlots(character) | |
return character.numBankSlots | |
end | |
local function _GetNumFreeBankSlots(character) | |
return character.numFreeBankSlots | |
end | |
local function _GetGuildBankItemCount(guild, searchedID) | |
local count = 0 | |
for _, container in pairs(guild.Tabs) do | |
for slotID, id in pairs(container.ids) do | |
if (id == searchedID) then | |
count = count + (container.counts[slotID] or 1) | |
end | |
end | |
end | |
return count | |
end | |
local function _GetGuildBankTab(guild, tabID) | |
return guild.Tabs[tabID] | |
end | |
local function _GetGuildBankTabName(guild, tabID) | |
return guild.Tabs[tabID].name | |
end | |
local function _IterateGuildBankSlots(guild, callback) | |
for tabID, tab in pairs(guild.Tabs) do | |
if tab.name then | |
for slotID = 1, 98 do | |
local itemID, itemLink, itemCount, isBattlePet = _GetSlotInfo(tab, slotID) | |
-- Callback only if there is an item in that slot | |
if itemID then | |
local location = format("%s, %s - col %d/row %d)", GUILD_BANK, tab.name, floor((slotID-1)/7)+1, ((slotID-1)%7)+1) | |
callback(location, itemID, itemLink, itemCount, isBattlePet) | |
end | |
end | |
end | |
end | |
end | |
local function _GetGuildBankTabIcon(guild, tabID) | |
return guild.Tabs[tabID].icon | |
end | |
local function _GetGuildBankTabItemCount(guild, tabID, searchedID) | |
local count = 0 | |
local container = guild.Tabs[tabID] | |
for slotID, id in pairs(container.ids) do | |
if (id == searchedID) then | |
count = count + (container.counts[slotID] or 1) | |
end | |
end | |
return count | |
end | |
local function _GetGuildBankTabLastUpdate(guild, tabID) | |
return guild.Tabs[tabID].ClientTime | |
end | |
local function _GetGuildBankMoney(guild) | |
return guild.money | |
end | |
local function _GetGuildBankFaction(guild) | |
return guild.faction | |
end | |
local function _ImportGuildBankTab(guild, tabID, data) | |
wipe(guild.Tabs[tabID]) -- clear existing data | |
guild.Tabs[tabID] = data | |
end | |
local function _GetGuildBankTabSuppliers() | |
return guildMembers | |
end | |
local function _GetGuildMemberBankTabInfo(member, tabName) | |
-- for the current guild, return the guild member's data about a given tab | |
if guildMembers[member] then | |
if guildMembers[member][tabName] then | |
local tab = guildMembers[member][tabName] | |
return tab.clientTime, tab.serverHour, tab.serverMinute | |
end | |
end | |
end | |
local function _RequestGuildMemberBankTab(member, tabName) | |
GuildWhisper(member, MSG_BANKTAB_REQUEST, tabName) | |
end | |
local function _RejectBankTabRequest(member) | |
GuildWhisper(member, MSG_BANKTAB_REQUEST_REJECTED) | |
end | |
local function _SendBankTabToGuildMember(member, tabName) | |
-- send the actual content of a bank tab to a guild member | |
local thisGuild = GetThisGuild() | |
if thisGuild then | |
local tabID | |
if guildMembers[member] then | |
if guildMembers[member][tabName] then | |
tabID = guildMembers[member][tabName].id | |
end | |
end | |
if tabID then | |
GuildWhisper(member, MSG_BANKTAB_TRANSFER, thisGuild.Tabs[tabID]) | |
end | |
end | |
end | |
local function _IterateBags(character, callback) | |
if not character.Containers then return end | |
for containerName, container in pairs(character.Containers) do | |
for slotID = 1, container.size do | |
local itemID = container.ids[slotID] | |
local stop = callback(containerName, container, slotID, itemID) | |
if stop then return end -- exit if the callback returns true | |
end | |
end | |
end | |
local function _SearchBagsForItem(character, searchedItemID, onItemFound) | |
-- Iterate bag contents, call the callback if the item is found | |
_IterateBags(character, function(bagName, bag, slotID, itemID) | |
if itemID == searchedItemID then | |
onItemFound(bagName, bag, slotID) | |
end | |
end) | |
end | |
local PublicMethods = { | |
GetContainer = _GetContainer, | |
GetContainers = _GetContainers, | |
GetContainerInfo = _GetContainerInfo, | |
GetContainerSize = _GetContainerSize, | |
GetColoredContainerSize = _GetColoredContainerSize, | |
GetSlotInfo = _GetSlotInfo, | |
GetContainerCooldownInfo = _GetContainerCooldownInfo, | |
GetContainerItemCount = _GetContainerItemCount, | |
GetNumBagSlots = _GetNumBagSlots, | |
GetNumFreeBagSlots = _GetNumFreeBagSlots, | |
GetNumBankSlots = _GetNumBankSlots, | |
GetNumFreeBankSlots = _GetNumFreeBankSlots, | |
GetGuildBankItemCount = _GetGuildBankItemCount, | |
GetGuildBankTab = _GetGuildBankTab, | |
GetGuildBankTabName = _GetGuildBankTabName, | |
IterateGuildBankSlots = _IterateGuildBankSlots, | |
GetGuildBankTabIcon = _GetGuildBankTabIcon, | |
GetGuildBankTabItemCount = _GetGuildBankTabItemCount, | |
GetGuildBankTabLastUpdate = _GetGuildBankTabLastUpdate, | |
GetGuildBankMoney = _GetGuildBankMoney, | |
GetGuildBankFaction = _GetGuildBankFaction, | |
ImportGuildBankTab = _ImportGuildBankTab, | |
GetGuildMemberBankTabInfo = _GetGuildMemberBankTabInfo, | |
RequestGuildMemberBankTab = _RequestGuildMemberBankTab, | |
RejectBankTabRequest = _RejectBankTabRequest, | |
SendBankTabToGuildMember = _SendBankTabToGuildMember, | |
GetGuildBankTabSuppliers = _GetGuildBankTabSuppliers, | |
IterateBags = _IterateBags, | |
SearchBagsForItem = _SearchBagsForItem, | |
} | |
-- *** Guild Comm *** | |
--[[ *** Protocol *** | |
At login: | |
Broadcast of guild bank timers on the guild channel | |
After the guild bank frame is closed: | |
Broadcast of guild bank timers on the guild channel | |
Client addon calls: DataStore:RequestGuildMemberBankTab() | |
Client Server | |
==> MSG_BANKTAB_REQUEST | |
<== MSG_BANKTAB_REQUEST_ACK (immediate ack) | |
<== MSG_BANKTAB_REQUEST_REJECTED (stop) | |
or | |
<== MSG_BANKTAB_TRANSFER (actual data transfer) | |
--]] | |
local function OnAnnounceLogin(self, guildName) | |
-- when the main DataStore module sends its login info, share the guild bank last visit time across guild members | |
local timestamps = GetBankTimestamps(guildName) | |
if timestamps then -- nil if guild bank hasn't been visited yet, so don't broadcast anything | |
GuildBroadcast(MSG_SEND_BANK_TIMESTAMPS, timestamps) | |
end | |
end | |
local function OnGuildMemberOffline(self, member) | |
guildMembers[member] = nil | |
addon:SendMessage("DATASTORE_GUILD_BANKTABS_UPDATED", member) | |
end | |
local GuildCommCallbacks = { | |
[MSG_SEND_BANK_TIMESTAMPS] = function(sender, timestamps) | |
if sender ~= UnitName("player") then -- don't send back to self | |
local timestamps = GetBankTimestamps() | |
if timestamps then | |
GuildWhisper(sender, MSG_BANK_TIMESTAMPS_REPLY, timestamps) -- reply by sending my own data.. | |
end | |
end | |
SaveBankTimestamps(sender, timestamps) | |
end, | |
[MSG_BANK_TIMESTAMPS_REPLY] = function(sender, timestamps) | |
SaveBankTimestamps(sender, timestamps) | |
end, | |
[MSG_BANKTAB_REQUEST] = function(sender, tabName) | |
-- trigger the event only, actual response (ack or not) must be handled by client addons | |
GuildWhisper(sender, MSG_BANKTAB_REQUEST_ACK) -- confirm that the request has been received | |
addon:SendMessage("DATASTORE_BANKTAB_REQUESTED", sender, tabName) | |
end, | |
[MSG_BANKTAB_REQUEST_ACK] = function(sender) | |
addon:SendMessage("DATASTORE_BANKTAB_REQUEST_ACK", sender) | |
end, | |
[MSG_BANKTAB_REQUEST_REJECTED] = function(sender) | |
addon:SendMessage("DATASTORE_BANKTAB_REQUEST_REJECTED", sender) | |
end, | |
[MSG_BANKTAB_TRANSFER] = function(sender, data) | |
local guildName = GetGuildInfo("player") | |
local guild = GetThisGuild() | |
for tabID, tab in pairs(guild.Tabs) do | |
if tab.name == data.name then -- this is the tab being updated | |
_ImportGuildBankTab(guild, tabID, data) | |
addon:SendMessage("DATASTORE_BANKTAB_UPDATE_SUCCESS", sender, guildName, data.name, tabID) | |
GuildBroadcast(MSG_SEND_BANK_TIMESTAMPS, GetBankTimestamps(guildName)) | |
end | |
end | |
end, | |
} | |
function addon:OnInitialize() | |
addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults) | |
UpdateDB() | |
DataStore:RegisterModule(addonName, addon, PublicMethods) | |
DataStore:SetGuildCommCallbacks(commPrefix, GuildCommCallbacks) | |
DataStore:SetCharacterBasedMethod("GetContainer") | |
DataStore:SetCharacterBasedMethod("GetContainers") | |
DataStore:SetCharacterBasedMethod("GetContainerInfo") | |
DataStore:SetCharacterBasedMethod("GetContainerSize") | |
DataStore:SetCharacterBasedMethod("GetColoredContainerSize") | |
DataStore:SetCharacterBasedMethod("GetContainerItemCount") | |
DataStore:SetCharacterBasedMethod("GetNumBagSlots") | |
DataStore:SetCharacterBasedMethod("GetNumFreeBagSlots") | |
DataStore:SetCharacterBasedMethod("GetNumBankSlots") | |
DataStore:SetCharacterBasedMethod("GetNumFreeBankSlots") | |
DataStore:SetCharacterBasedMethod("IterateBags") | |
DataStore:SetCharacterBasedMethod("SearchBagsForItem") | |
DataStore:SetGuildBasedMethod("GetGuildBankItemCount") | |
DataStore:SetGuildBasedMethod("GetGuildBankTab") | |
DataStore:SetGuildBasedMethod("GetGuildBankTabName") | |
DataStore:SetGuildBasedMethod("IterateGuildBankSlots") | |
DataStore:SetGuildBasedMethod("GetGuildBankTabIcon") | |
DataStore:SetGuildBasedMethod("GetGuildBankTabItemCount") | |
DataStore:SetGuildBasedMethod("GetGuildBankTabLastUpdate") | |
DataStore:SetGuildBasedMethod("GetGuildBankMoney") | |
DataStore:SetGuildBasedMethod("GetGuildBankFaction") | |
DataStore:SetGuildBasedMethod("ImportGuildBankTab") | |
addon:RegisterMessage("DATASTORE_ANNOUNCELOGIN", OnAnnounceLogin) | |
addon:RegisterMessage("DATASTORE_GUILD_MEMBER_OFFLINE", OnGuildMemberOffline) | |
addon:RegisterComm(commPrefix, DataStore:GetGuildCommHandler()) | |
end | |
function addon:OnEnable() | |
-- manually update bags 0 to 4, then register the event, this avoids reacting to the flood of BAG_UPDATE events at login | |
for bagID = 0, NUM_BAG_SLOTS do | |
ScanBag(bagID) | |
end | |
if HasKey() then | |
ScanBag(-2) | |
end | |
addon:RegisterEvent("BAG_UPDATE", OnBagUpdate) | |
addon:RegisterEvent("BANKFRAME_OPENED", OnBankFrameOpened) | |
addon:RegisterEvent("GUILDBANKFRAME_OPENED", OnGuildBankFrameOpened) | |
-- disable bag updates during multi sell at the AH | |
addon:RegisterEvent("AUCTION_HOUSE_SHOW", OnAuctionHouseShow) | |
end | |
function addon:OnDisable() | |
addon:UnregisterEvent("BAG_UPDATE") | |
addon:UnregisterEvent("BANKFRAME_OPENED") | |
addon:UnregisterEvent("GUILDBANKFRAME_OPENED") | |
addon:UnregisterEvent("AUCTION_HOUSE_SHOW") | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ItemRack = {} | |
local _ | |
ItemRack.Version = "3.73" | |
function ItemRack.IsClassic() | |
return WOW_PROJECT_ID == WOW_PROJECT_CLASSIC | |
end | |
function ItemRack.IsBCC() | |
return WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC | |
end | |
function ItemRack.IsWrath() | |
return WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC | |
end | |
local LDB = LibStub("LibDataBroker-1.1") | |
local LDBIcon = LibStub("LibDBIcon-1.0") | |
ItemRackUser = { | |
Sets = {}, -- user's sets | |
ItemsUsed = {}, -- items that have been used (for notify purposes) | |
Hidden = {}, -- items the user chooses to hide in menus | |
Queues = {}, -- item auto queue sorts | |
QueuesEnabled = {}, -- which queues are enabled | |
Locked = "OFF", -- buttons locked | |
EnableEvents = "ON", -- whether all events enabled | |
EnableQueues = "ON", -- whether all auto queues enabled | |
EnablePerSetQueues = "OFF", | |
ButtonSpacing = 4, -- padding between docked buttons | |
Alpha = 1, -- alpha of buttons | |
MainScale = 1, -- scale of the dockable buttons | |
MenuScale = .85, -- scale of the menu in relation to docked buttons | |
SetMenuWrap = "OFF", -- whether user defines when to wrap the menu | |
SetMenuWrapValue = 3, -- when to wrap the menu if user defined | |
} | |
ItemRackSettings = { | |
MenuOnShift = "OFF", -- open menus on shift only | |
MenuOnRight = "OFF", -- open menus on right-click only | |
HideOOC = "OFF", -- hide dockable buttons when out of combat | |
HidePetBattle = "ON", -- hide dockable buttons during pet battles | |
Notify = "ON", -- notify when a used item comes off cooldown | |
NotifyThirty = "OFF", -- notify when a used item reaches 30 seconds cooldown | |
NotifyChatAlso = "OFF", -- send cooldown notifications to chat also | |
ShowTooltips = "ON", -- show all itemrack tooltips | |
TinyTooltips = "OFF", -- whether to condense tooltips to most important info | |
TooltipFollow = "OFF", -- whether tooltip follows pointer | |
CooldownCount = "OFF", -- whether cooldowns displayed numerically over buttons | |
LargeNumbers = "OFF", -- whether cooldown numbers displayed in large font | |
AllowEmpty = "ON", -- allow empty slot as a choice in menus | |
HideTradables = "OFF", -- allow non-soulbound gear to appear in menu | |
AllowHidden = "ON", -- allow the ability to hide items/sets in the menu with alt+click | |
ShowMinimap = true, -- whether to show the minimap button | |
TrinketMenuMode = "OFF", -- whether to merge top/bottom trinkets to one menu (leftclick=top,rightclick=bottom) | |
AnotherOther = "OFF", -- whether to dock the merged trinket menu to bottom trinket | |
EquipToggle = "OFF", -- whether to toggle equipping a set when choosing to equip it | |
ShowHotKeys = "OFF", -- show key bindings on dockable buttons | |
Cooldown90 = "OFF", -- whether to count cooldown in seconds at 90 instead of 60 | |
EquipOnSetPick = "OFF", -- whether to equip a set when picked in the set tab of options | |
MinimapTooltip = "ON", -- whether to display the minimap button tooltip to explain clicks | |
CharacterSheetMenus = "ON", -- whether to display slot menus on mouseover of the character sheet | |
DisableAltClick = "OFF", -- whether to disable Alt+click from toggling auto queue (to allow self cast through) | |
} | |
-- these are default items with non-standard behavior | |
-- keep = 1/nil whether to suspend auto queue while equipped | |
-- priority = 1/nil whether to equip as it comes off cooldown even if equipped is off cooldown waiting to be used | |
-- delay = time(seconds) after use before swapping out | |
ItemRackItems = { | |
["11122"] = { keep=1 }, -- carrot on a stick | |
["13209"] = { keep=1 }, -- seal of the dawn | |
["19812"] = { keep=1 }, -- rune of the dawn | |
["12846"] = { keep=1 }, -- argent dawn commission | |
["25653"] = { keep=1 }, -- riding crop | |
} | |
ItemRack.NoTitansGrip = { | |
["Polearms"] = 1, | |
["Fishing Poles"] = 1, | |
["Staves"] = 1 | |
} | |
ItemRack.Menu = {} | |
ItemRack.LockList = {} -- index -2 to 11, flag whether item is tagged already for swap | |
if ItemRack.IsClassic() then | |
ItemRack.BankSlots = { -1,5,6,7,8,9,10 } | |
elseif ItemRack.IsBCC() or ItemRack.IsWrath() then | |
ItemRack.BankSlots = { -1,5,6,7,8,9,10,11 } | |
end | |
ItemRack.KnownItems = {} -- cache of known item locations for fast lookup | |
ItemRack.SlotInfo = { | |
[0] = { name="AmmoSlot", real="Ammo", INVTYPE_AMMO=1 }, | |
[1] = { name="HeadSlot", real="Head", INVTYPE_HEAD=1 }, | |
[2] = { name="NeckSlot", real="Neck", INVTYPE_NECK=1 }, | |
[3] = { name="ShoulderSlot", real="Shoulder", INVTYPE_SHOULDER=1 }, | |
[4] = { name="ShirtSlot", real="Shirt", INVTYPE_BODY=1 }, | |
[5] = { name="ChestSlot", real="Chest", INVTYPE_CHEST=1, INVTYPE_ROBE=1 }, | |
[6] = { name="WaistSlot", real="Waist", INVTYPE_WAIST=1 }, | |
[7] = { name="LegsSlot", real="Legs", INVTYPE_LEGS=1 }, | |
[8] = { name="FeetSlot", real="Feet", INVTYPE_FEET=1 }, | |
[9] = { name="WristSlot", real="Wrist", INVTYPE_WRIST=1 }, | |
[10] = { name="HandsSlot", real="Hands", INVTYPE_HAND=1 }, | |
[11] = { name="Finger0Slot", real="Top Finger", INVTYPE_FINGER=1, other=12 }, | |
[12] = { name="Finger1Slot", real="Bottom Finger", INVTYPE_FINGER=1, other=11 }, | |
[13] = { name="Trinket0Slot", real="Top Trinket", INVTYPE_TRINKET=1, other=14 }, | |
[14] = { name="Trinket1Slot", real="Bottom Trinket", INVTYPE_TRINKET=1, other=13 }, | |
[15] = { name="BackSlot", real="Cloak", INVTYPE_CLOAK=1 }, | |
[16] = { name="MainHandSlot", real="Main hand", INVTYPE_WEAPONMAINHAND=1, INVTYPE_2HWEAPON=1, INVTYPE_WEAPON=1, other=17}, | |
[17] = { name="SecondaryHandSlot", real="Off hand", INVTYPE_WEAPON=1, INVTYPE_WEAPONOFFHAND=1, INVTYPE_SHIELD=1, INVTYPE_HOLDABLE=1, other=16}, | |
[18] = { name="RangedSlot", real="Ranged", INVTYPE_RANGED=1, INVTYPE_RANGEDRIGHT=1, INVTYPE_THROWN=1, INVTYPE_RELIC=1}, | |
[19] = { name="TabardSlot", real="Tabard", INVTYPE_TABARD=1 }, | |
} | |
ItemRack.DockInfo = { -- docking-dependent values | |
LEFT = { xoff=1, yoff=0, menuSide="TOP", menuDir="TOP", orient="VERT", xadd=40, yadd=0 }, | |
RIGHT = { xoff=-1, yoff=0, menuSide="TOP", menuDir="TOP", orient="VERT", xadd=40, yadd=0 }, | |
TOP = { xoff=0, yoff=-1, menuSide="LEFT", menuDir="LEFT", orient="HORZ", xadd=0, yadd=40 }, | |
BOTTOM = { xoff=0, yoff=1, menuSide="LEFT", menuDir="LEFT", orient="HORZ", xadd=0, yadd=40 }, | |
TOPRIGHTTOPLEFT = { xoff=0, yoff=8, xdir=1, ydir=-1, xstart=8, ystart=-8 }, | |
BOTTOMRIGHTBOTTOMLEFT = { xoff=0, yoff=-8, xdir=1, ydir=1, xstart=8, ystart=44 }, | |
TOPLEFTTOPRIGHT = { xoff=0, yoff=8, xdir=-1, ydir=-1, xstart=-44, ystart=-8 }, | |
BOTTOMLEFTBOTTOMRIGHT = { xoff=0, yoff=-8, xdir=-1, ydir=1, xstart=-44, ystart=44 }, | |
TOPRIGHTBOTTOMRIGHT = { xoff=8, yoff=0, xdir=-1, ydir=1, xstart=-44, ystart=44 }, | |
BOTTOMRIGHTTOPRIGHT = { xoff=8, yoff=0, xdir=-1, ydir=-1, xstart=-44, ystart=-8 }, | |
TOPLEFTBOTTOMLEFT = { xoff=-8, yoff=0, xdir=1, ydir=1, xstart=8, ystart=44 }, | |
BOTTOMLEFTTOPLEFT = { xoff=-8, yoff=0, xdir=1, ydir=-1, xstart=8, ystart=-8 }, | |
} | |
ItemRack.OppositeSide = { LEFT="RIGHT", RIGHT="LEFT", TOP="BOTTOM", BOTTOM="TOP" } | |
ItemRack.MenuMouseoverFrames = {PaperDollFrame=1,CharacterTrinket1Slot=1} -- frames besides ItemRackMenuFrame that can keep menu open on mouseover | |
ItemRack.CombatQueue = {} -- items waiting to swap in | |
ItemRack.RunAfterCombat = {} -- functions to run when player drops out of combat | |
-- miscellaneous tooltips ElementName, Line1, Line2 | |
ItemRack.TooltipInfo = { | |
{"ItemRackButtonMenuLock","Lock Buttons","Toggle locked state to prevent buttons/menus from moving and to hide borders and control buttons.\n\nHold ALT while you open a menu to access these control buttons while locked."}, | |
{"ItemRackButtonMenuQueue","Auto Queue","Set up the auto queue for this slot.\n\nAlt+click the slot this menu opened from to toggle its auto queue on/off."}, | |
{"ItemRackButtonMenuOptions","Options","Open Options window to change settings, configure sets or auto queues."}, | |
{"ItemRackButtonMenuClose","Remove","Remove the slot this menu opened from."}, | |
{"ItemRackOptSetsHideCheckButton","Hide Set","Check this to make the set hidden in menus."}, | |
{"ItemRackOptItemStatsPriority","Priority","Check this to make this item auto equip when it comes off cooldown even if the equipped item is off cooldown and waiting to be used."}, | |
{"ItemRackOptItemStatsKeepEquipped","Pause Queue","Check this to suspend the auto queue for this slot until the item is unequipped. (For instance if you have another mod handling the auto equip of a riding crop."}, | |
{"ItemRackOptQueueEnable","Auto Queue This Slot","Check this to allow this slot to auto queue. When an item goes on cooldown, it will swap for an item higher on the list that's off cooldown."}, | |
{"ItemRackOptSetsHideCheckButton","Hide","Hide this set in menus. (Equivalent of Alt+clicking the set in the menu)"}, | |
{"ItemRackOptSetsSaveButton","Save Set","Save this set. Some settings like key binding, cloak/helm visibility and whether it's hidden can only be changed to a saved set."}, | |
{"ItemRackOptSetsDeleteButton","Delete Set","Delete this set definition. If you want to remove it from the menu and may want it again in the future, check 'Hide' to the left."}, | |
{"ItemRackOptSetsBindButton","Bind Key to Set","This will let you bind a key or key combination to equip a set."}, | |
{"ItemRackOptEventNew","New Event","Create a new event."}, | |
{"ItemRackOptEventEdit","Edit Event","Edit this event. Note: if you edit the name and save, it will create a copy of the event with the new name."}, | |
{"ItemRackOptEventDelete","Delete Event","If this event is enabled or has a set associated with it, it will remove the tags and drop it in the list. If this is an untagged event, it will delete it entirely."}, | |
{"ItemRackOptEventEditSave","Save Event","Saves changes to this event. Note: if you edit the name and save, it will create a copy of the event with the new name."}, | |
{"ItemRackOptEventEditCancel","Cancel Changes","Cancel any changes just made to this event and return to event list."}, | |
{"ItemRackOptEventEditBuffAnyMount","Any mount","Checking this will check if any mount is active instead of a specific buff."}, | |
{"ItemRackOptEventEditExpand","Edit in Editor","This will detach the script edit box above to a resizable text editor."}, | |
{"ItemRackFloatingEditorUndo","Undo","Revert the text to its last saved state."}, | |
{"ItemRackFloatingEditorTest","Test","Run the text below as a script to make sure there are no syntax errors. (Script Errors in Interface Options should be enabled to see any)\nNote: This test cannot simulate any condition or test for expected behavior other than the ability to run."}, | |
{"ItemRackFloatingEditorSave","Save Event","Save changes to this event and return to the event list."}, | |
{"ItemRackOptToggleInvAll","Toggle All","This will toggle between selecting all slots and selecting no slots."} | |
} | |
ItemRack.BankOpen = nil -- 1 if bank is open, nil if not | |
ItemRack.EventHandlers = {} | |
ItemRack.ExternalEventHandlers = {} | |
function ItemRack.InitEventHandlers() | |
local handler = ItemRack.EventHandlers | |
handler.ITEM_LOCK_CHANGED = ItemRack.OnItemLockChanged | |
handler.ACTIONBAR_UPDATE_COOLDOWN = ItemRack.UpdateButtonCooldowns | |
handler.UNIT_INVENTORY_CHANGED = ItemRack.OnUnitInventoryChanged | |
handler.UPDATE_BINDINGS = ItemRack.KeyBindingsChanged | |
handler.PLAYER_REGEN_ENABLED = ItemRack.OnLeavingCombatOrDeath | |
handler.PLAYER_UNGHOST = ItemRack.OnLeavingCombatOrDeath | |
handler.PLAYER_ALIVE = ItemRack.OnLeavingCombatOrDeath | |
handler.PLAYER_REGEN_DISABLED = ItemRack.OnEnteringCombat | |
handler.BANKFRAME_CLOSED = ItemRack.OnBankClose | |
handler.BANKFRAME_OPENED = ItemRack.OnBankOpen | |
handler.UNIT_SPELLCAST_START = ItemRack.OnCastingStart | |
handler.UNIT_SPELLCAST_STOP = ItemRack.OnCastingStop | |
handler.UNIT_SPELLCAST_SUCCEEDED = ItemRack.OnCastingStop | |
handler.UNIT_SPELLCAST_INTERRUPTED = ItemRack.OnCastingStop | |
handler.UNIT_SPELLCAST_FAILED = ItemRack.OnCastingStop | |
handler.CHARACTER_POINTS_CHANGED = ItemRack.UpdateClassSpecificStuff | |
handler.PLAYER_TALENT_UPDATE = ItemRack.UpdateClassSpecificStuff | |
handler.PLAYER_ENTERING_WORLD = ItemRack.OnEnterWorld | |
handler.ACTIVE_TALENT_GROUP_CHANGED = ItemRack.UpdateClassSpecificStuff | |
-- handler.PET_BATTLE_OPENING_START = ItemRack.OnEnteringPetBattle | |
-- handler.PET_BATTLE_CLOSE = ItemRack.OnLeavingPetBattle | |
end | |
do | |
local Masque = LibStub("Masque", true) or (LibMasque and LibMasque("Button")) | |
if Masque then | |
ItemRack.MasqueGroups = {} | |
ItemRack.MasqueGroups[1] = Masque:Group("ItemRack", "On screen panels") | |
ItemRack.MasqueGroups[2] = Masque:Group("ItemRack", "On screen menus") | |
ItemRack.MasqueGroups[3] = Masque:Group("ItemRack", "Character info menus") | |
ItemRack.MasqueGroups[4] = Masque:Group("ItemRack", "Map icon menu") | |
end | |
end | |
function ItemRack.OnEvent(self,event,...) | |
ItemRack.EventHandlers[event](self,event,...) | |
end | |
--- Allows third-party addons to listen to ItemRack events, like saving and deleting a set. | |
function ItemRack.RegisterExternalEventListener(self,event,handler) | |
local handlers = ItemRack.ExternalEventHandlers[event] | |
if handlers == nil then | |
handlers = {} | |
ItemRack.ExternalEventHandlers[event] = handlers | |
end | |
table.insert(handlers, handler) | |
end | |
function ItemRack.FireItemRackEvent(self,event,...) | |
local handlers = ItemRack.ExternalEventHandlers[event] | |
if handlers ~= nil then | |
for _, handler in pairs(handlers) do | |
handler(event,...) | |
end | |
end | |
end | |
function ItemRack.OnPlayerLogin() | |
-- Normally some of these methods cannot be called in combat without causing errors, but since we run these IMMEDIATELY | |
-- on PLAYER_LOGIN event we get a grace period where it allows us to run secure code in combat. | |
ItemRack.InitBroker() | |
ItemRack.InitEventHandlers() | |
ItemRack.InitTimers() | |
ItemRack.InitCore() | |
ItemRack.InitButtons() | |
ItemRack.InitEvents() | |
end | |
function ItemRack.OnEnterWorld() | |
ItemRack.SetSetBindings() | |
end | |
local loader = CreateFrame("Frame",nil, self, BackdropTemplateMixin and "BackdropTemplate") -- need a new temp frame here, ItemRackFrame is not created yet | |
loader:RegisterEvent("PLAYER_LOGIN") | |
loader:SetScript("OnEvent", ItemRack.OnPlayerLogin) | |
function ItemRack.OnCastingStart(self,event,unit) | |
if unit=="player" then | |
if CastingInfo() or ChannelInfo() then | |
ItemRack.NowCasting = true | |
end | |
end | |
end | |
function ItemRack.OnCastingStop(self,event,unit) | |
if unit=="player" then | |
if not ItemRack.NowCasting then | |
return | |
else | |
ItemRack.NowCasting = nil | |
-- This check is in the event that a successful spellcast puts you in combat | |
if event ~= "UNIT_SPELLCAST_SUCCEEDED" and not ItemRack.inCombat then | |
ItemRack.ProcessCombatQueue() | |
else | |
ItemRack.OnSpellSucceed() | |
end | |
--[[ | |
if #(ItemRack.SetsWaiting)>0 and not ItemRack.AnythingLocked() then | |
ItemRack.ProcessSetsWaiting() | |
end | |
]] | |
end | |
end | |
end | |
function ItemRack.OnItemLockChanged() | |
ItemRack.StartTimer("LocksChanged") | |
ItemRack.LocksHaveChanged = 1 | |
end | |
function ItemRack.OnSpellSucceed() | |
ItemRack.StartTimer("DelayedCombatQueue") | |
end | |
function ItemRack.DelayedCombatQueue() | |
if ItemRack.inCombat or ItemRack.NowCasting then | |
return | |
end | |
ItemRack.ProcessCombatQueue() | |
end | |
function ItemRack.OnUnitInventoryChanged(self,event,unit) | |
if unit=="player" then | |
ItemRack.UpdateButtons() | |
if ItemRackMenuFrame:IsVisible() then | |
ItemRack.BuildMenu() | |
end | |
if ItemRackOptFrame and ItemRackOptFrame:IsVisible() then | |
for i=0,19 do | |
if not ItemRackOpt.Inv[i].selected then | |
ItemRackOpt.Inv[i].id = ItemRack.GetID(i) | |
end | |
end | |
ItemRackOpt.UpdateInv() | |
end | |
end | |
end | |
function ItemRack.OnLeavingCombatOrDeath() | |
ItemRack.inCombat = InCombatLockdown() | |
if ItemRack.NowCasting then | |
return | |
end | |
ItemRack.ProcessCombatQueue() | |
end | |
function ItemRack.ProcessCombatQueue() | |
if not ItemRack.IsPlayerReallyDead() and next(ItemRack.CombatQueue) then | |
local combat = ItemRackUser.Sets["~CombatQueue"].equip | |
local queue = ItemRack.CombatQueue | |
for i in pairs(combat) do | |
combat[i] = nil | |
end | |
for i in pairs(queue) do | |
combat[i] = queue[i] | |
queue[i] = nil | |
end | |
ItemRackUser.Sets["~CombatQueue"].oldset = ItemRack.CombatSet | |
ItemRack.UpdateCombatQueue() | |
ItemRack.EquipSet("~CombatQueue") | |
end | |
local inLockdown = InCombatLockdown() | |
if not inLockdown then | |
if ItemRackOptFrame and ItemRackOptFrame:IsVisible() then | |
ItemRackOpt.ListScrollFrameUpdate() | |
ItemRackOptSetsBindButton:Enable() | |
end | |
if ItemRack.ReflectHideOOC then | |
ItemRack.ReflectHideOOC() | |
end | |
if next(ItemRack.RunAfterCombat) then | |
for i=1,#(ItemRack.RunAfterCombat) do | |
ItemRack[ItemRack.RunAfterCombat[i]]() | |
end | |
for i=1,#(ItemRack.RunAfterCombat) do | |
table.remove(ItemRack.RunAfterCombat,i) | |
end | |
end | |
end | |
end | |
function ItemRack.OnEnteringCombat() | |
ItemRack.inCombat = 1 | |
if ItemRackOptFrame and ItemRackOptFrame:IsVisible() then | |
ItemRackOpt.ListScrollFrameUpdate() | |
ItemRackOptSetsBindButton:Disable() | |
end | |
if ItemRack.ReflectHideOOC then | |
ItemRack.ReflectHideOOC() | |
end | |
end | |
function ItemRack.OnEnteringPetBattle() | |
ItemRack.inPetBattle = 1 | |
if ItemRack.ReflectHidePetBattle then | |
ItemRack.ReflectHidePetBattle() | |
end | |
end | |
function ItemRack.OnLeavingPetBattle() | |
ItemRack.inPetBattle = nil | |
if ItemRack.ReflectHidePetBattle then | |
ItemRack.ReflectHidePetBattle() | |
end | |
end | |
function ItemRack.OnBankClose() | |
ItemRack.BankOpen = nil | |
ItemRackMenuFrame:Hide() | |
end | |
function ItemRack.OnBankOpen() | |
ItemRack.BankOpen = 1 | |
end | |
function ItemRack.UpdateClassSpecificStuff() | |
local _,class = UnitClass("player") | |
if class=="WARRIOR" or class=="ROGUE" or class=="HUNTER" or class=="MAGE" or class=="WARLOCK" or class=="SHAMAN" or class=="DEATHKNIGHT" then | |
ItemRack.CanWearOneHandOffHand = 1 | |
end | |
if ItemRack.IsWrath() and class=="WARRIOR" then | |
if select(5,GetTalentInfo(2,26))>0 then | |
ItemRack.HasTitansGrip = 1 | |
ItemRack.SlotInfo[17].INVTYPE_2HWEAPON = 1 | |
else | |
ItemRack.HasTitansGrip = nil | |
ItemRack.SlotInfo[17].INVTYPE_2HWEAPON = nil | |
end | |
end | |
end | |
function ItemRack.OnSetBagItem(tooltip, bag, slot) | |
ItemRack.ListSetsHavingItem(tooltip, ItemRack.GetID(bag, slot)) | |
end | |
function ItemRack.OnSetInventoryItem(tooltip, unit, inv_slot) | |
ItemRack.ListSetsHavingItem(tooltip, ItemRack.GetID(inv_slot)) | |
end | |
function ItemRack.OnSetHyperlink(tooltip, link) | |
ItemRack.ListSetsHavingItem(tooltip, link:match("item:(.+)")) | |
end | |
do | |
local data = {} | |
function ItemRack.ListSetsHavingItem(tooltip, id) | |
if ItemRackSettings.ShowSetInTooltip ~= "ON" then | |
return | |
end | |
local same_ids = ItemRack.SameID | |
if not id or id == 0 then return end | |
for name, set in pairs(ItemRackUser.Sets) do | |
for _, item in pairs(set.equip) do | |
if same_ids(item, id) then | |
data[name] = true | |
end | |
end | |
end | |
for name in pairs(data) do | |
tooltip:AddDoubleLine("ItemRack Set: ", name, 0,.6,1, 0,.6,1) | |
data[name] = nil | |
end | |
tooltip:Show() | |
end | |
end | |
function ItemRack.InitCore() | |
ItemRackUser.Sets["~Unequip"] = { equip={} } | |
ItemRackUser.Sets["~CombatQueue"] = { equip={} } | |
ItemRack.UpdateClassSpecificStuff() | |
ItemRack.DURABILITY_PATTERN = string.match(DURABILITY_TEMPLATE,"(.+) .+/.+") or "" | |
ItemRack.REQUIRES_PATTERN = string.gsub(ITEM_MIN_SKILL,"%%.",".+") | |
-- pattern splitter by Maldivia http://forums.worldofwarcraft.com/thread.html?topicId=6441208576 | |
local function split(str, t) | |
local start, stop, single, plural = str:find("\1244(.-):(.-);") | |
if start then | |
split(str:sub(1, start - 1) .. single .. str:sub(stop + 1), t) | |
split(str:sub(1, start - 1) .. plural .. str:sub(stop + 1), t) | |
else | |
tinsert(t, (str:gsub("%%d","%%d+"))) | |
end | |
return t | |
end | |
ItemRack.CHARGES_PATTERNS = {} | |
split(ITEM_SPELL_CHARGES,ItemRack.CHARGES_PATTERNS) | |
tinsert(ItemRack.CHARGES_PATTERNS,ITEM_SPELL_CHARGES_NONE) | |
-- for enUS, ItemRack.CHARGES_PATTERNS now {"%d+ Charge","%d+ Charges","No Charges"} | |
ItemRack.CreateTimer("MenuMouseover",ItemRack.MenuMouseover,.25,1) | |
ItemRack.CreateTimer("TooltipUpdate",ItemRack.TooltipUpdate,1,1) | |
ItemRack.CreateTimer("CooldownUpdate",ItemRack.CooldownUpdate,1,1) | |
ItemRack.CreateTimer("LocksChanged",ItemRack.LocksChanged,.2) | |
ItemRack.CreateTimer("DelayedCombatQueue",ItemRack.DelayedCombatQueue,.1) | |
for i=-2,11 do | |
ItemRack.LockList[i] = {} | |
end | |
hooksecurefunc("UseInventoryItem",ItemRack.newUseInventoryItem) | |
hooksecurefunc("UseAction",ItemRack.newUseAction) | |
hooksecurefunc("UseItemByName",ItemRack.newUseItemByName) | |
hooksecurefunc("PaperDollFrame_OnShow",ItemRack.newPaperDollFrame_OnShow) | |
hooksecurefunc(GameTooltip, "SetBagItem", ItemRack.OnSetBagItem) | |
hooksecurefunc(GameTooltip, "SetInventoryItem", ItemRack.OnSetInventoryItem) | |
hooksecurefunc(GameTooltip, "SetHyperlink", ItemRack.OnSetHyperlink) | |
ItemRackFrame:RegisterEvent("PLAYER_REGEN_ENABLED") | |
ItemRackFrame:RegisterEvent("PLAYER_REGEN_DISABLED") | |
ItemRackFrame:RegisterEvent("PLAYER_UNGHOST") | |
ItemRackFrame:RegisterEvent("PLAYER_ALIVE") | |
ItemRackFrame:RegisterEvent("BANKFRAME_CLOSED") | |
ItemRackFrame:RegisterEvent("BANKFRAME_OPENED") | |
ItemRackFrame:RegisterEvent("CHARACTER_POINTS_CHANGED") | |
if ItemRack.IsWrath() then | |
ItemRackFrame:RegisterEvent("PLAYER_TALENT_UPDATE") | |
ItemRackFrame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") | |
end | |
-- ItemRackFrame:RegisterEvent("PET_BATTLE_OPENING_START") | |
-- ItemRackFrame:RegisterEvent("PET_BATTLE_CLOSE") | |
--if not disable_delayed_swaps then | |
-- in the event delayed swaps while casting don't work well, | |
-- make disable_delayed_swaps=1 at top of this file to disable it | |
ItemRackFrame:RegisterEvent("UNIT_SPELLCAST_START") | |
ItemRackFrame:RegisterEvent("UNIT_SPELLCAST_STOP") | |
ItemRackFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") | |
ItemRackFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") | |
ItemRackFrame:RegisterEvent("UNIT_SPELLCAST_FAILED") | |
ItemRackFrame:RegisterEvent("PLAYER_ENTERING_WORLD") | |
--end | |
ItemRack.StartTimer("CooldownUpdate") | |
ItemRack.ReflectAlpha() | |
SlashCmdList["ItemRack"] = ItemRack.SlashHandler | |
SLASH_ItemRack1 = "/itemrack" | |
EquipSet = ItemRack.EquipSet -- for convenience in macros/events, shorter names | |
ToggleSet = ItemRack.ToggleSet | |
UnequipSet = ItemRack.UnequipSet | |
IsSetEquipped = ItemRack.IsSetEquipped | |
-- new option defaults to pre-existing settings here | |
ItemRackSettings.Cooldown90 = ItemRackSettings.Cooldown90 or "OFF" -- 2.14 | |
ItemRackSettings.EquipOnSetPick = ItemRackSettings.EquipOnSetPick or "OFF" -- 2.21 | |
ItemRackUser.SetMenuWrap = ItemRackUser.SetMenuWrap or "OFF" -- 2.21 | |
ItemRackUser.SetMenuWrapValue = ItemRackUser.SetMenuWrapValue or 3 -- 2.21 | |
ItemRackSettings.MinimapTooltip = ItemRackSettings.MinimapTooltip or "ON" -- 2.21 | |
ItemRackSettings.CharacterSheetMenus = ItemRackSettings.CharacterSheetMenus or "ON" -- 2.22 | |
ItemRackSettings.DisableAltClick = ItemRackSettings.DisableAltClick or "OFF" -- 2.23 | |
ItemRackSettings.HidePetBattle = ItemRackSettings.HidePetBattle or "ON" -- 2.87 | |
end | |
function ItemRack.Print(msg) | |
if msg then | |
DEFAULT_CHAT_FRAME:AddMessage("|cFFCCCCCCItemRack: |cFFFFFFFF"..msg) | |
end | |
end | |
function ItemRack.UpdateCurrentSet() | |
local texture = ItemRack.GetTextureBySlot(20) | |
local setname = ItemRackUser.CurrentSet or "" | |
if ItemRackButton20 and ItemRackUser.Buttons[20] then | |
ItemRackButton20Icon:SetTexture(texture) | |
ItemRackButton20Name:SetText(setname) | |
end | |
ItemRack.Broker.icon = texture | |
ItemRack.Broker.text = setname | |
end | |
--[[ Item info gathering ]] | |
function ItemRack.GetTextureBySlot(slot) | |
if slot==20 then | |
if ItemRackUser.CurrentSet and ItemRackUser.Sets[ItemRackUser.CurrentSet] then | |
return ItemRackUser.Sets[ItemRackUser.CurrentSet].icon | |
else | |
return "Interface\\AddOns\\ItemRack\\ItemRackIcon" | |
end | |
else | |
local texture = GetInventoryItemTexture("player",slot) | |
if texture then | |
return texture | |
else | |
_,texture = GetInventorySlotInfo(ItemRack.SlotInfo[slot].name) | |
return texture | |
end | |
end | |
end | |
-- itemlink/itemstring converter. | |
-- give it a regular itemLink/itemString and leave the second AND third parameters blank to receive an ItemRack-style ID: "62384:0:4041:4041:0:0:0:0:85:146" | |
-- give it an ItemRack-style ID and set the second parameter to true to receive the base itemID (ONLY for ItemRack-style IDs!): "62384" | |
-- give it a regular itemLink/itemString and set the second AND third parameters to true to receive the base itemID (ONLY for regular itemLinks/itemStrings!): "62384" | |
-- returns 0 on pattern matching failure (happens if no itemstring found/invalid itemstring format) | |
ItemRack.iSPatternRegularToIR = "item:(.-)\124h" --example: "62384:0:4041:4041:0:0:0:0:85:146:0:0", where 85 is the player's level when the itemLink/itemString was captured, in other words it's a regular itemString with the "item:" part removed | |
ItemRack.iSPatternBaseIDFromIR = "^(%-?%d+)" --this must *only* be used on ItemRack-style IDs, and will return the first field (the itemID), allowing us to do loose item matching | |
ItemRack.iSPatternBaseIDFromRegular = "item:(%-?%d+)" --this must *only* be used regular itemLinks/itemStrings, and will return the first field (the itemID), allowing us to do loose item matching | |
function ItemRack.GetIRString(inputString,baseid,regular) | |
return string.match(inputString or "", (baseid and (regular and ItemRack.iSPatternBaseIDFromRegular or ItemRack.iSPatternBaseIDFromIR) or ItemRack.iSPatternRegularToIR)) or 0 | |
end | |
-- itemrack itemstring updater. | |
-- takes a saved ItemRack-style ID and returns an updated version with the latest player level and spec injected, which helps us update outdated IDs saved when the player was lower level or different spec | |
function ItemRack.UpdateIRString(itemRackID) | |
return (string.gsub(itemRackID or "", "^("..strrep("%d+:", 8)..")%d+:%d+", "%1"..UnitLevel("player")..":".."0")) --note: parenthesis to discard 2nd return value (number of substitutions, which will always be 1) | |
end | |
-- returns the provided ItemRack-style ID string with "item:" prepended, which turns it into a normal itemstring which we can then use for item lookups, itemlink generation and so on. | |
-- sure, it's a simple function right now, but if the itemrack ID format above ever needs changing it'll be very easy to update the IRString to ItemString code in this one place. | |
function ItemRack.IRStringToItemString(itemRackID) | |
return "item:"..(itemRackID or "") | |
end | |
-- returns an ItemRack-style ID (62384:0:4041:4041:0:0:0:0:85:146) if an item exists in that slot, or 0 for none | |
-- bag,nil = inventory slot; bag,slot = container slot | |
function ItemRack.GetID(bag,slot) | |
local itemLink | |
if slot then | |
if bag >= 0 and slot >= 0 then | |
itemLink = C_Container.GetContainerItemLink(bag,slot) | |
end | |
else | |
itemLink = GetInventoryItemLink("player",bag) | |
end | |
return ItemRack.GetIRString(itemLink) | |
end | |
-- takes two ItemRack-style IDs (one or both of the parameters can be a baseID instead if needed) and returns true if those items share the same base itemID | |
function ItemRack.SameID(id1,id2) | |
return ItemRack.GetIRString(id1,true) == ItemRack.GetIRString(id2,true) | |
end | |
-- takes an ItemRack-style ID and returns the name, texture, equipslot and quality | |
function ItemRack.GetInfoByID(id) | |
local name,texture,equip,quality | |
if id and id~=0 then | |
name,_,quality,_,_,_,_,_,equip,texture = GetItemInfo(ItemRack.IRStringToItemString(ItemRack.UpdateIRString(id))) --ensure the stored ID is brought up to date, then generate a regular ItemString from it and get the item info | |
else | |
name,texture,quality = "(empty)","Interface\\Icons\\INV_Misc_QuestionMark",0 --default response on invalid ID | |
end | |
return name,texture,equip,quality | |
end | |
-- takes an ItemRack-style ID and returns how many items you own with that particular baseID (will not differentiate between enchanted/unenchanted versions, etc) | |
function ItemRack.GetCountByID(id) | |
return tonumber(GetItemCount(ItemRack.GetIRString(id,true))) | |
end | |
-- searches player's inventory&equipment and returns inv,bag,slot of a specific ItemRack-style ID (62384:0:4041:4041:0:0:0:0:85:146) or the first matching item with the same base id (62384) if specific id not found | |
-- nil,bag,slot = item found in a bag; inv,nil,nil = item found in one of the player's equipment slots; nil,nil,nil = item not found (at least not in equipment/inventory, but it might still exist in bank, we cannot check that though since the player has to be at the bank to read its contents) | |
-- what it does: it first looks for an EXACT match in the list of "known IDs", which is a cache of the last known location of every item the player has in their equipment and inventory | |
-- it then looks for an EXACT match in the player's equipment and inventory, and if that fails it looks for a BASEID match in the player's equipment and inventory | |
function ItemRack.FindItem(id,lock) | |
local locklist, getid, sameid = ItemRack.LockList, ItemRack.GetID, ItemRack.SameID --GetID will be used to look up the ItemRack-style ID for each item we pass over while we loop through the player's equipment/inventory | |
id = ItemRack.UpdateIRString(id) --we must update the incoming ItemRack-style ID to always match the player's current level no matter what, since all WoW ItemStrings contain the player's current level at the time of query, thus if we don't update the level in our OLD ID it won't match the CURRENT ID even if it is the EXACT same item. this simple update ensures that the exact item can be accurately located even if the player has dinged since last saving the set. | |
-- look for item in known items cache first (this cache is frequently rebuilt, such as when clicking the buttons to change a set, AS WELL as when the actual set change takes place, it's a bit overkill in fact, but at least it is up to date -- in fact the entire design is stupid. if the cache is ALWAYS rebuilt EVERY TIME a set change takes place, then the MANUAL search code further down will never take place unless the item is COMPLETELY MISSING. likewise, it means that we're constantly rebuilding a cache of ItemRack-style IDs, and then doing the EXACT same job AGAIN further down, in the "search for..." sections at the bottom of this function... bad design and lots of redundancy, heh. a better design would be to just search through our cache twice, first to look for an exact match, and then to look for a baseID match.) | |
local knownID = ItemRack.KnownItems[id] | |
if knownID then | |
local bag,slot = math.floor(knownID/100),mod(knownID,100) | |
if bag<0 and not slot then | |
bag = bag*-1 | |
if id==getid(bag) and (not lock or not locklist[-2][bag]) then | |
if lock then locklist[-2][bag]=1 end | |
return bag | |
end | |
else | |
if id==getid(bag,slot) and (not lock or not locklist[bag][slot]) then | |
if lock then locklist[bag][slot]=1 end | |
return nil,bag,slot | |
end | |
end | |
end | |
-- search bags | |
for i=4,0,-1 do | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if id==getid(i,j) and (not lock or not locklist[i][j]) then | |
if lock then locklist[i][j]=1 end | |
return nil,i,j | |
end | |
end | |
end | |
-- search worn equipment | |
for i=0,19 do | |
if id==getid(i) and (not lock or not locklist[-2][i]) then | |
if lock then locklist[-2][i]=1 end | |
return i | |
end | |
end | |
-- search bags for base id matches | |
for i=4,0,-1 do | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if sameid(id,getid(i,j)) and (not lock or not locklist[i][j]) then | |
if lock then locklist[i][j]=1 end | |
return nil,i,j | |
end | |
end | |
end | |
-- search worn equipment for base id matches | |
for i=0,19 do | |
if sameid(id,getid(i)) and (not lock or not locklist[-2][i]) then | |
if lock then locklist[-2][i]=1 end | |
return i | |
end | |
end | |
end | |
-- searches player's bank and returns bag,slot of a specific ItemRack-style ID (62384:0:4041:4041:0:0:0:0:85:146) or the first matching item with the same base id (62384) if specific id not found | |
-- bag,slot = item found in a bank bag; nil, nil = item not found in bank | |
function ItemRack.FindInBank(id,lock) | |
local locklist, getid, sameid = ItemRack.LockList, ItemRack.GetID, ItemRack.SameID --GetID will be used to look up the ItemRack-style ID for each item we pass over while we loop through the player's bank | |
id = ItemRack.UpdateIRString(id) --just as with the FindItem() patch above, we must ensure that the incoming ID to this function is brought up to date before we start scanning | |
if ItemRack.BankOpen then -- only proceed if bank is open | |
for _,i in pairs(ItemRack.BankSlots) do -- try to find an exact match at first | |
if ItemRack.ValidBag(i) then | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if id==getid(i,j) and (not lock or locklist[i][j]) then | |
if lock then locklist[i][j]=1 end | |
return i,j | |
end | |
end | |
end | |
end | |
for _,i in pairs(ItemRack.BankSlots) do -- otherwise resort to a loose baseID match | |
if ItemRack.ValidBag(i) then | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if sameid(id,getid(i,j)) and (not lock or not locklist[i][j]) then | |
if lock then locklist[i][j]=1 end | |
return i,j | |
end | |
end | |
end | |
end | |
end | |
end | |
-- returns true if the bagid (0-4) is a normal "Container", as opposed to quivers and ammo pouches | |
function ItemRack.ValidBag(bagid) | |
local baseID,bagtype | |
if bagid==0 or bagid==-1 then | |
return 1 | |
else | |
local invID = ContainerIDToInventoryID(bagid) | |
baseID = ItemRack.GetIRString(GetInventoryItemLink("player",invID),true,true) --get the baseID for the container | |
if GetItemFamily(baseID)==0 then | |
return 1 | |
end | |
-- if baseID then | |
-- _,_,_,_,_,_,bagtype = GetItemInfo(baseID) | |
-- if bagtype=="Bag" or bagtype=="Conteneur" or bagtype=="Beh\195\164lter" then | |
-- return 1 | |
-- end | |
-- end | |
end | |
end | |
function ItemRack.ClearLockList() -- this function is called very frequently, such as every time you click a set popup button to change the current set, AS WELL as when the actual set change takes place, and will call PopulateKnownItems in order to re-build the cache of current item locations and their itemstrings | |
for i=-2,11 do | |
for j in pairs(ItemRack.LockList[i]) do | |
ItemRack.LockList[i][j] = nil | |
end | |
end | |
if ItemRack.LocksHaveChanged then | |
ItemRack.LocksHaveChanged = nil | |
ItemRack.PopulateKnownItems() | |
end | |
end | |
function ItemRack.FindSpace() | |
for i=4,0,-1 do | |
if ItemRack.ValidBag(i) then | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if not GetContainerItemLink(i,j) and not ItemRack.LockList[i][j] then | |
ItemRack.LockList[i][j] = 1 | |
return i,j | |
end | |
end | |
end | |
end | |
end | |
function ItemRack.FindBankSpace() | |
if not ItemRack.BankOpen then return end | |
for _,i in pairs(ItemRack.BankSlots) do | |
if ItemRack.ValidBag(i) then | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
if not C_Container.GetContainerItemLink(i,j) and not ItemRack.LockList[i][j] then | |
ItemRack.LockList[i][j] = 1 | |
return i,j | |
end | |
end | |
end | |
end | |
end | |
function ItemRack.IsRed(which) | |
local r,g,b = _G["ItemRackTooltipText"..which]:GetTextColor() | |
if r>.9 and g<.2 and b<.2 then | |
return 1 | |
end | |
end | |
function ItemRack.PlayerCanWear(invslot,bag,slot) | |
local found = false | |
local txt = false | |
local i=1 | |
while _G["ItemRackTooltipTextLeft"..i] do | |
-- ClearLines doesn't remove colors, manually remove them | |
_G["ItemRackTooltipTextLeft"..i]:SetTextColor(0,0,0) | |
_G["ItemRackTooltipTextRight"..i]:SetTextColor(0,0,0) | |
i=i+1 | |
end | |
ItemRackTooltip:SetBagItem(bag,slot) | |
for i=2,ItemRackTooltip:NumLines() do | |
txt = _G["ItemRackTooltipTextLeft"..i]:GetText() | |
-- if either left or right text is red and this isn't a Durability x/x line, this item can't be worn | |
if (ItemRack.IsRed("Left"..i) or ItemRack.IsRed("Right"..i)) and not string.find(txt,ItemRack.DURABILITY_PATTERN) and not string.match(txt,ItemRack.REQUIRES_PATTERN) then | |
return nil | |
end | |
end | |
local _,_,itemType = ItemRack.GetInfoByID(ItemRack.GetID(bag,slot)) | |
if itemType=="INVTYPE_WEAPON" and invslot==17 and not ItemRack.CanWearOneHandOffHand then | |
-- if this is a One-Hand going to offhand, and player can't wear one-hand offhands, this item can't be worn | |
return nil | |
end | |
-- the gammut was run, this item can be worn | |
return 1 | |
end | |
function ItemRack.IsSoulbound(bag,slot) | |
ItemRackTooltip:SetBagItem(bag,slot) | |
for i=2,5 do | |
local text = _G["ItemRackTooltipTextLeft"..i]:GetText() | |
if text==ITEM_SOULBOUND or text==ITEM_BIND_QUEST or text==ITEM_CONJURED then | |
return 1 | |
end | |
end | |
end | |
-- function happens .2 seconds after last ITEM_LOCK_CHANGE | |
function ItemRack.LocksChanged() | |
ItemRack.UpdateButtonLocks() | |
if ItemRack.SetSwapping then | |
ItemRack.LockChangedDuringSetSwap() | |
elseif ItemRackMenuFrame:IsVisible() and ItemRack.BankOpen and not ItemRack.AnythingLocked() then | |
ItemRackMenuFrame:Hide() | |
ItemRack.BuildMenu() | |
elseif #(ItemRack.SetsWaiting)>0 and not ItemRack.AnythingLocked() then | |
ItemRack.ProcessSetsWaiting() | |
end | |
end | |
function ItemRack.PopulateKnownItems() | |
local known = ItemRack.KnownItems | |
for i in pairs(known) do | |
known[i] = nil | |
end | |
local id | |
local getid = ItemRack.GetID | |
for i=0,19 do | |
id = getid(i) --grab ItemRack-style ID for every currently worn equipment piece | |
if id~=0 then | |
known[id] = i*-1 --we were able to generate a valid ID for this item, so store its location (slot) | |
end | |
end | |
for i=0,4 do | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
id = getid(i,j) --grab ItemRack-style ID for every bag item | |
if id~=0 then | |
if IsEquippableItem(ItemRack.GetIRString(id,true)) then --only proceed if this is an equippable item (test against the baseID of the item) | |
known[id] = i*100+j --we were able to generate a valid ID for this item, so store its location (as a bag container offset) | |
end | |
end | |
end | |
end | |
end | |
--[[ Timers ]] | |
function ItemRack.InitTimers() | |
ItemRack.TimerPool = {} | |
ItemRack.Timers = {} | |
end | |
-- ItemRack.CreateTimer(name,func,delay,rep) | |
-- name = arbitrary name to identify this timer | |
-- func = function to run when the delay finishes | |
-- delay = time (in seconds) after the timer is started before func is run | |
-- rep = nil or 1, whether to repeat the delay once it's reached | |
-- | |
-- The standard use is to create a timer, and then ItemRack.StartTimer | |
-- when you want to run the delayed function. | |
-- | |
-- You can do /script ItemRack.TimerDebug() anytime to see all timer status | |
function ItemRack.CreateTimer(name,func,delay,rep) | |
ItemRack.TimerPool[name] = { func=func,delay=delay,rep=rep,elapsed=delay } | |
end | |
function ItemRack.IsTimerActive(name) | |
for i,j in ipairs(ItemRack.Timers) do | |
if j==name then | |
return i | |
end | |
end | |
return nil | |
end | |
function ItemRack.StartTimer(name,delay) | |
ItemRack.TimerPool[name].elapsed = delay or ItemRack.TimerPool[name].delay | |
if not ItemRack.IsTimerActive(name) then | |
table.insert(ItemRack.Timers,name) | |
ItemRackFrame:Show() | |
end | |
end | |
function ItemRack.StopTimer(name) | |
local idx = ItemRack.IsTimerActive(name) | |
if idx then | |
table.remove(ItemRack.Timers,idx) | |
if #(ItemRack.Timers)<1 then | |
ItemRackFrame:Hide() | |
end | |
end | |
end | |
function ItemRack.OnUpdate(self,elapsed) | |
local timerPool | |
for _,name in ipairs(ItemRack.Timers) do | |
timerPool = ItemRack.TimerPool[name] | |
timerPool.elapsed = timerPool.elapsed - elapsed | |
if timerPool.elapsed < 0 then | |
timerPool.func(elapsed) | |
if timerPool.rep then | |
timerPool.elapsed = timerPool.delay | |
else | |
ItemRack.StopTimer(name) | |
end | |
end | |
end | |
end | |
function ItemRack.TimerDebug() | |
local on = "|cFF00FF00On" | |
local off = "|cFFFF0000Off" | |
DEFAULT_CHAT_FRAME:AddMessage("|cFF44AAFFItemRackFrame is "..(ItemRackFrame:IsVisible() and on or off)) | |
for i in pairs(ItemRack.TimerPool) do | |
DEFAULT_CHAT_FRAME:AddMessage(i.." is "..(ItemRack.IsTimerActive(i) and on or off)) | |
end | |
end | |
--[[ Menu ]] | |
function ItemRack.DockWindows(menuDock,relativeTo,mainDock,menuOrient,movable) | |
ItemRackMenuFrame:ClearAllPoints() | |
ItemRack.currentDock = mainDock..menuDock | |
ItemRackMenuFrame:SetPoint(menuDock,relativeTo,mainDock,ItemRack.DockInfo[ItemRack.currentDock].xoff,ItemRack.DockInfo[ItemRack.currentDock].yoff) | |
ItemRackMenuFrame:SetParent(relativeTo) | |
ItemRackMenuFrame:SetFrameStrata("HIGH") | |
ItemRack.mainDock = mainDock | |
ItemRack.menuDock = menuDock | |
ItemRack.menuOrient = menuOrient | |
ItemRack.menuMovable = movable | |
ItemRack.menuDockedTo = relativeTo:GetName() | |
ItemRack.MenuMouseoverFrames[relativeTo:GetName()] = 1 -- add frame to mouseover candidates | |
ItemRack.ReflectLock(not ItemRack.menuMovable) | |
ItemRack.ReflectMenuScale() | |
end | |
function ItemRack.AlreadyInMenu(id) | |
for i=1,#(ItemRack.Menu) do | |
if ItemRack.Menu[i]==id then | |
return 1 | |
end | |
end | |
end | |
function ItemRack.AddToMenu(itemID) | |
if ItemRackSettings.AllowHidden=="OFF" or (IsAltKeyDown() or not ItemRack.IsHidden(itemID)) then | |
table.insert(ItemRack.Menu,itemID) | |
end | |
end | |
-- builds a popout menu for slots or set button | |
-- id = 0-19 for inventory slots, or 20 for set, or nil for last defined slot/set menu (ItemRack.menuOpen) | |
-- before calling ItemRack.BuildMenu, you should call ItemRack.DockWindows | |
-- if menuInclude, then also include the worn item(s) in the menu | |
function ItemRack.BuildMenu(id,menuInclude,masqueGroup) | |
if id then | |
ItemRack.menuOpen = id | |
ItemRack.menuInclude = menuInclude | |
else | |
id = ItemRack.menuOpen | |
menuInclude = ItemRack.menuInclude | |
end | |
local showButtonMenu = (ItemRackButtonMenu and ItemRack.menuMovable) and (IsAltKeyDown() or ItemRackUser.Locked=="OFF") | |
for i in pairs(ItemRack.Menu) do | |
ItemRack.Menu[i] = nil | |
end | |
local itemLink,itemID,itemName,equipSlot,itemTexture | |
if id<20 then | |
if menuInclude then | |
itemID = ItemRack.GetID(id) | |
if itemID~=0 then | |
ItemRack.AddToMenu(itemID) | |
end | |
if ItemRack.SlotInfo[id].other then | |
itemID = ItemRack.GetID(ItemRack.SlotInfo[id].other) | |
if itemID~=0 then | |
ItemRack.AddToMenu(itemID) | |
end | |
end | |
end | |
for i=0,4 do | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
itemID = ItemRack.GetID(i,j) | |
itemName,itemTexture,equipSlot = ItemRack.GetInfoByID(itemID) | |
if ItemRack.SlotInfo[id][equipSlot] and ItemRack.PlayerCanWear(id,i,j) and (ItemRackSettings.HideTradables=="OFF" or ItemRack.IsSoulbound(i,j)) then | |
if id~=0 or not ItemRack.AlreadyInMenu(itemID) then | |
ItemRack.AddToMenu(itemID) | |
end | |
end | |
end | |
end | |
if ItemRack.BankOpen then | |
for _,i in pairs(ItemRack.BankSlots) do | |
for j=1,C_Container.GetContainerNumSlots(i) do | |
itemID = ItemRack.GetID(i,j) | |
itemName,itemTexture,equipSlot = ItemRack.GetInfoByID(itemID) | |
if ItemRack.SlotInfo[id][equipSlot] and ItemRack.PlayerCanWear(id,i,j) and (ItemRackSettings.HideTradables=="OFF" or ItemRack.IsSoulbound(i,j)) then | |
if id~=0 or not ItemRack.AlreadyInMenu(itemID) then | |
ItemRack.AddToMenu(itemID) | |
end | |
end | |
end | |
end | |
elseif ItemRack.GetID(id)~=0 and ItemRackSettings.AllowEmpty=="ON" then | |
table.insert(ItemRack.Menu,0) | |
end | |
else | |
for i in pairs(ItemRackUser.Sets) do | |
if not string.match(i,"^~") then --do not list internal sets, prefixed with ~ | |
ItemRack.AddToMenu(i) | |
end | |
table.sort(ItemRack.Menu) | |
end | |
end | |
if showButtonMenu then | |
table.insert(ItemRack.Menu,"MENU") | |
end | |
if #(ItemRack.Menu)<1 then | |
ItemRackMenuFrame:Hide() | |
else | |
-- display outward from docking point | |
local col,row,xpos,ypos = 0,0,ItemRack.DockInfo[ItemRack.currentDock].xstart,ItemRack.DockInfo[ItemRack.currentDock].ystart | |
local max_cols = 1 | |
local button, icon | |
if ItemRackUser.SetMenuWrap=="ON" then | |
max_cols = ItemRackUser.SetMenuWrapValue | |
elseif #(ItemRack.Menu)>24 then | |
max_cols = 5 | |
elseif #(ItemRack.Menu)>18 then | |
max_cols = 4 | |
elseif #(ItemRack.Menu)>9 then | |
max_cols = 3 | |
elseif #(ItemRack.Menu)>4 then | |
max_cols = 2 | |
end | |
for i=1,#(ItemRack.Menu) do | |
button = ItemRack.CreateMenuButton(i,ItemRack.Menu[i]) or ItemRackButtonMenu | |
button:SetPoint("TOPLEFT",ItemRackMenuFrame,ItemRack.menuDock,xpos,ypos) | |
button:SetFrameLevel(ItemRackMenuFrame:GetFrameLevel()+1) | |
if ItemRack.MasqueGroups then | |
for _, group in pairs(ItemRack.MasqueGroups) do | |
group:RemoveButton(button) | |
end | |
if ItemRack.MasqueGroups[masqueGroup] then | |
ItemRack.MasqueGroups[masqueGroup]:AddButton(button) | |
end | |
end | |
if ItemRack.menuOrient=="VERTICAL" then | |
xpos = xpos + ItemRack.DockInfo[ItemRack.currentDock].xdir*40 | |
col = col + 1 | |
if col==max_cols then | |
xpos = ItemRack.DockInfo[ItemRack.currentDock].xstart | |
col = 0 | |
ypos = ypos + ItemRack.DockInfo[ItemRack.currentDock].ydir*40 | |
row = row + 1 | |
end | |
button:Show() | |
else | |
ypos = ypos + ItemRack.DockInfo[ItemRack.currentDock].ydir*40 | |
col = col + 1 | |
if col==max_cols then | |
ypos = ItemRack.DockInfo[ItemRack.currentDock].ystart | |
col = 0 | |
xpos = xpos + ItemRack.DockInfo[ItemRack.currentDock].xdir*40 | |
row = row + 1 | |
end | |
button:Show() | |
end | |
icon = _G["ItemRackMenu"..i.."Icon"] | |
if icon then | |
icon:SetDesaturated(false) | |
if IsAltKeyDown() and ItemRackSettings.AllowHidden=="ON" and IsAltKeyDown() and ItemRack.IsHidden(ItemRack.Menu[i]) then | |
icon:SetDesaturated(true) | |
end | |
end | |
end | |
if showButtonMenu then | |
table.remove(ItemRack.Menu) | |
else | |
ItemRackButtonMenu:Hide() | |
end | |
local i = #(ItemRack.Menu)+1 | |
while _G["ItemRackMenu"..i] do | |
_G["ItemRackMenu"..i]:Hide() | |
i=i+1 | |
end | |
if col==0 then | |
row = row-1 | |
end | |
if ItemRack.menuOrient=="VERTICAL" then | |
ItemRackMenuFrame:SetWidth(12+(max_cols*40)) | |
ItemRackMenuFrame:SetHeight(12+((row+1)*40)) | |
else | |
ItemRackMenuFrame:SetWidth(12+((row+1)*40)) | |
ItemRackMenuFrame:SetHeight(12+(max_cols*40)) | |
end | |
ItemRack.StartTimer("MenuMouseover") | |
ItemRackMenuFrame:Show() | |
ItemRack.UpdateMenuCooldowns() | |
local count | |
local border | |
for i=1,#(ItemRack.Menu) do | |
border = _G["ItemRackMenu"..i.."Border"] | |
border:Hide() | |
if ItemRack.menuOpen==20 then | |
_G["ItemRackMenu"..i.."Name"]:SetText(ItemRack.Menu[i]) | |
local missing = ItemRack.MissingItems(ItemRack.Menu[i]) | |
if missing==0 then | |
border:SetVertexColor(1,.1,.1) | |
border:Show() | |
elseif missing==1 then | |
border:SetVertexColor(.3,.5,1) | |
border:Show() | |
end | |
else | |
_G["ItemRackMenu"..i.."Name"]:SetText("") | |
if ItemRack.Menu[i]~=0 and ItemRack.GetCountByID(ItemRack.Menu[i])==0 then | |
border:SetVertexColor(.3,.5,1) | |
border:Show() | |
end | |
end | |
if ItemRack.menuOpen==0 then | |
count = ItemRack.GetCountByID(ItemRack.Menu[i]) | |
_G["ItemRackMenu"..i.."Count"]:SetText(count>0 and count or "") | |
else | |
_G["ItemRackMenu"..i.."Count"]:SetText("") | |
end | |
end | |
end | |
end | |
function ItemRack.UpdateMenuCooldowns() | |
local baseID | |
for i=1,#(ItemRack.Menu) do | |
baseID = tonumber(ItemRack.GetIRString(ItemRack.Menu[i],true)) --get baseID and convert it to number to be able to use it in numerical comparisons below | |
if baseID and baseID>0 and ItemRack.menuOpen<20 then | |
CooldownFrame_Set(_G["ItemRackMenu"..i.."Cooldown"],C_Container.GetItemCooldown(baseID)) | |
else | |
_G["ItemRackMenu"..i.."Cooldown"]:Hide() | |
end | |
end | |
ItemRack.WriteMenuCooldowns() | |
end | |
function ItemRack.WriteMenuCooldowns() | |
if ItemRackSettings.CooldownCount=="ON" and ItemRackMenuFrame:IsVisible() then | |
local baseID | |
for i=1,#(ItemRack.Menu) do | |
baseID = ItemRack.GetIRString(ItemRack.Menu[i],true) | |
if baseID then | |
ItemRack.WriteCooldown(_G["ItemRackMenu"..i.."Time"],C_Container.GetItemCooldown(baseID)) | |
else | |
_G["ItemRackMenu"..i.."Time"]:SetText("") | |
end | |
end | |
end | |
end | |
function ItemRack.MenuMouseover() | |
local frame = GetMouseFocus() | |
if MouseIsOver(ItemRackMenuFrame) or IsShiftKeyDown() or (frame and frame:GetName() and frame:IsVisible() and ItemRack.MenuMouseoverFrames[frame:GetName()]) then | |
return -- keep menu open if mouse over menu, shift is down or mouse is immediately over a mouseover frame | |
end | |
for i in pairs(ItemRack.MenuMouseoverFrames) do | |
frame = _G[i] | |
if frame and frame:IsVisible() and MouseIsOver(frame) then | |
return -- keep menu open if some frame beneath mouse is a mouseover frame | |
end | |
end | |
ItemRack.StopTimer("MenuMouseover") | |
ItemRackMenuFrame:Hide() | |
end | |
function ItemRack.MenuOnHide() | |
ItemRack.menuDockedTo = nil | |
end | |
function ItemRack.CreateMenuButton(idx,itemID) | |
if itemID=="MENU" then return end | |
local button | |
if not _G["ItemRackMenu"..idx] then | |
button = CreateFrame("CheckButton","ItemRackMenu"..idx,ItemRackMenuFrame,"ActionButtonTemplate") | |
button:SetID(idx) | |
button:SetFrameStrata("HIGH") | |
-- button:SetFrameLevel(ItemRackMenuFrame:GetFrameLevel()+1) | |
button:RegisterForClicks("LeftButtonUp","RightButtonUp") | |
button:SetScript("OnClick",ItemRack.MenuOnClick) | |
button:SetScript("OnEnter",ItemRack.MenuTooltip) | |
button:SetScript("OnLeave",ItemRack.ClearTooltip) | |
CreateFrame("Frame",nil,button,"ItemRackTimeTemplate") | |
ItemRack.SetFont("ItemRackMenu"..idx) | |
-- local font = button:CreateFontString("ItemRackMenu"..idx.."Time","OVERLAY","NumberFontNormal") | |
-- font:SetJustifyH("CENTER") | |
-- font:SetWidth(36) | |
-- font:SetHeight(12) | |
-- font:SetPoint("BOTTOMRIGHT","ItemRackMenu"..idx,"BOTTOMRIGHT") | |
end | |
if itemID~=0 then | |
if ItemRackUser.Sets[itemID] then | |
_G["ItemRackMenu"..idx.."Icon"]:SetTexture(ItemRackUser.Sets[itemID].icon) | |
else | |
local _,texture = ItemRack.GetInfoByID(itemID) | |
_G["ItemRackMenu"..idx.."Icon"]:SetTexture(texture) | |
end | |
else | |
_G["ItemRackMenu"..idx.."Icon"]:SetTexture(select(2,GetInventorySlotInfo(ItemRack.SlotInfo[ItemRack.menuOpen].name))) | |
end | |
return _G["ItemRackMenu"..idx] | |
end | |
-- takes an ItemRack-style ID, finds the best match in the player's inventory, and puts its ItemLink to the chat editbox. | |
-- if the item is missing, it uses the ItemRack-style ID as-is to generate a clickable ItemLink from the stored data | |
function ItemRack.ChatLinkID(itemID) | |
local inv,bag,slot = ItemRack.FindItem(itemID) | |
if bag then | |
ChatFrame1EditBox:Insert(C_Container.GetContainerItemLink(bag,slot)) | |
elseif inv then | |
ChatFrame1EditBox:Insert(GetInventoryItemLink("player",inv)) | |
else | |
local _,itemLink = GetItemInfo(ItemRack.IRStringToItemString(ItemRack.UpdateIRString(itemID))) --ensure the stored ID is brought up to date, then generate a regular ItemString from it and get the item info | |
if itemLink then | |
ChatFrame1EditBox:Insert(itemLink) | |
end | |
end | |
end | |
function ItemRack.MenuOnClick(self,button) | |
self:SetChecked(false) | |
local item = ItemRack.Menu[self:GetID()] | |
ItemRack.ClearLockList() | |
if IsAltKeyDown() and ItemRackSettings.AllowHidden=="ON" then | |
ItemRack.ToggleHidden(item) | |
ItemRack.BuildMenu() | |
elseif IsShiftKeyDown() and ChatFrame1EditBox:IsVisible() then | |
ItemRack.ChatLinkID(item) | |
elseif ItemRack.menuInclude then | |
if ItemRackOptFrame and ItemRackOptFrame:IsVisible() then | |
ItemRackOpt.Inv[ItemRack.menuOpen].id = item | |
ItemRackOpt.Inv[ItemRack.menuOpen].selected = 1 | |
ItemRackOpt.UpdateInv() | |
ItemRackMenuFrame:Hide() | |
end | |
elseif ItemRack.menuOpen<20 then | |
if ItemRack.BankOpen then | |
if ItemRack.GetCountByID(item)==0 then | |
local bankBag,bankSlot = ItemRack.FindInBank(item) | |
if bankBag then | |
local freeBag,freeSlot = ItemRack.FindSpace() | |
if freeBag and not SpellIsTargeting() and not GetCursorInfo() then | |
PickupContainerItem(bankBag,bankSlot) | |
PickupContainerItem(freeBag,freeSlot) | |
else | |
ItemRack.Print("Not enough room in bags to pull this item from bank.") | |
end | |
end | |
else | |
local bankBag,bankSlot = ItemRack.FindBankSpace() | |
if bankBag then | |
local _,bag,slot = ItemRack.FindItem(item) | |
if bag and not SpellIsTargeting() and not GetCursorInfo() then | |
PickupContainerItem(bag,slot) | |
PickupContainerItem(bankBag,bankSlot) | |
end | |
else | |
ItemRack.Print("Not enough room in bank to put this item.") | |
end | |
end | |
else | |
if ItemRackSettings.EquipOnSetPick=="ON" and ItemRackOptFrame and ItemRackOptFrame:IsVisible() then | |
ItemRackOpt.Inv[ItemRack.menuOpen].id = item | |
ItemRackOpt.Inv[ItemRack.menuOpen].selected = 1 | |
ItemRackOpt.UpdateInv() | |
end | |
if ItemRack.menuOpen>=13 and ItemRack.menuOpen<=14 and ItemRackSettings.TrinketMenuMode=="ON" and ItemRackUser.Buttons[13] and ItemRackUser.Buttons[14] then | |
ItemRack.menuOpen = button=="RightButton" and 14 or 13 | |
end | |
ItemRack.EquipItemByID(item,ItemRack.menuOpen) | |
ItemRackMenuFrame:Hide() | |
end | |
elseif ItemRack.menuOpen==20 then | |
if ItemRack.BankOpen then | |
if ItemRack.MissingItems(item)==1 then | |
ItemRack.GetBankedSet(item) | |
else | |
ItemRack.PutBankedSet(item) | |
end | |
elseif ItemRackSettings.EquipToggle=="ON" or IsShiftKeyDown() then | |
ItemRack.ToggleSet(item) | |
else | |
ItemRack.EquipSet(item) | |
end | |
if not ItemRack.BankOpen then | |
ItemRack.StopTimer("MenuMouseover") | |
ItemRackMenuFrame:Hide() | |
end | |
end | |
end | |
function ItemRack.EquipItemByID(id,slot) | |
if not id then return end | |
if ItemRack.NowCasting or (not ItemRack.SlotInfo[slot].swappable and (UnitAffectingCombat("player") or ItemRack.IsPlayerReallyDead()) ) then | |
ItemRack.AddToCombatQueue(slot,id) | |
elseif not GetCursorInfo() and not SpellIsTargeting() then | |
if id~=0 then -- not an empty slot | |
local _,b,s = ItemRack.FindItem(id) | |
if b then | |
local _,_,isLocked = C_Container.GetContainerItemInfo(b,s) | |
if not isLocked and not IsInventoryItemLocked(slot) then | |
-- neither container item nor inventory item locked, perform swap | |
local _,_,equipSlot = ItemRack.GetInfoByID(id) | |
if equipSlot~="INVTYPE_2HWEAPON" or (ItemRack.HasTitansGrip and not ItemRack.NoTitansGrip[select(7,GetItemInfo(C_Container.GetContainerItemLink(b,s))) or ""]) or not GetInventoryItemLink("player",17) then | |
C_Container.PickupContainerItem(b,s) | |
PickupInventoryItem(slot) | |
else | |
local bfree,sfree = ItemRack.FindSpace() | |
if bfree then | |
PickupInventoryItem(17) | |
C_Container.PickupContainerItem(bfree,sfree) | |
PickupInventoryItem(slot) | |
C_Container.PickupContainerItem(b,s) | |
else | |
ItemRack.Print("Not enough room to perform swap.") | |
end | |
end | |
end | |
end | |
else | |
local b,s = ItemRack.FindSpace() | |
if b and not IsInventoryItemLocked(slot) then | |
PickupInventoryItem(slot) | |
C_Container.PickupContainerItem(b,s) | |
else | |
ItemRack.Print("Not enough room to perform swap.") | |
end | |
end | |
end | |
end | |
--[[ Hooks to capture item use outside the mod ]] | |
function ItemRack.ReflectItemUse(id) | |
if ItemRackUser.Buttons[id] then | |
_G["ItemRackButton"..id]:SetChecked(true) | |
ItemRack.ReflectClicked[id] = 1 | |
ItemRack.StartTimer("ReflectClickedUpdate") | |
end | |
local baseID = ItemRack.GetIRString(GetInventoryItemLink("player",id),true,true) | |
if baseID then | |
ItemRackUser.ItemsUsed[baseID] = 1 | |
end | |
end | |
function ItemRack.newPaperDollFrame_OnShow() | |
ItemRack.UpdateCombatQueue() | |
end | |
function ItemRack.newUseInventoryItem(slot) | |
ItemRack.ReflectItemUse(slot) | |
end | |
function ItemRack.newUseAction(slot,cursor,self) | |
if IsEquippedAction(slot) then | |
local actionType,actionId = GetActionInfo(slot) | |
if actionType=="item" then | |
for i=0,19 do | |
if tonumber(ItemRack.GetIRString(GetInventoryItemLink("player",i),true,true))==actionId then --compare baseID of given item (converted to number) to actionId | |
ItemRack.ReflectItemUse(i) | |
break | |
end | |
end | |
end | |
end | |
end | |
function ItemRack.newUseItemByName(name) | |
for i=0,19 do | |
if name==GetItemInfo(GetInventoryItemLink("player",i) or 0) then | |
ItemRack.ReflectItemUse(i) | |
break | |
end | |
end | |
end | |
--[[ Combat queue ]] | |
function ItemRack.IsPlayerReallyDead() | |
local dead = UnitIsDeadOrGhost("player") | |
if UnitIsFeignDeath("player") then | |
dead = false | |
end | |
return dead | |
end | |
function ItemRack.AddToCombatQueue(slot,id) | |
if ItemRack.CombatQueue[slot]==id then | |
ItemRack.CombatQueue[slot] = nil | |
else | |
ItemRack.CombatQueue[slot] = id | |
end | |
ItemRack.UpdateCombatQueue() | |
end | |
function ItemRack.UpdateCombatQueue() | |
local queue,id | |
for i in pairs(ItemRackUser.Buttons) do | |
queue = _G["ItemRackButton"..i.."Queue"] | |
if ItemRack.CombatQueue[i] then | |
queue:SetTexture(select(2,ItemRack.GetInfoByID(ItemRack.CombatQueue[i]))) | |
queue:SetAlpha(1) | |
queue:Show() | |
elseif ItemRack.GetQueuesEnabled()[i] then | |
queue:SetTexture("Interface\\AddOns\\ItemRack\\ItemRackGear") | |
queue:SetAlpha(ItemRackUser.EnableQueues=="ON" and 1 or .5) | |
queue:Show() | |
elseif i~=20 then | |
queue:Hide() | |
end | |
end | |
for i=1,19 do | |
queue = _G["Character"..ItemRack.SlotInfo[i].name.."Queue"] | |
if ItemRack.CombatQueue[i] then | |
queue:SetTexture(select(2,ItemRack.GetInfoByID(ItemRack.CombatQueue[i]))) | |
queue:Show() | |
else | |
queue:Hide() | |
end | |
end | |
end | |
--[[ Tooltip ]] | |
-- request a tooltip of an inventory slot | |
function ItemRack.InventoryTooltip(self) | |
local id = self:GetID() | |
if id==20 then | |
ItemRack.SetTooltip(self,ItemRackUser.CurrentSet) | |
else | |
ItemRack.TooltipOwner = self | |
ItemRack.TooltipType = "INVENTORY" | |
ItemRack.TooltipSlot = id | |
ItemRack.TooltipBag = ItemRack.CombatQueue[id] and ItemRack.GetInfoByID(ItemRack.CombatQueue[id]) | |
ItemRack.StartTimer("TooltipUpdate",0) | |
end | |
end | |
-- request a tooltip of a menu item (called when hovering over a button in the popout menu of SET NAMES that comes up when clicking the minimap button or bar addon plugin, this is NOT the "Sets" dropdown INSIDE ItemRack's GUI) | |
function ItemRack.MenuTooltip(self) | |
local id = self:GetID() | |
if ItemRack.menuOpen==20 then | |
ItemRack.SetTooltip(self,ItemRack.Menu[id]) | |
else | |
ItemRack.TooltipOwner = self | |
ItemRack.TooltipType = "BAG" | |
local invMaybe | |
invMaybe,ItemRack.TooltipBag,ItemRack.TooltipSlot = ItemRack.FindItem(ItemRack.Menu[self:GetID()]) | |
if ItemRack.TooltipBag and ItemRack.TooltipSlot then | |
ItemRack.StartTimer("TooltipUpdate",0) | |
else -- if invMaybe then | |
ItemRack.IDTooltip(self,ItemRack.Menu[id]) | |
end | |
end | |
end | |
-- request a tooltip of a straight item id (called when hovering over items from the currently displayed set inside ItemRack's GUI) | |
function ItemRack.IDTooltip(self,itemID) --itemID is an ItemRack-style ID | |
ItemRack.AnchorTooltip(self) | |
local inv,bag,slot = ItemRack.FindItem(itemID) --try to find the item in the player's equipment and inventory, first tries to find the exact item, then looks for any item with the same baseID | |
if inv then -- item found in player's worn equipment | |
GameTooltip:SetInventoryItem("player",inv) | |
elseif bag then -- item found in player's bags | |
GameTooltip:SetBagItem(bag,slot) | |
else --cannot find the item in player's inventory or worn equipment! | |
bag,slot = ItemRack.FindInBank(itemID) --try to find the item in the player's bank IF they currently have the bank frame open | |
if bag then -- item found in player's bank | |
itemID = C_Container.GetContainerItemLink(bag,slot) -- grab the itemLink from the found item in the player's bank | |
else -- item is completely missing (no such strict OR baseID found anywhere): it's not in inventory, bank or worn items | |
itemID = ItemRack.IRStringToItemString(ItemRack.UpdateIRString(itemID)) -- ensure the stored ID is brought up to date, then generate a regular ItemString from it which can be used to display the required tooltip | |
end | |
GameTooltip:SetHyperlink(itemID) | |
end | |
ItemRack.ShrinkTooltip(self) | |
GameTooltip:Show() | |
end | |
function ItemRack.ClearTooltip(self) | |
GameTooltip:Hide() | |
ItemRack.StopTimer("TooltipUpdate") | |
ItemRack.TooltipType = nil | |
end | |
function ItemRack.AnchorTooltip(owner) | |
if string.match(ItemRack.menuDockedTo or "","^Character") then | |
GameTooltip:SetOwner(owner,"ANCHOR_RIGHT") | |
elseif ItemRackSettings.TooltipFollow=="ON" then | |
if owner.GetLeft and owner:GetLeft() and owner:GetLeft()<400 then | |
GameTooltip:SetOwner(owner,"ANCHOR_RIGHT") | |
else | |
GameTooltip:SetOwner(owner,"ANCHOR_LEFT") | |
end | |
else | |
GameTooltip_SetDefaultAnchor(GameTooltip,owner) | |
end | |
end | |
-- display the tooltip created in the functions above, once a second if item has a cooldown | |
function ItemRack.TooltipUpdate() | |
if ItemRack.TooltipType then | |
local cooldown | |
ItemRack.AnchorTooltip(ItemRack.TooltipOwner) | |
if ItemRack.TooltipType=="BAG" then | |
GameTooltip:SetBagItem(ItemRack.TooltipBag,ItemRack.TooltipSlot) | |
cooldown = C_Container.GetContainerItemCooldown(ItemRack.TooltipBag,ItemRack.TooltipSlot) | |
else | |
GameTooltip:SetInventoryItem("player",ItemRack.TooltipSlot) | |
cooldown = GetInventoryItemCooldown("player",ItemRack.TooltipSlot) | |
end | |
ItemRack.ShrinkTooltip(ItemRack.TooltipOwner) -- if TinyTooltips on, shrink it | |
if ItemRack.TooltipType=="INVENTORY" and ItemRack.TooltipBag then | |
GameTooltip:AddLine("Queued: "..ItemRack.TooltipBag) | |
end | |
GameTooltip:Show() | |
if cooldown==0 then | |
-- stop updates if this trinket has no cooldown | |
ItemRack.StopTimer("TooltipUpdate") | |
ItemRack.TooltipType = nil | |
end | |
end | |
end | |
-- normal tooltip for options | |
function ItemRack.OnTooltip(self,line1,line2) | |
if ItemRackSettings.ShowTooltips=="ON" then | |
ItemRack.AnchorTooltip(self) | |
if line1 then | |
GameTooltip:AddLine(line1) | |
GameTooltip:AddLine(line2,.8,.8,.8,1) | |
GameTooltip:Show() | |
return | |
else | |
local name = self:GetName() or "" | |
for i=1,#(ItemRack.TooltipInfo) do | |
if ItemRack.TooltipInfo[i][1]==name and ItemRack.TooltipInfo[i][2] then | |
GameTooltip:AddLine(ItemRack.TooltipInfo[i][2]) | |
GameTooltip:AddLine(ItemRack.TooltipInfo[i][3],.8,.8,.8,1) | |
GameTooltip:Show() | |
return | |
end | |
end | |
end | |
end | |
end | |
function ItemRack.ShrinkTooltip(owner) | |
if ItemRackSettings.TinyTooltips=="ON" then | |
local r,g,b = GameTooltipTextLeft1:GetTextColor() | |
local name = GameTooltipTextLeft1:GetText() | |
local line,charge,durability,cooldown | |
for i=2,GameTooltip:NumLines() do | |
line = _G["GameTooltipTextLeft"..i] | |
if line:IsVisible() then | |
line = line:GetText() or "" | |
if string.match(line,ItemRack.DURABILITY_PATTERN) then | |
durability = line | |
end | |
if string.match(line,COOLDOWN_REMAINING) then | |
cooldown = line | |
end | |
for j in pairs(ItemRack.CHARGES_PATTERNS) do | |
if string.find(line,ItemRack.CHARGES_PATTERNS[j]) then | |
charge = line | |
end | |
end | |
end | |
end | |
ItemRack.AnchorTooltip(owner) | |
GameTooltip:AddLine(name,r,g,b) | |
GameTooltip:AddLine(charge,1,1,1) | |
GameTooltip:AddLine(durability,1,1,1) | |
GameTooltip:AddLine(cooldown,1,1,1) | |
end | |
end | |
function ItemRack.SetTooltip(self,setname) | |
local set = setname and ItemRackUser.Sets[setname] and ItemRackUser.Sets[setname].equip | |
if set then | |
local itemName,itemColor | |
ItemRack.AnchorTooltip(self) | |
GameTooltip:AddLine(setname) | |
if ItemRackSettings.TinyTooltips~="ON" then | |
for i=0,19 do | |
if set[i] then | |
itemName = ItemRack.GetInfoByID(set[i]) | |
if itemName then | |
if itemName~="(empty)" and ItemRack.GetCountByID(set[i])==0 then | |
if not ItemRack.FindInBank(set[i]) then | |
itemColor = "FFFF1111" | |
else | |
itemColor = "FF4C80FF" | |
end | |
else | |
itemColor = "FFAAAAAA" | |
end | |
GameTooltip:AddLine("|cFFFFFFFF"..ItemRack.SlotInfo[i].real..": |c"..itemColor..itemName) | |
end | |
end | |
end | |
end | |
GameTooltip:Show() | |
end | |
end | |
--[[ Notify ]] | |
function ItemRack.Notify(msg) | |
-- PlaySound("GnomeExploration") | |
PlaySound(SOUNDKIT.IG_CHARACTER_INFO_OPEN) | |
if SCT_Display then -- send via SCT if it exists | |
SCT_Display(msg,{r=.2,g=.7,b=.9}) | |
elseif SHOW_COMBAT_TEXT=="1" then | |
CombatText_AddMessage(msg, CombatText_StandardScroll, .2, .7, .9) -- or default UI's SCT | |
else | |
-- send vis UIErrorsFrame if neither SCT exists | |
UIErrorsFrame:AddMessage(msg,.2,.7,.9,1,UIERRORS_HOLD_TIME) | |
end | |
if ItemRackSettings.NotifyChatAlso=="ON" then | |
DEFAULT_CHAT_FRAME:AddMessage("|cff33b2e5"..msg) | |
end | |
end | |
function ItemRack.CooldownUpdate() | |
local inv,bag,slot,start,duration,name,remain | |
for i in pairs(ItemRackUser.ItemsUsed) do | |
start,duration = C_Container.GetItemCooldown(i) | |
if start and ItemRackUser.ItemsUsed[i]<3 then | |
ItemRackUser.ItemsUsed[i] = ItemRackUser.ItemsUsed[i] + 1 -- count for 3 seconds before seeing if this is a real cooldown | |
elseif start then | |
if start>0 then | |
remain = duration - (GetTime()-start) | |
if ItemRackUser.ItemsUsed[i]<5 then | |
if remain>29 then | |
ItemRackUser.ItemsUsed[i] = 30 -- first actual cooldown greater than 30 seconds, tag it for 30+0 notify | |
elseif remain>5 then | |
ItemRackUser.ItemsUsed[i] = 5 -- first actual cooldown less than 30 but greater than 5, tag for 0 notify | |
end | |
end | |
end | |
if ItemRackUser.ItemsUsed[i]==30 and start>0 and remain<30 then | |
if ItemRackSettings.NotifyThirty=="ON" then | |
name = GetItemInfo(i) | |
if name then | |
ItemRack.Notify(name.." ready soon!") | |
end | |
end | |
ItemRackUser.ItemsUsed[i]=5 -- tag for just 0 notify now | |
elseif ItemRackUser.ItemsUsed[i]==5 and start==0 then | |
if ItemRackSettings.Notify=="ON" then | |
name = GetItemInfo(i) | |
if name then | |
ItemRack.Notify(name.." ready!") | |
end | |
end | |
end | |
if start==0 then | |
ItemRackUser.ItemsUsed[i] = nil | |
end | |
end | |
end | |
-- update cooldown numbers | |
if ItemRackSettings.CooldownCount=="ON" then | |
ItemRack.WriteButtonCooldowns() | |
ItemRack.WriteMenuCooldowns() | |
end | |
if ItemRack.PeriodicQueueCheck then | |
ItemRack.PeriodicQueueCheck() | |
end | |
end | |
--[[ Character sheet menus ]] | |
ItemRack.oldPaperDollItemSlotButton_OnEnter = PaperDollItemSlotButton_OnEnter | |
function PaperDollItemSlotButton_OnEnter(self) | |
ItemRack.oldPaperDollItemSlotButton_OnEnter(self) | |
if ItemRack.menuDockedTo~=self:GetName() and (ItemRackSettings.MenuOnShift=="OFF" or IsShiftKeyDown()) and ItemRackSettings.CharacterSheetMenus=="ON" then | |
ItemRack.DockMenuToCharacterSheet(self) | |
end | |
end | |
function ItemRack.DockMenuToCharacterSheet(self) | |
local name = self:GetName() | |
local slot | |
for i=0,19 do | |
if name=="Character"..ItemRack.SlotInfo[i].name then | |
slot = i | |
end | |
end | |
if slot then | |
if slot==0 or (slot>=16 and slot<=18) then | |
ItemRack.DockWindows("TOPLEFT",self,"BOTTOMLEFT","VERTICAL") | |
else | |
if slot==14 and ItemRackSettings.TrinketMenuMode=="ON" then | |
self = CharacterTrinket0Slot | |
end | |
ItemRack.DockWindows("TOPLEFT",self,"TOPRIGHT","HORIZONTAL") | |
end | |
ItemRack.BuildMenu(slot, nil, 3) | |
end | |
end | |
--[[ Minimap button ]] | |
function ItemRack.InitBroker() | |
local texture = ItemRack.GetTextureBySlot(20) | |
texture = [[Interface\AddOns\ItemRack\ItemRackIcon]] | |
ItemRack.Broker = LDB:NewDataObject("ItemRack", { | |
type = "launcher", | |
text = "ItemRack", | |
icon = texture, | |
OnClick = ItemRack.MinimapOnClick, | |
OnTooltipShow = ItemRack.MinimapOnEnter, | |
}) | |
ItemRackSettings.minimap = ItemRackSettings.minimap or { hide = false } | |
LDBIcon:Register("ItemRack", ItemRack.Broker, ItemRackSettings.minimap) | |
ItemRack.ShowMinimap() | |
end | |
function ItemRack.ShowMinimap() | |
if ItemRackSettings.ShowMinimap == "ON" then | |
LDBIcon:Show("ItemRack") | |
else | |
LDBIcon:Hide("ItemRack") | |
end | |
end | |
function ItemRack.MinimapOnClick(self,button) | |
if IsShiftKeyDown() then | |
if ItemRackUser.CurrentSet and ItemRackUser.Sets[ItemRackUser.CurrentSet] then | |
ItemRack.UnequipSet(ItemRackUser.CurrentSet) | |
end | |
elseif IsAltKeyDown() and (button=="RightButton" or ItemRackSettings.AllowHidden=="OFF") then | |
ItemRack.ToggleEvents(self) | |
elseif button=="LeftButton" then | |
if ItemRackMenuFrame:IsVisible() then | |
ItemRackMenuFrame:Hide() | |
else | |
local xpos,ypos = GetCursorPosition() | |
if ypos>400 then | |
ItemRack.DockWindows("TOPRIGHT",self,"BOTTOMRIGHT","VERTICAL") | |
else | |
ItemRack.DockWindows("BOTTOMRIGHT",self,"TOPRIGHT","VERTICAL") | |
end | |
ItemRack.BuildMenu(20, nil, 4) | |
end | |
else | |
ItemRack.ToggleOptions(self) | |
end | |
end | |
function ItemRack.MinimapOnEnter(tooltip) | |
if ItemRackSettings.MinimapTooltip~="ON" then return end | |
tooltip:AddLine("ItemRack") | |
tooltip:AddLine("Left click: Select a set",.8,.8,.8,1) | |
tooltip:AddLine("Right click: Open options",.8,.8,.8,1) | |
tooltip:AddLine("Alt left click: Show hidden sets",.8,.8,.8,1) | |
tooltip:AddLine("Alt right click: Toggle events",.8,.8,.8,1) | |
tooltip:AddLine("Shift click: Unequip this set",.8,.8,.8,1) | |
end | |
--[[ Non-LoD options support ]] | |
function ItemRack.ToggleOptions(self,tab) | |
if not ItemRackOptFrame then | |
EnableAddOn("ItemRackOptions") -- it's LoD, and required. Enable if disabled | |
LoadAddOn("ItemRackOptions") | |
end | |
if ItemRackOptFrame:IsVisible() then | |
ItemRackOptFrame:Hide() | |
else | |
ItemRackOptFrame:Show() | |
if tab then | |
ItemRackOpt.TabOnClick(self,tab) | |
end | |
end | |
end | |
function ItemRack.ReflectLock(override) | |
if BackdropTemplateMixin then | |
Mixin(ItemRackMenuFrame, BackdropTemplateMixin) | |
end | |
ItemRackMenuFrame:SetBackdrop( | |
{ | |
bgFile = "Interface/Tooltips/UI-Tooltip-Background", | |
edgeFile = "Interface/Tooltips/UI-Tooltip-Border", | |
tile = true, tileSize = 16, edgeSize = 16, | |
insets = { left = 4, right = 4, top = 4, bottom = 4 } | |
} | |
); | |
if ItemRackUser.Locked=="ON" or override then | |
ItemRackMenuFrame:EnableMouse(0) | |
ItemRackMenuFrame:SetBackdropBorderColor(0,0,0,0) | |
ItemRackMenuFrame:SetBackdropColor(0,0,0,0) | |
else | |
ItemRackMenuFrame:EnableMouse(1) | |
ItemRackMenuFrame:SetBackdropBorderColor(.3,.3,.3,1) | |
ItemRackMenuFrame:SetBackdropColor(1,1,1,1) | |
end | |
if ItemRackOptFrame then | |
ItemRackOpt.ListScrollFrameUpdate() | |
end | |
end | |
function ItemRack.ReflectAlpha() | |
if ItemRackButton0 then | |
for i=0,20 do | |
_G["ItemRackButton"..i]:SetAlpha(ItemRackUser.Alpha) | |
end | |
end | |
ItemRackMenuFrame:SetAlpha(ItemRackUser.Alpha) | |
end | |
function ItemRack.ReflectMenuScale(scale) | |
scale = scale or ItemRackUser.MenuScale | |
ItemRackMenuFrame:SetScale(scale) | |
end | |
function ItemRack.SetFont(button) | |
local item = _G[button.."Time"] | |
if not item then | |
return | |
end | |
if ItemRackSettings.LargeNumbers=="ON" then | |
item:SetFont("Fonts\\FRIZQT__.TTF",16,"OUTLINE") | |
item:SetTextColor(1,.82,0,1) | |
item:ClearAllPoints() | |
item:SetPoint("CENTER",button,"CENTER") | |
else | |
item:SetFont("Fonts\\ARIALN.TTF",14,"OUTLINE") | |
item:SetTextColor(1,1,1,1) | |
item:ClearAllPoints() | |
item:SetPoint("BOTTOM",button,"BOTTOM") | |
end | |
end | |
function ItemRack.ReflectCooldownFont() | |
local item | |
for i=0,20 do | |
ItemRack.SetFont("ItemRackButton"..i) | |
end | |
local i=1 | |
while _G["ItemRackMenu"..i] do | |
ItemRack.SetFont("ItemRackMenu"..i) | |
i=i+1 | |
end | |
end | |
--[[ Hidden menu items ]] | |
function ItemRack.AddHidden(id) | |
if id then | |
for i=1,#(ItemRackUser.Hidden) do | |
if ItemRackUser.Hidden[i]==id then | |
return | |
end | |
end | |
table.insert(ItemRackUser.Hidden,id) | |
end | |
end | |
function ItemRack.RemoveHidden(id) | |
for i=1,#(ItemRackUser.Hidden) do | |
if ItemRackUser.Hidden[i]==id then | |
table.remove(ItemRackUser.Hidden,i) | |
break | |
end | |
end | |
end | |
function ItemRack.IsHidden(id) | |
for i=1,#(ItemRackUser.Hidden) do | |
if ItemRackUser.Hidden[i]==id then | |
return true | |
end | |
end | |
return nil | |
end | |
function ItemRack.ToggleHidden(id) | |
if ItemRack.IsHidden(id) then | |
ItemRack.RemoveHidden(id) | |
else | |
ItemRack.AddHidden(id) | |
end | |
end | |
--[[ Key bindings ]] | |
function ItemRack.SetSetBindings() | |
local inLockdown = InCombatLockdown() | |
if not inLockdown then | |
local buttonName,button | |
for i in pairs(ItemRackUser.Sets) do | |
if ItemRackUser.Sets[i].key then | |
buttonName = "ItemRack"..UnitName("player")..GetRealmName()..i | |
button = _G[buttonName] or CreateFrame("Button",buttonName,nil,"SecureActionButtonTemplate") | |
button:SetAttribute("type","macro") | |
local macrotext = "/script ItemRack.RunSetBinding(\""..i.."\")\n" | |
for slot = 16, 18 do | |
if ItemRackUser.Sets[i].equip[slot] then | |
local name,_,_,_,_,_,_,_,_,_ = GetItemInfo("item:"..ItemRackUser.Sets[i].equip[slot]) | |
if name then | |
macrotext = macrotext .. "/equipslot [combat]" .. slot .. " " .. name .. "\n"; | |
end | |
end | |
end | |
button:SetAttribute("macrotext",macrotext) | |
SetBindingClick(ItemRackUser.Sets[i].key,buttonName) | |
end | |
end | |
SaveBindings(GetCurrentBindingSet()) | |
else | |
ItemRack.Print("Cannot save hotkeys in combat, please try again out of combat!") | |
end | |
end | |
function ItemRack.RunSetBinding(setname) | |
if ItemRackSettings.EquipToggle=="ON" then | |
ItemRack.ToggleSet(setname) | |
else | |
ItemRack.EquipSet(setname) | |
end | |
end | |
--[[ Slash Handler ]] | |
function ItemRack.SlashHandler(arg1) | |
if arg1 and string.match(arg1,"equip") then | |
local set = string.match(arg1,"equip (.+)") | |
if not set then | |
ItemRack.Print("Usage: /itemrack equip set name") | |
ItemRack.Print("ie: /itemrack equip pvp gear") | |
else | |
ItemRack.EquipSet(set) | |
end | |
return | |
elseif arg1 and string.match(arg1,"toggle") then | |
local sets = string.match(arg1,"toggle (.+)") | |
if not sets then | |
ItemRack.Print("Usage: /itemrack toggle set name[, second set name]") | |
ItemRack.Print("ie: /itemrack toggle pvp gear, tanking set") | |
else | |
local set1,set2 = string.match(sets,"(.+), ?(.+)") | |
if not set1 then | |
ItemRack.ToggleSet(sets) | |
else | |
if ItemRack.IsSetEquipped(set1) then | |
ItemRack.EquipSet(set2) | |
else | |
ItemRack.EquipSet(set1) | |
end | |
end | |
end | |
return | |
end | |
arg1 = string.lower(arg1) | |
if arg1=="reset" then | |
ItemRack.ResetButtons() | |
elseif arg1=="reset everything" then | |
ItemRack.ResetEverything() | |
elseif arg1=="lock" then | |
ItemRackUser.Locked="ON" | |
ItemRack.ReflectLock() | |
elseif arg1=="unlock" then | |
ItemRackUser.Locked="OFF" | |
ItemRack.ReflectLock() | |
elseif arg1=="opt" or arg1=="options" or arg1=="config" then | |
ItemRack.ToggleOptions() | |
else | |
ItemRack.Print("/itemrack opt : summons options window.") | |
ItemRack.Print("/itemrack equip set name : equip set 'set name'.") | |
ItemRack.Print("/itemrack toggle set name[, second set] : toggles set 'set name'.") | |
ItemRack.Print("/itemrack reset : resets buttons and their settings.") | |
ItemRack.Print("/itemrack reset everything : wipes ItemRack to default.") | |
ItemRack.Print("/itemrack lock/unlock : locks/unlocks the buttons.") | |
end | |
end | |
--[[ Bank Support ]] | |
-- returns 1 if the set has a banked item, 0 if there is an item missing entirely, nil if item is on person | |
function ItemRack.MissingItems(setname) | |
local missing | |
if not setname or not ItemRackUser.Sets[setname] then return end | |
for _,i in pairs(ItemRackUser.Sets[setname].equip) do | |
if i~=0 and ItemRack.GetCountByID(i)==0 then | |
missing = 0 | |
if ItemRack.FindInBank(i) then | |
return 1 | |
end | |
end | |
end | |
return missing | |
end | |
-- pulls setname from bank to bags | |
function ItemRack.GetBankedSet(setname) | |
if ItemRack.MissingItems(setname)~=1 or SpellIsTargeting() or GetCursorInfo() then return end | |
local bag,slot,freeBag,freeSlot | |
ItemRack.ClearLockList() | |
for _,i in pairs(ItemRackUser.Sets[setname].equip) do | |
bag,slot = ItemRack.FindInBank(i) | |
if bag then | |
freeBag,freeSlot = ItemRack.FindSpace() | |
if freeBag then | |
PickupContainerItem(bag,slot) | |
PickupContainerItem(freeBag,freeSlot) | |
else | |
ItemRack.Print("Not enough room in bags to pull all items from '"..setname.."'.") | |
return | |
end | |
end | |
end | |
end | |
-- pushes setname from bags/worn to bank | |
function ItemRack.PutBankedSet(setname) | |
if SpellIsTargeting() or GetCursorInfo() then return end | |
local inv,bag,slot,freeBag,freeSlot | |
ItemRack.ClearLockList() | |
for _,i in pairs(ItemRackUser.Sets[setname].equip) do | |
if i~=0 then | |
freeBag,freeSlot = ItemRack.FindBankSpace() | |
if freeBag then | |
inv,bag,slot = ItemRack.FindItem(i) | |
if inv then | |
PickupInventoryItem(inv) | |
elseif bag then | |
PickupContainerItem(bag,slot) | |
end | |
if CursorHasItem() then | |
PickupContainerItem(freeBag,freeSlot) | |
end | |
else | |
ItemRack.Print("Not enough room in bank to store all items from '"..setname.."'.") | |
return | |
end | |
end | |
end | |
end | |
function ItemRack.ResetEverything() | |
StaticPopupDialogs["ItemRackCONFIRMRESET"] = { | |
text = "This will restore ItemRack to its default state, wiping all sets, buttons, events and settings.\nThe UI will be reloaded. Continue?", | |
button1 = "Yes", button2 = "No", timeout = 0, hideOnEscape = 1, showAlert = 1, | |
OnAccept = function() ItemRackUser=nil ItemRackSettings=nil ItemRackItems=nil ItemRackEvents=nil ReloadUI() end | |
} | |
StaticPopup_Show("ItemRackCONFIRMRESET") | |
end | |
-- if cpu profiling on, this will add a page to TinyPad with each ItemRack.func()'s time | |
function ItemRack.ProfileFuncs() | |
if TinyPadPages then | |
UpdateAddOnCPUUsage() | |
local total = 0 | |
local t = {} | |
local whole,decimal | |
for i in pairs(ItemRack) do | |
if type(ItemRack[i])=="function" then | |
whole = GetFunctionCPUUsage(ItemRack[i]) | |
decimal = whole - math.floor(whole) | |
whole = math.floor(whole) | |
table.insert(t,string.format("%04d.%02d %s",whole,decimal,i)) | |
end | |
end | |
table.sort(t) | |
local info = "ItemRack profile "..date().." "..UnitName("player").."\n" | |
for i=1,#(t) do | |
info = info..t[i].."\n" | |
end | |
table.insert(TinyPadPages,info) | |
end | |
end | |
-- returns Queues for the current set if EnablePerSetQueues is enabled, otherwise the global Queues | |
function ItemRack.GetQueues() | |
if ItemRackUser.EnablePerSetQueues == "ON" then | |
if not (ItemRackUser.CurrentSet and ItemRackUser.Sets[ItemRackUser.CurrentSet]) then | |
return ItemRackUser.Queues | |
end | |
local currentSet = ItemRackUser.Sets[ItemRackUser.CurrentSet] | |
if not currentSet.Queues then | |
currentSet.Queues = {} | |
end | |
return currentSet.Queues | |
else | |
return ItemRackUser.Queues | |
end | |
end | |
-- returns QueuesEnabled for the current set if EnablePerSetQueues is enabled, otherwise the global QueuesEnabled | |
function ItemRack.GetQueuesEnabled() | |
if ItemRackUser.EnablePerSetQueues == "ON" then | |
if not (ItemRackUser.CurrentSet and ItemRackUser.Sets[ItemRackUser.CurrentSet]) then | |
return ItemRackUser.QueuesEnabled | |
end | |
local currentSet = ItemRackUser.Sets[ItemRackUser.CurrentSet] | |
if not currentSet.QueuesEnabled then | |
currentSet.QueuesEnabled = {} | |
end | |
return currentSet.QueuesEnabled | |
else | |
return ItemRackUser.QueuesEnabled | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment