-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathSharedUtil.lua
More file actions
343 lines (296 loc) · 11.2 KB
/
SharedUtil.lua
File metadata and controls
343 lines (296 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
--- @class TTT_NS
local ns = select(2, ...);
--- @class TTT_Util
local Util = {};
ns.Util = Util;
local L = ns.L;
local ImportExportUtil = ns.ImportExportUtil;
local talentAddonName = 'Blizzard_PlayerSpells';
local LTT = LibStub('LibTalentTree-1.0');
Util.LibTalentTree = LTT;
Util.PlayerKey = UnitName('player') .. '-' .. GetRealmName();
Util.RightClickAtlasMarkup = CreateAtlasMarkup('NPE_RightClick', 18, 18);
Util.LeftClickAtlasMarkup = CreateAtlasMarkup('NPE_LeftClick', 18, 18);
Util.debug = false;
--@debug@
Util.debug = true;
--@end-debug@
Util.specToClassMap = {};
Util.classMap = {};
do
for classID = 1, GetNumClasses() do
Util.classMap[select(2, GetClassInfo(classID))] = classID;
for specIndex = 1, C_SpecializationInfo.GetNumSpecializationsForClassID(classID) do
Util.specToClassMap[(GetSpecializationInfoForClassID(classID, specIndex))] = classID;
end
end
end
Util.configIDLookup = {};
Util.addonLoadedRegistry = {};
function Util:DebugPrint(...)
if not self.debug then return; end
print('TalentTreeTweaks Debug:', ...);
end
function Util:OnInitialize()
self.dialogName = 'TalentTreeTweaksCopyTextDialog';
StaticPopupDialogs['TalentTreeTweaksCopyTextDialog'] = {
text = L['CTRL-C to copy %s'],
button1 = CLOSE,
--- @param dialog StaticPopupTemplate
--- @param data string
OnShow = function(dialog, data)
local function HidePopup()
dialog:Hide();
end
--- @type StaticPopupTemplate_EditBox
local editBox = dialog.GetEditBox and dialog:GetEditBox() or dialog.editBox;
editBox:SetScript('OnEscapePressed', HidePopup);
editBox:SetScript('OnEnterPressed', HidePopup);
editBox:SetScript('OnKeyUp', function(_, key)
if IsControlKeyDown() and (key == 'C' or key == 'X') then
HidePopup();
end
end);
editBox:SetMaxLetters(0);
editBox:SetText(data);
editBox:HighlightText();
end,
hasEditBox = true,
editBoxWidth = 240,
timeout = 0,
whileDead = true,
hideOnEscape = true,
preferredIndex = 3,
};
self:ResetRegistry();
local eventFrame = CreateFrame('FRAME');
eventFrame:RegisterEvent('ADDON_LOADED');
eventFrame:SetScript('OnEvent', function(_, event, ...)
if event == 'ADDON_LOADED' then
local addonName = ...;
if addonName == talentAddonName and self.classTalentUILoadCallbacks.registered then
self:RunOnLoadCallbacks();
end
if self.addonLoadedRegistry[addonName] then
for _, callback in ipairs(self.addonLoadedRegistry[addonName]) do
securecallfunction(callback);
end
self.addonLoadedRegistry[addonName] = nil;
end
end
if event == 'TRAIT_CONFIG_LIST_UPDATED' then
self:RefreshConfigIDLookup();
end
end);
self:RefreshConfigIDLookup();
end
function Util:ContinueOnAddonLoaded(addonName, callback)
if C_AddOns.IsAddOnLoaded(addonName) then
callback();
return;
end
self.addonLoadedRegistry[addonName] = self.addonLoadedRegistry[addonName] or {};
table.insert(self.addonLoadedRegistry[addonName], callback);
end
--- @param hasOwners boolean?
local function makeRegistry(hasOwners)
--- @class TTT_Util_Registry
--- @field [number] table<table|number, fun()>
--- @field minPriority number
--- @field maxPriority number
--- @field registered boolean
--- @field hasOwners boolean
return {
minPriority = math.huge,
maxPriority = 0,
registered = false,
hasOwners = hasOwners or false,
};
end
--- @param registry TTT_Util_Registry
--- @param ... any # arguments to pass to callbacks
local function executeRegistry(registry, ...)
local hasOwners = registry.hasOwners;
local iterator = hasOwners and pairs or ipairs;
for priority = registry.minPriority, registry.maxPriority do
if registry[priority] then
for owner, callback in iterator(registry[priority]) do
if hasOwners then
securecallfunction(callback, owner, ...);
else
securecallfunction(callback, ...);
end
end
end
end
end
function Util:ResetRegistry()
self.classTalentUILoadCallbacks = makeRegistry();
end
--- @param callback function
--- @param priority ?number - lower numbers are called first
function Util:OnTalentUILoad(callback, priority)
local actualPriority = priority or 10;
local registry = self.classTalentUILoadCallbacks;
registry[actualPriority] = registry[actualPriority] or {};
table.insert(registry[actualPriority], callback);
registry.minPriority = math.min(registry.minPriority, actualPriority);
registry.maxPriority = math.max(registry.maxPriority, actualPriority);
registry.registered = true;
if C_AddOns.IsAddOnLoaded(talentAddonName) then
self:RunOnLoadCallbacks();
end
end
function Util:RunOnLoadCallbacks()
executeRegistry(self.classTalentUILoadCallbacks);
self:ResetRegistry();
end
local eventFrame = CreateFrame("Frame");
do
eventFrame.combatLockdownQueue = {};
eventFrame:SetScript("OnEvent", function(self, event, ...)
if self[event] then
self[event](self, ...);
end
end);
function eventFrame:PLAYER_REGEN_ENABLED()
self:UnregisterEvent("PLAYER_REGEN_ENABLED");
if #self.combatLockdownQueue == 0 then return; end
for _, item in pairs(self.combatLockdownQueue) do
item.func(unpack(item.args));
end
self.combatLockdownQueue = {};
end
end
do
Util.eventRegistryCallbackRegistry = {};
---@param event string
---@param callback fun(...: any)
---@param owner table
---@param priority number? - lower numbers are called first
function Util:RegisterEventRegistryCallback(event, callback, owner, priority)
self.eventRegistryCallbackRegistry[event] = self.eventRegistryCallbackRegistry[event] or makeRegistry(true);
local registry = self.eventRegistryCallbackRegistry[event];
local actualPriority = priority or 10;
registry[actualPriority] = registry[actualPriority] or {};
registry[actualPriority][owner] = callback;
registry.minPriority = math.min(registry.minPriority, actualPriority);
registry.maxPriority = math.max(registry.maxPriority, actualPriority);
if not registry.registered then
registry.registered = true;
EventRegistry:RegisterCallback(event, function(_, ...)
executeRegistry(registry, ...);
end, self);
end
end
function Util:UnregisterEventRegistryCallback(event, owner)
local registry = self.eventRegistryCallbackRegistry[event];
if not registry then return; end
for priority = registry.minPriority, registry.maxPriority do
if registry[priority] and registry[priority][owner] then
registry[priority][owner] = nil;
end
if not registry[priority] or next(registry[priority]) == nil then
registry[priority] = nil;
if priority == registry.minPriority then
registry.minPriority = priority + 1;
end
if priority == registry.maxPriority then
registry.maxPriority = priority - 1;
end
end
end
if registry.minPriority > registry.maxPriority then
registry.minPriority = math.huge;
registry.maxPriority = 1;
EventRegistry:UnregisterCallback(event, self);
registry.registered = false;
end
end
end
--- @param func function
--- @param ... any # arguments
function Util:AddToCombatLockdownQueue(func, ...)
if not InCombatLockdown() then
func(...);
return;
end
if #eventFrame.combatLockdownQueue == 0 then
eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED");
end
tinsert(eventFrame.combatLockdownQueue, { func = func, args = { ... } });
end
function Util:CopyText(text, optionalTitleSuffix)
StaticPopup_Show(self.dialogName, optionalTitleSuffix or '', nil, text);
end
--- @return PlayerSpellsFrame
function Util:GetTalentContainerFrame()
local frameName = 'PlayerSpellsFrame';
if not _G[frameName] then
C_AddOns.LoadAddOn(talentAddonName);
end
return _G[frameName];
end
--- @return PlayerSpellsFrame|nil
function Util:GetTalentContainerFrameIfLoaded()
return PlayerSpellsFrame;
end
--- @return PlayerSpellsFrame_TalentsFrame
function Util:GetTalentFrame()
return self:GetTalentContainerFrame().TalentsFrame;
end
--- @return PlayerSpellsFrame_TalentsFrame|nil
function Util:GetTalentFrameIfLoaded()
local containerFrame = self:GetTalentContainerFrameIfLoaded();
if not containerFrame then return; end
return containerFrame.TalentsFrame;
end
function Util:RefreshConfigIDLookup()
wipe(self.configIDLookup);
local classID = PlayerUtil.GetClassID();
for specIndex = 1, C_SpecializationInfo.GetNumSpecializationsForClassID(classID) do
local specID = GetSpecializationInfoForClassID(classID, specIndex);
for _, configID in pairs(C_ClassTalents.GetConfigIDsBySpecID(specID)) do
self.configIDLookup[configID] = specID;
end
end
end
function Util:GetSpecIDFromConfigID(configID)
local specID = self.configIDLookup[configID] or nil;
if specID then return specID; end
local ok, configInfo = pcall(C_Traits.GetConfigInfo, configID);
if ok and configInfo and configInfo.type == 1 and configInfo.name then
local classID = PlayerUtil.GetClassID();
for specIndex = 1, C_SpecializationInfo.GetNumSpecializationsForClassID(classID) do
local specID, name = GetSpecializationInfoForClassID(classID, specIndex);
if name == configInfo.name then
self.configIDLookup[configID] = specID;
return specID;
end
end
end
end
function Util:GetActiveSubTreeIDByNodeInfo(nodeInfo)
local activeEntryID = nodeInfo and nodeInfo.activeEntry and nodeInfo.activeEntry.entryID;
if not activeEntryID then return; end
local entryInfo = LTT:GetEntryInfo(activeEntryID);
return entryInfo and entryInfo.subTreeID;
end
function Util:GetLoadoutExportString(talentsTab, configIDOverride)
return ImportExportUtil:GetLoadoutExportString(talentsTab, configIDOverride);
end
--- @return false|number # specID or false on error
--- @return number|string # classID or errorMessage on error
--- @return nil|TTT_Util_LoadoutContent[] # loadoutInfo or nothing on error
function Util:ParseTalentBuildString(importString)
return ImportExportUtil:ParseTalentBuildString(importString);
end
function Util:ReadLoadoutHeader(importStream)
return ImportExportUtil:ReadLoadoutHeader(importStream);
end
function Util:IsHashValid(treeHash, treeID)
return ImportExportUtil:IsHashValid(treeHash, treeID);
end
function Util:ReadLoadoutContent(importStream, treeID)
return ImportExportUtil:ReadLoadoutContent(importStream, treeID);
end