Skip to content

Commit 140d223

Browse files
committed
TWW: Apply most of the anti-taint safeguards that Talent Tree Tweaks ships with, for users who don't use the TTT addons
1 parent 06bd249 commit 140d223

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed

TalentLoadoutManager.toc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ core\ImportExportV1.lua
1515
core\ImportExportV2.lua
1616
core\IcyVeinsImport.lua
1717
core\TalentLoadoutManager.lua
18+
core\ReduceTaint.lua
1819
core\ManagerApi.lua
1920
core\BlizzardLoadoutChanger.lua
2021
core\Config.lua

core/ReduceTaint.lua

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
local isDF = select(4, GetBuildInfo()) < 110000;
2+
if isDF then return; end -- I don't feel like making sure it works on DF, with just 1 week left :)
3+
4+
-- Only run if TTT (with its taint module) is missing
5+
-- This module is pretty much a copy/paste my TTT taint module
6+
if C_AddOns.IsAddOnLoaded('TalentTreeTweaks') then return; end
7+
8+
local addonName, ns = ...;
9+
10+
--- @type TalentLoadoutManager
11+
local TLM = ns.TLM;
12+
13+
--- @class TLM_ReduceTaintModule: AceModule, AceHook-3.0
14+
local Module = TLM:NewModule('ReduceTaint', 'AceHook-3.0');
15+
16+
function Module:OnInitialize()
17+
Menu.ModifyMenu('MENU_CLASS_TALENT_PROFILE', function(dropdown, rootDescription, contextData)
18+
if not self:IsEnabled() then return; end
19+
self:OnLoadoutMenuOpen(dropdown, rootDescription);
20+
end);
21+
self.copyDialogName = 'TalentLoadoutManager_ReduceTaint_CopyTextDialog';
22+
StaticPopupDialogs[self.copyDialogName] = {
23+
text = 'CTRL-C to copy %s',
24+
button1 = CLOSE,
25+
OnShow = function(dialog, data)
26+
local function HidePopup()
27+
dialog:Hide();
28+
end
29+
dialog.editBox:SetScript('OnEscapePressed', HidePopup);
30+
dialog.editBox:SetScript('OnEnterPressed', HidePopup);
31+
dialog.editBox:SetScript('OnKeyUp', function(_, key)
32+
if IsControlKeyDown() and key == 'C' then
33+
HidePopup();
34+
end
35+
end);
36+
dialog.editBox:SetMaxLetters(0);
37+
dialog.editBox:SetText(data);
38+
dialog.editBox:HighlightText();
39+
end,
40+
hasEditBox = true,
41+
editBoxWidth = 240,
42+
timeout = 0,
43+
whileDead = true,
44+
hideOnEscape = true,
45+
preferredIndex = 3,
46+
};
47+
end
48+
49+
function Module:OnEnable()
50+
EventUtil.ContinueOnAddOnLoaded('Blizzard_PlayerSpells', function()
51+
self:SetupHook();
52+
end);
53+
self:HandleActionBarEventTaintSpread();
54+
end
55+
56+
function Module:OnDisable()
57+
self:UnhookAll();
58+
end
59+
60+
--- @return FRAME
61+
function Module:GetTalentFrame()
62+
return PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame;
63+
end
64+
--- @return FRAME
65+
function Module:GetTalentContainerFrame()
66+
return PlayerSpellsFrame;
67+
end
68+
function Module:CopyText(text, optionalTitleSuffix)
69+
StaticPopup_Show(self.copyDialogName, optionalTitleSuffix or '', nil, text);
70+
end
71+
72+
function Module:SetupHook()
73+
local talentsTab = self:GetTalentFrame();
74+
talentsTab:RegisterCallback(TalentFrameBaseMixin.Event.TalentButtonAcquired, self.OnTalentButtonAcquired, self);
75+
for talentButton in talentsTab:EnumerateAllTalentButtons() do
76+
self:OnTalentButtonAcquired(talentButton);
77+
end
78+
self:SecureHook(talentsTab, 'ShowSelections', 'OnShowSelections');
79+
80+
-- ToggleTalentFrame starts of with a talentContainerFrame:SetInspecting call, which has a high likelihood of tainting execution
81+
self:SecureHook('ShowUIPanel', 'OnShowUIPanel')
82+
self:SecureHook('HideUIPanel', 'OnHideUIPanel')
83+
84+
self:SecureHook(talentsTab, 'UpdateInspecting', 'OnUpdateInspecting');
85+
self:ReplaceCopyLoadoutButton(talentsTab);
86+
87+
self:HandleMultiActionBarTaint();
88+
end
89+
90+
function Module:OnUpdateInspecting(talentsTab)
91+
local isInspecting = talentsTab:IsInspecting();
92+
if not isInspecting then
93+
self.cachedInspectExportString = nil;
94+
95+
return;
96+
end
97+
self.cachedInspectExportString = talentsTab:GetInspectUnit() and C_Traits.GenerateInspectImportString(talentsTab:GetInspectUnit()) or talentsTab:GetInspectString();
98+
end
99+
100+
function Module:ReplaceCopyLoadoutButton(talentsTab)
101+
talentsTab.InspectCopyButton:SetOnClickHandler(function()
102+
local loadoutString =
103+
self.cachedInspectExportString
104+
or (talentsTab:GetInspectUnit() and C_Traits.GenerateInspectImportString(talentsTab:GetInspectUnit()) or talentsTab:GetInspectString());
105+
if loadoutString and (loadoutString ~= '') then
106+
Util:CopyText(loadoutString, 'Inspected Build');
107+
end
108+
end);
109+
end
110+
111+
local function purgeKey(table, key)
112+
TextureLoadingGroupMixin.RemoveTexture({textures = table}, key);
113+
end
114+
local function makeFEnvReplacement(original, replacement)
115+
local fEnv = {};
116+
setmetatable(fEnv, { __index = function(t, k)
117+
return replacement[k] or original[k];
118+
end});
119+
return fEnv;
120+
end
121+
122+
function Module:HandleMultiActionBarTaint()
123+
local talentContainerFrame = self:GetTalentContainerFrame();
124+
self.originalOnShowFEnv = self.originalOnShowFEnv or getfenv(talentContainerFrame.OnShow);
125+
126+
setfenv(talentContainerFrame.OnShow, makeFEnvReplacement(self.originalOnShowFEnv, {
127+
PlayerSpellsMicroButton = {
128+
EvaluateAlertVisibility = function()
129+
HelpTip:HideAllSystem('MicroButtons');
130+
end,
131+
},
132+
MultiActionBar_ShowAllGrids = nop,
133+
UpdateMicroButtons = function() self:TriggerMicroButtonUpdate() end,
134+
}));
135+
136+
self:SecureHook(FrameUtil, 'UnregisterFrameForEvents', function(frame)
137+
if frame == talentContainerFrame then
138+
self:MakeOnHideSafe();
139+
end
140+
end);
141+
local microButton = TalentMicroButton or PlayerSpellsMicroButton;
142+
if
143+
self.originalOnShowFEnv
144+
and microButton and microButton.HasTalentAlertToShow
145+
and not self:IsHooked(microButton, 'HasTalentAlertToShow')
146+
then
147+
self:SecureHook(microButton, 'HasTalentAlertToShow', function()
148+
purgeKey(microButton, 'canUseTalentUI');
149+
purgeKey(microButton, 'canUseTalentSpecUI');
150+
end);
151+
end
152+
end
153+
154+
function Module:MakeOnHideSafe()
155+
local talentContainerFrame = self:GetTalentContainerFrame();
156+
if not issecurevariable(talentContainerFrame, 'lockInspect') then
157+
if not talentContainerFrame.lockInspect then
158+
purgeKey(talentContainerFrame, 'lockInspect');
159+
else
160+
-- get blizzard to set the value to true
161+
TextureLoadingGroupMixin.AddTexture({textures = talentContainerFrame}, 'lockInspect');
162+
end
163+
end
164+
local isInspecting = talentContainerFrame:IsInspecting();
165+
if not issecurevariable(talentContainerFrame, 'inspectUnit') then
166+
purgeKey(talentContainerFrame, 'inspectUnit');
167+
end
168+
if not issecurevariable(talentContainerFrame, 'inspectString') then
169+
purgeKey(talentContainerFrame, 'inspectString');
170+
end
171+
if isInspecting then
172+
purgeKey(talentContainerFrame, 'inspectString');
173+
purgeKey(talentContainerFrame, 'inspectUnit');
174+
RunNextFrame(function()
175+
talentContainerFrame:SetInspecting(nil, nil, nil);
176+
end);
177+
end
178+
end
179+
180+
function Module:TriggerMicroButtonUpdate()
181+
local cvarName = 'Numy_TalentLoadoutManager';
182+
-- the LFDMicroButton will trigger UpdateMicroButtons() in its OnEvent, without checking the event itself.
183+
-- CVAR_UPDATE is easy enough to trigger at will, so we make use of that
184+
LFDMicroButton:RegisterEvent('CVAR_UPDATE');
185+
if not self.cvarRegistered then
186+
C_CVar.RegisterCVar(cvarName);
187+
self.cvarRegistered = true;
188+
end
189+
C_CVar.SetCVar(cvarName, GetCVar(cvarName) == '1' and '0' or '1');
190+
LFDMicroButton:UnregisterEvent('CVAR_UPDATE');
191+
end
192+
193+
function Module:OnShowUIPanel(frame)
194+
if frame ~= self:GetTalentContainerFrame() then return end
195+
if (frame.IsShown and not frame:IsShown()) then
196+
-- if possible, force show the frame, ignoring the INTERFACE_ACTION_BLOCKED message
197+
frame:Show()
198+
end
199+
end
200+
201+
function Module:OnHideUIPanel(frame)
202+
if frame ~= self:GetTalentContainerFrame() then return end
203+
if (frame.IsShown and frame:IsShown()) then
204+
-- if possible, force hide the frame, ignoring the INTERFACE_ACTION_BLOCKED message
205+
frame:Hide()
206+
end
207+
end
208+
209+
function Module:OnShowSelections()
210+
for _, button in pairs(self:GetTalentFrame().SelectionChoiceFrame.selectionFrameArray) do
211+
self:OnTalentButtonAcquired(button);
212+
end
213+
end
214+
215+
local function replacedShareButtonCallback()
216+
local exportString = Module:GetTalentFrame():GetLoadoutExportString();
217+
Module:CopyText(exportString, 'Talent Loadout String');
218+
end
219+
220+
function Module:OnLoadoutMenuOpen(dropdown, rootDescription)
221+
if not self:ShouldReplaceShareButton() then return; end
222+
223+
for _, elementDescription in rootDescription:EnumerateElementDescriptions() do
224+
if elementDescription.text == TALENT_FRAME_DROP_DOWN_EXPORT then
225+
for _, subElementDescription in elementDescription:EnumerateElementDescriptions() do
226+
-- for unlock restrictions module: subElementDescription:SetEnabled(function() return true end); -- try without func wrapper too
227+
if subElementDescription.text == TALENT_FRAME_DROP_DOWN_EXPORT_CLIPBOARD then
228+
subElementDescription:SetResponder(replacedShareButtonCallback);
229+
end
230+
end
231+
end
232+
end
233+
end
234+
235+
function Module:ShouldReplaceShareButton()
236+
return not issecurevariable(self:GetTalentFrame(), 'configID');
237+
end
238+
239+
function Module:HandleActionBarEventTaintSpread()
240+
local events = {
241+
['PLAYER_ENTERING_WORLD'] = true,
242+
['ACTIONBAR_SLOT_CHANGED'] = true,
243+
['UPDATE_BINDINGS'] = true,
244+
['GAME_PAD_ACTIVE_CHANGED'] = true,
245+
['UPDATE_SHAPESHIFT_FORM'] = true,
246+
['ACTIONBAR_UPDATE_COOLDOWN'] = true,
247+
['PET_BAR_UPDATE'] = true,
248+
['PLAYER_MOUNT_DISPLAY_CHANGED'] = true,
249+
};
250+
local petUnitEvents = {
251+
['UNIT_FLAGS'] = true,
252+
['UNIT_AURA'] = true,
253+
}
254+
for _, actionButton in pairs(ActionBarButtonEventsFrame.frames) do
255+
--@debug@
256+
hooksecurefunc(actionButton, 'UnregisterEvent', function(_, event)
257+
if events[event] then
258+
print('TLM-ReduceTaint Module Debug:', actionButton:GetName(), 'UnregisterEvent', event);
259+
end
260+
end);
261+
--@end-debug@
262+
for event in pairs(events) do
263+
actionButton:RegisterEvent(event);
264+
end
265+
for petUnitEvent in pairs(petUnitEvents) do
266+
actionButton:RegisterUnitEvent(petUnitEvent, 'pet');
267+
end
268+
end
269+
for event in pairs(events) do
270+
ActionBarButtonEventsFrame:UnregisterEvent(event);
271+
end
272+
for petUnitEvent in pairs(petUnitEvents) do
273+
ActionBarButtonEventsFrame:UnregisterEvent(petUnitEvent, 'pet');
274+
end
275+
end
276+
277+
function Module:SetActionBarHighlights(talentButton, shown)
278+
local notMissing = ActionButtonUtil and ActionButtonUtil.ActionBarActionStatus and ActionButtonUtil.ActionBarActionStatus.NotMissing;
279+
local spellID = talentButton:GetSpellID();
280+
if (spellID and (talentButton.GetActionBarStatus and talentButton:GetActionBarStatus() == notMissing)) then
281+
self:HandleBlizzardActionButtonHighlights(shown and spellID);
282+
self:HandleLibActionButtonHighlights(shown and spellID);
283+
end
284+
end
285+
286+
function Module:HandleBlizzardActionButtonHighlights(spellID)
287+
local ON_BAR_HIGHLIGHT_MARKS = spellID and tInvert(C_ActionBar.FindSpellActionButtons(spellID) or {}) or {};
288+
for _, actionButton in pairs(ActionBarButtonEventsFrame.frames) do
289+
if ( actionButton.SpellHighlightTexture and actionButton.SpellHighlightAnim ) then
290+
SharedActionButton_RefreshSpellHighlight(actionButton, ON_BAR_HIGHLIGHT_MARKS[actionButton.action]);
291+
end
292+
end
293+
end
294+
295+
function Module:HandleLibActionButtonHighlights(spellID)
296+
local name = 'LibActionButton-1.';
297+
for mayor, lib in LibStub:IterateLibraries() do
298+
if mayor:sub(1, string.len(name)) == name then
299+
for button in pairs(lib:GetAllButtons()) do
300+
if button.SpellHighlightTexture and button.SpellHighlightAnim and button.GetSpellId then
301+
local shown = spellID and button:GetSpellId() == spellID;
302+
SharedActionButton_RefreshSpellHighlight(button, shown);
303+
end
304+
end
305+
end
306+
end
307+
end
308+
309+
local function ShowActionBarHighlightsReplacement(talentButton)
310+
Module:SetActionBarHighlights(talentButton, true);
311+
end
312+
local function HideActionBarHighlightsReplacement(talentButton)
313+
Module:SetActionBarHighlights(talentButton, false);
314+
end
315+
316+
function Module:OnTalentButtonAcquired(button)
317+
button.ShowActionBarHighlights = ShowActionBarHighlightsReplacement;
318+
button.HideActionBarHighlights = HideActionBarHighlightsReplacement;
319+
end

0 commit comments

Comments
 (0)