You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3030 lines
100 KiB

local ADDON_NAME,Internal = ...
local L = Internal.L
local ClearCursor = ClearCursor
local PickupInventoryItem = PickupInventoryItem
local PickupContainerItem = C_Container and C_Container.PickupContainerItem or PickupContainerItem
local GetContainerFreeSlots = C_Container and C_Container.GetContainerFreeSlots or GetContainerFreeSlots
local GetContainerItemInfo = C_Container and C_Container.GetContainerItemInfo or GetContainerItemInfo
local EquipmentManager_UnpackLocation = EquipmentManager_UnpackLocation
local GetInventoryItemLink = GetInventoryItemLink
local GetContainerItemLink = C_Container and C_Container.GetContainerItemLink or GetContainerItemLink
local GetContainerNumSlots = C_Container and C_Container.GetContainerNumSlots or GetContainerNumSlots
local GetVoidItemHyperlinkString = GetVoidItemHyperlinkString
local GetItemUniqueness = GetItemUniqueness
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
local AddSet = Internal.AddSet
local sort = table.sort
local format = string.format
local GetCharacterSlug = Internal.GetCharacterSlug
local GetCharacterInfo = Internal.GetCharacterInfo
local AddSetToMapData, RemoveSetFromMapData, UpdateSetItemInMapData
local function PackLocation(bag, slot)
if bag == nil then -- Inventory slot
if slot >= 52 and slot <= 79 then -- Bank Slot
return bit.bor(ITEM_INVENTORY_LOCATION_BANK, slot);
else -- Equipment slot
return bit.bor(ITEM_INVENTORY_LOCATION_PLAYER, slot);
end
else
if bag == BANK_CONTAINER then
return bit.bor(ITEM_INVENTORY_LOCATION_BANK, slot + 51); -- Bank slots are stored as inventory slots, and start at 52
elseif bag >= 0 and bag <= NUM_BAG_SLOTS then
return bit.bor(ITEM_INVENTORY_LOCATION_PLAYER, ITEM_INVENTORY_LOCATION_BAGS, bit.lshift(bag, ITEM_INVENTORY_BAG_BIT_OFFSET), slot);
elseif bag >= NUM_BAG_SLOTS+1 and bag <= NUM_BAG_SLOTS+NUM_BANKBAGSLOTS then
return bit.bor(ITEM_INVENTORY_LOCATION_BANK, ITEM_INVENTORY_LOCATION_BAGS, bit.lshift(bag - ITEM_INVENTORY_BANK_BAG_OFFSET, ITEM_INVENTORY_BAG_BIT_OFFSET), slot);
end
end
end
local function LocationIsInventory(location, slot)
if slot then
return PackLocation(nil, slot) == location
else
return bit.band(ITEM_INVENTORY_LOCATION_PLAYER, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) == 0
end
end
local function LocationIsBag(location, bag, slot)
if bag and slot then
return PackLocation(bag, slot) == location
elseif bag then
local bagmask = PackLocation(bag, 0)
return bit.band(location, bagmask) == bagmask
else
return bit.band(ITEM_INVENTORY_LOCATION_PLAYER, ITEM_INVENTORY_LOCATION_BAG, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) == 0
end
end
local function LocationIsBank(location, slot)
if slot then
return bit.band(ITEM_INVENTORY_LOCATION_BANK, location) ~= 0 and (location - ITEM_INVENTORY_LOCATION_BANK) == slot
else
return bit.band(ITEM_INVENTORY_LOCATION_BANK, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) == 0
end
end
local function LocationIsBankBag(location, bag, slot)
if bag and slot then
return PackLocation(bag, slot) == location
elseif bag then
local bagmask = PackLocation(bag, 0)
return bit.band(location, bagmask) == bagmask
else
return bit.band(ITEM_INVENTORY_LOCATION_BANK, ITEM_INVENTORY_LOCATION_BAG, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) == 0
end
end
-- Convert an ItemLocationMixin into a location number
local function GetLocationFromItemLocation(itemLocation)
if itemLocation:IsEquipmentSlot() then
return PackLocation(nil, itemLocation:GetEquipmentSlot())
elseif itemLocation:IsBagAndSlot() then
return PackLocation(itemLocation:GetBagAndSlot())
end
end
-- Updated an ItemLocationMixin with location from a numeric location
local function SetItemLocationFromLocation(itemLocation, location)
local player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(location)
if not player and not bank and not bags and not voidStorage then
itemLocation:Clear()
elseif bags then
itemLocation:SetBagAndSlot(bag, slot)
elseif player then
itemLocation:SetEquipmentSlot(slot)
elseif bank then
itemLocation:SetBagAndSlot(BANK_CONTAINER, slot - 51)
else
error("@TODO")
end
return itemLocation
end
local function IsLocationEquipmentSlot(location)
local player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(location)
if player and not bags then
return true
end
return false
end
-- Get azerite item data from a location
local function GetAzeriteDataForItemLocation(itemLocation)
if itemLocation and itemLocation:HasAnyLocation() and itemLocation:IsValid() and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation) then
local azerite = {};
local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(itemLocation);
for index,tier in ipairs(tiers) do
for _,powerID in ipairs(tier.azeritePowerIDs) do
if C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
azerite[index] = powerID;
break;
end
end
end
return "-azerite:" .. #azerite .. ":" .. table.concat(azerite, ":")
end
return ""
end
Internal.GetAzeriteDataForItemLocation = GetAzeriteDataForItemLocation
local GetAzeriteDataForLocation
do
local itemLocation = ItemLocation:CreateEmpty()
function GetAzeriteDataForLocation(location)
SetItemLocationFromLocation(itemLocation, location)
return GetAzeriteDataForItemLocation(itemLocation)
end
end
local function GetExtrasForItemLocation(itemLocation, extras)
extras = extras or {}
if itemLocation and itemLocation:HasAnyLocation() and itemLocation:IsValid() and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation) then
local azerite = {};
local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(itemLocation);
for index,tier in ipairs(tiers) do
for _,powerID in ipairs(tier.azeritePowerIDs) do
if C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
azerite[index] = powerID;
break;
end
end
end
extras.azerite = azerite
else
extras.azerite = nil -- Clear azerite data
end
return extras
end
local GetExtrasForLocation
do
local itemLocation = ItemLocation:CreateEmpty()
function GetExtrasForLocation(location, extras)
SetItemLocationFromLocation(itemLocation, location)
return GetExtrasForItemLocation(itemLocation, extras)
end
end
Internal.GetExtrasForLocation = GetExtrasForLocation
local function GetItemString(itemLink)
local itemString = string.match(itemLink, "item[%-?%d:]+Player%-[%d]+%-[%dA-F]+[%-?%d:]+")
if itemString == nil then -- Without GUID match
itemString = string.match(itemLink, "item[%-?%d:]+")
end
return itemString;
end
local function DeEnchantItemLink(itemLink)
local itemString = GetItemString(itemLink)
return string.format("%s::::::%s:::%s", string.match(itemString, "^(item:%d+):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]*:[^:]*):[^:]*:[^:]*:(.*)$"))
end
-- Updates an item string, correcting the number of parts
local function FixItemString(itemString)
local parts = {strsplit(':', itemString)}
local count = 14
local numBonusIDs = tonumber(parts[count])
if numBonusIDs then
count = count + numBonusIDs
end
count = count + 1
local numModifiers = tonumber(parts[count])
if numModifiers then
count = count + (numModifiers * 2)
end
count = count + 1
local relic1NumBonusIDs = tonumber(parts[count])
if relic1NumBonusIDs then
count = count + relic1NumBonusIDs
end
count = count + 1
local relic2NumBonusIDs = tonumber(parts[count])
if relic2NumBonusIDs then
count = count + relic2NumBonusIDs
end
count = count + 1
local relic3NumBonusIDs = tonumber(parts[count])
if relic3NumBonusIDs then
count = count + relic3NumBonusIDs
end
count = count + 1
-- Add crafterGUID, added in 9.1
if count > #parts then
parts[#parts+1] = ''
end
count = count + 1
-- Add extraEnchantID, added in 9.1
if count > #parts then
parts[#parts+1] = ''
end
count = count + 1
return table.concat(parts, ':')
end
-- Remove parts of the item string that dont reflect item variations, including linkLevel, specializationID, and crafterGUID
local function SanitiseItemString(itemString)
return string.format("%s:::%s::%s", string.match(FixItemString(itemString), "^(item:%d+:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*):[^:]*:[^:]*:(.*):[^:]*:([^:]*)$"))
end
local function UnsanitiseItemString(itemString)
local a, b = string.match(itemString, "^(item:%d+:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*):[^:]*:[^:]*:(.*)$")
return string.format("%s:%d:%d:%s", a, UnitLevel("player"), (GetSpecializationInfo(GetSpecialization())), b)
end
local function FixItemData(itemData)
local itemString, azeriteString = string.match(itemData, "^(.+)-azerite([^-]+)$")
if azeriteString then
return FixItemString(itemString) .. "-azerite" .. azeriteString
end
return FixItemString(itemData)
end
local function EncodeItemData(itemLink, azerite)
local itemString = GetItemString(itemLink)
if type(azerite) == "table" then
if #azerite == 0 then
azerite = "-azerite:0"
else
azerite = "-azerite:" .. #azerite .. ":" .. table.concat(azerite, ":")
end
elseif type(azerite) ~= "string" then
azerite = ""
end
return SanitiseItemString(itemString) .. azerite
end
Internal.EncodeItemData = EncodeItemData
local function GetEncodedItemDataForItemLocation(itemLocation)
if itemLocation:IsValid() then
local itemLink = C_Item.GetItemLink(itemLocation)
if itemLink then -- Some items in inventory dont have item links, keystones, battlepets
local itemString = GetItemString(itemLink)
if itemString then
local azerite = GetAzeriteDataForItemLocation(itemLocation)
return SanitiseItemString(itemString) .. azerite
end
end
end
end
local GetEncodedItemDataForLocation
do
local itemLocation = ItemLocation:CreateEmpty()
function GetEncodedItemDataForLocation(location)
SetItemLocationFromLocation(itemLocation, location)
return GetEncodedItemDataForItemLocation(itemLocation)
end
end
-- takes a list of locations and itemData and finds the matching item
local function GetItemWithinLocationsByItemData(locations, itemData)
local itemID = GetItemInfoInstant(itemData)
for location,locationItemID in pairs(locations) do
if itemID == locationItemID and itemData == GetEncodedItemDataForLocation(location) then
return location
end
end
end
local freeSlotsCache = {}
local function GetContainerItemLocked(bag, slot)
local locked = select(3, GetContainerItemInfo(bag, slot))
return locked and true or false
end
local function IsLocationLocked(location)
local player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(location);
if not player and not bank and not bags and not voidStorage then -- Invalid location
return;
end
local locked;
if voidStorage then
locked = select(3, GetVoidItemInfo(tab, voidSlot))
elseif not bags then -- and (player or bank)
locked = IsInventoryItemLocked(slot)
else -- bags
locked = select(3, GetContainerItemInfo(bag, slot))
end
return locked;
end
local function EmptyInventorySlot(inventorySlotId, reason)
local itemBagType = GetItemFamily(GetInventoryItemLink("player", inventorySlotId))
local foundSlot = false
local containerId, slotId
for i = NUM_BAG_SLOTS, 0, -1 do
local _, bagType = GetContainerNumFreeSlots(i)
local freeSlots = freeSlotsCache[i]
if #freeSlots > 0 and (bit.band(bagType, itemBagType) > 0 or bagType == 0) then
foundSlot = true
containerId = i
slotId = freeSlots[#freeSlots]
freeSlots[#freeSlots] = nil
break
end
end
local complete = false;
if foundSlot then
ClearCursor()
PickupInventoryItem(inventorySlotId)
if CursorHasItem() then
PickupContainerItem(containerId, slotId)
-- If the swap succeeded then the cursor should be empty
if not CursorHasItem() then
complete = true;
end
end
Internal.LogMessage("Empty inventory slot %d (%s,%s)", inventorySlotId, reason or "default", complete and "true" or "false")
ClearCursor();
else
Internal.LogMessage("Empty inventory slot %d (%s,%s)", inventorySlotId, reason or "default", "false")
end
return complete, foundSlot
end
-- Modified version of EquipmentManager_GetItemInfoByLocation but gets the item link instead
local function GetItemLinkByLocation(location)
local player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(location);
if not player and not bank and not bags and not voidStorage then -- Invalid location
return;
end
local itemLink;
if voidStorage then
itemLink = GetVoidItemHyperlinkString(tab, voidSlot);
elseif not bags then -- and (player or bank)
itemLink = GetInventoryItemLink("player", slot);
else -- bags
itemLink = GetContainerItemLink(bag, slot);
end
return itemLink;
end
local function SwapInventorySlot(inventorySlotId, itemLink, location, reason)
local complete = false;
local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
if not voidStorage and not (player and not bags and slot == inventorySlotId) and not IsLocationLocked(location) then
ClearCursor()
if bag == nil then
PickupInventoryItem(slot)
else
PickupContainerItem(bag, slot)
end
if CursorHasItem() then
PickupInventoryItem(inventorySlotId)
-- If the swap succeeded then the cursor should be empty
if not CursorHasItem() then
complete = true;
end
end
Internal.LogMessage("Switching inventory slot %d to %s (%s,%s)", inventorySlotId, GetItemLinkByLocation(location), reason or "default", complete and "true" or "false")
ClearCursor();
end
return complete
end
Internal.GetItemLinkByLocation = GetItemLinkByLocation;
local function CompareItemLinks(a, b)
local itemIDA = GetItemInfoInstant(a);
local itemIDB = GetItemInfoInstant(b);
return itemIDA == itemIDB;
end
-- item:127454::::::::120::::1:0:
-- item:127454::::::::120::512::1:5473:120
-- item:127454::::::::120:268:512:22:2:6314:6313:120:::
local function GetCompareItemInfo(itemLink)
local itemString = GetItemString(itemLink)
local linkData = {strsplit(":", itemString)};
local itemID = tonumber(linkData[2]);
local enchantID = tonumber(linkData[3]);
local gemIDs = {n = 4, [tonumber(linkData[4]) or 0] = true, [tonumber(linkData[5]) or 0] = true, [tonumber(linkData[6]) or 0] = true, [tonumber(linkData[7]) or 0] = true};
local suffixID = tonumber(linkData[8]);
local uniqueID = tonumber(linkData[9]);
local upgradeTypeID = tonumber(linkData[12]);
local index = 14;
local numBonusIDs = tonumber(linkData[index]) or 0;
local bonusIDs = {n = numBonusIDs};
for i=1,numBonusIDs do
local id = tonumber(linkData[index + i])
if id then
bonusIDs[id] = true;
end
end
index = index + numBonusIDs + 1;
local numModifiers = tonumber(linkData[index]) or 0;
index = index + 1;
local upgradeTypeIDs = {n = numModifiers};
for i=1,numModifiers do
local id = tonumber(linkData[index + 1]);
local value = tonumber(linkData[index + 2]);
if id then
upgradeTypeIDs[id] = value
end
index = index + 2;
end
local relic1NumBonusIDs = tonumber(linkData[index]) or 0;
local relic1BonusIDs = {n = relic1NumBonusIDs};
for i=1,relic1NumBonusIDs do
local id = tonumber(linkData[index + i])
if id then
relic1BonusIDs[id] = true;
end
end
index = index + relic1NumBonusIDs + 1;
local relic2NumBonusIDs = tonumber(linkData[index]) or 0;
local relic2BonusIDs = {n = relic2NumBonusIDs};
for i=1,relic2NumBonusIDs do
local id = tonumber(linkData[index + i])
if id then
relic2BonusIDs[id] = true;
end
end
index = index + relic2NumBonusIDs + 1;
local relic3NumBonusIDs = tonumber(linkData[index]) or 0;
local relic3BonusIDs = {n = relic3NumBonusIDs};
for i=1,relic3NumBonusIDs do
local id = tonumber(linkData[index + i])
if id then
relic3BonusIDs[id] = true;
end
end
index = index + relic3NumBonusIDs + 1;
-- These were added in 9.1, we return false for links that were saved pre-9.1
-- When testing these items we check if the saved version is false and then ignore
-- comparing these values
local crafter = false
if linkData[index] ~= nil then
crafter = linkData[index]
end
index = index + 1;
local enchantID2 = false
if linkData[index] ~= nil then
enchantID2 = tonumber(linkData[index])
end
return itemID, enchantID, gemIDs, suffixID, uniqueID, upgradeTypeID, bonusIDs, upgradeTypeIDs, relic1BonusIDs, relic2BonusIDs, relic3BonusIDs, crafter, enchantID2;
end
--[[
GetItemUniqueness will sometimes return Unique-Equipped info instead of Legion Legendary info,
this is a cache of items with that or similar issues
]]
local itemUniquenessCache = {
[144259] = {357, 2},
[144258] = {357, 2},
[144249] = {357, 2},
[152626] = {357, 2},
[151650] = {357, 2},
[151649] = {357, 2},
[151647] = {357, 2},
[151646] = {357, 2},
[151644] = {357, 2},
[151643] = {357, 2},
[151642] = {357, 2},
[151641] = {357, 2},
[151640] = {357, 2},
[151639] = {357, 2},
[151636] = {357, 2},
[150936] = {357, 2},
[138854] = {357, 2},
[137382] = {357, 2},
[137276] = {357, 2},
[137223] = {357, 2},
[137220] = {357, 2},
[137055] = {357, 2},
[137054] = {357, 2},
[137052] = {357, 2},
[137051] = {357, 2},
[137050] = {357, 2},
[137049] = {357, 2},
[137048] = {357, 2},
[137047] = {357, 2},
[137046] = {357, 2},
[137045] = {357, 2},
[137044] = {357, 2},
[137043] = {357, 2},
[137042] = {357, 2},
[137041] = {357, 2},
[137040] = {357, 2},
[137039] = {357, 2},
[137038] = {357, 2},
[137037] = {357, 2},
[133974] = {357, 2},
[133973] = {357, 2},
[132460] = {357, 2},
[132452] = {357, 2},
[132449] = {357, 2},
[132410] = {357, 2},
[132378] = {357, 2},
[132369] = {357, 2},
}
-- Returns the same as GetItemUniqueness except uses the above cache, also converts -1 family to itemID
local function GetItemUniquenessCached(itemLink)
local itemID = GetItemInfoInstant(itemLink)
local uniqueFamily, maxEquipped
local _, _, _, _, _, _, bonusIDs = GetCompareItemInfo(itemLink)
if bonusIDs then -- Unity Lego
for i=8119,8130 do
if bonusIDs[i] then
return 496, 1
end
end
end
if itemUniquenessCache[itemID] then
uniqueFamily, maxEquipped = unpack(itemUniquenessCache[itemID])
else
uniqueFamily, maxEquipped = GetItemUniqueness(itemLink)
end
if uniqueFamily == -1 then
uniqueFamily = -itemID
end
return uniqueFamily, maxEquipped
end
local GetBestMatch;
do
local itemLocation = ItemLocation:CreateEmpty();
local function GetMatchValue(itemLink, extras, location)
local player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(location);
if not player and not bank and not bags and not voidStorage then -- Invalid location
return 0;
end
SetItemLocationFromLocation(itemLocation, location)
local locationItemLink = C_Item.GetItemLink(itemLocation)
-- if voidStorage then
-- locationItemLink = GetVoidItemHyperlinkString(tab, voidSlot);
-- itemLocation:Clear();
-- elseif not bags then -- and (player or bank)
-- locationItemLink = GetInventoryItemLink("player", slot);
-- itemLocation:SetEquipmentSlot(slot);
-- else -- bags
-- locationItemLink = GetContainerItemLink(bag, slot);
-- itemLocation:SetBagAndSlot(bag, slot);
-- end
local match = 0;
local itemID, enchantID, gemIDs, suffixID, uniqueID, upgradeTypeID, bonusIDs, upgradeTypeIDs, relic1BonusIDs, relic2BonusIDs, relic3BonusIDs, crafter, enchantID2 = GetCompareItemInfo(itemLink);
local locationItemID, locationEnchantID, locationGemIDs, locationSuffixID, locationUniqueID, locationUpgradeTypeID, locationBonusIDs, locationUpgradeTypeIDs, locationRelic1BonusIDs, locationRelic2BonusIDs, locationRelic3BonusIDs, locationCrafter, locationEnchantID2 = GetCompareItemInfo(locationItemLink);
if enchantID == locationEnchantID then
match = match + 1;
end
if suffixID == suffixID then
match = match + 1;
end
if uniqueID == locationUniqueID then
match = match + 1;
end
if upgradeTypeID == locationUpgradeTypeID then
match = match + 1;
end
local id = nil
for i=1,math.max(gemIDs.n,locationGemIDs.n) do
id = next(gemIDs, id)
if id and locationGemIDs[id] then
match = match + 1;
end
end
id = nil
for i=1,math.max(bonusIDs.n,locationBonusIDs.n) do
id = next(bonusIDs, id)
if id and locationBonusIDs[id] then
match = match + 1;
end
end
id = nil
for i=1,math.max(upgradeTypeIDs.n,locationUpgradeTypeIDs.n) do
id = next(upgradeTypeIDs, id)
if upgradeTypeIDs[id] == locationUpgradeTypeIDs[id] then
match = match + 1;
end
end
id = nil
for i=1,math.max(relic1BonusIDs.n,locationRelic1BonusIDs.n) do
id = next(relic1BonusIDs, id)
if id and locationRelic1BonusIDs[id] then
match = match + 1;
end
end
id = nil
for i=1,math.max(relic2BonusIDs.n,locationRelic2BonusIDs.n) do
id = next(relic2BonusIDs, id)
if id and locationRelic2BonusIDs[id] then
match = match + 1;
end
end
id = nil
for i=1,math.max(relic3BonusIDs.n,locationRelic3BonusIDs.n) do
id = next(relic3BonusIDs, id)
if id and locationRelic3BonusIDs[id] then
match = match + 1;
end
end
if crafter == locationCrafter then
match = match + 1;
end
if enchantID2 == locationEnchantID2 then
match = match + 1;
end
if extras and extras.azerite and itemLocation:HasAnyLocation() and itemLocation:IsValid() and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation) then
for _,powerID in ipairs(extras.azerite) do
if C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
match = match + 1;
end
end
end
return match;
end
local locationMatchValue, locationFiltered = {}, {};
function GetBestMatch(itemLink, extras, locations)
local itemID = GetItemInfoInstant(itemLink);
wipe(locationMatchValue);
wipe(locationFiltered);
for location,locationItemLink in pairs(locations) do
local locationItemID = GetItemInfoInstant(locationItemLink)
if itemID == locationItemID then
locationMatchValue[location] = GetMatchValue(itemLink, extras, location);
locationFiltered[#locationFiltered+1] = location;
end
end
sort(locationFiltered, function (a,b)
if locationMatchValue[a] == locationMatchValue[b] then
return a < b
end
return locationMatchValue[a] > locationMatchValue[b];
end);
return locationFiltered[1];
end
end
local function CompareTables(a, b)
if a.n ~= b.n then
return false
end
for k in pairs(a) do
if not b[k] then
return false
end
end
return true
end
local function CompareItems(itemLinkA, itemLinkB, extrasA, extrasB)
if itemLinkA == nil and itemLinkB == nil then
return true
end
if itemLinkA == nil or itemLinkB == nil then
return false
end
if (extrasA ~= nil and extrasB == nil) or (extrasA == nil and extrasB ~= nil) then
return false
end
if GetItemInfoInstant(itemLinkA) ~= GetItemInfoInstant(itemLinkB) then
return false
end
local itemIDA, enchantIDA, gemIDsA, suffixIDA, uniqueIDA, upgradeTypeIDA, bonusIDsA, upgradeTypeIDsA, relic1BonusIDsA, relic2BonusIDsA, relic3BonusIDsA = GetCompareItemInfo(itemLinkA)
local itemIDB, enchantIDB, gemIDsB, suffixIDB, uniqueIDB, upgradeTypeIDB, bonusIDsB, upgradeTypeIDsB, relic1BonusIDsB, relic2BonusIDsB, relic3BonusIDsB = GetCompareItemInfo(itemLinkB)
if itemIDA ~= itemIDB then
return false
end
if enchantIDA ~= enchantIDB or #gemIDsA ~= #gemIDsB or suffixIDA ~= suffixIDB or
uniqueIDA ~= uniqueIDB or upgradeTypeIDA ~= upgradeTypeIDB or
#bonusIDsA ~= #bonusIDsB or #relic1BonusIDsA ~= #relic1BonusIDsB or #relic2BonusIDsA ~= #relic2BonusIDsB or #relic3BonusIDsA ~= #relic3BonusIDsB then
return false;
end
if not CompareTables(gemIDsA, gemIDsB) then
return false
end
if not CompareTables(bonusIDsA, bonusIDsB) then
return false
end
if not CompareTables(upgradeTypeIDsA, upgradeTypeIDsB) then
return false
end
if not CompareTables(relic1BonusIDsA, relic1BonusIDsB) then
return false
end
if not CompareTables(relic2BonusIDsA, relic2BonusIDsB) then
return false
end
if not CompareTables(relic3BonusIDsA, relic3BonusIDsB) then
return false
end
if extrasA and extrasA.azerite and extrasB and extrasB.azerite then
if #extrasA.azerite ~= #extrasB.azerite then
return false
end
for index,powerID in ipairs(extrasA.azerite) do
if powerID ~= extrasB.azerite[index] then
return false
end
end
end
return true
end
local IsItemInLocation;
do
local itemLocation = ItemLocation:CreateEmpty();
function IsItemInLocation(itemLink, extras, player, bank, bags, voidStorage, slot, bag, tab, voidSlot)
if type(player) == "number" then
player, bank, bags, voidStorage, slot, bag, tab, voidSlot = EquipmentManager_UnpackLocation(player);
end
if not player and not bank and not bags and not voidStorage then -- Invalid location
return false;
end
local locationItemLink;
if voidStorage then
locationItemLink = GetVoidItemHyperlinkString(tab, voidSlot);
itemLocation:Clear();
elseif not bags then -- and (player or bank)
locationItemLink = GetInventoryItemLink("player", slot);
itemLocation:SetEquipmentSlot(slot);
else -- bags
locationItemLink = GetContainerItemLink(bag, slot);
itemLocation:SetBagAndSlot(bag, slot);
end
if itemLink ~= nil and locationItemLink == nil then
return false;
end
if itemLink == nil and locationItemLink ~= nil then
return false;
end
local itemID, enchantID, gemIDs, suffixID, uniqueID, upgradeTypeID, bonusIDs, upgradeTypeIDs, relic1BonusIDs, relic2BonusIDs, relic3BonusIDs, crafter, enchantID2 = GetCompareItemInfo(itemLink);
local locationItemID, locationEnchantID, locationGemIDs, locationSuffixID, locationUniqueID, locationUpgradeTypeID, locationBonusIDs, locationUpgradeTypeIDs, locationRelic1BonusIDs, locationRelic2BonusIDs, locationRelic3BonusIDs, locationCrafter, locationEnchantID2 = GetCompareItemInfo(locationItemLink);
if itemID ~= locationItemID or enchantID ~= locationEnchantID or #gemIDs ~= #locationGemIDs or suffixID ~= locationSuffixID or uniqueID ~= locationUniqueID or upgradeTypeID ~= locationUpgradeTypeID or #bonusIDs ~= #bonusIDs or #relic1BonusIDs ~= #locationRelic1BonusIDs or #relic2BonusIDs ~= #locationRelic2BonusIDs or #relic3BonusIDs ~= #locationRelic3BonusIDs or (crafter ~= false and crafter ~= locationCrafter) or (enchantID2 ~= false and enchantID2 ~= locationEnchantID2) then
return false;
end
if not CompareTables(gemIDs, locationGemIDs) then
return false
end
if not CompareTables(bonusIDs, locationBonusIDs) then
return false
end
if not CompareTables(upgradeTypeIDs, locationUpgradeTypeIDs) then
return false
end
if not CompareTables(relic1BonusIDs, locationRelic1BonusIDs) then
return false
end
if not CompareTables(relic2BonusIDs, locationRelic2BonusIDs) then
return false
end
if not CompareTables(relic3BonusIDs, locationRelic3BonusIDs) then
return false
end
local itemHasAzerite = (extras and extras.azerite) and true or false
local locationItemHasAzerite = itemLocation:HasAnyLocation() and itemLocation:IsValid() and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation)
if itemHasAzerite ~= locationItemHasAzerite then
return false
end
if itemHasAzerite and locationItemHasAzerite then
for _,powerID in ipairs(extras.azerite) do
if not C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
return false;
end
end
end
return true;
end
end
local CheckEquipmentSetForIssues
do
local uniqueFamilies = {}
local uniqueFamilyItems = {}
function CheckEquipmentSetForIssues(set)
local ignored = set.ignored
local expected = set.equipment
local locations = set.locations
local errors = set.errors or {}
wipe(uniqueFamilies)
wipe(uniqueFamilyItems)
local firstEquipped = INVSLOT_FIRST_EQUIPPED
local lastEquipped = INVSLOT_LAST_EQUIPPED
for inventorySlotId = firstEquipped, lastEquipped do
errors[inventorySlotId] = nil
if not ignored[inventorySlotId] and expected[inventorySlotId] then
local itemLink = expected[inventorySlotId]
local uniqueFamily, maxEquipped = GetItemUniquenessCached(itemLink)
if uniqueFamily ~= nil then
uniqueFamilies[uniqueFamily] = (uniqueFamilies[uniqueFamily] or maxEquipped) - 1
if uniqueFamilyItems[uniqueFamily] then
uniqueFamilyItems[uniqueFamily][#uniqueFamilyItems[uniqueFamily]+1] = inventorySlotId
else
uniqueFamilyItems[uniqueFamily] = {inventorySlotId}
end
end
local index = 1
local gemName, gemLink = GetItemGem(itemLink, index)
while gemName do
uniqueFamily, maxEquipped = GetItemUniquenessCached(gemLink)
if uniqueFamily ~= nil then
uniqueFamilies[uniqueFamily] = (uniqueFamilies[uniqueFamily] or maxEquipped) - 1
if uniqueFamilyItems[uniqueFamily] then
uniqueFamilyItems[uniqueFamily][#uniqueFamilyItems[uniqueFamily]+1] = inventorySlotId
else
uniqueFamilyItems[uniqueFamily] = {inventorySlotId}
end
end
index = index + 1
gemName, gemLink = GetItemGem(itemLink, index)
end
end
end
for uniqueFamily, maxEquipped in pairs(uniqueFamilies) do
if maxEquipped < 0 then
for _,inventorySlotId in ipairs(uniqueFamilyItems[uniqueFamily]) do
if errors[inventorySlotId] then
errors[inventorySlotId] = format("%s\n%s", errors[inventorySlotId], ERR_ITEM_UNIQUE_EQUIPPABLE)
elseif uniqueFamily < 0 then -- Item
errors[inventorySlotId] = ERR_ITEM_UNIQUE_EQUIPPABLE
else
errors[inventorySlotId] = ERR_ITEM_UNIQUE_EQUIPPABLE
end
end
end
end
local character = GetCharacterSlug()
if character == set.character then
for inventorySlotId = firstEquipped, lastEquipped do
if not ignored[inventorySlotId] and expected[inventorySlotId] then
local location = locations[inventorySlotId]
if not location then
errors[inventorySlotId] = L["Exact item is missing and may not be equippable"]
elseif not BankFrame:IsShown() and bit.band(location, ITEM_INVENTORY_LOCATION_BANK) == ITEM_INVENTORY_LOCATION_BANK then
errors[inventorySlotId] = L["Exact item is in the bank"]
end
end
end
end
set.errors = errors
return errors
end
end
local function IsEquipmentSetActive(set)
local expected = set.equipment;
local extras = set.extras;
local locations = set.locations;
local ignored = set.ignored;
local firstEquipped = INVSLOT_FIRST_EQUIPPED;
local lastEquipped = INVSLOT_LAST_EQUIPPED;
-- if combatSwap then
-- firstEquipped = INVSLOT_MAINHAND;
-- lastEquipped = INVSLOT_RANGED;
-- end
for inventorySlotId=firstEquipped,lastEquipped do
if not ignored[inventorySlotId] then
if expected[inventorySlotId] then
if locations[inventorySlotId] then
local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(locations[inventorySlotId]);
if not (player and not bags and slot == inventorySlotId) then
return false;
end
else
local itemLink = GetInventoryItemLink("player", inventorySlotId)
if not itemLink or not CompareItemLinks(itemLink, expected[inventorySlotId]) then
return false;
end
end
elseif GetInventoryItemLink("player", inventorySlotId) ~= nil then
return false;
end
end
end
return true;
end
local ActivateEquipmentSet;
do
local correctSlots = {};
local possibleItems = {};
local bestMatchForSlot = {};
local uniqueFamilies = {};
-- This function is destructive to the set
function ActivateEquipmentSet(set, state)
if not state or not state.allowPartial then
local ignored = set.ignored;
local expected = set.equipment;
local extras = set.extras;
local locations = set.locations;
local errors = set.errors;
local anyLockedSlots, anyFoundFreeSlots, anyChangedSlots = nil, nil, nil
wipe(correctSlots)
wipe(uniqueFamilies)
local firstEquipped = INVSLOT_FIRST_EQUIPPED
local lastEquipped = INVSLOT_LAST_EQUIPPED
-- if combatSwap then
-- firstEquipped = INVSLOT_MAINHAND
-- lastEquipped = INVSLOT_RANGED
-- end
-- Store a list of all available empty slots
local totalFreeSlots = 0
for i=BACKPACK_CONTAINER,NUM_BAG_SLOTS do
if not freeSlotsCache[i] then
freeSlotsCache[i] = {}
else
wipe(freeSlotsCache[i])
end
if GetContainerFreeSlots(i, freeSlotsCache[i]) then
totalFreeSlots = totalFreeSlots + #freeSlotsCache[i]
end
end
-- Loop through and empty slots that should be empty, also store locations for other slots
for inventorySlotId = firstEquipped, lastEquipped do
if errors and errors[inventorySlotId] then -- If there is an error in a slot, normally due to unique-equipped items then just ignore it
ignored[inventorySlotId] = true
end
-- Update the expected item link by location, if the target location is empty than ignore the slot, this does NOT effect the original set
if not ignored[inventorySlotId] and locations[inventorySlotId] and locations[inventorySlotId] > 0 and not expected[inventorySlotId] then
expected[inventorySlotId] = GetItemLinkByLocation(locations[inventorySlotId])
if not expected[inventorySlotId] then
ignored[inventorySlotId] = true
end
end
if not ignored[inventorySlotId] then
local slotLocked = IsInventoryItemLocked(inventorySlotId)
anyLockedSlots = anyLockedSlots or slotLocked
local itemLink = expected[inventorySlotId];
if itemLink then -- Get the location of the best match for the slot
local location = locations[inventorySlotId];
if location and location > 0 and IsItemInLocation(itemLink, extras[inventorySlotId], location) then
local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
if player and not bags and slot == inventorySlotId then -- The item is already in the desired location
correctSlots[inventorySlotId] = true;
ignored[inventorySlotId] = true;
else
bestMatchForSlot[inventorySlotId] = location;
end
else
-- The item is already in the desired location
if IsItemInLocation(itemLink, extras[inventorySlotId], true, false, false, false, inventorySlotId, false) then
correctSlots[inventorySlotId] = true;
ignored[inventorySlotId] = true;
else
GetInventoryItemsForSlot(inventorySlotId, possibleItems)
for completedSlotId in pairs(correctSlots) do
possibleItems[PackLocation(nil, completedSlotId)] = nil
end
location = GetBestMatch(itemLink, extras[inventorySlotId], possibleItems)
wipe(possibleItems);
if location == nil then -- Could not find the requested item @TODO Error
ignored[inventorySlotId] = true;
else
local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
if player and not bags and slot == inventorySlotId then -- The item is already in the desired location, this shouldnt happen
correctSlots[inventorySlotId] = true;
ignored[inventorySlotId] = true;
end
bestMatchForSlot[inventorySlotId] = location;
end
end
end
else -- Unequip
if GetInventoryItemLink("player", inventorySlotId) ~= nil then
if not IsInventoryItemLocked(inventorySlotId) then
local complete, foundSlot = EmptyInventorySlot(inventorySlotId)
anyChangedSlots = anyChangedSlots or complete
anyFoundFreeSlots = anyFoundFreeSlots or foundSlot
end
else -- Already unequipped
ignored[inventorySlotId] = true;
end
end
end
-- If we arent swapping an item out and its in some way unique we may need to skip swapping another unique item in
if ignored[inventorySlotId] then
local itemLink = GetInventoryItemLink("player", inventorySlotId)
if itemLink then
local itemID = GetItemInfoInstant(itemLink)
local uniqueFamily, maxEquipped = GetItemUniquenessCached(itemLink)
if uniqueFamily ~= nil then
uniqueFamilies[uniqueFamily] = (uniqueFamilies[uniqueFamily] or maxEquipped) - 1
end
local index = 1
local gemName, gemLink = GetItemGem(itemLink, index)
while gemName do
itemID = GetItemInfoInstant(gemLink)
uniqueFamily, maxEquipped = GetItemUniquenessCached(gemLink)
if uniqueFamily ~= nil then
uniqueFamilies[uniqueFamily] = (uniqueFamilies[uniqueFamily] or maxEquipped) - 1
end
index = index + 1
gemName, gemLink = GetItemGem(itemLink, index)
end
end
end
end
-- Check expected items uniqueness
for inventorySlotId = firstEquipped, lastEquipped do
if not ignored[inventorySlotId] and expected[inventorySlotId] then
local itemLink = expected[inventorySlotId];
local itemID = GetItemInfoInstant(itemLink);
local uniqueFamily, maxEquipped = GetItemUniquenessCached(itemLink)
if uniqueFamily then
if uniqueFamilies[uniqueFamily] then
if uniqueFamilies[uniqueFamily] <= 0 then
ignored[inventorySlotId] = true -- To many of the unique items already equipped
else
uniqueFamilies[uniqueFamily] = uniqueFamilies[uniqueFamily] - 1
end
else
uniqueFamilies[uniqueFamily] = maxEquipped - 1
end
end
if not ignored[inventorySlotId] then
local index = 1
local gemName, gemLink = GetItemGem(itemLink, index)
while gemName do
itemID = GetItemInfoInstant(gemLink);
uniqueFamily, maxEquipped = GetItemUniquenessCached(gemLink)
if uniqueFamily then
if uniqueFamilies[uniqueFamily] then
if uniqueFamilies[uniqueFamily] <= 0 then
ignored[inventorySlotId] = true -- To many of the unique gems already equipped
else
uniqueFamilies[uniqueFamily] = uniqueFamilies[uniqueFamily] - 1
end
else
uniqueFamilies[uniqueFamily] = maxEquipped - 1
end
end
index = index + 1
gemName, gemLink = GetItemGem(itemLink, index)
end
end
end
end
-- Swap currently equipped "unique" items that need to be swapped out before others can be swapped in
for inventorySlotId = firstEquipped, lastEquipped do
local itemLink = GetInventoryItemLink("player", inventorySlotId)
if not ignored[inventorySlotId] and not IsInventoryItemLocked(inventorySlotId) and expected[inventorySlotId] and itemLink ~= nil then
local itemID = GetItemInfoInstant(itemLink);
local uniqueFamily, maxEquipped = GetItemUniquenessCached(itemLink)
local swapSlot = uniqueFamilies[uniqueFamily] ~= nil and uniqueFamilies[uniqueFamily] <= 0
if not swapSlot then
local index = 1
local gemName, gemLink = GetItemGem(itemLink, index)
while gemName do
itemID = GetItemInfoInstant(gemLink)
uniqueFamily, maxEquipped = GetItemUniquenessCached(gemLink)
swapSlot = uniqueFamilies[uniqueFamily] ~= nil and uniqueFamilies[uniqueFamily] <= 0
if swapSlot then
break
end
index = index + 1
gemName, gemLink = GetItemGem(itemLink, index)
end
end
if swapSlot then
local expectedUniqueFamily = GetItemUniquenessCached(expected[inventorySlotId])
-- Under some situations we need to just remove a unique item before equipping its replacement, this is emptying slots more than I'd like
if expectedUniqueFamily and expectedUniqueFamily ~= uniqueFamily and not IsLocationEquipmentSlot(bestMatchForSlot[inventorySlotId]) then
local complete, foundSlot = EmptyInventorySlot(inventorySlotId, "unique")
anyChangedSlots = anyChangedSlots or complete
anyFoundFreeSlots = anyFoundFreeSlots or foundSlot
elseif SwapInventorySlot(inventorySlotId, expected[inventorySlotId], bestMatchForSlot[inventorySlotId], "unique") then
anyChangedSlots = true
end
end
end
end
-- Swap out items
for inventorySlotId = firstEquipped, lastEquipped do
if not ignored[inventorySlotId] and not IsInventoryItemLocked(inventorySlotId) and expected[inventorySlotId] then
if SwapInventorySlot(inventorySlotId, expected[inventorySlotId], bestMatchForSlot[inventorySlotId]) then
anyChangedSlots = true
end
end
end
ClearCursor()
-- We assume that if we have any locked slots or any changed slots we are not complete yet
local complete = not anyLockedSlots and not anyChangedSlots
if complete then
-- If there are no locked slots and not changed slots and we never found a free slot
-- to remove an item, we will consider ourselves complete but with an error
if anyFoundFreeSlots == false then
return complete, L["Failed to change equipment set"]
end
for inventorySlotId = firstEquipped, lastEquipped do
-- We mark slots as ignored when they are finished
if not ignored[inventorySlotId] then
complete = false
end
end
end
return complete, false;
end
return true, false
end
end
local function UpdateSetFilters(set)
set.filters = set.filters or {}
Internal.UpdateRestrictionFilters(set)
set.filters.character = set.character
return set
end
local function GetEquipmentSet(id)
if type(id) == "table" then
return id;
else
return BtWLoadoutsSets.equipment[id];
end
end
local function GetSetsForCharacter(tbl, slug)
tbl = tbl or {}
for _,set in pairs(BtWLoadoutsSets.equipment) do
if type(set) == "table" and set.character == slug then
tbl[#tbl+1] = set
end
end
return tbl
end
-- returns isValid and isValidForPlayer
local function EquipmentSetIsValid(set)
local set = GetEquipmentSet(set);
local isValidForPlayer = (set.character == GetCharacterSlug())
return true, isValidForPlayer
end
-- Adds a blank equipment set for the current character
local function AddBlankEquipmentSet()
local set = {
setID = Internal.GetNextSetID(BtWLoadoutsSets.equipment),
character = GetCharacterSlug(),
name = "",
equipment = {},
ignored = {},
extras = {},
locations = {},
data = {},
filters = {character = GetCharacterSlug()},
useCount = 0,
};
UpdateSetFilters(set)
BtWLoadoutsSets.equipment[set.setID] = set;
return set;
end
-- Update an equipment set with the currently equipped gear
local function RefreshEquipmentSet(set)
if set.character ~= GetCharacterSlug() then
return
end
for inventorySlotId=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
local previousLocation = set.locations[inventorySlotId]
set.equipment[inventorySlotId] = GetInventoryItemLink("player", inventorySlotId);
set.locations[inventorySlotId] = set.equipment[inventorySlotId] and PackLocation(nil, inventorySlotId) or nil; -- Only set location if there is an item
local itemLocation = ItemLocation:CreateFromEquipmentSlot(inventorySlotId);
if itemLocation and itemLocation:HasAnyLocation() and itemLocation:IsValid() and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation) then
set.extras[inventorySlotId] = set.extras[inventorySlotId] or {};
local extras = set.extras[inventorySlotId];
extras.azerite = extras.azerite or {};
wipe(extras.azerite);
local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(itemLocation);
for index,tier in ipairs(tiers) do
for _,powerID in ipairs(tier.azeritePowerIDs) do
if C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
extras.azerite[index] = powerID;
break;
end
end
end
else
set.extras[inventorySlotId] = nil;
end
-- We want this to supersede the other 2, but need those for fallback still
set.data[inventorySlotId] = set.equipment[inventorySlotId] and EncodeItemData(set.equipment[inventorySlotId], set.extras[inventorySlotId] and set.extras[inventorySlotId].azerite) or nil
if set.setID then -- Only do this for previously created sets
UpdateSetItemInMapData(set, inventorySlotId, previousLocation, set.locations[inventorySlotId])
end
end
-- Need to update the built in manager too
if set.managerID then
C_EquipmentSet.SaveEquipmentSet(set.managerID)
end
return UpdateSetFilters(set)
end
local function AddEquipmentSet()
local characterName, characterRealm = UnitFullName("player");
local result = AddSet("equipment", RefreshEquipmentSet({
character = characterRealm .. "-" .. characterName,
name = format(L["New %s Equipment Set"], characterName),
useCount = 0,
equipment = {},
ignored = {
[INVSLOT_BODY] = true,
[INVSLOT_TABARD] = true,
},
extras = {},
locations = {},
data = {},
}))
AddSetToMapData(result)
return result
end
local function GetEquipmentSetsByName(name)
return Internal.GetSetsByName("equipment", name)
end
local function GetEquipmentSetByName(name)
return Internal.GetSetByName("equipment", name, EquipmentSetIsValid)
end
local function GetEquipmentSets(id, ...)
if id ~= nil then
return BtWLoadoutsSets.equipment[id], Internal.GetEquipmentSets(...);
end
end
function Internal.GetEquipmentSetIfNeeded(id)
if id == nil then
return;
end
local set = Internal.GetEquipmentSet(id);
if IsEquipmentSetActive(set) then
return;
end
return set;
end
local function CombineEquipmentSets(result, state, ...)
result = result or {};
local playerCharacter = GetCharacterSlug()
result.equipment = {};
result.extras = {};
result.locations = {};
result.ignored = {};
result.data = {};
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
result.ignored[slot] = true;
end
for i=1,select('#', ...) do
local set = select(i, ...);
if set.character == playerCharacter and Internal.AreRestrictionsValidForPlayer(set.restrictions) then -- Skip other characters
if set.managerID then -- Just making sure everything is up to date
local ignored = C_EquipmentSet.GetIgnoredSlots(set.managerID);
local locations = C_EquipmentSet.GetItemLocations(set.managerID);
if ignored and locations then
for inventorySlotId=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
set.ignored[inventorySlotId] = ignored[inventorySlotId] and true or nil;
local location = locations[inventorySlotId] or 0;
if location > -1 then -- If location is -1 we ignore it as we cant get the item link for the item
set.equipment[inventorySlotId] = GetItemLinkByLocation(location)
set.extras[inventorySlotId] = Internal.GetExtrasForLocation(location, set.extras[inventorySlotId] or {})
set.data[inventorySlotId] = set.equipment[inventorySlotId] and Internal.EncodeItemData(set.equipment[inventorySlotId], set.extras[inventorySlotId] and set.extras[inventorySlotId].azerite) or nil
end
set.locations[inventorySlotId] = location;
if set.extras[inventorySlotId] then
wipe(set.extras[inventorySlotId])
end
end
end
end
for inventorySlotId=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if not set.ignored[inventorySlotId] then
result.ignored[inventorySlotId] = nil;
result.equipment[inventorySlotId] = set.equipment[inventorySlotId];
result.extras[inventorySlotId] = set.extras[inventorySlotId] or nil;
result.locations[inventorySlotId] = set.locations[inventorySlotId] or nil;
result.data[inventorySlotId] = set.data[inventorySlotId] or nil;
end
end
end
end
if state then
if state.blockers and not IsEquipmentSetActive(result) then
state.blockers[Internal.GetCombatBlocker()] = true
state.blockers[Internal.GetMythicPlusBlocker()] = true
state.blockers[Internal.GetJailersChainBlocker()] = true
end
if result.ignored[INVSLOT_NECK] then
state.heartEquipped = GetInventoryItemID("player", INVSLOT_NECK) == 158075
elseif result.equipment[INVSLOT_NECK] then
state.heartEquipped = GetItemInfoInstant(result.equipment[INVSLOT_NECK]) == 158075
else
state.heartEquipped = false
end
end
return result;
end
local function DeleteEquipmentSet(id)
do
local set = GetEquipmentSet(id)
if set.character == Internal.GetCharacterSlug() and set.locations then
RemoveSetFromMapData(set)
end
end
Internal.DeleteSet(BtWLoadoutsSets.equipment, id)
if type(id) == "table" then
id = id.setID;
end
for _,set in pairs(BtWLoadoutsSets.profiles) do
if type(set) == "table" then
for index,setID in ipairs(set.equipment) do
if setID == id then
table.remove(set.equipment, index)
end
end
set.character = nil
end
end
local frame = BtWLoadoutsFrame.Equipment;
local set = frame.set;
if set and set.setID == id then
frame.set = nil;
BtWLoadoutsFrame:Update();
end
end
local function EquipementSetContainsItem(set, itemLink, extras, ignoreSlotsWithLocation)
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if not set.ignored[slot] then
if (not ignoreSlotsWithLocation or not set.locations[slot]) and CompareItems(set.equipment[slot], itemLink, set.extras[slot], extras) then
return slot
end
end
end
return false
end
local function CheckErrors(errorState, set)
set = GetEquipmentSet(set)
if errorState.specID then
errorState.role, errorState.class = select(5, GetSpecializationInfoByID(errorState.specID))
end
local characterInfo = GetCharacterInfo(set.character);
errorState.class = errorState.class or characterInfo.class;
if errorState.class ~= characterInfo.class then
return L["Incompatible Class"]
end
if not Internal.AreRestrictionsValidFor(set.restrictions, errorState.specID) then
return L["Incompatible Restrictions"]
end
end
Internal.GetEquipmentSet = GetEquipmentSet
Internal.GetEquipmentSetsByName = GetEquipmentSetsByName
Internal.GetEquipmentSetByName = GetEquipmentSetByName
Internal.AddBlankEquipmentSet = AddBlankEquipmentSet
Internal.AddEquipmentSet = AddEquipmentSet
Internal.RefreshEquipmentSet = RefreshEquipmentSet
Internal.DeleteEquipmentSet = DeleteEquipmentSet
Internal.ActivateEquipmentSet = ActivateEquipmentSet
Internal.IsEquipmentSetActive = IsEquipmentSetActive
Internal.CombineEquipmentSets = CombineEquipmentSets
Internal.CheckEquipmentSetForIssues = CheckEquipmentSetForIssues
Internal.IsItemInLocation = IsItemInLocation
Internal.GetEquipmentSets = GetEquipmentSets
local GetCursorItemSource
do
local currentCursorSource = {};
local function Hook_PickupContainerItem(bag, slot)
if CursorHasItem() then
currentCursorSource.bag = bag;
currentCursorSource.slot = slot;
else
wipe(currentCursorSource);
end
end
if C_Container and C_Container.PickupContainerItem then
hooksecurefunc(C_Container, "PickupContainerItem", Hook_PickupContainerItem);
else
hooksecurefunc("PickupContainerItem", Hook_PickupContainerItem);
end
local function Hook_PickupInventoryItem(slot)
if CursorHasItem() then
currentCursorSource.slot = slot;
else
wipe(currentCursorSource);
end
end
hooksecurefunc("PickupInventoryItem", Hook_PickupInventoryItem);
function GetCursorItemSource()
return currentCursorSource.bag or false, currentCursorSource.slot or false;
end
end
-- Initializes the set dropdown menu for the Loadouts page
local function SetDropDownInit(self, set, index)
Internal.SetDropDownInit(self, set, index, "equipment", BtWLoadoutsFrame.Equipment)
end
Internal.AddLoadoutSegment({
id = "equipment",
name = L["Equipment"],
events = "PLAYER_EQUIPMENT_CHANGED",
add = AddEquipmentSet,
get = GetEquipmentSets,
getByName = GetEquipmentSetByName,
combine = CombineEquipmentSets,
isActive = IsEquipmentSetActive,
activate = ActivateEquipmentSet,
dropdowninit = SetDropDownInit,
checkerrors = CheckErrors,
})
local gameTooltipErrorLink;
local gameTooltipErrorText;
local equipLocToInvSlot = {
["INVTYPE_HEAD"] = {[1] = true},
["INVTYPE_NECK"] = {[2] = true},
["INVTYPE_SHOULDER"] = {[3] = true},
["INVTYPE_BODY"] = {[4] = true},
["INVTYPE_CHEST"] = {[5] = true},
["INVTYPE_ROBE"] = {[5] = true},
["INVTYPE_WAIST"] = {[6] = true},
["INVTYPE_LEGS"] = {[7] = true},
["INVTYPE_FEET"] = {[8] = true},
["INVTYPE_WRIST"] = {[9] = true},
["INVTYPE_HAND"] = {[10] = true},
["INVTYPE_FINGER"] = {[11] = true, [12] = true},
["INVTYPE_TRINKET"] = {[13] = true, [14] = true},
["INVTYPE_CLOAK"] = {[15] = true},
["INVTYPE_RANGED"] = {[16] = true},
["INVTYPE_2HWEAPON"] = {[16] = true},
["INVTYPE_WEAPONMAINHAND"] = {[16] = true},
["INVTYPE_WEAPONOFFHAND"] = {[16] = true},
["INVTYPE_THROWN"] = {[16] = true},
["INVTYPE_RANGEDRIGHT"] = {[16] = true},
["INVTYPE_WEAPON"] = {[16] = true},
["INVTYPE_SHIELD"] = {[17] = true},
["INVTYPE_HOLDABLE"] = {[17] = true},
["INVTYPE_TABARD"] = {[19] = true},
-- ["INVTYPE_BAG"]
-- ["INVTYPE_AMMO"]
-- ["INVTYPE_QUIVER"]
-- ["INVTYPE_RELIC"]
}
local function IsItemValidForSlot(itemLink, invSlot, classID)
local _, _, _, itemEquipLoc = GetItemInfoInstant(itemLink)
if itemEquipLoc == "INVTYPE_2HWEAPON" and invSlot == 17 and classID == 1 then -- Double 2 handers for fury
return true
elseif itemEquipLoc == "INVTYPE_WEAPON" and invSlot == 17 and (classID == 1 or classID == 4 or classID == 6 or classID == 7 or classID == 10 or classID == 11 or classID == 12) then -- Duel Weilders
return true
end
return (equipLocToInvSlot[itemEquipLoc] and equipLocToInvSlot[itemEquipLoc][invSlot]) and true or false
end
BtWLoadoutsItemSlotButtonMixin = {};
function BtWLoadoutsItemSlotButtonMixin:OnLoad()
self:RegisterForClicks("LeftButtonUp", "RightButtonUp");
-- self:RegisterEvent("GET_ITEM_INFO_RECEIVED");
local id, textureName, checkRelic = GetInventorySlotInfo(self:GetSlot());
self:SetID(id);
self.icon:SetTexture(textureName);
self.backgroundTextureName = textureName;
self.ignoreTexture:Hide();
local popoutButton = self.popoutButton;
if ( popoutButton ) then
if ( self.verticalFlyout ) then
popoutButton:SetHeight(16);
popoutButton:SetWidth(38);
popoutButton:GetNormalTexture():SetTexCoord(0.15625, 0.84375, 0.5, 0);
popoutButton:GetHighlightTexture():SetTexCoord(0.15625, 0.84375, 1, 0.5);
popoutButton:ClearAllPoints();
popoutButton:SetPoint("TOP", self, "BOTTOM", 0, 4);
else
popoutButton:SetHeight(38);
popoutButton:SetWidth(16);
popoutButton:GetNormalTexture():SetTexCoord(0.15625, 0.5, 0.84375, 0.5, 0.15625, 0, 0.84375, 0);
popoutButton:GetHighlightTexture():SetTexCoord(0.15625, 1, 0.84375, 1, 0.15625, 0.5, 0.84375, 0.5);
popoutButton:ClearAllPoints();
popoutButton:SetPoint("LEFT", self, "RIGHT", -8, 0);
end
-- popoutButton:Show();
end
end
function BtWLoadoutsItemSlotButtonMixin:OnClick()
local cursorType, _, itemLink = GetCursorInfo();
if cursorType == "item" then
if self:SetItem(itemLink, GetCursorItemSource()) then
ClearCursor();
end
elseif IsModifiedClick("SHIFT") then
local set = self:GetParent().set;
self:SetIgnored(not set.ignored[self:GetID()]);
else
self:SetItem(nil);
end
end
function BtWLoadoutsItemSlotButtonMixin:OnReceiveDrag()
if not self:IsEnabled() then
return
end
local cursorType, _, itemLink = GetCursorInfo();
if self:GetParent().set and cursorType == "item" then
if self:SetItem(itemLink, GetCursorItemSource()) then
ClearCursor();
end
end
end
function BtWLoadoutsItemSlotButtonMixin:OnEvent(event, itemID, success)
if success then
local set = self:GetParent().set;
local slot = self:GetID();
local itemLink = set.equipment[slot];
if itemLink and itemID == GetItemInfoInstant(itemLink) then
self:Update();
self:UnregisterEvent("GET_ITEM_INFO_RECEIVED");
end
end
end
function BtWLoadoutsItemSlotButtonMixin:OnEnter()
local character = GetCharacterSlug()
local set = self:GetParent().set;
local slot = self:GetID();
local location = set.locations[slot];
local itemLink = set.equipment[slot];
GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
if set.character == character and location and location > 0 and (BankFrame:IsShown() or bit.band(location, ITEM_INVENTORY_LOCATION_BANK) ~= ITEM_INVENTORY_LOCATION_BANK) then
local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
if not bags then
GameTooltip:SetInventoryItem("player", slot)
else
GameTooltip:SetBagItem(bag, slot)
end
elseif itemLink then
GameTooltip:SetHyperlink(itemLink);
itemLink = select(2, GameTooltip:GetItem())
else
return
end
if self.errors then
gameTooltipErrorLink = itemLink
gameTooltipErrorText = self.errors
else
gameTooltipErrorLink = nil
gameTooltipErrorText = nil
end
end
function BtWLoadoutsItemSlotButtonMixin:OnLeave()
gameTooltipErrorLink = nil
gameTooltipErrorText = nil
GameTooltip:Hide();
end
function BtWLoadoutsItemSlotButtonMixin:OnUpdate()
if GameTooltip:IsOwned(self) then
self:OnEnter();
end
end
function BtWLoadoutsItemSlotButtonMixin:GetSlot()
return self.slot;
end
function BtWLoadoutsItemSlotButtonMixin:SetItem(itemLink, bag, slot)
local set = self:GetParent().set;
if itemLink == nil then -- Clearing slot
local previousLocation = set.locations[self:GetID()]
set.equipment[self:GetID()] = nil;
set.locations[self:GetID()] = nil;
set.data[self:GetID()] = nil;
Internal.UpdateEquipmentSetItemInMapData(set, self:GetID(), previousLocation, nil)
self:Update();
return true;
else
if IsItemValidForSlot(itemLink, self:GetID(), select(3, UnitClass("player"))) then
local previousLocation = set.locations[self:GetID()]
set.equipment[self:GetID()] = itemLink;
local itemLocation;
if bag and slot then
itemLocation = ItemLocation:CreateFromBagAndSlot(bag, slot);
elseif slot then
itemLocation = ItemLocation:CreateFromEquipmentSlot(slot);
end
set.locations[self:GetID()] = GetLocationFromItemLocation(itemLocation)
if itemLocation and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(itemLocation) then
set.extras[self:GetID()] = set.extras[self:GetID()] or {};
local extras = set.extras[self:GetID()];
extras.azerite = extras.azerite or {};
wipe(extras.azerite);
local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(itemLocation);
for index,tier in ipairs(tiers) do
for _,powerID in ipairs(tier.azeritePowerIDs) do
if C_AzeriteEmpoweredItem.IsPowerSelected(itemLocation, powerID) then
extras.azerite[index] = powerID;
break;
end
end
end
else
set.extras[self:GetID()] = nil;
end
set.data[self:GetID()] = EncodeItemData(itemLink, set.extras[self:GetID()] and set.extras[self:GetID()].azerite);
Internal.UpdateEquipmentSetItemInMapData(set, self:GetID(), previousLocation, set.locations[self:GetID()])
BtWLoadoutsFrame:Update(); -- Refresh everything, this'll update the error handling too
return true;
end
end
return false;
end
function BtWLoadoutsItemSlotButtonMixin:SetIgnored(ignored)
local set = self:GetParent().set;
set.ignored[self:GetID()] = ignored and true or nil;
BtWLoadoutsFrame:Update(); -- Refresh everything, this'll update the error handling too
end
function BtWLoadoutsItemSlotButtonMixin:Update()
local set = self:GetParent().set;
local slot = self:GetID();
local ignored = set and set.ignored[slot] or false;
local errors = set and set.errors[slot];
local itemLink = set and set.equipment[slot];
if itemLink then
local itemID = GetItemInfoInstant(itemLink);
local _, _, quality, _, _, _, _, _, _, texture = GetItemInfo(itemLink);
if quality == nil or texture == nil then
self:RegisterEvent("GET_ITEM_INFO_RECEIVED");
end
SetItemButtonTexture(self, texture);
SetItemButtonQuality(self, quality, itemID);
else
SetItemButtonTexture(self, self.backgroundTextureName);
SetItemButtonQuality(self, nil, nil);
end
self.errors = errors -- For tooltip display
self.ErrorBorder:SetShown(errors ~= nil)
self.ErrorOverlay:SetShown(errors ~= nil)
self.ignoreTexture:SetShown(ignored);
end
if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall then
TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, function (self, data)
if self ~= GameTooltip then
return
end
local name, link = self:GetItem()
if gameTooltipErrorLink == link and gameTooltipErrorText then
self:AddLine(format("\n|cffff0000%s|r", gameTooltipErrorText))
end
end)
else
GameTooltip:HookScript("OnTooltipSetItem", function (self)
local name, link = self:GetItem()
if gameTooltipErrorLink == link and gameTooltipErrorText then
self:AddLine(format("\n|cffff0000%s|r", gameTooltipErrorText))
end
end)
end
BtWLoadoutsEquipmentMixin = {}
function BtWLoadoutsEquipmentMixin:OnLoad()
self.RestrictionsDropDown:SetSupportedTypes("covenant", "spec", "race")
self.RestrictionsDropDown:SetScript("OnChange", function ()
self:Update()
end)
end
function BtWLoadoutsEquipmentMixin:ChangeSet(set)
self.set = set
self:Update()
end
function BtWLoadoutsEquipmentMixin:UpdateSetName(value)
if self.set and self.set.name ~= not value then
self.set.name = value;
self:Update();
end
end
function BtWLoadoutsEquipmentMixin:OnButtonClick(button)
CloseDropDownMenus()
if button.isAdd then
self.Name:ClearFocus();
self:ChangeSet(AddEquipmentSet())
C_Timer.After(0, function ()
self.Name:HighlightText();
self.Name:SetFocus();
end);
elseif button.isDelete then
local set = self.set;
if set.useCount > 0 then
StaticPopup_Show("BTWLOADOUTS_DELETEINUSESET", set.name, nil, {
set = set,
func = Internal.DeleteEquipmentSet,
});
else
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
set = set,
func = Internal.DeleteEquipmentSet,
});
end
elseif button.isRefresh then
local set = self.set;
RefreshEquipmentSet(set)
self:Update()
elseif button.isActivate then
Internal.ActivateProfile({
equipment = {self.set.setID}
});
end
end
function BtWLoadoutsEquipmentMixin:OnSidebarItemClick(button)
CloseDropDownMenus()
if button.isHeader then
button.collapsed[button.id] = not button.collapsed[button.id]
self:Update()
else
if IsModifiedClick("SHIFT") then
Internal.ActivateProfile({
equipment = {button.id}
});
else
self.Name:ClearFocus();
self:ChangeSet(GetEquipmentSet(button.id))
end
end
end
function BtWLoadoutsEquipmentMixin:OnSidebarItemDoubleClick(button)
CloseDropDownMenus()
if button.isHeader then
return
end
Internal.ActivateProfile({
equipment = {button.id}
});
end
function BtWLoadoutsEquipmentMixin:OnSidebarItemDragStart(button)
CloseDropDownMenus()
if button.isHeader then
return
end
local icon = "INV_Misc_QuestionMark";
local set = GetEquipmentSet(button.id);
local command = format("/btwloadouts activate equipment %d", button.id);
if set.managerID then
icon = select(2, C_EquipmentSet.GetEquipmentSetInfo(set.managerID))
end
if command then
local macroId;
local numMacros = GetNumMacros();
for i=1,numMacros do
if GetMacroBody(i):trim() == command then
macroId = i;
break;
end
end
if not macroId then
if numMacros == MAX_ACCOUNT_MACROS then
print(L["Cannot create any more macros"]);
return;
end
if InCombatLockdown() then
print(L["Cannot create macros while in combat"]);
return;
end
macroId = CreateMacro(set.name, icon, command, false);
else
-- Rename the macro while not in combat
if not InCombatLockdown() then
icon = select(2,GetMacroInfo(macroId))
EditMacro(macroId, set.name, icon, command)
end
end
if macroId then
PickupMacro(macroId);
end
end
end
function BtWLoadoutsEquipmentMixin:Update()
self:GetParent():SetTitle(L["Equipment"]);
local sidebar = BtWLoadoutsFrame.Sidebar
sidebar:SetSupportedFilters("covenant", "spec", "class", "role", "race", "character")
sidebar:SetSets(BtWLoadoutsSets.equipment)
sidebar:SetCollapsed(BtWLoadoutsCollapsed.equipment)
sidebar:SetCategories(BtWLoadoutsCategories.equipment)
sidebar:SetFilters(BtWLoadoutsFilters.equipment)
sidebar:SetSelected(self.set)
sidebar:Update()
self.set = sidebar:GetSelected()
local set = self.set
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Equipment"], L["Create gear sets or use the Blizzard equipment set manager."])
self:GetParent().ExportButton:SetEnabled(false)
if not showingNPE then
UpdateSetFilters(set)
sidebar:Update()
set.restrictions = set.restrictions or {}
self.RestrictionsButton:SetEnabled(true);
self.RestrictionsDropDown:SetSelections(set.restrictions)
self.RestrictionsDropDown:SetLimitations("character", set.character)
local errors = CheckEquipmentSetForIssues(set)
local character = set.character;
local characterInfo = Internal.GetCharacterInfo(character);
local equipment = set.equipment;
local characterName, characterRealm = UnitFullName("player");
local playerCharacter = characterRealm .. "-" .. characterName;
-- Update the name for the built in equipment set, but only for the current player
if set.character == playerCharacter and set.managerID then
local managerName = C_EquipmentSet.GetEquipmentSetInfo(set.managerID);
if managerName ~= set.name then
C_EquipmentSet.ModifyEquipmentSet(set.managerID, set.name);
end
end
if not self.Name:HasFocus() then
self.Name:SetText(set.name or "");
end
self.Name:SetEnabled(set.managerID == nil or set.character == playerCharacter);
local model = self.Model;
if not characterInfo or character == playerCharacter then
model:SetUnit("player");
else
model:SetCustomRace(characterInfo.race, characterInfo.sex);
end
model:Undress();
for _,item in pairs(self.Slots) do
if equipment[item:GetID()] then
model:TryOn(equipment[item:GetID()]);
end
item:Update();
item:SetEnabled(character == playerCharacter and set.managerID == nil);
end
self:GetParent().RefreshButton:SetEnabled(set.character == GetCharacterSlug())
self:GetParent().ActivateButton:SetEnabled(character == playerCharacter);
self:GetParent().DeleteButton:SetEnabled(set.managerID == nil);
local helpTipBox = self:GetParent().HelpTipBox;
if character ~= playerCharacter then
if not BtWLoadoutsHelpTipFlags["INVALID_PLAYER"] then
helpTipBox.closeFlag = "INVALID_PLAYER";
HelpTipBox_Anchor(helpTipBox, "TOP", self:GetParent().ActivateButton);
helpTipBox:Show();
HelpTipBox_SetText(helpTipBox, L["Can not equip sets for other characters."]);
else
helpTipBox.closeFlag = nil;
helpTipBox:Hide();
end
elseif set.managerID ~= nil then
if not BtWLoadoutsHelpTipFlags["EQUIPMENT_MANAGER_BLOCK"] then
helpTipBox.closeFlag = "EQUIPMENT_MANAGER_BLOCK";
HelpTipBox_Anchor(helpTipBox, "RIGHT", self.HeadSlot);
helpTipBox:Show();
HelpTipBox_SetText(helpTipBox, L["Can not edit equipment manager sets."]);
else
helpTipBox.closeFlag = nil;
helpTipBox:Hide();
end
else
if not BtWLoadoutsHelpTipFlags["EQUIPMENT_IGNORE"] then
helpTipBox.closeFlag = "EQUIPMENT_IGNORE";
HelpTipBox_Anchor(helpTipBox, "RIGHT", self.HeadSlot);
helpTipBox:Show();
HelpTipBox_SetText(helpTipBox, L["Shift+Left Mouse Button to ignore a slot."]);
else
helpTipBox.closeFlag = nil;
helpTipBox:Hide();
end
end
else
local characterName = UnitFullName("player");
self.Name:SetText(format(L["New %s Equipment Set"], characterName))
local model = self.Model;
model:SetUnit("player");
for _,item in pairs(self.Slots) do
item:Update()
end
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
end
end
-- Inventory item tracking
local GetSetsForLocation, GetEnabledSetsForLocation, GetLocationForItem, GetEnabledSetsForItem, GetEnabledSetsForItemID
do
local itemLocation = ItemLocation:CreateEmpty(); -- Reusable item location, be careful not to double use it
local possibleItems = {} -- Reused for storing lists of items
--[[
`itemData` == `itemLink`-azerite:::::
`locationItems` is a map of `location` => `itemData`
`locationSets` is a map of `location` => {"set:slot" => true}
]]
local locationItems = {}
local locationSets = setmetatable({}, {
__index = function (self, key)
if type(key) == "number" then
local result = {}
self[key] = result
return result
end
end
})
function AddSetToMapData(set)
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if set.equipment[slot] then
local location = set.locations[slot]
if location and location <= 0 then
location = nil
end
if location and location > 0 then
local data = set.data[slot] and FixItemData(set.data[slot]) or EncodeItemData(set.equipment[slot], set.extras[slot] and set.extras[slot].azerite)
if locationItems[location] == nil or locationItems[location] == data then
locationItems[location] = locationItems[location] or data
locationSets[location][(set.setID .. ":" .. slot)] = true
else
location = nil -- Somehow 2 equipment sets are expecting different items in the same slot
end
end
set.locations[slot] = location
end
end
end
Internal.AddEquipmentSetToMapData = AddSetToMapData
function RemoveSetFromMapData(set)
for _,location in pairs(set.locations) do
local sets = locationSets[location]
if sets then
for setLocation in pairs(sets) do
local setID = tonumber((strsplit(":", setLocation)))
if setID == set.setID then
sets[setLocation] = nil
end
end
if next(sets) == nil then
locationItems[location] = nil
end
end
end
end
Internal.RemoveEquipmentSetFromMapData = RemoveSetFromMapData
function UpdateSetItemInMapData(set, inventorySlotId, oldLocation, newLocation, force)
-- Remove it from the old location
if oldLocation ~= nil then
local sets = locationSets[oldLocation]
for setLocation in pairs(sets) do
if setLocation == (set.setID .. ":" .. inventorySlotId) then
sets[setLocation] = nil
end
end
if next(sets) == nil then
locationItems[oldLocation] = nil
end
end
-- Add it to the new location
if newLocation ~= nil then
local sets = locationSets[newLocation]
local data = set.data[inventorySlotId]
if locationItems[newLocation] == nil or locationItems[newLocation] == data then
locationItems[newLocation] = locationItems[newLocation] or data
locationSets[newLocation][(set.setID .. ":" .. inventorySlotId)] = true
elseif force then
locationItems[newLocation] = locationItems[newLocation] or data
locationSets[newLocation][(set.setID .. ":" .. inventorySlotId)] = true
else
error("ERROR")
end
end
end
Internal.UpdateEquipmentSetItemInMapData = UpdateSetItemInMapData
--[[
2 locations have had their items swap, update sets and data store with swapped locations
]]
local function SwapItems(fromLocation, toLocation)
local fromSets, toSets = locationSets[fromLocation], locationSets[toLocation]
-- Update the sets
for setLocation in pairs(fromSets) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
assert(set)
set.locations[setSlot] = toLocation
end
for setLocation in pairs(toSets) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
assert(set)
set.locations[setSlot] = fromLocation
end
-- Swap the data store
locationItems[fromLocation], locationItems[toLocation] = locationItems[toLocation], locationItems[fromLocation]
locationSets[fromLocation], locationSets[toLocation] = toSets, fromSets
end
--[[
Generic update funciton for when inventory items have been moved.
Veryifies all items are correct and if not updates the data store with new locations
]]
local UpdateLocations, UpdateAllLocations
do
local newLocationItems = {}
local newLocationSets = setmetatable({}, {
__index = function (self, key)
if type(key) == "number" then
local result = {}
self[key] = result
return result
end
end
})
local missingItemDatas = {}
local missingItemDatasLocationSets = {}
local function UpdateLocation(newLocation)
SetItemLocationFromLocation(itemLocation, newLocation)
if not newLocationItems[newLocation] and itemLocation:IsValid() then
local itemData = GetEncodedItemDataForItemLocation(itemLocation)
if missingItemDatas[itemData] then
local oldLocation = missingItemDatas[itemData]
local delete = true
if type(oldLocation) == "table" then
if #oldLocation > 1 then
delete = false
end
oldLocation = table.remove(oldLocation, 1)
end
if oldLocation == 0 then
local sets = missingItemDatasLocationSets[itemData]
for setLocation in pairs(sets) do
local setId, slotId = strsplit(":", setLocation)
setId, slotId = tonumber(setId), tonumber(slotId)
local set = GetEquipmentSet(setId)
assert(set)
set.locations[slotId] = newLocation
end
newLocationItems[newLocation] = itemData
newLocationSets[newLocation] = sets
else
local sets = locationSets[oldLocation]
for setLocation in pairs(sets) do
local setId, slotId = strsplit(":", setLocation)
setId, slotId = tonumber(setId), tonumber(slotId)
local set = GetEquipmentSet(setId)
assert(set)
set.locations[slotId] = newLocation
end
newLocationItems[newLocation] = locationItems[oldLocation]
newLocationSets[newLocation] = locationSets[oldLocation]
end
if delete then
missingItemDatas[itemData] = nil
end
end
end
end
local function ScanDataMapForMissingItems(newLocationItems, newLocationSets, missingItemDatas)
for location,expectedItemData in pairs(locationItems) do
local actualItemData = GetEncodedItemDataForLocation(location)
if actualItemData == expectedItemData then -- item is in location
newLocationItems[location] = locationItems[location]
newLocationSets[location] = locationSets[location]
elseif missingItemDatas[expectedItemData] then
local tbl = missingItemDatas[expectedItemData]
if type(tbl) == "table" then
tbl[#tbl+1] = location
else
missingItemDatas[expectedItemData] = {tbl, location}
end
else
local foundInManager = false
local sets = locationSets[location]
for setLocation in pairs(sets) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
if set.managerID then
-- Item is in blizzard manager set, they know the knew location
local locations = C_EquipmentSet.GetItemLocations(set.managerID)
if locations[setSlot] and locations[setSlot] > -1 then
newLocationItems[locations[setSlot]] = locationItems[location]
newLocationSets[locations[setSlot]] = locationSets[location]
-- Update the location for every set using this exact item
local location = locations[setSlot]
for setLocation in pairs(sets) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
set.locations[setSlot] = location
end
foundInManager = true
end
break
end
end
if not foundInManager then
missingItemDatas[expectedItemData] = location
end
end
end
end
function UpdateLocations(inventorySlots, bags)
wipe(newLocationItems)
wipe(newLocationSets)
wipe(missingItemDatas)
ScanDataMapForMissingItems(newLocationItems, newLocationSets, missingItemDatas)
if next(missingItemDatas) ~= nil then
for inventorySlotId in pairs(inventorySlots) do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(nil, inventorySlotId)
UpdateLocation(newLocation)
end
end
if next(missingItemDatas) ~= nil then
for bagId in pairs(bags) do
if next(missingItemDatas) == nil then
break
end
for slotId=1,GetContainerNumSlots(bagId) do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(bagId, slotId)
UpdateLocation(newLocation)
end
end
end
locationItems, newLocationItems = newLocationItems, locationItems
locationSets, newLocationSets = newLocationSets, locationSets
end
function UpdateAllLocations(skipInventory, skipBags, skipBank, includeMissingLocations)
wipe(newLocationItems)
wipe(newLocationSets)
wipe(missingItemDatas)
ScanDataMapForMissingItems(newLocationItems, newLocationSets, missingItemDatas)
if includeMissingLocations then
local character = GetCharacterSlug()
for setID,set in pairs(BtWLoadoutsSets.equipment) do
if type(set) == "table" and set.character == character and set.managerID == nil then
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if set.equipment[slot] and set.locations[slot] == nil then
local data = set.data[slot] or EncodeItemData(set.equipment[slot], set.extras[slot] and set.extras[slot].azerite)
do
local tbl = missingItemDatas[data]
if type(tbl) == "table" then
tbl[#tbl+1] = 0
elseif tbl ~= nil then
missingItemDatas[data] = {tbl, 0}
else
missingItemDatas[data] = 0
end
end
do
local tbl = missingItemDatasLocationSets[data] or {}
tbl[(set.setID .. ":" .. slot)] = true
missingItemDatasLocationSets[data] = tbl
end
end
end
end
end
end
if not skipInventory and next(missingItemDatas) ~= nil then
for inventorySlotId=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(nil, inventorySlotId)
UpdateLocation(newLocation)
end
end
if not skipBags and next(missingItemDatas) ~= nil then
for bagId=BACKPACK_CONTAINER,NUM_BAG_SLOTS do
if next(missingItemDatas) == nil then
break
end
for slotId=1,GetContainerNumSlots(bagId) do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(bagId, slotId)
UpdateLocation(newLocation)
end
end
end
if not skipBank and next(missingItemDatas) ~= nil then
for slotId=1,GetContainerNumSlots(BANK_CONTAINER) do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(BANK_CONTAINER, slotId)
UpdateLocation(newLocation)
end
for bagId=NUM_BAG_SLOTS+1,NUM_BAG_SLOTS+NUM_BANKBAGSLOTS do
if next(missingItemDatas) == nil then
break
end
for slotId=1,GetContainerNumSlots(bagId) do
if next(missingItemDatas) == nil then
break
end
local newLocation = PackLocation(bagId, slotId)
UpdateLocation(newLocation)
end
end
end
for itemData,tbl in pairs(missingItemDatas) do
if type(tbl) == "number" then
tbl = {tbl}
end
for _,location in ipairs(tbl) do
if location > 0 then
local isInventory = bit.band(ITEM_INVENTORY_LOCATION_PLAYER, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) == 0
local isBags = bit.band(ITEM_INVENTORY_LOCATION_PLAYER, location) ~= 0 and bit.band(ITEM_INVENTORY_LOCATION_BAGS, location) ~= 0
local isBank = bit.band(ITEM_INVENTORY_LOCATION_BANK, location) ~= 0
if (not skipInventory == isInventory) or (not skipBags == isBags) or (not skipBank == isBank) then
for setLocation in pairs(locationSets[location]) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
assert(set)
set.locations[setSlot] = nil
end
end
end
end
end
locationItems, newLocationItems = newLocationItems, locationItems
locationSets, newLocationSets = newLocationSets, locationSets
end
end
do -- Event handling for inventory changes
local frame = CreateFrame("Frame")
local changedBags = {}
local changedInventorySlots = {}
function frame:BAG_UPDATE(bagId)
changedBags[bagId] = true
self:Show()
end
function frame:PLAYERBANKSLOTS_CHANGED(slotId)
changedInventorySlots[51 + slotId] = true
self:Show()
end
function frame:PLAYER_EQUIPMENT_CHANGED(slotId)
changedInventorySlots[slotId] = true
self:Show()
end
function frame:OnEvent(event, ...)
self[event](self, ...)
end
function frame:OnUpdate()
self:Hide()
UpdateLocations(changedInventorySlots, changedBags)
wipe(changedBags)
wipe(changedInventorySlots)
end
frame:SetScript("OnEvent", frame.OnEvent)
frame:SetScript("OnUpdate", frame.OnUpdate)
frame:RegisterEvent("BAG_UPDATE")
frame:RegisterEvent("PLAYERBANKSLOTS_CHANGED")
frame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
frame:Hide()
end
--[[
Takes an itemLink and extra data with a list of item locations, returns the first location that matches the itemLink and extras
Needs a better name
]]
local function GetItemFromLocations(itemLink, extras, locations)
local itemID = GetItemInfoInstant(itemLink);
for location,locationItemLink in pairs(locations) do
if itemID == GetItemInfoInstant(locationItemLink) and IsItemInLocation(itemLink, extras, location) then
return location
end
end
end
-- Populate item data maps from empty
local function InitializeEquipmentTracking()
local character = GetCharacterSlug()
--[[
It might be worth doing this in a few stages.
First we loop through all the equipment sets and fill in `locationItems` and `locationSets`,
verifying they have the item that we expect. If so the location is flagged as being in use
We then loop through each item that isnt correct and look for the correct item. Flagging them
as in use when found.
Doing it this way means we dont cross-contaminate sets that have the same itemData but were
actually different items in the inventory.
We should probably take into account sets from the built in manager though. Those should always
be correct and could maybe allow us to detect item movements while we were disabled.
In the case of bank items we just leave them as is and let `InitializeBankItems` deal with it.
]]
for setID,set in pairs(BtWLoadoutsSets.equipment) do
if type(set) == "table" and set.character == character then
AddSetToMapData(set)
end
end
UpdateAllLocations(false, false, true, true) -- Skip banks, check items without locations
end
-- Run when the bank is first opened, verifies items are where we expect them to be and if not updates locations with the correct place
-- Also look for items with missing locations and check if they are in the bank
local bankInitialized
local function InitializeBankItems()
if bankInitialized then
return
end
bankInitialized = true
local character = GetCharacterSlug()
UpdateAllLocations(true, true, false, true) -- Skip inventory and bags, check items without locations
end
-- Gets the sets items are used for
local temp = {}
function GetSetsForLocation(location, result)
result = result or {}
local sets = locationSets[location]
if sets then
for setLocation in pairs(sets) do
local setID = tonumber((strsplit(":", setLocation)))
if not temp[setID] then
local set = GetEquipmentSet(setID)
for slot,targetLocation in pairs(set.locations) do
if location == targetLocation and not set.ignored[slot] then
result[#result+1] = set
temp[setID] = true
break
end
end
end
end
end
wipe(temp)
table.sort(result, function (a, b)
return a.name < b.name
end)
return result
end
function GetEnabledSetsForLocation(location, result)
result = result or {}
local sets = locationSets[location]
if sets then
for setLocation in pairs(sets) do
local setID, setInventorySlotID = strsplit(":", setLocation)
setID, setInventorySlotID = tonumber(setID), tonumber(setInventorySlotID)
if not temp[setID] then
local set = GetEquipmentSet(setID)
if not set.disabled and not set.ignored[setInventorySlotID] then
result[#result+1] = set
end
temp[setID] = true
end
end
end
wipe(temp)
table.sort(result, function (a, b)
return a.name < b.name
end)
return result
end
function GetLocationForItem(itemLink, extras)
end
function GetEnabledSetsForItemID(itemID, result)
result = result or {}
local character = GetCharacterSlug()
local sets = BtWLoadoutsSets.equipment
for _,set in pairs(sets) do
if type(set) == "table" and set.character == character and not set.disabled then
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if not set.ignored[slot] and set.equipment[slot] and GetItemInfoInstant(set.equipment[slot]) == itemID then
result[#result+1] = set
break;
end
end
end
end
table.sort(result, function (a, b)
return a.name < b.name
end)
return result
end
function GetEnabledSetsForItem(itemLink, result)
if type(itemLink) == "number" then
return GetEnabledSetsForItemID(itemLink, result)
end
result = result or {}
local character = GetCharacterSlug()
local sets = BtWLoadoutsSets.equipment
for _,set in pairs(sets) do
if type(set) == "table" and set.character == character and not set.disabled then
for slot=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do
if not set.ignored[slot] and set.equipments[slot] == itemLink then
result[#result+1] = set
break;
end
end
end
end
table.sort(result, function (a, b)
return a.name < b.name
end)
return result
end
--[[
The item at the locations has been changed, for example, selecting azerite traits or upgrading legendary cloak
]]
local function UpdateItemAtLocation(location)
local itemLink = GetItemLinkByLocation(location)
if not itemLink or not IsEquippableItem(itemLink) then
return
end
SetItemLocationFromLocation(itemLocation, location)
local extras = GetExtrasForItemLocation(itemLocation)
local itemData = EncodeItemData(itemLink, extras and extras.azerite)
if itemData ~= locationItems[location] then -- Item has actually changed
locationItems[location] = itemData
local sets = locationSets[location]
for setLocation in pairs(sets) do
local setID, setSlot = strsplit(":", setLocation)
setID, setSlot = tonumber(setID), tonumber(setSlot)
local set = GetEquipmentSet(setID)
set.data[setSlot] = itemData
set.equipment[setSlot] = itemLink
set.extras[setSlot] = extras
end
end
end
-- Triggered by the AZERITE_EMPOWERED_ITEM_SELECTION_UPDATED event
local function AzeriteItemUpdated(itemLocation)
UpdateItemAtLocation(GetLocationFromItemLocation(itemLocation))
end
-- Triggered by the ITEM_CHANGED, followed by a BAG_UPDATE_DELAYED or UNIT_INVENTORY_CHANGED
local function RuneforgeItemUpdated(previousHyperlink, newHyperlink)
local previousItemData, newItemData = DeEnchantItemLink(previousHyperlink), DeEnchantItemLink(newHyperlink)
for location,itemData in pairs(locationItems) do
if DeEnchantItemLink(itemData) == previousItemData and DeEnchantItemLink(GetEncodedItemDataForLocation(location)) == newItemData then
UpdateItemAtLocation(location)
return
end
end
end
Internal.RuneforgeItemUpdated = RuneforgeItemUpdated
local function SwapItemDataEnchantID(itemData, enchantID)
local prefix, itemID, previousEnchantID, rest = strsplit(":", itemData, 4)
return prefix .. ":" .. itemID .. ":" .. enchantID .. ":".. rest
end
-- Triggered by UNIT_SPELLCAST_SUCCESSFUL followed by a BAG_UPDATE_DELAYED or UNIT_INVENTORY_CHANGED,
-- Searched through items looking for the one with the previous
local function EnchantApplied(enchantID)
for location,previousItemData in pairs(locationItems) do
local currentItemData = GetEncodedItemDataForLocation(location)
-- Items itemData has changed, the only change is the enchantID swapping to what we just cast
if previousItemData ~= currentItemData and SwapItemDataEnchantID(previousItemData, enchantID) == currentItemData then
UpdateItemAtLocation(location)
break
end
end
end
Internal.EnchantApplied = EnchantApplied
do -- Update items from socketing gems
local function HasGem(itemLink)
for i=1,3 do
local itemLink = GetItemGem(itemLink, i)
if itemLink then
return true
end
end
return false
end
local itemLocation = ItemLocation:CreateEmpty();
local function GemApplied()
if itemLocation:HasAnyLocation() and itemLocation:IsValid() then
local itemLink = C_Item.GetItemLink(itemLocation)
if itemLink and HasGem(itemLink) then
UpdateItemAtLocation(GetLocationFromItemLocation(itemLocation))
itemLocation:Clear()
end
end
end
local function hook_SocketInventoryItem(slotId)
itemLocation:SetEquipmentSlot(slotId)
end
hooksecurefunc("SocketInventoryItem", hook_SocketInventoryItem)
local function hook_SocketContainerItem(bagId, slotId)
itemLocation:SetBagAndSlot(bagId, slotId)
end
if C_Container and C_Container.SocketContainerItem then
hooksecurefunc(C_Container, "SocketContainerItem", hook_SocketContainerItem)
else
hooksecurefunc("SocketContainerItem", hook_SocketContainerItem)
end
Internal.GemApplied = GemApplied
end
do
local dominationGems = {
[187284] = true,
[187295] = true,
[187286] = true,
[187287] = true,
[187288] = true,
[187289] = true,
[187299] = true,
[187308] = true,
[187309] = true,
[187310] = true,
}
local function IsDominationGem(itemLink)
local itemID = GetItemInfoInstant(itemLink)
return dominationGems[itemID] and true or false
end
local function HasDominationGem(itemLink)
for i=1,3 do
local itemLink = GetItemGem(itemLink, i)
if itemLink and IsDominationGem(itemLink) then
return true
end
end
return false
end
local itemLocation = ItemLocation:CreateEmpty();
local function hook_PickupInventoryItem(slotId)
itemLocation:SetEquipmentSlot(slotId)
end
hooksecurefunc("PickupInventoryItem", hook_PickupInventoryItem)
local function hook_UseContainerItem(bagId, slotId)
itemLocation:SetBagAndSlot(bagId, slotId)
end
if C_Container and C_Container.UseContainerItem then
hooksecurefunc(C_Container, "UseContainerItem", hook_UseContainerItem)
else
hooksecurefunc("UseContainerItem", hook_UseContainerItem)
end
local isRemovingDominationSocket = false
function Internal.CastedSoulFireChisel()
if itemLocation:HasAnyLocation() and itemLocation:IsValid() then
local itemLink = C_Item.GetItemLink(itemLocation)
if itemLink and HasDominationGem(itemLink) then
isRemovingDominationSocket = true
return true
end
end
end
function Internal.RemovedDominationGem()
if isRemovingDominationSocket then
if itemLocation:HasAnyLocation() and itemLocation:IsValid() then
UpdateItemAtLocation(GetLocationFromItemLocation(itemLocation))
end
itemLocation:Clear()
isRemovingDominationSocket = false
end
end
end
--[[@TODO
Handle events for inventory items moving, there is a lot
BAG_UPDATE, bagId - May fire multiple times, wait for BAG_UPDATE_DELAYED or maybe end of frame?
BAG_UPDATE_DELAYED
PLAYERBANKSLOTS_CHANGED, slotId - May fire multiple times per frame, way of end of frame?
PLAYER_EQUIPMENT_CHANGED, slotId - May fire multiple times per frame, way of end of frame?
All the ways to move items:
Bag/Bank sort, fires BAG_UPDATE and PLAYERBANKSLOTS_CHANGED events
Moving bags, fires BAG_UPDATE event
Moving items within bank, fires PLAYERBANKSLOTS_CHANGED event
Moving items within inventory, fires PLAYER_EQUIPMENT_CHANGED event
Moving items between the previous 3, fires combinations of their events
Right clicking item in inventory, fires BAG_UPDATE and PLAYER_EQUIPMENT_CHANGED
Clicking item on actionbar, fires BAG_UPDATE and PLAYER_EQUIPMENT_CHANGED
Trading, fires BAG_UPDATE and PLAYER_EQUIPMENT_CHANGED
Equiping Manager set, fires EQUIPMENT_SWAP_FINISHED, but also BAG_UPDATE and PLAYER_EQUIPMENT_CHANGED
React to equipment set changes
]]
Internal.BuildEquipmentMap = InitializeEquipmentTracking
Internal.InitializeBankItems = InitializeBankItems
end
-- GameTooltip
do
local tooltipMatch = "^" .. string.gsub(EQUIPMENT_SETS, "%%s", "(.*)") .. "$"
local tooltipSellMatch = "^" .. SELL_PRICE .. ": .*$"
local location
--[[
Searches an item tooltip looking for important lines, like Equipment Sets, Sell Price and similar
returns "Equipment Sets: ..." line number, last line number before equipment set line should be added, first line number after equipment set line should be added, any money frame that needs moving
]]
local equipmentSetPattern = "^" .. string.gsub(EQUIPMENT_SETS, "%%s", "(.*)") .. "$"
local itemCreatedPattern = "^" .. (ITEM_CREATED_BY:gsub("%%s", ".*")) .. "$"
local durabilityPattern = "^" .. (DURABILITY_TEMPLATE:gsub("%%d", "%%d+"):gsub("%%%d%$d", "%%d+")) .. "$"
local sellPricePrefix = string.format("%s:", SELL_PRICE)
local function GetTooltipLineNumbers(tooltip)
local equipmentSetLine, beforeLine, afterLine, sellPriceFrameResult, sellPriceFrameXOffsetResult
local tooltipName = tooltip:GetName()
local sellPriceFrame, sellPriceFrameAnchor, sellPriceFrameXOffset, _
if tooltip.shownMoneyFrames and tooltip.shownMoneyFrames >= 1 then
for i=1,tooltip.shownMoneyFrames do
local name = tooltipName.."MoneyFrame" .. i
local moneyFrame = _G[name];
if _G[name .. "PrefixText"]:GetText() == sellPricePrefix then
sellPriceFrame = moneyFrame
if sellPriceFrame.GetPointByName then
_, sellPriceFrameAnchor, _, sellPriceFrameXOffset = sellPriceFrame:GetPointByName("LEFT")
else
_, sellPriceFrameAnchor, _, sellPriceFrameXOffset = sellPriceFrame:GetPoint("LEFT")
end
break
end
end
end
for i=1,tooltip:NumLines() do
local left, right = _G[tooltipName .. "TextLeft" .. i], _G[tooltipName .. "TextRight" .. i]
local leftText = left:GetText()
if leftText then
if leftText:match(equipmentSetPattern) then
equipmentSetLine = i
break
elseif leftText == ITEM_SOCKETABLE or leftText == ITEM_ARTIFACT_VIEWABLE or leftText == ITEM_AZERITE_EMPOWERED_VIEWABLE or leftText == ITEM_AZERITE_ESSENCES_VIEWABLE or leftText:match(itemCreatedPattern) or leftText:match(durabilityPattern) then
beforeLine = i
elseif left == sellPriceFrameAnchor then
afterLine = i
sellPriceFrameResult, sellPriceFrameXOffsetResult = sellPriceFrame, sellPriceFrameXOffset
elseif leftText == ITEM_UNSELLABLE then
afterLine = i
end
end
end
if beforeLine and not afterLine then
afterLine = beforeLine + 1
end
return equipmentSetLine, beforeLine, afterLine, sellPriceFrameResult, sellPriceFrameXOffsetResult
end
local function UpdateTooltip(self, location)
local sets = GetSetsForLocation(location, {})
if #sets == 0 then
return
end
for index,set in ipairs(sets) do
sets[index] = set.name
end
sets = string.format(EQUIPMENT_SETS, table.concat(sets, ", "))
local tooltipName = self:GetName()
local equipmentSetLine, beforeLine, afterLine, sellPriceFrame, sellPriceFrameXOffset = GetTooltipLineNumbers(self)
if equipmentSetLine then
_G[tooltipName .. "TextLeft" .. equipmentSetLine]:SetText(sets)
elseif afterLine and afterLine < self:NumLines() then
local left, right = _G[tooltipName .. "TextLeft" .. self:NumLines()], _G[tooltipName .. "TextRight" .. self:NumLines()]
local leftText, leftR, leftG, leftB = left:GetText(), left:GetTextColor()
local rightText, rightR, rightG, rightB = right:GetText(), right:GetTextColor()
self:AddDoubleLine(leftText, rightText, leftR, leftG, leftB, rightR, rightG, rightB)
for i=self:NumLines()-1,afterLine,-1 do
local left, right = _G[tooltipName .. "TextLeft" .. (i - 1)], _G[tooltipName .. "TextRight" .. (i - 1)]
local leftNext, rightNext = _G[tooltipName .. "TextLeft" .. i], _G[tooltipName .. "TextRight" .. i]
leftNext:SetTextColor(left:GetTextColor())
leftNext:SetText(left:GetText())
leftNext:SetShown(left:IsShown())
rightNext:SetTextColor(right:GetTextColor())
rightNext:SetText(right:GetText())
rightNext:SetShown(right:IsShown())
end
left, right = _G[tooltipName .. "TextLeft" .. afterLine], _G[tooltipName .. "TextRight" .. afterLine]
left:SetTextColor(NORMAL_FONT_COLOR:GetRGB())
left:SetText(sets)
right:SetText("")
if sellPriceFrame then
sellPriceFrame:ClearAllPoints()
sellPriceFrame:SetPoint("LEFT", _G[tooltipName .. "TextLeft" .. (afterLine + 1)], "LEFT", sellPriceFrameXOffset, 0);
end
else
self:AddLine(sets)
end
self:Show()
end
-- GameTooltip:HookScript("OnTooltipSetItem", function (self, ...)
-- if location then
-- end
-- end)
hooksecurefunc(GameTooltip, "SetInventoryItem", function (self, unit, slot, nameOnly)
if not nameOnly and unit == "player" then
location = PackLocation(nil, slot)
UpdateTooltip(self, location)
else
location = nil
end
end)
hooksecurefunc(GameTooltip, "SetBagItem", function (self, bag, slot)
location = PackLocation(bag, slot)
UpdateTooltip(self, location)
end)
hooksecurefunc(ItemRefTooltip, "SetInventoryItem", function (self, unit, slot, nameOnly)
if not nameOnly and unit == "player" then
location = PackLocation(nil, slot)
UpdateTooltip(self, location)
else
location = nil
end
end)
hooksecurefunc(ItemRefTooltip, "SetBagItem", function (self, bag, slot)
location = PackLocation(bag, slot)
UpdateTooltip(self, location)
end)
end
-- Adibags
if LibStub and LibStub:GetLibrary("AceAddon-3.0", true) then
local AdiBags = LibStub("AceAddon-3.0"):GetAddon("AdiBags", true)
if AdiBags then
local setFilter = AdiBags:RegisterFilter("BtWLoadouts", 94, "ABEvent-1.0")
setFilter.uiName = L["BtWLoadouts"]
setFilter.uiDesc = L["BtWLoadouts."]
function setFilter:Update()
self:SendMessage("AdiBags_FiltersChanged")
end
function setFilter:OnEnable()
AdiBags:UpdateFilters()
end
function setFilter:OnDisable()
AdiBags:UpdateFilters()
end
function setFilter:Filter(slotData)
local location = PackLocation(slotData.bag, slotData.slot)
local sets = {}
local set = GetEnabledSetsForLocation(location, sets)[1]
if set then
return format(L["Set: %s"], set.name), L["Equipment"]
end
end
end
end
-- LibItemSearch, used by Bagnon to display borders around items within equipment sets
if LibStub and LibStub:GetLibrary("LibItemSearch-1.2", true) then
local Lib = LibStub("LibItemSearch-1.2")
local Search = LibStub('CustomSearch-1.0')
-- I dont really like replacing this, maybe there is a better solution?
function Lib:BelongsToSet(id, search)
local result = GetEnabledSetsForItemID(id)
for _,set in ipairs(result) do
if Search:Find(search, set.name) then
return true
end
end
end
end
-- Character deletion
Internal.OnEvent("CHARACTER_DELETE", function (event, slug)
local sets = GetSetsForCharacter({}, slug)
for _,set in ipairs(sets) do
DeleteEquipmentSet(set.setID)
end
return true
end)