Files
breakables/Breakables.lua

1564 lines
44 KiB
Lua

local L = LibStub("AceLocale-3.0"):GetLocale("Breakables", false)
Breakables = LibStub("AceAddon-3.0"):NewAddon("Breakables", "AceConsole-3.0", "AceEvent-3.0")
local babbleInv = LibStub("LibBabble-Inventory-3.0"):GetLookupTable()
local LBF = LibStub("Masque", true)
local lbfGroup
local IsArtifactRelicItem = IsArtifactRelicItem
if not IsArtifactRelicItem then
IsArtifactRelicItem = function()
return false
end
end
local WowVer = select(4, GetBuildInfo())
local IsClassic = false
local IsClassicBC = false
local IsClassicWrath = false
if GetClassicExpansionLevel then
IsClassic = GetClassicExpansionLevel() == 0
IsClassicBC = GetClassicExpansionLevel() == 1
IsClassicWrath = GetClassicExpansionLevel() == 2
else
IsClassic = WOW_PROJECT_ID and WOW_PROJECT_ID == WOW_PROJECT_CLASSIC
IsClassicBC = false
IsClassicWrath = false
if WOW_PROJECT_ID and WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC then
if not LE_EXPANSION_LEVEL_CURRENT or LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_BURNING_CRUSADE then
IsClassicBC = true
elseif LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WRATH_OF_THE_LICH_KING then
IsClassicWrath = true
end
elseif WOW_PROJECT_WRATH_CLASSIC and WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC then
IsClassicWrath = true
end
end
local MillingId = 51005
local MillingItemSubType = babbleInv["Herb"]
local MillingItemSecondarySubType = babbleInv["Other"]
local CanMill = false
local AdditionalMillableItems = {
-- WoD herbs
109124,
109125,
109126,
109127,
109128,
109129,
-- Legion herbs
124101,
124102,
124103,
124104,
124105,
124106,
128304,
151565,
-- BfA herbs
152505,
152506,
152507,
152508,
152509,
152510,
152511,
168487,
-- Shadowlands herbs
168586, -- rising glory
168589, -- marrowroot
170554, -- vigil's torch
168583, -- widowbloom
169701, -- death blossom
171315, -- nightshade
187699, -- first flower, 9.2.0
}
local AdditionalProspectableItems = {
-- Legion ore
123918,
123919,
151564,
-- BfA ore
152512,
152513,
152579,
168185,
-- Shadowlands ore
171828, -- laestrite
171833, -- elethium
171829, -- solenium
171830, -- oxxein
171831, -- phaedrum
171832, -- sinvyr
187700, -- progenium ore, 9.2.0
}
local MassMilling = {
-- wod
[109124] = 190381,
[109125] = 190382,
[109126] = 190383,
[109127] = 190384,
[109128] = 190385,
[109129] = 190386,
-- legion
[124101] = 209658,
[124102] = 209659,
[124103] = 209660,
[124104] = 209661,
[124105] = 209662,
[124106] = 209664,
[128304] = 210116,
[151565] = 247861,
-- shadowlands
[168586] = 311417,
[168589] = 311416,
[170554] = 311414,
[168583] = 311415,
[169701] = 311413,
[171315] = 311418,
[187699] = 359490,
}
local HerbCombineItems = {
-- MoP
97619, -- torn green tea leaf
97620, -- rain poppy petal
97621, -- silkweed stem
97622, -- snow lily petal
97623, -- fool's cap spores
97624, -- desecrated herb pod
-- WoD
109624, -- broken frostweed stem
109625, -- broken fireweed stem
109626, -- gorgrond flytrap ichor
109627, -- starflower petal
109628, -- nagrand arrowbloom petal
109629, -- talador orchid petal
-- shadowlands
169550, -- rising glory petal
168591, -- marrowroot petal
169699, -- vigil's torch petal
169698, -- widowbloom petal
169700, -- death blossom petal
169697, -- nightshade petal
}
local UnProspectableItems = {
109119, -- WoD True Iron Ore
}
local ProspectingId = 31252
local ProspectingItemSubType = babbleInv["Metal & Stone"]
local CanProspect = false
local OreCombineItems = {
-- MoP
97512, -- ghost iron nugget
97546, -- kyparite fragment
90407, -- sparkling shard
-- WoD
109991, -- true iron nugget
109992, -- blackrock fragment
}
local DisenchantId = 13262
local DisenchantTypes = {babbleInv["Armor"], babbleInv["Weapon"]}
local CanDisenchant = false
local EnchantingProfessionId = 333
local AdditionalDisenchantableItems = {
137195, -- highmountain armor
}
local PickLockId = 1804
local PickableItems = {
16882, -- battered junkbox
16883, -- worn junkbox
16884, -- sturdy junkbox
16885, -- heavy junkbox
29569, -- strong junkbox
43575, -- reinforced junkbox
63349, -- flame-scarred junkbox
88165, -- vine-cracked junkbox
106895, -- iron-bound junkbox
4632, -- ornate bronze lockbox
4633, -- heavy bronze lockbox
4634, -- iron lockbox
4636, -- strong iron lockbox
4637, -- steel lockbox
4638, -- reinforced steel lockbox
5758, -- mithril lockbox
5759, -- throium lockbox
5760, -- eternium lockbox
31952, -- khorium lockbox
43622, -- froststeel lockbox
43624, -- titanium lockbox
45986, -- tiny titanium lockbox
68729, -- elementium lockbox
88567, -- ghost iron lockbox
116920, -- true steel lockbox
121331, -- leystone lockbox
169475, -- barnacled lockbox
-- shadowlands
179311, -- venthyr
180532, -- maldraxxi
180533, -- kyrian
180522, -- night fae
186161, -- stygian lockbox, 9.1.0
}
local CanPickLock = false
-- item rarity must meet or surpass this to be considered for disenchantability (is that a word?)
local RARITY_UNCOMMON = 2
local RARITY_RARE = 3
local RARITY_EPIC = 4
local RARITY_HEIRLOOM = 7
local IDX_LINK = 1
local IDX_COUNT = 2
local IDX_TYPE = 3
local IDX_TEXTURE = 4
local IDX_BAG = 5
local IDX_SLOT = 6
local IDX_SUBTYPE = 7
local IDX_LEVEL = 8
local IDX_BREAKABLETYPE = 9
local IDX_SOULBOUND = 10
local IDX_NAME = 11
local IDX_RARITY = 12
local BREAKABLE_HERB = 1
local BREAKABLE_ORE = 2
local BREAKABLE_DE = 3
local BREAKABLE_PICK = 4
local BREAKABLE_COMBINE = 5
local BagUpdateCheckDelay = 0.1
local nextCheck = {}
for i=0,NUM_BAG_SLOTS do
nextCheck[i] = -1
end
local buttonSize = 45
local _G = _G
local validGrowDirections = {L["Left"], L["Right"], L["Up"], L["Down"]}
-- can be 1, 2, or 3 (in the case of a rogue with pick lock)
local numEligibleProfessions = 0
local showingTooltip = nil
Breakables.optionsFrame = {}
Breakables.justClicked = false
function Breakables:OnInitialize()
self.defaults = {
profile = {
buttonFrameLeft = {100, 100},
buttonFrameTop = {700, 650},
hideIfNoBreakables = true,
maxBreakablesToShow = 5,
showSoulbound = false,
hideEqManagerItems = true,
hide = false,
hideInCombat = false,
hideInPetBattle = true,
buttonScale = 1,
fontSize = 11,
growDirection = 2,
ignoreList = {},
showTooltipForBreakables = true,
showTooltipForProfession = true,
}
}
self.db = LibStub("AceDB-3.0"):New("BreakablesDB", self.defaults, true)
self.settings = self.db.profile
self:RegisterChatCommand("brk", "OnSlashCommand")
if type(self.settings.buttonFrameLeft) ~= "table" then
local old = self.settings.buttonFrameLeft
self.settings.buttonFrameLeft = {}
self.settings.buttonFrameLeft[1] = old
self.settings.buttonFrameLeft[2] = self.defaults.profile.buttonFrameLeft[2]
end
if type(self.settings.buttonFrameTop) ~= "table" then
local old = self.settings.buttonFrameTop
self.settings.buttonFrameTop = {}
self.settings.buttonFrameTop[1] = old
self.settings.buttonFrameTop[2] = self.defaults.profile.buttonFrameTop[2]
end
self:InitLDB()
end
function Breakables:ButtonFacadeCallback(Group, SkinID, Gloss, Backdrop, Colors, Disabled)
if not Group then
self.settings.SkinID = SkinID
self.settings.Gloss = Gloss
self.settings.Backdrop = Backdrop
self.settings.Colors = Colors
end
end
function Breakables:InitLDB()
local LDB = LibStub and LibStub("LibDataBroker-1.1", true)
if (LDB) then
local ldbButton = LDB:NewDataObject("Breakables", {
type = "launcher",
text = L["Breakables"],
icon = "Interface\\Icons\\ability_warrior_sunder",
OnClick = function(button, msg)
self:OnSlashCommand()
end,
})
if ldbButton then
function ldbButton:OnTooltipShow()
self:AddLine(L["Breakables"] .. " @project-version@")
self:AddLine(L["Click to open Breakables options."], 1, 1, 1)
end
end
end
end
function Breakables:OnEnable()
CanMill = IsUsableSpell(GetSpellInfo(MillingId))
CanProspect = IsUsableSpell(GetSpellInfo(ProspectingId))
CanDisenchant = IsUsableSpell(GetSpellInfo(DisenchantId))
CanPickLock = IsUsableSpell(GetSpellInfo(PickLockId))
self.EnchantingLevel = 0
LibStub("AceConfig-3.0"):RegisterOptionsTable("Breakables", self:GetOptions(), "breakables")
self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Breakables")
if LBF then
LBF:Register("Breakables", self.ButtonFacadeCallback, self)
lbfGroup = LBF:Group("Breakables")
if lbfGroup then
lbfGroup:ReSkin()
end
end
self:RegisterEvents()
if CanMill or CanProspect or CanDisenchant or CanPickLock then
if CanMill then
numEligibleProfessions = numEligibleProfessions + 1
end
if CanProspect then
numEligibleProfessions = numEligibleProfessions + 1
end
if CanDisenchant then
numEligibleProfessions = numEligibleProfessions + 1
self:GetEnchantingLevel()
end
if CanPickLock then
numEligibleProfessions = numEligibleProfessions + 1
end
self:CreateButtonFrame()
if self.settings.hide then
self:ToggleButtonFrameVisibility(false)
else
self:FindBreakables()
end
if not self.frame.OnUpdateFunc then
self.frame.OnUpdateFunc = function() self:CheckShouldFindBreakables() end
end
self.frame:SetScript("OnUpdate", self.frame.OnUpdateFunc)
else
self:UnregisterAllEvents()
end
end
function Breakables:ToggleButtonFrameVisibility(show)
for i=1,numEligibleProfessions do
if self.buttonFrame[i] then
if show == nil then
show = not self.buttonFrame[i]:IsVisible()
end
if not show then
self.buttonFrame[i]:Hide()
else
self.buttonFrame[i]:Show()
end
end
end
end
function Breakables:RegisterEvents()
-- would have used ITEM_PUSH here, but that seems to fire after looting and before the bag actually gets the item
-- another alternative is to parse the chat msg, but that seems lame...however, that should only fire once as opposed to BAG_UPDATE's potential double-fire
self:RegisterEvent("BAG_UPDATE", "OnItemReceived")
self:RegisterEvent("PLAYER_REGEN_DISABLED", "OnEnterCombat")
self:RegisterEvent("PLAYER_REGEN_ENABLED", "OnLeaveCombat")
self:RegisterEvent("MODIFIER_STATE_CHANGED", "FindBreakables")
if CanDisenchant and WowVer < 80000 then
self:RegisterEvent("TRADE_SKILL_UPDATE", "OnTradeSkillUpdate")
end
if CanPickLock then
self:RegisterEvent("CHAT_MSG_OPENING", "OnBagItemLockPicked")
end
if UnitCanPetBattle then
self:RegisterEvent("PET_BATTLE_OPENING_START", "PetBattleStarted")
self:RegisterEvent("PET_BATTLE_OVER", "PetBattleEnded")
end
end
function Breakables:OnModifierChanged()
if showingTooltip ~= nil and not self.bCombat then
self:OnEnterBreakableButton(showingTooltip)
end
end
function Breakables:OnDisable()
self:UnregisterAllEvents()
self.frame:SetScript("OnUpdate", nil)
end
function Breakables:OnSlashCommand(input)
InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
end
function Breakables:OnItemReceived(event, bag)
if self.justClicked then
self:FindBreakables()
self.justClicked = false
self:OnLeaveProfessionButton()
elseif not bag or bag >= 0 then
nextCheck[bag] = GetTime() + BagUpdateCheckDelay
end
end
function Breakables:CheckShouldFindBreakables()
local latestTime = -1
for i=0,#nextCheck do
if nextCheck[i] and nextCheck[i] > latestTime then
latestTime = nextCheck[i]
end
end
if latestTime > 0 and latestTime <= GetTime() then
self:FindBreakables()
for i=0,#nextCheck do
nextCheck[i] = -1
end
end
end
function Breakables:OnEnterCombat()
self.bCombat = true
if self.settings.hideInCombat then
self:ToggleButtonFrameVisibility(false)
end
end
function Breakables:OnLeaveCombat()
self.bCombat = false
if self.bPendingUpdate or self.settings.hideInCombat then
self.bPendingUpdate = false
self:FindBreakables()
end
end
function Breakables:OnTradeSkillUpdate()
self:GetEnchantingLevel()
self:FindBreakables()
end
function Breakables:OnBagItemLockPicked()
self:FindBreakables()
end
function Breakables:PetBattleStarted()
if self.settings.hideInPetBattle then
self:ToggleButtonFrameVisibility(false)
end
end
function Breakables:PetBattleEnded()
self:ToggleButtonFrameVisibility(true)
end
function Breakables:FindLevelOfProfessionIndex(idx)
if idx ~= nil then
local name, texture, rank, maxRank, numSpells, spelloffset, skillLine = GetProfessionInfo(idx)
return skillLine, rank
end
end
function Breakables:GetEnchantingLevel()
if GetProfessions then
local prof1, prof2 = GetProfessions()
local skillId, rank = self:FindLevelOfProfessionIndex(prof1)
if skillId ~= nil and skillId == EnchantingProfessionId then
self.EnchantingLevel = rank
else
skillId, rank = self:FindLevelOfProfessionIndex(prof2)
if skillId ~= nil and skillId == EnchantingProfessionId then
self.EnchantingLevel = rank
end
end
elseif GetSkillLineInfo then
for i=1,100 do
local skillName, header, isExpanded, skillRank, numTempPoints, skillModifier, skillMaxRank, isAbandonable, stepCost, rankCost, minLevel, skillCostType = GetSkillLineInfo(i)
if skillName == babbleInv["Enchanting"] then
print("found enchanting, setting level to "..skillRank)
self.EnchantingLevel = skillRank
break
end
end
end
end
local function GetIgnoreListOptions()
local ret = {}
for k,v in pairs(Breakables.settings.ignoreList) do
local name, _, _, _, _, _, _, _, _, texture = GetItemInfo(k)
if texture ~= nil and name ~= nil then
ret[k] = ("|T%s:0|t %s"):format(texture, name)
end
end
return ret
end
local function IsIgnoringAnything()
for k,v in pairs(Breakables.settings.ignoreList) do
if v ~= nil then
return true
end
end
return false
end
function Breakables:GetOptions()
local opts = {
name = L["Breakables"],
handler = Breakables,
type = "group",
args = {
intro = {
type = "description",
fontSize = "small",
name = L["Welcome"],
order = 0,
},
hideAlways = {
type = "toggle",
name = L["Hide bar"],
desc = L["This will completely hide the breakables bar whether you have anything to break down or not. Note that you can toggle this in a macro using the /breakables command as well."],
get = function(info)
return self.settings.hide
end,
set = function(info, v)
self.settings.hide = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78maxBreakables|r to " .. tostring(self.settings.hide))
end
self:ToggleButtonFrameVisibility(not v)
if not v then
self:FindBreakables()
end
end,
order = 1
},
hideNoBreakables = {
type = "toggle",
name = L["Hide if no breakables"],
desc = L["Whether or not to hide the action bar if no breakables are present in your bags"],
get = function(info)
return self.settings.hideIfNoBreakables
end,
set = function(info, v)
self.settings.hideIfNoBreakables = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78hideIfNoBreakables|r to " .. tostring(self.settings.hideIfNoBreakables))
end
self:FindBreakables()
end,
order = 2,
},
hideInCombat = {
type = "toggle",
name = L["Hide during combat"],
desc = L["Whether or not to hide the breakables bar when you enter combat and show it again when leaving combat."],
get = function(info)
return self.settings.hideInCombat
end,
set = function(info, v)
self.settings.hideInCombat = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78hideInCombat|r to " .. tostring(self.settings.hideInCombat))
end
end,
order = 3,
},
maxBreakables = {
type = 'range',
name = L["Max number to display"],
desc = L["How many breakable buttons to display next to the profession button at maximum"],
min = 1,
max = 50,
step = 1,
get = function(info)
return self.settings.maxBreakablesToShow
end,
set = function(info, v)
self.settings.maxBreakablesToShow = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78maxBreakables|r to " .. tostring(self.settings.maxBreakablesToShow))
end
self:FindBreakables()
end,
order = 4,
},
buttonScale = {
type = 'range',
name = L["Button scale"],
desc = L["This will scale the size of each button up or down."],
min = 0.1,
max = 2,
step = 0.01,
get = function(info)
return self.settings.buttonScale
end,
set = function(info, v)
self.settings.buttonScale = v
Breakables:ApplyScale()
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78buttonScale|r to " .. tostring(self.settings.buttonScale))
end
end,
order = 5,
},
fontSize = {
type = 'range',
name = L["Font size"],
desc = L["This sets the size of the text that shows how many items you have to break."],
min = 4,
max = 90,
step = 1,
get = function(info)
return self.settings.fontSize
end,
set = function(info, v)
self.settings.fontSize = v
Breakables:ApplyScale()
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78fontSize|r to " .. tostring(self.settings.fontSize))
end
end,
order = 6,
},
growDirection = {
type = 'select',
name = L["Button grow direction"],
desc = L["This controls which direction the breakable buttons grow toward."],
values = validGrowDirections,
get = function()
return self.settings.growDirection
end,
set = function(info, v)
self.settings.growDirection = v
self:FindBreakables()
end,
order = 7,
},
showTooltipForBreakables = {
type = "toggle",
name = L["Show tooltip on breakables"],
desc = L["Whether or not to show an item tooltip when hovering over a breakable item button."],
get = function(info)
return self.settings.showTooltipForBreakables
end,
set = function(info, v)
self.settings.showTooltipForBreakables = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78showTooltipForBreakables|r to " .. tostring(self.settings.showTooltipForBreakables))
end
end,
order = 8,
},
showTooltipForProfession = {
type = "toggle",
name = L["Show tooltip on profession"],
desc = L["Whether or not to show an item tooltip when hovering over a profession button on the Breakables bar."],
get = function(info)
return self.settings.showTooltipForProfession
end,
set = function(info, v)
self.settings.showTooltipForProfession = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78showTooltipForProfession|r to " .. tostring(self.settings.showTooltipForProfession))
end
end,
order = 9,
},
ignoreList = {
type = 'multiselect',
name = L["Ignore list"],
desc = L["Items that have been right-clicked to exclude from the breakable list. Un-check the box to remove the item from the ignore list."],
get = function(info, key)
return true
end,
set = function(info, key)
Breakables.settings.ignoreList[key] = nil
Breakables:FindBreakables()
end,
confirm = function()
return L["Are you sure you want to remove this item from the ignore list?"]
end,
values = GetIgnoreListOptions,
hidden = function() return not IsIgnoringAnything() end,
order = 30,
},
clearIgnoreList = {
type = 'execute',
func = function()
for k,v in pairs(Breakables.settings.ignoreList) do
Breakables.settings.ignoreList[k] = nil
end
Breakables:FindBreakables()
end,
name = L["Clear ignore list"],
confirm = function()
return L["Are you sure you want to clear the ignore list?"]
end,
hidden = function() return not IsIgnoringAnything() end,
order = 31,
},
},
}
if CanDisenchant then
opts.args.showSoulbound = {
type = "toggle",
name = L["Show soulbound items"],
desc = L["Whether or not to display soulbound items as breakables."],
get = function(info)
return self.settings.showSoulbound
end,
set = function(info, v)
self.settings.showSoulbound = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78showSoulbound|r to " .. tostring(self.settings.showSoulbound))
end
self:FindBreakables()
end,
order = 20,
}
if GetNumEquipmentSets or C_EquipmentSet then
opts.args.hideEqManagerItems = {
type = "toggle",
name = L["Hide Eq. Mgr items"],
desc = L["Whether or not to hide items that are part of an equipment set in the game's equipment manager."],
get = function(info)
return self.settings.hideEqManagerItems
end,
set = function(info, v)
self.settings.hideEqManagerItems = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78hideEqManagerItems|r to " .. tostring(self.settings.hideEqManagerItems))
end
self:FindBreakables()
end,
hidden = function()
return not self.settings.showSoulbound
end,
order = 21,
}
end
if WowVer >= 80000 then
opts.args.hideTabards = {
type = "toggle",
name = L["Hide Tabards"],
desc = L["Whether or not to hide tabards from the disenchantable items list."],
get = function(info)
return self.settings.hideTabards
end,
set = function(info, v)
self.settings.hideTabards = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78hideTabards|r to " .. tostring(self.settings.hideTabards))
end
self:FindBreakables()
end,
order = 22,
}
end
end
if UnitCanPetBattle then
opts.args.hideInPetBattle = {
type = "toggle",
name = L["Hide during pet battles"],
desc = L["Whether or not to hide the breakables bar when you enter a pet battle."],
get = function(info)
return self.settings.hideInPetBattle
end,
set = function(info, v)
self.settings.hideInPetBattle = v
if info.uiType == "cmd" then
print("|cff33ff99Breakables|r: set |cffffff78hideInPetBattle|r to " .. tostring(self.settings.hideInPetBattle))
end
end,
order = 3.5,
}
end
return opts
end
function Breakables:CreateButtonFrame()
if not self.frame then
self.frame = CreateFrame("Frame", nil, UIParent)
end
self.frame:SetScale(self.settings.buttonScale)
if not self.buttonFrame then
self.buttonFrame = {}
end
for i=1,numEligibleProfessions do
if not self.buttonFrame[i] then
self.buttonFrame[i] = CreateFrame("Button", "BREAKABLES_BUTTON_FRAME"..i, self.frame, "SecureActionButtonTemplate")
end
local frame = self.buttonFrame[i]
frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", self.settings.buttonFrameLeft[i], self.settings.buttonFrameTop[i])
if CanMill and (i == 1 or self.buttonFrame[1].type ~= BREAKABLE_HERB) then
frame.type = BREAKABLE_HERB
elseif CanDisenchant and (i == 1 or self.buttonFrame[1].type ~= BREAKABLE_DE) then
frame.type = BREAKABLE_DE
elseif CanProspect and (i == 1 or self.buttonFrame[1].type ~= BREAKABLE_ORE) then
frame.type = BREAKABLE_ORE
elseif CanPickLock and (i == 1 or self.buttonFrame[1].type ~= BREAKABLE_PICK) then
frame.type = BREAKABLE_PICK
end
if frame.type then
frame:SetWidth(buttonSize)
frame:SetHeight(buttonSize)
frame:EnableMouse(true)
frame:RegisterForClicks("LeftButtonUp")
if not frame.OnMouseDownFunc then
frame.OnMouseDownFunc = function(frame) self:OnMouseDown(frame) end
end
if not frame.OnMouseUpFunc then
frame.OnMouseUpFunc = function(frame) self:OnMouseUp(frame) end
end
frame:SetMovable(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnMouseDown", frame.OnMouseDownFunc)
frame:SetScript("OnMouseUp", frame.OnMouseUpFunc)
frame:SetClampedToScreen(true)
local spellName, _, texture = GetSpellInfo(self:GetSpellIdFromProfessionButton(frame.type))
frame:SetAttribute("type1", "spell")
frame:SetAttribute("spell1", spellName)
if not lbfGroup then
frame:SetNormalTexture(texture)
else
frame.icon = frame:CreateTexture(frame:GetName().."Icon", "BACKGROUND")
frame.icon:SetTexture(texture)
lbfGroup:AddButton(frame)
end
if not frame.OnEnterFunc then
frame.OnEnterFunc = function(this) self:OnEnterProfessionButton(this) end
end
if not frame.OnLeaveFunc then
frame.OnLeaveFunc = function() self:OnLeaveProfessionButton() end
end
frame:SetScript("OnEnter", frame.OnEnterFunc)
frame:SetScript("OnLeave", frame.OnLeaveFunc)
end
end
end
function Breakables:GetSpellIdFromProfessionButton(itemType, itemId)
if itemType == BREAKABLE_HERB and itemId ~= nil then
if MassMilling[itemId] ~= nil and IsPlayerSpell(MassMilling[itemId]) then
--return MassMilling[itemId]
end
end
if itemType == BREAKABLE_COMBINE then
return nil
end
return (itemType == BREAKABLE_HERB and MillingId)
or (itemType == BREAKABLE_ORE and ProspectingId)
or (itemType == BREAKABLE_DE and DisenchantId)
or PickLockId
end
function Breakables:ApplyScale()
if not self.buttonFrame then
return
end
self.frame:SetScale(self.settings.buttonScale)
for i=1,numEligibleProfessions do
if self.breakableButtons[i] then
for j=1,#self.breakableButtons[i] do
self.breakableButtons[i][j].text:SetFont(NumberFont_Outline_Med:GetFont(), self.settings.fontSize, "OUTLINE")
end
end
end
end
function Breakables:OnMouseDown(frame)
if IsShiftKeyDown() then
frame:StartMoving()
end
end
function Breakables:OnMouseUp(frame)
frame:StopMovingOrSizing()
local frameNum = 1
for i=1,numEligibleProfessions do
if self.buttonFrame[i] == frame then
frameNum = i
break
end
end
self.settings.buttonFrameLeft[frameNum] = frame:GetLeft()
self.settings.buttonFrameTop[frameNum] = frame:GetTop()
end
local function IgnoreFunc(self, button)
if button == "RightButton" and not InCombatLockdown() then
Breakables.settings.ignoreList[self.itemId] = true
Breakables:FindBreakables()
LibStub("AceConfigRegistry-3.0"):NotifyChange("Breakables")
end
end
function Breakables:FindBreakables(bag)
if self.settings.hide then
return
end
if self.bCombat then
self.bPendingUpdate = true
return
end
local foundBreakables = {}
local i=1
local numBreakableStacks = {}
for bagId=0,NUM_BAG_SLOTS do
-- this is where i tried to throttle updates...can't just yet since the full breakables list is rebuilt every time this function is called
-- consider ways of caching off the last-known state of all breakables
--if bag == nil or bag == bagId then
local found = self:FindBreakablesInBag(bagId)
for n=1,#found do
local addedToExisting = self:MergeBreakables(found[n], foundBreakables)
if not addedToExisting then
foundBreakables[i] = found[n]
i = i + 1
end
end
--end
end
self:SortBreakables(foundBreakables)
if not self.breakableButtons then
self.breakableButtons = {}
end
for i=1,#foundBreakables do
for j=1,numEligibleProfessions do
if not self.breakableButtons[j] then
self.breakableButtons[j] = {}
end
if not numBreakableStacks[j] then
numBreakableStacks[j] = 0
end
if (foundBreakables[i][IDX_BREAKABLETYPE] == self.buttonFrame[j].type or (foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_COMBINE and foundBreakables[i][IDX_COUNT] >= 10)) and numBreakableStacks[j] < self.settings.maxBreakablesToShow then
local isDisenchantable = self:BreakableIsDisenchantable(foundBreakables[i][IDX_TYPE], foundBreakables[i][IDX_LEVEL], foundBreakables[i][IDX_RARITY], foundBreakables[i][IDX_LINK])
local isLockedItem = foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_PICK
if (CanDisenchant and isDisenchantable) or (CanPickLock and isLockedItem) or (foundBreakables[i][IDX_COUNT] >= 5) then
numBreakableStacks[j] = numBreakableStacks[j] + 1
local btnIdx = numBreakableStacks[j]
local btn = self.breakableButtons[j][btnIdx]
if not self.breakableButtons[j][btnIdx] then
self.breakableButtons[j][btnIdx] = CreateFrame("Button", "BREAKABLES_BUTTON"..j.."-"..btnIdx, self.buttonFrame[j], "SecureActionButtonTemplate")
btn = self.breakableButtons[j][btnIdx]
if lbfGroup then
btn.icon = btn:CreateTexture(btn:GetName().."Icon", "BACKGROUND")
end
btn:SetWidth(buttonSize)
btn:SetHeight(buttonSize)
btn:EnableMouse(true)
btn:RegisterForClicks("AnyUp")
btn:SetAttribute("type1", "spell")
if not btn.text then
btn.text = btn:CreateFontString()
btn.text:SetPoint("BOTTOM", btn, "BOTTOM", 0, 2)
end
btn.text:SetFont(NumberFont_Outline_Med:GetFont(), self.settings.fontSize, "OUTLINE")
btn:HookScript("OnClick", IgnoreFunc)
if lbfGroup then
lbfGroup:AddButton(btn)
end
end
btn.itemId = self:GetItemIdFromLink(foundBreakables[i][IDX_LINK])
local attachFrom = "LEFT"
local attachTo = "RIGHT"
if self.settings.growDirection then
if self.settings.growDirection == 1 then -- left
attachFrom = "RIGHT"
attachTo = "LEFT"
--elseif self.settings.growDirection == 2 then -- right
elseif self.settings.growDirection == 3 then -- up
attachFrom = "BOTTOM"
attachTo = "TOP"
elseif self.settings.growDirection == 4 then -- down
attachFrom = "TOP"
attachTo = "BOTTOM"
end
end
btn:ClearAllPoints()
btn:SetPoint(attachFrom, btnIdx == 1 and self.buttonFrame[j] or self.breakableButtons[j][btnIdx - 1], attachTo)
if not isDisenchantable then
local appendText = ""
if not isLockedItem then
local breakStackSize = 5
if foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_COMBINE then
breakStackSize = 10
end
appendText = " ("..(floor(foundBreakables[i][IDX_COUNT]/breakStackSize))..")"
end
btn.text:SetText(foundBreakables[i][IDX_COUNT] .. appendText)
end
local BreakableAbilityName = GetSpellInfo(self:GetSpellIdFromProfessionButton(foundBreakables[i][IDX_BREAKABLETYPE], self:GetItemIdFromLink(foundBreakables[i][IDX_LINK])))
--GetSpellInfo((foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_HERB and MillingId)
--or (foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_ORE and ProspectingId)
--or (foundBreakables[i][IDX_BREAKABLETYPE] == BREAKABLE_DE and DisenchantId)
--or PickLockId)
if BreakableAbilityName then
btn:SetAttribute("type1", "spell")
btn:SetAttribute("spell", BreakableAbilityName)
btn:SetAttribute("target-bag", foundBreakables[i][IDX_BAG])
btn:SetAttribute("target-slot", foundBreakables[i][IDX_SLOT])
else
btn:SetAttribute("type1", "item")
btn:SetAttribute("item", "item:" .. self:GetItemIdFromLink(foundBreakables[i][IDX_LINK]))
end
if lbfGroup then
btn.icon:SetTexture(foundBreakables[i][IDX_TEXTURE])
else
btn:SetNormalTexture(foundBreakables[i][IDX_TEXTURE])
end
btn.bag = foundBreakables[i][IDX_BAG]
btn.slot = foundBreakables[i][IDX_SLOT]
if not btn.OnEnterFunc then
btn.OnEnterFunc = function(this) self:OnEnterBreakableButton(this) end
end
if not btn.OnLeaveFunc then
btn.OnLeaveFunc = function() self:OnLeaveBreakableButton() end
end
if not btn.PostClickedFunc then
btn.PostClickedFunc = function(this) self:PostClickedBreakableButton(this) end
end
btn:SetScript("OnEnter", btn.OnEnterFunc)
btn:SetScript("OnLeave", btn.OnLeaveFunc)
btn:SetScript("PostClick", btn.PostClickedFunc)
btn:Show()
end
end
end
end
for i=1,numEligibleProfessions do
if not numBreakableStacks[i] then
numBreakableStacks[i] = 0
end
if self.breakableButtons[i] and numBreakableStacks[i] < #self.breakableButtons[i] then
for j=numBreakableStacks[i]+1,#self.breakableButtons[i] do
self.breakableButtons[i][j]:Hide()
end
end
if self.buttonFrame[i] then
if numBreakableStacks[i] == 0 and self.settings.hideIfNoBreakables then
self.buttonFrame[i]:Hide()
else
self.buttonFrame[i]:Show()
end
end
end
if showingTooltip ~= nil then
self:OnEnterBreakableButton(showingTooltip)
end
end
function Breakables:OnEnterProfessionButton(btn)
local spellId = self:GetSpellIdFromProfessionButton(btn.type)
if spellId and self.settings.showTooltipForProfession then
GameTooltip:SetOwner(btn, "ANCHOR_BOTTOMLEFT")
GameTooltip:SetSpellByID(spellId)
GameTooltip:AddLine(" ")
GameTooltip:AddLine(L["Hold shift and left-click to drag the Breakables bar around."], 1, 1, 1, 1)
GameTooltip:Show()
end
end
function Breakables:OnLeaveProfessionButton()
GameTooltip:Hide()
end
function Breakables:OnEnterBreakableButton(this)
if self.settings.showTooltipForBreakables then
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
GameTooltip:SetBagItem(this.bag, this.slot)
GameTooltip:AddLine(" ")
GameTooltip:AddLine(L["You can click on this button to break this item without having to click on the profession button first."], 1, 1, 1, 1)
GameTooltip:AddLine(" ")
GameTooltip:AddLine(L["You can right-click on this button to ignore this item. Items can be unignored from the options screen."], 1, 1, 1, 1)
GameTooltip:Show()
showingTooltip = this
end
end
function Breakables:OnLeaveBreakableButton()
if showingTooltip then
GameTooltip:Hide()
showingTooltip = nil
end
end
function Breakables:PostClickedBreakableButton(this)
if this.type == BREAKABLE_HERB or this.type == BREAKABLE_ORE or this.type == BREAKABLE_DE or this.type == BREAKABLE_COMBINE then
self.justClicked = true
end
end
function Breakables:FindBreakablesInBag(bagId)
local foundBreakables = {}
local i=1
if GetBagName(bagId) then
for slotId=1,GetContainerNumSlots(bagId) do
local found = self:FindBreakablesInSlot(bagId, slotId)
if found then
local addedToExisting = self:MergeBreakables(found, foundBreakables)
if not addedToExisting then
foundBreakables[i] = found
i = i + 1
end
end
end
end
return foundBreakables
end
function Breakables:FindBreakablesInSlot(bagId, slotId)
if not self.myTooltip then
self.myTooltip = CreateFrame("GameTooltip", "BreakablesTooltip", nil, "GameTooltipTemplate")
self.myTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
end
local texture, itemCount, locked, quality, readable = GetContainerItemInfo(bagId, slotId)
if texture then
local itemLink = GetContainerItemLink(bagId, slotId)
local itemId = self:GetItemIdFromLink(itemLink)
if self.settings.ignoreList[itemId] then
return nil
end
local itemName, _, itemRarity, itemLevel, _, itemType, itemSubType, _, equipSlot, itemTexture, vendorPrice = GetItemInfo(itemLink)
self.myTooltip:SetBagItem(bagId, slotId)
if CanDisenchant and itemRarity and itemRarity >= RARITY_UNCOMMON and itemRarity < RARITY_HEIRLOOM
and self:BreakableIsDisenchantable(itemType, itemLevel, itemRarity, itemLink, itemId) then
local i = 1
local soulbound = false
for i=1,15 do
if _G["BreakablesTooltipTextLeft"..i] then
local textLine = _G["BreakablesTooltipTextLeft"..i]:GetText()
if textLine == ITEM_SOULBOUND or textLine == ITEM_ACCOUNTBOUND or textLine == ITEM_BNETACCOUNTBOUND then
soulbound = true
break
end
end
end
local isInEquipmentSet = false
if self.settings.hideEqManagerItems then
isInEquipmentSet = self:IsInEquipmentSet(itemId)
end
local isTabard = false
if self.settings.hideTabards then
isTabard = equipSlot == "INVTYPE_TABARD"
end
local shouldHideThisItem = (self.settings.hideEqManagerItems and isInEquipmentSet) or (self.settings.hideTabards and isTabard)
or equipSlot == nil or (equipSlot == "" and not IsArtifactRelicItem(itemLink))
if (not soulbound or self.settings.showSoulbound) and not shouldHideThisItem then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_DE, soulbound, itemName, itemRarity}
else
return nil
end
end
local idx = 1
local millable = false
local prospectable = false
for idx=1,5 do
if _G["BreakablesTooltipTextLeft"..idx] then
if _G["BreakablesTooltipTextLeft"..idx]:GetText() == ITEM_MILLABLE then
millable = true
break
elseif _G["BreakablesTooltipTextLeft"..idx]:GetText() == ITEM_PROSPECTABLE then
prospectable = true
break
end
end
end
if CanMill and not millable then
for i=1,#AdditionalMillableItems do
if AdditionalMillableItems[i] == itemId then
millable = true
end
end
end
if CanProspect then
if not prospectable then
for i=1,#AdditionalProspectableItems do
if AdditionalProspectableItems[i] == itemId then
prospectable = true
end
end
end
if prospectable then
for i=1,#UnProspectableItems do
if UnProspectableItems[i] == itemId then
prospectable = false
end
end
end
end
if CanMill --[[and (itemSubType == MillingItemSubType or itemSubType == MillingItemSecondarySubType)]] then
if millable then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_HERB, false, itemName, itemRarity}
else
for i=1,#HerbCombineItems do
if HerbCombineItems[i] == itemId then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_COMBINE, false, itemName, itemRarity}
end
end
end
end
if CanProspect --[[and itemSubType == ProspectingItemSubType]] then
if prospectable then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_ORE, false, itemName, itemRarity}
else
for i=1,#OreCombineItems do
if OreCombineItems[i] == itemId then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_COMBINE, false, itemName, itemRarity}
end
end
end
end
if CanPickLock and self:ItemIsPickable(itemId) and self:ItemIsLocked(bagId, slotId) then
return {itemLink, itemCount, itemType, itemTexture, bagId, slotId, itemSubType, itemLevel, BREAKABLE_PICK, false, itemName, itemRarity}
end
end
return nil
end
function Breakables:ItemIsPickable(itemId)
for i=1,#PickableItems do
if PickableItems[i] == itemId then
return true
end
end
return nil
end
do
local regions = {}
local tooltipBuffer = CreateFrame("GameTooltip","tooltipBuffer",nil,"GameTooltipTemplate")
tooltipBuffer:SetOwner(WorldFrame, "ANCHOR_NONE")
local function makeTable(t, ...)
wipe(t)
for i = 1, select("#", ...) do
t[i] = select(i, ...)
end
end
function Breakables:ItemIsLocked(bagId, slotId)
tooltipBuffer:ClearLines()
tooltipBuffer:SetBagItem(bagId, slotId)
-- Grab all regions, stuff em into our table
makeTable(regions, tooltipBuffer:GetRegions())
-- Convert FontStrings to strings, replace anything else with ""
for i=1, #regions do
local region = regions[i]
if region:GetObjectType() == "FontString" then
if region:GetText() == LOCKED then
return true
end
end
end
return false
end
end
function Breakables:IsInEquipmentSet(itemId)
if WowVer < 80000 and GetNumEquipmentSets then
for setIdx=1, GetNumEquipmentSets() do
local set = GetEquipmentSetInfo(setIdx)
local itemArray = GetEquipmentSetItemIDs(set)
for i=1, EQUIPPED_LAST do
if itemArray[i] and itemArray[i] == itemId then
return true
end
end
end
elseif C_EquipmentSet then
local sets = C_EquipmentSet.GetEquipmentSetIDs()
for k, v in ipairs(sets) do
local itemArray = C_EquipmentSet.GetItemIDs(v)
for i=1, EQUIPPED_LAST do
if itemArray[i] and itemArray[i] == itemId then
return true
end
end
end
end
return false
end
function Breakables:GetItemIdFromLink(itemLink)
local _, foundItemId = strsplit(":", itemLink)
return tonumber(foundItemId)
end
function Breakables:MergeBreakables(foundBreakable, breakableList)
for n=1,#breakableList do
if foundBreakable[IDX_LINK] == breakableList[n][IDX_LINK] then
-- always prefer the larger stack
if foundBreakable[IDX_COUNT] > breakableList[n][IDX_COUNT] then
breakableList[n][IDX_BAG] = foundBreakable[IDX_BAG]
breakableList[n][IDX_SLOT] = foundBreakable[IDX_SLOT]
end
breakableList[n][IDX_COUNT] = breakableList[n][IDX_COUNT] + foundBreakable[IDX_COUNT]
return true
end
end
return false
end
function Breakables:SortBreakables(foundBreakables)
for i=1,#foundBreakables do
local iId = self:GetItemIdFromLink(foundBreakables[i][IDX_LINK])
for j=i,#foundBreakables do
local jId = self:GetItemIdFromLink(foundBreakables[j][IDX_LINK])
if iId < jId then
local temp = foundBreakables[i]
foundBreakables[i] = foundBreakables[j]
foundBreakables[j] = temp
end
end
end
end
function Breakables:BreakableIsDisenchantable(itemType, itemLevel, itemRarity, itemLink, itemId)
for i=1,#DisenchantTypes do
if DisenchantTypes[i] == itemType or IsArtifactRelicItem(itemLink) then
-- bfa+ no longer has skill level requirements for disenchanting
if WowVer >= 80000 then
return true
end
-- if we couldn't figure out the player's enchanting skill level, err on the side of showing stuff
if self.EnchantingLevel == 0 then
return true
end
-- account for WoD and higher no longer needing specific ilvl. numbers from http://wow.gamepedia.com/Item_level
if (itemRarity == RARITY_UNCOMMON and itemLevel >= 483)
or (itemRarity == RARITY_RARE and itemLevel >= 515)
or (itemRarity == RARITY_EPIC and itemLevel >= 640) then
return true
end
-- this is awful. is there an easier way? taken from www.wowpedia.org/Disenchanting
if itemRarity == RARITY_UNCOMMON then
if itemLevel <= 20 then
return self.EnchantingLevel >= 1
elseif itemLevel <= 25 then
return self.EnchantingLevel >= 25
elseif itemLevel <= 30 then
return self.EnchantingLevel >= 50
elseif itemLevel <= 35 then
return self.EnchantingLevel >= 75
elseif itemLevel <= 40 then
return self.EnchantingLevel >= 100
elseif itemLevel <= 45 then
return self.EnchantingLevel >= 125
elseif itemLevel <= 50 then
return self.EnchantingLevel >= 150
elseif itemLevel <= 55 then
return self.EnchantingLevel >= 175
elseif itemLevel <= 60 then
return self.EnchantingLevel >= 200
elseif itemLevel <= 99 then
return self.EnchantingLevel >= 225
elseif itemLevel <= 120 then
return self.EnchantingLevel >= 275
elseif itemLevel <= 150 then
return self.EnchantingLevel >= 325
elseif itemLevel <= 182 then
return self.EnchantingLevel >= 350
elseif itemLevel <= 318 then
return self.EnchantingLevel >= 425
elseif itemLevel <= 437 then
return self.EnchantingLevel >= 475
else
return self.EnchantingLevel >= 475
end
elseif itemRarity == RARITY_RARE then
if itemLevel <= 25 then
return self.EnchantingLevel >= 25
elseif itemLevel <= 30 then
return self.EnchantingLevel >= 50
elseif itemLevel <= 35 then
return self.EnchantingLevel >= 75
elseif itemLevel <= 40 then
return self.EnchantingLevel >= 100
elseif itemLevel <= 45 then
return self.EnchantingLevel >= 125
elseif itemLevel <= 50 then
return self.EnchantingLevel >= 150
elseif itemLevel <= 55 then
return self.EnchantingLevel >= 175
elseif itemLevel <= 60 then
return self.EnchantingLevel >= 200
elseif itemLevel <= 97 then
return self.EnchantingLevel >= 225
elseif itemLevel <= 115 then
return self.EnchantingLevel >= 275
elseif itemLevel <= 200 then
return self.EnchantingLevel >= 325
elseif itemLevel <= 346 then
return self.EnchantingLevel >= 450
elseif itemLevel <= 424 then
return self.EnchantingLevel >= 525
elseif itemLevel <= 463 then
return self.EnchantingLevel >= 550
else
return self.EnchantingLevel >= 550
end
elseif itemRarity == RARITY_EPIC then
if itemLevel <= 95 then
return self.EnchantingLevel >= 225
elseif itemLevel <= 164 then
return self.EnchantingLevel >= 300
elseif itemLevel <= 277 then
return self.EnchantingLevel >= 375
elseif itemLevel <= 416 then
return self.EnchantingLevel >= 475
elseif itemLevel <= 575 then
return self.EnchantingLevel >= 575
else
return self.EnchantingLevel >= 575
end
else
return false
end
return true
end
end
for i=1,#AdditionalDisenchantableItems do
if AdditionalDisenchantableItems[i] == itemId then
return true
end
end
return false
end