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.
474 lines
15 KiB
474 lines
15 KiB
-- Right-click a lockbox in your bag to unlock when you are not in combat
|
|
-- Game Issue #1: The game prioritizes the Soft Targeting target, so if the player is facing an object (e.g. mailbox, chair), the character will perform Pick Lock on that object
|
|
-- Game Issue #2: You'll get a "Invalid Target" error if the targeted lockbox is in the bank. (/use a bank item moves it into your bag)
|
|
|
|
local _, addon = ...
|
|
local _, _, classID = UnitClass("player");
|
|
local _, _, raceID = UnitRace("player");
|
|
if classID ~= 4 and raceID ~= 37 then return end;
|
|
|
|
|
|
local API = addon.API;
|
|
local IsWarningColor = API.IsWarningColor;
|
|
|
|
local InCombatLockdown = InCombatLockdown;
|
|
local IsSpellKnown = IsSpellKnown;
|
|
local SpellIsTargeting = SpellIsTargeting;
|
|
local GetMouseFocus = API.GetMouseFocus;
|
|
local GetSpellInfo = API.GetSpellInfo;
|
|
local GetCursorInfo = GetCursorInfo;
|
|
local match = string.match;
|
|
local IsInteractingWithNpcOfType = (C_PlayerInteractionManager and C_PlayerInteractionManager.IsInteractingWithNpcOfType) or function(type) return false end
|
|
|
|
|
|
local TEXT_LOCKED = LOCKED or "Locked";
|
|
local SPELL_ID_PICK_LOCK = ((classID == 4) and 1804) or (312890); --Rogue: Lock Pick Mechagnome: Skeleton Pinkie
|
|
local SPELL_NAME_PICK_LOCK = nil; --Localized with GetSpellInfo
|
|
local INSTRUCTION_PICK_LOCK = addon.L["Instruction Pick Lock"];
|
|
local NOT_TRADED_ITEM_SLOT_INDEX = 7; --TRADE_ENCHANT_SLOT
|
|
|
|
|
|
local MODULE_ENABLED = false;
|
|
local TOOLTIP_CALLBACK_ADDED = false;
|
|
local OLD_BAG_ID, OLD_SLOT_ID = nil, nil;
|
|
|
|
|
|
local Processor = CreateFrame("Frame");
|
|
local TooltipFrame;
|
|
local ActiveActionButton;
|
|
local CursorProgressIndicator;
|
|
|
|
|
|
local function HideActionButton()
|
|
if ActiveActionButton then
|
|
ActiveActionButton:Release();
|
|
end
|
|
end
|
|
|
|
local function HideProgressIndicator()
|
|
if CursorProgressIndicator then
|
|
CursorProgressIndicator:ClearWatch();
|
|
end
|
|
end
|
|
|
|
function Processor:OnUpdate_ProcessAfter(elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t >= 0.0 then
|
|
self:SetScript("OnUpdate", nil);
|
|
self:ProcessItem();
|
|
end
|
|
end
|
|
|
|
function Processor:OnUpdate_CheckOwnerVisibility(elapsed)
|
|
self.t = self.t + elapsed;
|
|
if self.t >= 0.05 then
|
|
self.t = 0;
|
|
if (not self.owner) or (not self.owner:IsVisible()) then
|
|
self:SetScript("OnUpdate", nil);
|
|
HideActionButton();
|
|
HideProgressIndicator();
|
|
end
|
|
end
|
|
end
|
|
|
|
function Processor:OnEvent(event, ...)
|
|
if event == "CURSOR_CHANGED" then --Unused Event
|
|
HideActionButton();
|
|
self:UnregisterEvent(event);
|
|
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
|
local _, _, spellID = ...
|
|
if spellID == SPELL_ID_PICK_LOCK then
|
|
self:UnregisterEvent(event);
|
|
local bag, slot = OLD_BAG_ID, OLD_SLOT_ID;
|
|
HideActionButton();
|
|
OLD_BAG_ID, OLD_SLOT_ID = bag, slot; --Item doesn't change status imediately so we pause our processing
|
|
C_Timer.After(1, function()
|
|
OLD_BAG_ID, OLD_SLOT_ID = nil, nil;
|
|
end);
|
|
end
|
|
elseif event == "PLAYER_REGEN_DISABLED" then
|
|
self:UnregisterEvent(event);
|
|
self:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED");
|
|
self:SetScript("OnUpdate", nil);
|
|
OLD_BAG_ID, OLD_SLOT_ID = nil, nil;
|
|
elseif event == "TRADE_TARGET_ITEM_CHANGED" then
|
|
local tradeSlotIndex = ...
|
|
if tradeSlotIndex == NOT_TRADED_ITEM_SLOT_INDEX then
|
|
HideActionButton();
|
|
end
|
|
end
|
|
end
|
|
|
|
Processor:SetScript("OnEvent", Processor.OnEvent);
|
|
|
|
function Processor:Initiate(customDelay)
|
|
if customDelay then
|
|
self.t = -customDelay;
|
|
else
|
|
self.t = 0;
|
|
end
|
|
self:SetScript("OnUpdate", self.OnUpdate_ProcessAfter);
|
|
end
|
|
|
|
local function IsPlayerInteracingBank()
|
|
--Merchant is checked by another function
|
|
--Banker, GuildBanker, MailInfo
|
|
return IsInteractingWithNpcOfType(8) or IsInteractingWithNpcOfType(10) or IsInteractingWithNpcOfType(17)
|
|
end
|
|
|
|
local function ShouldShowOverlay()
|
|
return (not InCombatLockdown()) and (not GetCursorInfo()) and (not SpellIsTargeting()) and IsSpellKnown(SPELL_ID_PICK_LOCK) and (not(MerchantFrame and MerchantFrame:IsShown()))
|
|
end
|
|
|
|
local function GetUnlockSpellName()
|
|
if not SPELL_NAME_PICK_LOCK then
|
|
SPELL_NAME_PICK_LOCK = GetSpellInfo(SPELL_ID_PICK_LOCK);
|
|
end
|
|
return SPELL_NAME_PICK_LOCK or ""
|
|
end
|
|
|
|
|
|
-- Copied from SharedTooltipTemplates.lua to prevent potential issues
|
|
local function GameTooltip_AddColoredLine(tooltip, text, color, wrap, leftOffset)
|
|
local r, g, b = color:GetRGB();
|
|
if wrap == nil then
|
|
wrap = true;
|
|
end
|
|
tooltip:AddLine(text, r, g, b, wrap, leftOffset);
|
|
end
|
|
|
|
local function GameTooltip_AddColoredDoubleLine(tooltip, leftText, rightText, leftColor, rightColor, wrap, leftOffset)
|
|
local leftR, leftG, leftB = leftColor:GetRGB();
|
|
local rightR, rightG, rightB = rightColor:GetRGB();
|
|
if wrap == nil then
|
|
wrap = true;
|
|
end
|
|
tooltip:AddDoubleLine(leftText, rightText, leftR, leftG, leftB, rightR, rightG, rightB, wrap, leftOffset);
|
|
end
|
|
|
|
local function AddLineDataText(tooltip, lineData, matchLineFunc)
|
|
local leftText = lineData.leftText;
|
|
local leftColor = lineData.leftColor or NORMAL_FONT_COLOR;
|
|
local wrapText = lineData.wrapText or false;
|
|
local rightText = lineData.rightText;
|
|
local leftOffset = lineData.leftOffset;
|
|
if rightText then
|
|
local rightColor = lineData.rightColor or NORMAL_FONT_COLOR;
|
|
GameTooltip_AddColoredDoubleLine(tooltip, leftText, rightText, leftColor, rightColor, wrapText, leftOffset);
|
|
elseif leftText then
|
|
GameTooltip_AddColoredLine(tooltip, leftText, leftColor, wrapText, leftOffset);
|
|
if matchLineFunc then
|
|
return matchLineFunc(leftText);
|
|
end
|
|
end
|
|
end
|
|
-- End of the copy
|
|
|
|
|
|
local function ActionButton_Bag_OnEnter(self)
|
|
if self.bag and self.slot then
|
|
TooltipFrame:Hide();
|
|
|
|
--[[
|
|
--This noticeably affects RAM count, so we use another way to display info
|
|
local tooltipInfo = {
|
|
getterName = "GetBagItem",
|
|
getterArgs = {self.bag, self.slot, calledByPlumber = true};
|
|
};
|
|
TooltipFrame:ProcessInfo(tooltipInfo);
|
|
--]]
|
|
|
|
local tooltipData = C_TooltipInfo.GetBagItem(self.bag, self.slot);
|
|
if not tooltipData then return end;
|
|
|
|
TooltipFrame:SetOwner(self, "ANCHOR_LEFT");
|
|
|
|
for i, lineData in ipairs(tooltipData.lines) do
|
|
AddLineDataText(TooltipFrame, lineData);
|
|
end
|
|
|
|
TooltipFrame:AddLine(INSTRUCTION_PICK_LOCK, 0.400, 0.733, 1.000, true); --Use a different color to distinguish it from other <Action> text in Pure Green
|
|
TooltipFrame:Show();
|
|
end
|
|
end
|
|
|
|
local PATTERN_ITEM_PROPOSED_ENCHANT;
|
|
do
|
|
PATTERN_ITEM_PROPOSED_ENCHANT = string.gsub((ITEM_PROPOSED_ENCHANT or "Will receive %s."), "%.", "%%.");
|
|
PATTERN_ITEM_PROPOSED_ENCHANT = string.gsub(PATTERN_ITEM_PROPOSED_ENCHANT, "%%s", "(%.+)");
|
|
--print(PATTERN_ITEM_PROPOSED_ENCHANT)
|
|
end
|
|
|
|
local function MatchProposedEnchantText(text)
|
|
local enchantName = match(text, PATTERN_ITEM_PROPOSED_ENCHANT);
|
|
if enchantName then
|
|
return enchantName
|
|
end
|
|
end
|
|
|
|
local function ActionButton_Trade_OnEnter(self)
|
|
TooltipFrame:Hide();
|
|
|
|
local tooltipData = C_TooltipInfo.GetTradeTargetItem(NOT_TRADED_ITEM_SLOT_INDEX);
|
|
if not tooltipData then return end;
|
|
|
|
TooltipFrame:SetOwner(self, "ANCHOR_RIGHT");
|
|
|
|
local matchedText;
|
|
local enchantTextFound = false;
|
|
|
|
for i, lineData in ipairs(tooltipData.lines) do
|
|
matchedText = AddLineDataText(TooltipFrame, lineData, MatchProposedEnchantText);
|
|
if not enchantTextFound then
|
|
enchantTextFound = matchedText ~= nil;
|
|
end
|
|
end
|
|
|
|
if not enchantTextFound then
|
|
TooltipFrame:AddLine(INSTRUCTION_PICK_LOCK, 0.400, 0.733, 1.000, true);
|
|
end
|
|
|
|
TooltipFrame:Show();
|
|
end
|
|
|
|
local function ActionButton_OnHideCallback(self)
|
|
Processor:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED");
|
|
Processor:UnregisterEvent("PLAYER_REGEN_DISABLED");
|
|
Processor:UnregisterEvent("TRADE_TARGET_ITEM_CHANGED");
|
|
Processor:SetScript("OnUpdate", nil);
|
|
end
|
|
|
|
local function ActionButton_OnLeave(self)
|
|
OLD_BAG_ID = nil;
|
|
OLD_SLOT_ID = nil;
|
|
|
|
TooltipFrame:Hide();
|
|
self:Release();
|
|
HideProgressIndicator();
|
|
end
|
|
|
|
local function SetupActionButton(bag, slot, tradeItem)
|
|
if not ShouldShowOverlay() then return end;
|
|
|
|
if not tradeItem then
|
|
--When mouseover a lockbox in your bag while trading with another player
|
|
--We don't create our ActionButton. By default, right-click put items in trade
|
|
if IsInteractingWithNpcOfType(1) then
|
|
return
|
|
end
|
|
end
|
|
|
|
local privateKey = "RogueLockpick";
|
|
local ActionButton = addon.AcquireSecureActionButton(privateKey);
|
|
|
|
ActionButton:SetSize(37, 37);
|
|
ActionButton:SetPassThroughButtons("LeftButton");
|
|
ActionButton:SetFrameStrata("FULLSCREEN_DIALOG");
|
|
ActionButton:SetFixedFrameStrata(true);
|
|
ActionButton:RegisterForClicks("RightButtonDown", "RightButtonUp");
|
|
ActionButton.bag = bag;
|
|
ActionButton.slot = slot;
|
|
|
|
ActionButton:SetScript("OnLeave", ActionButton_OnLeave);
|
|
ActionButton.onHideCallback = ActionButton_OnHideCallback;
|
|
|
|
ActionButton:ShowDebugHitRect(false);
|
|
|
|
local spellName = GetUnlockSpellName();
|
|
local macroText;
|
|
if tradeItem then
|
|
macroText = string.format("/cast %s\r/click TradeRecipientItem%sItemButton", spellName, NOT_TRADED_ITEM_SLOT_INDEX);
|
|
ActionButton:SetScript("OnEnter", ActionButton_Trade_OnEnter);
|
|
else
|
|
macroText = string.format("/cast %s\r/use %s %s", spellName, bag, slot);
|
|
ActionButton:SetScript("OnEnter", ActionButton_Bag_OnEnter);
|
|
end
|
|
ActionButton:SetAttribute("type2", "macro");
|
|
ActionButton:SetMacroText(macroText);
|
|
|
|
if not ActionButton.Highlight then
|
|
local hl = ActionButton:CreateTexture(nil, "HIGHLIGHT");
|
|
ActionButton.Highlight = hl;
|
|
hl:SetAllPoints(true);
|
|
hl:SetTexture("Interface/AddOns/Plumber/Art/Button/Highlight-Square-Inner");
|
|
hl:SetBlendMode("ADD");
|
|
end
|
|
|
|
return ActionButton
|
|
end
|
|
|
|
local function IsMouseoverItemLocked()
|
|
local line2 = TooltipFrame.TextLeft2;
|
|
if line2 and line2:GetText() == TEXT_LOCKED then
|
|
local r, g, b = line2:GetTextColor();
|
|
if not IsWarningColor(r, g, b) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
local function IsMouseOverObjectItemButton(object)
|
|
return object.GetBagID ~= nil
|
|
end
|
|
|
|
local function SetupButtonAndTooltip(bag, slot, tradeItem)
|
|
local mouseoverObject = GetMouseFocus();
|
|
if mouseoverObject and IsMouseOverObjectItemButton(mouseoverObject) then
|
|
local button = SetupActionButton(bag, slot, tradeItem);
|
|
ActiveActionButton = button;
|
|
|
|
if not button then return end;
|
|
|
|
|
|
local x, y = mouseoverObject:GetCenter();
|
|
local scale = mouseoverObject:GetEffectiveScale();
|
|
local w, h = mouseoverObject:GetSize();
|
|
|
|
button:ClearAllPoints();
|
|
button:SetSize(w, h);
|
|
button:SetScale(scale);
|
|
button:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
|
|
button:SetIgnoreParentScale(true);
|
|
button:SetParent(UIParent);
|
|
button:Show();
|
|
|
|
Processor:StartWatchingOwner(mouseoverObject);
|
|
|
|
if not CursorProgressIndicator then
|
|
CursorProgressIndicator = addon.AcquireCursorProgressIndicator();
|
|
end
|
|
|
|
CursorProgressIndicator:ClearAllPoints();
|
|
CursorProgressIndicator:SetIgnoreParentScale(true);
|
|
CursorProgressIndicator:SetScale(scale);
|
|
CursorProgressIndicator:SetParent(UIParent);
|
|
CursorProgressIndicator:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
|
|
CursorProgressIndicator:WatchSpell(SPELL_ID_PICK_LOCK);
|
|
end
|
|
end
|
|
|
|
function Processor:StartWatchingOwner(itemButton)
|
|
self.owner = itemButton;
|
|
self.t = 0;
|
|
self:SetScript("OnUpdate", self.OnUpdate_CheckOwnerVisibility);
|
|
self:RegisterEvent("PLAYER_REGEN_DISABLED");
|
|
self:RegisterEvent("TRADE_TARGET_ITEM_CHANGED");
|
|
self:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player");
|
|
end
|
|
|
|
function Processor:ProcessItem()
|
|
if InCombatLockdown() or IsPlayerInteracingBank() then return end;
|
|
|
|
local info = TooltipFrame.processingInfo;
|
|
if info and TooltipFrame:IsVisible() then
|
|
if info.getterArgs and not info.getterArgs.calledByPlumber then
|
|
if info.getterName == "GetBagItem" then
|
|
local bag, slot = info.getterArgs[1], info.getterArgs[2];
|
|
if bag and slot then
|
|
if IsMouseoverItemLocked() then
|
|
SetupButtonAndTooltip(bag, slot);
|
|
return
|
|
end
|
|
end
|
|
elseif info.getterName == "GetTradeTargetItem" then
|
|
local tradeSlotIndex = info.getterArgs[1];
|
|
if tradeSlotIndex and tradeSlotIndex == NOT_TRADED_ITEM_SLOT_INDEX then
|
|
if IsMouseoverItemLocked() then
|
|
SetupButtonAndTooltip(nil, nil, true);
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Processor:StopAll()
|
|
HideActionButton();
|
|
HideProgressIndicator();
|
|
ActionButton_OnHideCallback();
|
|
end
|
|
|
|
|
|
local function TooltipCall_SetInventoryItem(tooltip, ...)
|
|
if not MODULE_ENABLED then return end;
|
|
|
|
local info = tooltip.processingInfo;
|
|
if info then
|
|
if info.getterName == "GetBagItem" then
|
|
Processor:Initiate();
|
|
end
|
|
end
|
|
end
|
|
|
|
local function TooltipCall_SetTradeTargetItem(tooltip, ...)
|
|
if not MODULE_ENABLED then return end;
|
|
|
|
end
|
|
|
|
local function Tooltip_OnSetBagItem(tooltip, bag, slot)
|
|
if not MODULE_ENABLED then return end;
|
|
|
|
if bag == OLD_BAG_ID and slot == OLD_SLOT_ID then
|
|
return
|
|
end
|
|
|
|
OLD_BAG_ID = bag;
|
|
OLD_SLOT_ID = slot;
|
|
|
|
local info = tooltip.processingInfo;
|
|
if info then
|
|
if info.getterName == "GetBagItem" then
|
|
Processor:Initiate();
|
|
end
|
|
end
|
|
end
|
|
|
|
local function Tooltip_OnSetTradeTargetItem(tooltip, tradeSlotIndex)
|
|
if not MODULE_ENABLED then return end;
|
|
|
|
if tradeSlotIndex == NOT_TRADED_ITEM_SLOT_INDEX then
|
|
Processor:Initiate();
|
|
end
|
|
end
|
|
|
|
|
|
local function AddTooltipPostCall()
|
|
if TOOLTIP_CALLBACK_ADDED then return end;
|
|
TOOLTIP_CALLBACK_ADDED = true;
|
|
|
|
local tooltip = GameTooltip;
|
|
|
|
if tooltip.SetBagItem then
|
|
hooksecurefunc(tooltip, "SetBagItem", Tooltip_OnSetBagItem);
|
|
end
|
|
|
|
if tooltip.SetTradeTargetItem then
|
|
hooksecurefunc(tooltip, "SetTradeTargetItem", Tooltip_OnSetTradeTargetItem);
|
|
end
|
|
end
|
|
|
|
local function EnableModule(state)
|
|
if state then
|
|
MODULE_ENABLED = true;
|
|
TooltipFrame = GameTooltip;
|
|
AddTooltipPostCall();
|
|
GetUnlockSpellName();
|
|
else
|
|
MODULE_ENABLED = false;
|
|
Processor:StopAll();
|
|
end
|
|
end
|
|
|
|
do
|
|
local moduleData = {
|
|
name = addon.L["ModuleName HandyLockpick"],
|
|
dbKey = "HandyLockpick",
|
|
description = addon.L["ModuleDescription HandyLockpick"],
|
|
toggleFunc = EnableModule,
|
|
categoryID = 1,
|
|
uiOrder = 100,
|
|
};
|
|
|
|
addon.ControlCenter:AddModule(moduleData);
|
|
end
|