Deconstruct mode

This commit is contained in:
BanditTech
2025-11-08 03:31:18 -08:00
parent b7f68f9c33
commit 52ef5a3733
3 changed files with 591 additions and 0 deletions
+18
View File
@@ -1438,6 +1438,24 @@ function B:ContructContainerFrame(name, isBank)
f.vendorGraysButton:SetScript("OnLeave", GameTooltip_Hide) f.vendorGraysButton:SetScript("OnLeave", GameTooltip_Hide)
f.vendorGraysButton:SetScript("OnClick", B.VendorGrayCheck) f.vendorGraysButton:SetScript("OnClick", B.VendorGrayCheck)
-- Deconstruct button
f.deconstructButton = CreateFrame("Button", nil, f.holderFrame)
f.deconstructButton:Size(16 + E.Border)
f.deconstructButton:SetTemplate()
f.deconstructButton:Point("RIGHT", f.vendorGraysButton, "LEFT", -5, 0)
f.deconstructButton:SetNormalTexture("Interface\\ICONS\\INV_Rod_Enchantedcobalt")
f.deconstructButton:GetNormalTexture():SetTexCoord(unpack(E.TexCoords))
f.deconstructButton:GetNormalTexture():SetInside()
f.deconstructButton:SetPushedTexture("Interface\\ICONS\\INV_Rod_Enchantedcobalt")
f.deconstructButton:GetPushedTexture():SetTexCoord(unpack(E.TexCoords))
f.deconstructButton:GetPushedTexture():SetInside()
f.deconstructButton:StyleButton(nil, true)
f.deconstructButton.ttText = "Deconstruct Mode"
f.deconstructButton.ttText2 = "Allow you to disenchant/mill/prospect/unlock items.\nClick to toggle.\nCurrent state: |cffFF0000Disabled|r"
f.deconstructButton:SetScript("OnEnter", B.Tooltip_Show)
f.deconstructButton:SetScript("OnLeave", GameTooltip_Hide)
-- OnClick handler will be set by Deconstruct module
--Search --Search
f.editBox = CreateFrame("EditBox", name.."EditBox", f) f.editBox = CreateFrame("EditBox", name.."EditBox", f)
f.editBox:SetFrameLevel(f.editBox:GetFrameLevel() + 2) f.editBox:SetFrameLevel(f.editBox:GetFrameLevel() + 2)
+572
View File
@@ -0,0 +1,572 @@
--[[
Deconstruct Module for ElvUI (WoW 3.3.5a / Ascension)
Adapted from ElvUI_SLE retail version
This module provides functionality to disenchant, mill, prospect, and unlock items
directly from bags by creating an overlay button when mousing over compatible items.
]] --
local E, L, V, P, G = unpack(select(2, ...))
local B = E:GetModule("Bags")
local D = B:NewModule("Deconstruct", "AceHook-3.0", "AceEvent-3.0")
-- Lua functions
local _G = _G
local format, strfind, type, tostring = format, strfind, type, tostring
local pairs, select, unpack = pairs, select, unpack
-- WoW API
local GetTradeTargetItemLink = GetTradeTargetItemLink
local InCombatLockdown = InCombatLockdown
local GetContainerItemLink = GetContainerItemLink
local GetSpellInfo = GetSpellInfo
local GetItemInfo = GetItemInfo
local GetItemCount = GetItemCount
local CreateFrame = CreateFrame
local GameTooltip = GameTooltip
-- Constants (with fallbacks in case they're not loaded yet)
local LOCKED = LOCKED or "Locked"
local VIDEO_OPTIONS_ENABLED = VIDEO_OPTIONS_ENABLED or "Enabled"
local VIDEO_OPTIONS_DISABLED = VIDEO_OPTIONS_DISABLED or "Disabled"
-- Module variables
D.DeconstructMode = false
D.ItemTable = {
['DoNotDE'] = {
['49715'] = true, -- Rose helm
['44731'] = true, -- Rose offhand
['21524'] = true, -- Red winter hat
['51525'] = true, -- Green winter hat
['70923'] = true, -- Sweater
['34486'] = true, -- Orgrimmar achievement fish
['11287'] = true, -- Lesser Magic Wand
['11288'] = true -- Greater Magic Wand
},
['Cooking'] = {
['46349'] = true -- Chef's Hat
},
['Fishing'] = {
['19022'] = true, -- Nat Pagle's Extreme Angler FC-5000
['19970'] = true, -- Arcanite Fishing Pole
['25978'] = true, -- Seth's Graphite Fishing Pole
['44050'] = true, -- Mastercraft Kalu'ak Fishing Pole
['45858'] = true, -- Nat's Lucky Fishing Pole
['45991'] = true, -- Bone Fishing Pole
['45992'] = true -- Jeweled Fishing Pole
}
}
D.Keys = {}
D.BlacklistDE = {}
D.BlacklistLOCK = {}
-- Profession spell names
D.DEname = GetSpellInfo(13262) -- Disenchant
D.MILLname = GetSpellInfo(51005) -- Milling
D.PROSPECTname = GetSpellInfo(31252) -- Prospecting
D.LOCKname = GetSpellInfo(1804) -- Pick Lock
-- Check if player has any relevant professions
function D:HasRelevantProfession()
if D.HasEnchanting then return true end
if D.HasInscription then return true end
if D.HasJewelcrafting then return true end
if D.HasPickLock then return true end
return false
end
-- Check which professions the player has
function D:UpdateProfessions()
D.HasEnchanting = false
D.HasInscription = false
D.HasJewelcrafting = false
D.HasPickLock = false
if D.DEname and GetSpellInfo(D.DEname) then D.HasEnchanting = true end
if D.MILLname and GetSpellInfo(D.MILLname) then D.HasInscription = true end
if D.PROSPECTname and GetSpellInfo(D.PROSPECTname) then D.HasJewelcrafting = true end
if D.LOCKname and GetSpellInfo(D.LOCKname) then D.HasPickLock = true end
end
-- Helper function to check if player has a skeleton key
local function HaveKey() for key in pairs(D.Keys) do if GetItemCount(key) > 0 then return key end end end
-- Build blacklist from settings
function D:Blacklisting(skill) D['BuildBlacklist' .. skill](self) end
function D:BuildBlacklistDE(...)
wipe(D.BlacklistDE)
for index = 1, select('#', ...) do
local name = select(index, ...)
if name and name ~= "" then
local itemName = GetItemInfo(name)
if itemName then D.BlacklistDE[itemName] = true end
end
end
end
function D:BuildBlacklistLOCK(...)
wipe(D.BlacklistLOCK)
for index = 1, select('#', ...) do
local name = select(index, ...)
if name and name ~= "" then
local itemName = GetItemInfo(name)
if itemName then D.BlacklistLOCK[itemName] = true end
end
end
end
-- Check if item can be disenchanted
function D:IsBreakable(itemId, itemName, itemQuality, equipSlot)
if not itemId then return false end
if type(itemId) == "number" then itemId = tostring(itemId) end
-- Check blacklists
if D.ItemTable['DoNotDE'][itemId] then return false end
if D.ItemTable['Cooking'][itemId] then return false end
if D.ItemTable['Fishing'][itemId] then return false end
if D.BlacklistDE[itemName] then return false end
return true
end
-- Check if item is disenchantable
function D:IsDisenchantable(itemId)
if not itemId then return false end
local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc = GetItemInfo(itemId)
if not itemName then return false end
-- Quality: 2=Uncommon, 3=Rare, 4=Epic
if not itemRarity or itemRarity < 2 or itemRarity > 4 then return false end
if itemType ~= "Armor" and itemType ~= "Weapon" then return false end
if not itemEquipLoc or itemEquipLoc == "" then return false end
if not D.HasEnchanting then return false end
return true
end
-- Check if item can be prospected
function D:IsProspectable(itemId)
if not itemId or not D.HasJewelcrafting then return false end
-- Prospectable ores in WotLK (3.3.5a)
local prospectableOres = {
[2770] = true, -- Copper Ore
[2771] = true, -- Tin Ore
[2772] = true, -- Iron Ore
[3858] = true, -- Mithril Ore
[10620] = true, -- Thorium Ore
[23424] = true, -- Fel Iron Ore
[23425] = true, -- Adamantite Ore
[36909] = true, -- Cobalt Ore
[36912] = true, -- Saronite Ore
[36910] = true -- Titanium Ore
}
return prospectableOres[tonumber(itemId)] or false
end
-- Check if item can be milled
function D:IsMillable(itemId)
if not itemId or not D.HasInscription then return false end
-- Millable herbs in WotLK (3.3.5a)
local millableHerbs = {
[765] = true, -- Silverleaf
[2447] = true, -- Peacebloom
[2449] = true, -- Earthroot
[785] = true, -- Mageroyal
[2450] = true, -- Briarthorn
[2452] = true, -- Swiftthistle
[2453] = true, -- Bruiseweed
[3820] = true, -- Stranglekelp
[3369] = true, -- Grave Moss
[3356] = true, -- Kingsblood
[3357] = true, -- Liferoot
[3818] = true, -- Fadeleaf
[3821] = true, -- Goldthorn
[3358] = true, -- Khadgar's Whisker
[3819] = true, -- Dragon's Teeth (Wintersbite)
[8836] = true, -- Arthas' Tears
[8838] = true, -- Sungrass
[8839] = true, -- Blindweed
[8845] = true, -- Ghost Mushroom
[8846] = true, -- Gromsblood
[13464] = true, -- Golden Sansam
[13463] = true, -- Dreamfoil
[13465] = true, -- Mountain Silversage
[13466] = true, -- Plaguebloom
[13467] = true, -- Icecap
[22785] = true, -- Felweed
[22786] = true, -- Dreaming Glory
[22787] = true, -- Ragveil
[22789] = true, -- Terocone
[22790] = true, -- Ancient Lichen
[22791] = true, -- Netherbloom
[22792] = true, -- Nightmare Vine
[22793] = true, -- Mana Thistle
[36901] = true, -- Goldclover
[36903] = true, -- Adder's Tongue
[36904] = true, -- Tiger Lily
[36905] = true, -- Lichbloom
[36906] = true, -- Icethorn
[36907] = true, -- Talandra's Rose
[37921] = true, -- Deadnettle
[39970] = true -- Fire Leaf
}
return millableHerbs[tonumber(itemId)] or false
end
-- Check if item is currently locked (scan tooltip)
function D:IsUnlockable(itemLink)
if not itemLink then return false end
-- Scan tooltip for "Locked" text
GameTooltip:SetOwner(UIParent, "ANCHOR_NONE")
GameTooltip:SetHyperlink(itemLink)
for i = 2, GameTooltip:NumLines() do
local line = _G["GameTooltipTextLeft" .. i]
if line then
local text = line:GetText()
if text and strfind(text, LOCKED) then
GameTooltip:Hide()
return true
end
end
end
GameTooltip:Hide()
return false
end
-- Apply the deconstruct overlay to a bag slot
function D:ApplyDeconstruct(itemLink, itemId, spell, spellType, r, g, b, slot)
if not slot then return end
if slot == D.DeconstructionReal then return end
local bag = slot:GetParent():GetID()
-- Verify the bag and slot exist in ElvUI's bag or bank structure
local validBag = (B.BagFrame and B.BagFrame.Bags and B.BagFrame.Bags[bag]) or (B.BankFrame and B.BankFrame.Bags and B.BankFrame.Bags[bag])
if not validBag then return end
D.DeconstructionReal.Bag = bag
D.DeconstructionReal.Slot = slot:GetID()
-- Check if in trade window
if GetTradeTargetItemLink and GetTradeTargetItemLink(7) == itemLink then
return
elseif GetContainerItemLink(bag, slot:GetID()) == itemLink then
D.DeconstructionReal.ID = itemId
D.DeconstructionReal:SetAttribute('type1', spellType)
D.DeconstructionReal:SetAttribute(spellType, spell)
D.DeconstructionReal:SetAttribute('target-bag', D.DeconstructionReal.Bag)
D.DeconstructionReal:SetAttribute('target-slot', D.DeconstructionReal.Slot)
D.DeconstructionReal:SetAllPoints(slot)
D.DeconstructionReal:Show()
-- Apply visual effect (ActionButton_ShowOverlayGlow confirmed on Ascension)
ActionButton_ShowOverlayGlow(D.DeconstructionReal)
end
end
-- Get deconstruct mode status text
function D:GetDeconMode()
local text
if D.DeconstructMode then
text = '|cff00FF00 ' .. VIDEO_OPTIONS_ENABLED .. '|r'
else
text = '|cffFF0000 ' .. VIDEO_OPTIONS_DISABLED .. '|r'
end
return text
end
-- Toggle deconstruct mode (called from button click)
function D:ToggleMode()
D.DeconstructMode = not D.DeconstructMode
if D.DeconstructButton then
local normalTex = D.DeconstructButton:GetNormalTexture()
if normalTex then
if D.DeconstructMode then
normalTex:SetTexture([[Interface\ICONS\INV_Enchant_EssenceCosmicGreater]])
ActionButton_ShowOverlayGlow(D.DeconstructButton)
else
normalTex:SetTexture([[Interface\ICONS\INV_Rod_Enchantedcobalt]])
ActionButton_HideOverlayGlow(D.DeconstructButton)
end
end
D.DeconstructButton.ttText2 = format("Allow you to disenchant/mill/prospect/unlock items.\nClick to toggle.\nCurrent state: %s.", D:GetDeconMode())
if GameTooltip:IsOwned(D.DeconstructButton) then B.Tooltip_Show(D.DeconstructButton) end
end
-- Update both bag and bank slots
if B.BagFrame then D:UpdateBagSlots(B.BagFrame, D.DeconstructMode) end
if B.BankFrame then D:UpdateBagSlots(B.BankFrame, D.DeconstructMode) end
end
-- Create the actual deconstruct button overlay
function D:ConstructRealDecButton()
D.DeconstructionReal = CreateFrame('Button', 'ElvUI_DeconReal', E.UIParent, 'SecureActionButtonTemplate')
D.DeconstructionReal:SetScript('OnEvent', function(obj, event, ...) obj[event](obj, ...) end)
D.DeconstructionReal:RegisterForClicks('AnyUp', 'AnyDown')
D.DeconstructionReal:SetFrameStrata('TOOLTIP')
D.DeconstructionReal.OnLeave = function(frame)
if InCombatLockdown() then
frame:SetAlpha(0)
frame:RegisterEvent('PLAYER_REGEN_ENABLED')
else
frame:ClearAllPoints()
frame:SetAlpha(1)
if GameTooltip then GameTooltip:Hide() end
-- Stop visual effects
ActionButton_HideOverlayGlow(frame)
frame:Hide()
end
end
D.DeconstructionReal.SetTip = function(f)
GameTooltip:SetOwner(f, 'ANCHOR_LEFT', 0, 4)
GameTooltip:ClearLines()
GameTooltip:SetBagItem(f.Bag, f.Slot)
end
D.DeconstructionReal:SetScript('OnEnter', D.DeconstructionReal.SetTip)
D.DeconstructionReal:SetScript('OnLeave', function() D.DeconstructionReal:OnLeave() end)
D.DeconstructionReal:Hide()
function D.DeconstructionReal:PLAYER_REGEN_ENABLED()
self:UnregisterEvent('PLAYER_REGEN_ENABLED')
D.DeconstructionReal:OnLeave()
end
end
-- Parse tooltip to determine if item should show deconstruct button
function D:DeconstructParser()
if not D.DeconstructMode then return end
if not GameTooltip:IsVisible() then return end
local owner = GameTooltip:GetOwner()
if not owner then return end
local ownerName = owner.GetName and owner:GetName()
if not ownerName then return end
-- Only process ElvUI bag and bank slots
if not (strfind(ownerName, 'ElvUI_ContainerFrameBag') or strfind(ownerName, 'ElvUI_BankContainerFrameBag')) then return end
-- Get the bag and slot from the owner
local bag, slot
if owner.GetParent then
local parent = owner:GetParent()
if parent.GetID then bag = parent:GetID() end
end
if owner.GetID then slot = owner:GetID() end
if not bag or not slot then return end
local itemLink = GetContainerItemLink(bag, slot)
if not itemLink then return end
local itemId = tonumber(itemLink:match("item:(%d+)"))
if not itemId then return end
if InCombatLockdown() then return end
-- Check what can be done with this item
local r, g, b
-- Check for lockboxes
local hasKey = HaveKey()
if (D.HasPickLock or hasKey) and D:IsUnlockable(itemLink) then
r, g, b = 0, 1, 1
if D.HasPickLock then
D:ApplyDeconstruct(itemLink, itemId, D.LOCKname, 'spell', r, g, b, owner)
elseif hasKey then
D:ApplyDeconstruct(itemLink, itemId, hasKey, 'item', r, g, b, owner)
end
return
end
-- Check for prospectable ores (Jewelcrafting only)
if D.HasJewelcrafting and D:IsProspectable(itemId) then
r, g, b = 1, 0.5, 0
D:ApplyDeconstruct(itemLink, itemId, D.PROSPECTname, 'spell', r, g, b, owner)
return
end
-- Check for millable herbs (Inscription only)
if D.HasInscription and D:IsMillable(itemId) then
r, g, b = 0, 1, 0
D:ApplyDeconstruct(itemLink, itemId, D.MILLname, 'spell', r, g, b, owner)
return
end
-- Check for disenchantable items (Enchanting only)
if D.HasEnchanting and D:IsDisenchantable(itemId) then
local itemName, _, itemQuality, _, _, _, _, _, equipSlot = GetItemInfo(itemId)
if D:IsBreakable(itemId, itemName, itemQuality, equipSlot) then
r, g, b = 0.5, 0, 1
D:ApplyDeconstruct(itemLink, itemId, D.DEname, 'spell', r, g, b, owner)
return
end
end
end
-- Check if an item can be processed (disenchant/mill/prospect/unlock)
function D:CanProcessItem(itemLink, hasKey)
if not itemLink then return false end
local itemId = tonumber(itemLink:match("item:(%d+)"))
if not itemId then return false end
-- Check lockboxes (Rogues only)
if (D.HasPickLock or hasKey) and D:IsUnlockable(itemLink) then return true end
-- Check prospectable (Jewelcrafting only)
if D.HasJewelcrafting and D:IsProspectable(itemId) then return true end
-- Check millable (Inscription only)
if D.HasInscription and D:IsMillable(itemId) then return true end
-- Check disenchantable (Enchanting only)
if D.HasEnchanting and D:IsDisenchantable(itemId) then
local itemName, _, itemQuality, _, _, _, _, _, equipSlot = GetItemInfo(itemId)
if D:IsBreakable(itemId, itemName, itemQuality, equipSlot) then return true end
end
return false
end
-- Update all bag/bank slots to dim non-processable items
function D:UpdateBagSlots(frame, isActive)
if not frame or not frame.Bags then return end
local hasKey = HaveKey()
for _, bagID in ipairs(frame.BagIDs) do
if frame.Bags[bagID] then
for slotID = 1, GetContainerNumSlots(bagID) do
local slot = frame.Bags[bagID][slotID]
if slot then
if isActive then
-- Dim items that can't be processed
local itemLink = GetContainerItemLink(bagID, slotID)
if itemLink and D:CanProcessItem(itemLink, hasKey) then
slot:SetAlpha(1)
else
slot:SetAlpha(0.3)
end
else
-- Restore normal alpha
slot:SetAlpha(1)
end
end
end
end
end
end
-- Helper function to setup button and hooks (called once when bags are first opened)
local function SetupDeconstructButton()
if D.DeconstructButton then return end
-- Update professions first
D:UpdateProfessions()
-- Only show button if player has relevant professions
if not D:HasRelevantProfession() then return end
if not B.BagFrame or not B.BagFrame.deconstructButton then return end
-- Store button reference
D.DeconstructButton = B.BagFrame.deconstructButton
D.DeconstructButton:SetScript("OnClick", function() D:ToggleMode() end)
-- Only create the real deconstruct button and hooks once
if not D.DeconstructionReal then
D:ConstructRealDecButton()
-- Hook GameTooltip to detect when mousing over items
GameTooltip:HookScript('OnShow', function() D:DeconstructParser() end)
GameTooltip:HookScript('OnUpdate', function() D:DeconstructParser() end)
end
-- Hide deconstruct mode when bags close
B.BagFrame:HookScript('OnHide', function()
D.DeconstructMode = false
if D.DeconstructButton then
local normalTex = D.DeconstructButton:GetNormalTexture()
if normalTex then normalTex:SetTexture([[Interface\ICONS\INV_Rod_Enchantedcobalt]]) end
ActionButton_HideOverlayGlow(D.DeconstructButton)
end
if B.BagFrame then D:UpdateBagSlots(B.BagFrame, false) end
if B.BankFrame then D:UpdateBagSlots(B.BankFrame, false) end
if D.DeconstructionReal then D.DeconstructionReal:OnLeave() end
end)
end
-- Initialize the module
function D:Initialize()
if not E.private.bags.enable then return end
-- Hook into Layout to setup button for bags and reapply dimming
hooksecurefunc(B, "Layout", function(_, isBank)
if not isBank then if B.BagFrame and not D.DeconstructButton then E:Delay(0.1, function() SetupDeconstructButton() end) end end
-- Reapply dimming after layout (layout resets slot alpha)
if D.DeconstructMode then
E:Delay(0.05, function()
if B.BagFrame then D:UpdateBagSlots(B.BagFrame, true) end
if B.BankFrame then D:UpdateBagSlots(B.BankFrame, true) end
end)
end
end)
-- Build blacklists
D:Blacklisting('DE')
D:Blacklisting('LOCK')
-- Register events
D:RegisterEvent('BAG_UPDATE')
D:RegisterEvent('SKILL_LINES_CHANGED')
-- If bag frame already exists, setup button now
if B.BagFrame and not D.DeconstructButton then E:Delay(0.1, function() SetupDeconstructButton() end) end
end
-- Handle SKILL_LINES_CHANGED event
function D:SKILL_LINES_CHANGED()
D:UpdateProfessions()
-- Show/hide button based on professions
local hasProf = D:HasRelevantProfession()
if D.DeconstructButton then
if hasProf then
D.DeconstructButton:Show()
else
D.DeconstructButton:Hide()
D.DeconstructMode = false
end
end
end
-- Handle BAG_UPDATE event
function D:BAG_UPDATE()
if D.DeconstructMode then
if B.BagFrame then D:UpdateBagSlots(B.BagFrame, true) end
if B.BankFrame then D:UpdateBagSlots(B.BankFrame, true) end
end
end
-- Wait for ElvUI to fully load before initializing
hooksecurefunc(B, "Initialize", function() D:Initialize() end)
+1
View File
@@ -2,4 +2,5 @@
<Script file="Bags.lua"/> <Script file="Bags.lua"/>
<Script file="BagBar.lua"/> <Script file="BagBar.lua"/>
<Script file="Sort.lua"/> <Script file="Sort.lua"/>
<Script file="Deconstruct.lua"/>
</Ui> </Ui>