@@ -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 ;
3333
3434function Module :OnInitialize ()
3535 self :InitializeButtonPools ();
36+ self :InitTeleportOverlayButton ();
3637end
3738
3839--- @type table<Frame , MPT_DTP_Button>
3940Module .buttons = {};
4041function 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()
5168end
5269
5370function 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()
6786end
6887
6988function 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 ();
227257end
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+
229362function Module :ACHIEVEMENT_EARNED ()
230363 for _ , button in pairs (self .buttons ) do
231364 local spellID = button :GetRegisteredSpell ();
244377--- @param tooltip GameTooltip
245378function 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