Skip to content

Commit c0ebf52

Browse files
committed
Added an option to CTRL-Click a keystone/keystone link to teleport to that dungeon
1 parent 719bb55 commit c0ebf52

File tree

1 file changed

+138
-5
lines changed

1 file changed

+138
-5
lines changed

modules/dungeonTeleports.lua

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ local frameSetAttribute = GetFrameMetatable().__index.SetAttribute;
2323
--- returns the remaining cooldown of a spell
2424
--- @param spellID number
2525
--- @return number
26-
local function GetSpellCooldown(spellID)
26+
local function GetRemainingSpellCooldown(spellID)
2727
local cooldownInfo = C_Spell.GetSpellCooldown(spellID);
2828
if not cooldownInfo then return 0; end
2929
local start, duration = cooldownInfo.startTime, cooldownInfo.duration;
@@ -33,11 +33,28 @@ end
3333

3434
function Module:OnInitialize()
3535
self:InitializeButtonPools();
36+
self:InitTeleportOverlayButton();
3637
end
3738

3839
--- @type table<Frame, MPT_DTP_Button>
3940
Module.buttons = {};
4041
function Module:OnEnable()
42+
self.enabled = true;
43+
self.hookedTooltips = {};
44+
TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, function(tooltip) Module:ItemTooltipPostCall(tooltip); end);
45+
for _, frameName in pairs(CHAT_FRAMES) do
46+
local frame = _G[frameName];
47+
self:SecureHookScript(frame, 'OnHyperlinkEnter');
48+
self:SecureHookScript(frame, 'OnHyperlinkLeave');
49+
end
50+
self:SecureHook('FloatingChatFrame_SetupScrolling', function(frame)
51+
self:SecureHookScript(frame, 'OnHyperlinkEnter');
52+
self:SecureHookScript(frame, 'OnHyperlinkLeave');
53+
end);
54+
if Chattynator and Chattynator.API and Chattynator.API.GetHyperlinkHandler and Chattynator.API.GetHyperlinkHandler() then
55+
self:SecureHookScript(Chattynator.API.GetHyperlinkHandler(), 'OnHyperlinkEnter');
56+
self:SecureHookScript(Chattynator.API.GetHyperlinkHandler(), 'OnHyperlinkLeave');
57+
end
4158
EventUtil.ContinueOnAddOnLoaded('Blizzard_ChallengesUI', function()
4259
for _, button in pairs(self.buttons) do
4360
button:Show();
@@ -51,6 +68,8 @@ function Module:OnEnable()
5168
end
5269

5370
function Module:OnDisable()
71+
self.enabled = false;
72+
self.hookedTooltips = {};
5473
self:UnhookAll();
5574
self:UnregisterEvent('ACHIEVEMENT_EARNED');
5675
for _, button in pairs(self.buttons) do
@@ -67,10 +86,13 @@ function Module:GetDescription()
6786
end
6887

6988
function Module:GetOptions(defaultOptionsTable, db)
89+
--- @type MPT_DungeonTeleportsDB
7090
self.db = db;
91+
--- @class MPT_DungeonTeleportsDB
7192
local defaults = {
7293
showAlternates = true,
7394
shuffleSharedCooldown = true,
95+
teleportOnKeystoneCtrlClick = true,
7496
[TYPE_DUNGEON_PORTAL] = OPTION_MAIN_UNKNOWN,
7597
[TYPE_TOY] = OPTION_MAIN_ON_COOLDOWN,
7698
[TYPE_HEARTHSTONE] = OPTION_MAIN_ON_COOLDOWN,
@@ -92,7 +114,15 @@ function Module:GetOptions(defaultOptionsTable, db)
92114
desc = 'Open the Mythic+ UI and you\'ll be able to click any of the icons to teleport to the dungeons, if you have earned the Hero achievement.',
93115
func = function() PVEFrame_ToggleFrame('ChallengesFrame'); end,
94116
};
95-
117+
defaultOptionsTable.args.teleportOnKeystoneCtrlClick = {
118+
type = 'toggle',
119+
order = increment(),
120+
name = 'Teleport on Keystone CTRL+Click',
121+
desc = 'Allows you to teleport to the dungeon entrance by CTRL+Clicking a keystone chat link or in your bags.',
122+
get = get,
123+
set = set,
124+
width = 'double',
125+
};
96126
defaultOptionsTable.args.showAlternates = {
97127
type = 'toggle',
98128
order = increment(),
@@ -226,6 +256,109 @@ function Module:InitializeButtonPools()
226256
self.buttonPool:ReleaseAll();
227257
end
228258

259+
function Module:InitTeleportOverlayButton()
260+
self.overlayTrackerFrame = CreateFrame('Frame');
261+
self.overlayTrackerFrame:SetScript('OnUpdate', function()
262+
local spellID = self.overlayTrackerFrame.spellID;
263+
if not spellID then
264+
self.overlayTrackerFrame:Hide();
265+
return;
266+
end
267+
268+
self:SetShownTeleportOverlayButton(IsControlKeyDown(), spellID);
269+
end);
270+
271+
self.teleportOverlayButton = CreateFrame('Button', nil, self.overlayTrackerFrame, 'InsecureActionButtonTemplate');
272+
local button = self.teleportOverlayButton;
273+
button:Hide();
274+
button:SetAttribute('type', 'spell');
275+
button:SetFrameStrata('TOOLTIP');
276+
button:SetAllPoints(nil);
277+
button:RegisterForClicks('AnyUp', 'AnyDown');
278+
button:SetPropagateMouseMotion(true);
279+
end
280+
281+
function Module:SetShownTeleportOverlayButton(shown, spellID)
282+
local button = self.teleportOverlayButton;
283+
button:SetAttribute('spell', spellID);
284+
button:SetShown(shown);
285+
end
286+
287+
function Module:OnHyperlinkEnter(frame, link)
288+
if not self.db.teleportOnKeystoneCtrlClick then return; end
289+
local mapID = link:match('keystone:%d+:(%d+)');
290+
if not mapID then
291+
local itemId = link:match('item:(%d+)');
292+
if not itemId or not C_Item.IsItemKeystoneByID(itemId) then return end
293+
mapID = link:match(string.format(':%s:(%%d+):', Enum.ItemModification.KeystoneMapChallengeModeID));
294+
end
295+
if not mapID then return end
296+
GameTooltip:SetOwner(frame, 'ANCHOR_CURSOR');
297+
GameTooltip:SetHyperlink(link);
298+
GameTooltip:Show();
299+
self.tooltipShown = true;
300+
end
301+
302+
function Module:OnHyperlinkLeave()
303+
if self.tooltipShown then
304+
GameTooltip:Hide();
305+
self:SetShownTeleportOverlayButton(false);
306+
end
307+
self.tooltipShown = false;
308+
end
309+
310+
--- @param tooltip GameTooltip
311+
function Module:ItemTooltipPostCall(tooltip)
312+
if tooltip ~= GameTooltip then return; end
313+
314+
self.overlayTrackerFrame:Hide();
315+
if not self.enabled or not self.db.teleportOnKeystoneCtrlClick then return; end
316+
if not tooltip or not tooltip.GetItem then return end
317+
318+
local _, itemLink = tooltip:GetItem();
319+
if not itemLink then return; end
320+
local mapID = itemLink:match('keystone:%d+:(%d+)');
321+
if not mapID then
322+
local itemId = itemLink:match('item:(%d+)');
323+
if not itemId or not C_Item.IsItemKeystoneByID(itemId) then return end
324+
mapID = itemLink:match(string.format(':%s:(%%d+):', Enum.ItemModification.KeystoneMapChallengeModeID));
325+
end
326+
if not mapID then return end
327+
328+
self:OnKeystoneTooltip(tooltip, tonumber(mapID));
329+
end
330+
331+
---@param tooltip GameTooltip
332+
---@param mapID number
333+
function Module:OnKeystoneTooltip(tooltip, mapID)
334+
local spellID = self:GetSpellIDForMapID(mapID);
335+
if not spellID then return; end
336+
337+
self.overlayTrackerFrame.spellID = spellID;
338+
self.overlayTrackerFrame:Show();
339+
GameTooltip_AddInstructionLine(GameTooltip, 'CTRL+Click to teleport to the instance.');
340+
GameTooltip:Show();
341+
342+
if self.hookedTooltips[tooltip] then return; end
343+
self.hookedTooltips[tooltip] = true;
344+
self:SecureHookScript(tooltip, 'OnHide', function()
345+
self.overlayTrackerFrame:Hide();
346+
end);
347+
end
348+
349+
--- @param mapID number
350+
--- @return number|nil spellID # nil if unknown or on cooldown
351+
function Module:GetSpellIDForMapID(mapID)
352+
local mapKey = Data.Portals.maps[mapID];
353+
if not mapKey then return nil; end
354+
355+
local spell = Data.Portals.dungeonPortals[mapKey];
356+
local spellID = spell and spell:spellID();
357+
if not spell or not spell:available() or GetRemainingSpellCooldown(spellID) > 3 then return nil; end
358+
359+
return spellID;
360+
end
361+
229362
function Module:ACHIEVEMENT_EARNED()
230363
for _, button in pairs(self.buttons) do
231364
local spellID = button:GetRegisteredSpell();
@@ -244,7 +377,7 @@ end
244377
--- @param tooltip GameTooltip
245378
function Module:AddInfoToTooltip(tooltip, spellID)
246379
GameTooltip_AddInstructionLine(tooltip, 'Click to teleport to the dungeon entrance.', true);
247-
local duration = GetSpellCooldown(spellID);
380+
local duration = GetRemainingSpellCooldown(spellID);
248381
if duration > 3 then -- global cooldown is counted here as well, so lets just ignore anything below 3 seconds
249382
local minutes = math.floor(duration / 60);
250383
tooltip:AddLine(string.format('%sDungeon teleport is on cooldown.|r (%02d:%02d)', ERROR_COLOR_CODE, math.floor(minutes / 60), minutes % 60));
@@ -299,7 +432,7 @@ function Module:InitButton(button)
299432

300433
local spellID = button:GetRegisteredSpell();
301434
if not spellID then return; end
302-
local duration = GetSpellCooldown(spellID);
435+
local duration = GetRemainingSpellCooldown(spellID);
303436
if duration > 3 then -- global cooldown is counted here as well, so lets just ignore anything below 3 seconds
304437
highlight:SetVertexColor(1, 0, 0);
305438
else
@@ -387,7 +520,7 @@ function Module:AttachAlternates(button, mapID, mainKnown, mainSpellID)
387520

388521
local onCooldown = false;
389522
if mainKnown then
390-
local duration = GetSpellCooldown(mainSpellID);
523+
local duration = GetRemainingSpellCooldown(mainSpellID);
391524
if duration > 3 then -- global cooldown is counted here as well, so lets just ignore anything below 3 seconds
392525
onCooldown = true;
393526
end

0 commit comments

Comments
 (0)