├── .gitignore
├── Bindings.xml
├── CHANGELOG.txt
├── Changelog and Notes.txt
├── Leatrix_InputScrollFrameTemplate.xml
├── Leatrix_Plus.blp
├── Leatrix_Plus.lua
├── Leatrix_Plus.toc
├── Leatrix_Plus_Flight_Alliance.lua
├── Leatrix_Plus_Flight_Horde.lua
├── Leatrix_Plus_Library.lua
├── Leatrix_Plus_Locale.lua
├── Leatrix_Plus_Media.lua
├── Leatrix_Plus_Wrath.toc
├── README.md
├── assets
├── ROTATING-MINIMAPARROW.blp
├── minimapicon.tga
└── ui-guildachievement-parchment-horizontal-desaturated.blp
└── libs
├── CallbackHandler-1.0
├── CallbackHandler-1.0.lua
└── CallbackHandler-1.0.xml
├── LibCompat-1.0
├── Backdrop
│ ├── Backdrop.lua
│ └── Backdrop.xml
├── LibCompat-1.0.lua
├── LibCompat-1.0.toc
├── Libs
│ ├── CallbackHandler-1.0
│ │ └── CallbackHandler-1.0.lua
│ ├── LibGroupTalents-1.0
│ │ ├── LibGroupTalents-1.0.lua
│ │ ├── LibTalentQuery-1.0.lua
│ │ └── lib.xml
│ └── LibStub
│ │ └── LibStub.lua
├── Templates.xml
└── lib.xml
├── LibDBIcon-1.0
└── LibDBIcon-1.0.lua
├── LibDataBroker-1.1
├── LibDataBroker-1.1.lua
└── README.textile
└── LibStub
├── LibStub.lua
├── LibStub.toc
└── tests
├── test.lua
├── test2.lua
├── test3.lua
└── test4.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | desktop.ini
2 | /.idea/.gitignore
3 | /.idea/discord.xml
4 | /.idea/icon.svg
5 | /.idea/Leatrix_Plus.iml
6 | /.idea/misc.xml
7 | /.idea/modules.xml
8 | /unitscan.blp
9 | /.idea/vcs.xml
10 |
--------------------------------------------------------------------------------
/Bindings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SlashCmdList["Leatrix_Plus"]()
6 |
7 |
8 |
9 |
10 | SlashCmdList["Leatrix_Plus"]("id")
11 |
12 |
13 |
14 |
15 | SlashCmdList["Leatrix_Plus"]("ra")
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | 3.0.131 - 10th May 2023
2 | - Added flight times.
3 |
4 | 3.0.130 - 3rd May 2023
5 | - For 'Enhance tooltip' ('Interface'), fixed an issue with 'Hide health bar' which caused the health bar to not be hidden.
6 | - For 'Easy item destroy' ('System'), fixed an issue which caused a Lua error when clicking outside of the destroy confirmation.
7 |
8 | 3.0.129 - 25th April 2023
9 | - Added flight times.
10 |
11 | 3.0.128 - 22nd April 2023
12 | - Added flight times.
13 |
14 | 3.0.127 - 19th April 2023
15 | - Added flight times.
16 | - Fixed /run leaplus("quest ") functionality.
17 |
18 | 3.0.126 - 15th April 2023
19 | - Added flight times.
20 |
21 | 3.0.125 - 2nd April 2023
22 | - Added flight times.
23 |
24 | 3.0.124 - 31st March 2023
25 | - Added flight times.
26 |
27 | 3.0.123 - 27th March 2023
28 | - Added flight times.
29 |
30 | 3.0.122 - 26th March 2023
31 | - Added flight times.
32 |
33 | 3.0.121 - 23rd March 2023
34 | - Added flight times.
35 |
36 | 3.0.120 - 18th March 2023
37 | - Added flight times.
38 |
39 | 3.0.118 - 13th March 2023
40 | - Added flight times.
41 |
42 | 3.0.117 - 7th March 2023
43 | - Added flight times.
44 |
45 | 3.0.116 - 5th March 2023
46 | - Added flight times.
47 |
48 | 3.0.115 - 4th March 2023
49 | - Added flight times.
50 |
51 | 3.0.114 - 3rd March 2023
52 | - Added flight times.
53 |
54 | 3.0.113 - 2nd March 2023
55 | - Added flight times.
56 |
57 | 3.0.112 - 1st March 2023
58 | - Added flight times.
59 |
60 | 3.0.111 - 28th February 2023
61 | - Added flight times.
62 |
63 | 3.0.110 - 25th February 2023
64 | - Added flight times.
65 |
66 | 3.0.109 - 22nd February 2023
67 | - For 'Mute game sounds' ('System'), added new sounds to the 'Gyrocopters' setting (including Mimiron's Head).
68 | - Added flight times.
69 |
70 | 3.0.108 - 19th February 2023
71 | - Added flight times.
72 |
73 | 3.0.107 - 12th February 2023
74 | - Added flight times.
75 |
76 | 3.0.106 - 10th February 2023
77 | - Added flight times.
78 |
79 | 3.0.105 - 8th February 2023
80 | - Added flight times.
81 |
82 | 3.0.104 - 6th February 2023
83 | - Updated friend checking code for the options in the social menu to more closely match Dragonflight code.
84 | - For 'Recent chat window' ('Chat'), fixed an issue which caused texture atlases to break dragging and selecting messages.
85 | - For 'Show durability status' ('Interface'), fixed a bug which caused ranged weapons to be excluded.
86 | - Added flight times.
87 |
88 | 3.0.103 - 5th February 2023
89 | - Added flight times.
90 |
91 | 3.0.102 - 4th February 2023
92 | - For 'Mute game sounds' ('System'), you can now mute Rhonin.
93 | - For 'Hide chat buttons' ('Chat'), the text to speech configuration button will now be hidden.
94 | - Added flight times.
95 |
96 | 3.0.101 - 2nd February 2023
97 | - Fixed a minor issue with 'Show auction controls' ('Interface') which caused the 'Find Item' button to only show one page of results.
98 | - Added flight times. There are now 7954 flight times in Leatrix Plus for Wrath of the Lich King Classic.
99 |
100 | 3.0.100 - 30th January 2023
101 | - Updated 'Recent chat window' ('Chat') code and 'Show flight times' ('Interface') code.
102 | - Added flight times.
103 |
104 | 3.0.99 - 30th January 2023
105 | - Updated LibDBIcon.
106 | - Added flight times.
107 |
108 | 3.0.98 - 29th January 2023
109 | - Added flight times.
110 |
111 | 3.0.97 - 28th January 2023
112 | - Added flight times.
113 |
114 | 3.0.96 - 27th January 2023
115 | - Added flight times.
116 |
117 | 3.0.95 - 26th January 2023
118 | - Added flight times.
119 |
120 | 3.0.94 - 25th January 2023
121 | - Added flight times.
122 |
123 | 3.0.93 - 24th January 2023
124 | - Added flight times.
125 |
126 | 3.0.92 - 23rd January 2023
127 | - Added flight times.
128 |
129 | 3.0.91 - 22nd January 2023
130 | - Added flight times.
131 |
132 | 3.0.90 - 22nd January 2023
133 | - Updated the 'Login' setting code ('Mute game sounds') so that it works the same way as Dragonflight and Classic Era.
134 | - Fixed a situational Lua error with 'Automate quests' ('Automation').
135 | - Added flight times.
136 |
137 | 3.0.89 - 21st January 2023
138 | - Added flight times.
139 |
140 | 3.0.88 - 20th January 2023
141 | - For 'Mute game sounds' ('System'), the 'Login' setting now mutes the login screen music too.
142 | - Added flight times.
143 |
144 | 3.0.87 - 19th January 2023
145 | - Fixed an issue with 'Resize quest text' ('Text') which caused gossip greeting panel scroll lists (such as city guard directions) to be shown outside of the gossip frame. If you use ElvUI, the 'Resize quest text' option will now be disabled if you have ElvUI Skins > Blizzard > Gossip Frame checked.
146 | - Added flight times.
147 |
148 | 3.0.86 - 19th January 2023
149 | - Fixed an issue with 'Enhance trainers' ('Interface') which may have caused the trainers frame to open alongside the gossip frame.
150 | - Added flight times.
151 |
152 | 3.0.85 - 19th January 2023
153 | - For 'Mute game sounds' ('System'), you can now mute login screen sounds. If checked, login screen sounds will be muted when you logout of the game. Note that login screen sounds will not be muted when you initially launch the game. They will only be muted when you logout of the game. This includes manually logging out as well as being forcefully logged out by the game server for reasons such as being away for an extended period of time. No more dragons roaring when you fall asleep while the game is running!
154 | - Added flight times.
155 |
156 | 3.0.84 - 18th January 2023
157 | - Added flight times.
158 |
159 | 3.0.83 - 18th January 2023
160 | - Updated for game version 3.4.1.
161 | - There are a lot of code changes in 3.4.1.
162 | - Due to problems with Blizzard's slash command system in 3.4.1, the /ltp command has been replaced with /run leaplus(). If you wish to pass a parameter, enter it in quotes between the brackets.
163 | - Added flight times.
164 |
165 | 3.0.82 - 16th January 2023
166 | - Added flight times.
167 |
168 | 3.0.81 - 14th January 2023
169 | - Added flight times.
170 |
171 | 3.0.80 - 12th January 2023
172 | - Added flight times.
173 |
174 | 3.0.79 - 11th January 2023
175 | - Added flight times.
176 |
177 | 3.0.78 - 8th January 2023
178 | - Added a new option called 'Mute custom sounds' ('System'). You can enter sound file IDs (separated by comma) along with a brief note describing each ID in the configuration panel editbox. After that, click the Mute button and you will never hear the sounds again. For example, entering 'DevAura 569679, RetAura 568744' will mute both aura sounds. Read the help button tooltip for more details.
179 | - For 'Show train all button' ('Interface'), the train all button now has a global name of LeaPlusGlobalTrainAllButton.
180 | - Added flight times.
181 | - PTR (3.4.1): Fixed the Lua error when logging into the PTR.
182 |
183 | 3.0.77 - 4th January 2023
184 | - Added flight times.
185 |
186 | 3.0.76 - 2nd January 2023
187 | - For 'Filter chat messages' ('Chat'), the 'Block spell links during combat' setting now also applies to the yell channel.
188 | - Added flight times.
189 |
190 | 3.0.75 - 31st December 2022
191 | - Added flight times.
192 |
193 | 3.0.74 - 27th December 2022
194 | - Added flight times.
195 |
196 | 3.0.73 - 24th December 2022
197 | - Added flight times.
198 |
199 | 3.0.71 - 23rd December 2022
200 | - Added flight times. There are now 7393 flight times in Leatrix Plus for Wrath of the Lich King Classic.
201 |
202 | 3.0.70 - 21st December 2022
203 | - Added flight times.
204 |
205 | 3.0.69 - 19th December 2022
206 | - Added flight times.
207 |
208 | 3.0.68 - 16th December 2022
209 | - Added flight times.
210 |
211 | 3.0.67 - 13th December 2022
212 | - Added flight times.
213 |
214 | 3.0.66 - 10th December 2022
215 | - Added flight times.
216 |
217 | 3.0.65 - 8th December 2022
218 | - Added flight times.
219 |
220 | 3.0.64 - 5th December 2022
221 | - Added flight times.
222 |
223 | 3.0.63 - 2nd December 2022
224 | - Added flight times.
225 |
226 | 3.0.62 - 30th November 2022
227 | - For 'Enhance minimap' ('Interface'), the 'Combine addon buttons' setting now supports the Armory addon.
228 | - Added flight times.
229 |
230 | 3.0.61 - 27th November 2022
231 | - 3.4.1: For 'Automate gossip' ('Automation'), replaced the (now removed) NPC types with NPC IDs.
232 | - Added flight times.
233 |
234 | 3.0.60 - 23rd November 2022
235 | - Updated for game version 3.4.1 (currently on the PTR). This is a major update as there are a lot of code changes in 3.4.1.
236 | - These options have been updated for 3.4.1: Automate quests, Automate gossip, Sell junk automatically, Resize quest text, Resize mail text, Resize book text, Hide chat buttons, Restore chat messages and Show flight times.
237 | - In 3.4.1, it's no longer possible to automatically skip gossip based on NPC type (such as banker or battlemaster).
238 | - In 3.4.1, Blizzard's slash command system causes errors (just like retail) so /ltp has been replaced with /run leaplus() (just like retail). When Blizzard fixes their slash command system, the /ltp slash command will return.
239 | - Added flight times.
240 |
241 | 3.0.59 - 22nd November 2022
242 | - Updated 'Sell junk automatically' ('Automation') internal code.
243 | - Fixed a rare and situational Lua error with 'Restore chat messages' ('Chat').
244 |
245 | 3.0.58 - 22nd November 2022
246 | - Added flight times.
247 |
248 | 3.0.57 - 18th November 2022
249 | - Added flight times.
250 |
251 | 3.0.56 - 16th November 2022
252 | - Fixed the profession frames.
253 | - Added flight times.
254 |
255 | 3.0.55 - 16th November 2022
256 | - Due to problems with Blizzard's code in Wrath of the Lich King Classic, the taller quest log setting (part of 'Enhance quest log') has been removed.
257 | - Updated LibDBIcon, LibChatAnims and LibCandyBar.
258 | - Added flight times.
259 |
260 | 3.0.54 - 14th November 2022
261 | - Added flight times.
262 |
263 | 3.0.53 - 10th November 2022
264 | - Added flight times.
265 |
266 | 3.0.52 - 9th November 2022
267 | - Added flight times.
268 |
269 | 3.0.51 - 7th November 2022
270 | - If you use the Glass addon, some chat settings will now be locked out of Leatrix Plus in order to ensure that the addon loads correctly.
271 | - Added flight times.
272 |
273 | 3.0.50 - 6th November 2022
274 | - For addon authors, the enhanced minimap combined button frame now has a global reference of LeaPlusGlobalMinimapCombinedButtonFrame.
275 | - Added flight times.
276 |
277 | 3.0.49 - 5th November 2022
278 | - Added flight times.
279 |
280 | 3.0.48 - 2nd November 2022
281 | - Added flight times.
282 |
283 | 3.0.47 - 31st October 2022
284 | - Added flight times.
285 |
286 | 3.0.46 - 29th October 2022
287 | - Added flight times.
288 |
289 | 3.0.45 - 28th October 2022
290 | - Added flight times.
291 |
292 | 3.0.44 - 27th October 2022
293 | - Added flight times.
294 |
295 | 3.0.43 - 26th October 2022
296 | - Added flight times.
297 |
298 | 3.0.42 - 25th October 2022
299 | - Added flight times.
300 |
301 | 3.0.41 - 24th October 2022
302 | - Added flight times.
303 |
304 | 3.0.40 - 23rd October 2022
305 | - Added flight times.
306 |
307 | 3.0.39 - 22nd October 2022
308 | - If you use 'Show flight times' ('Interface') with ElvUI, the flight progress bar will now use ElvUI style.
309 | - Added flight times.
310 |
311 | 3.0.38 - 21st October 2022
312 | - Having ElvUI installed will no longer uncheck conflicting option checkboxes. Conflicting option checkboxes will still be locked but they won't be unchecked. So if a conflicting option checkbox was checked before installing ElvUI or before enabling a conflicting ElvUI module, it will still be checked after disabling ElvUI or disabling the conflicting ElvUI module.
313 | - Added flight times.
314 |
315 | 3.0.37 - 20th October 2022
316 | - Added a new option 'Manage vehicle' ('Frames') which will allow you to move and scale the vehicle seat indicator frame.
317 | - For 'Mute game sounds' ('System'), you can now mute broom mount sounds.
318 | - Added flight times.
319 |
320 | 3.0.36 - 19th October 2022
321 | - Added flight times.
322 |
323 | 3.0.35 - 18th October 2022
324 | - Added flight times.
325 |
326 | 3.0.34 - 17th October 2022
327 | - For 'Show flight times' text to speech, the speech volume will now be set to the game master volume slider and the option is now disabled by default.
328 | - For 'Automate quests' ('Automation'), quests from Lillehoff (The Sons of Hodir Quartermaster, The Storm Peaks) will not be selected, accepted or turned-in automatically if you are exalted with Sons of Hodir.
329 | - Added flight times.
330 |
331 | 3.0.33 - 16th October 2022
332 | - For 'Show flight times', you can now have the remaining time spoken to you using text to speech. The time will be announced once a minute and at 30 seconds, 20 seconds and 10 seconds. This setting is enabled by default.
333 | - Fixed a minor issue with LibCandyBar.
334 | - Added flight times.
335 |
336 | 3.0.32 - 15th October 2022
337 | - Fixed 'Use class colors in chat' ('Chat') for game version 3.4.0.46158.
338 | - Added a flight time.
339 |
340 | 3.0.31 - 15th October 2022
341 | - Added flight times.
342 |
343 | 3.0.30 - 14th October 2022
344 | - Update for 'Enhance dressup' ('Interface') and ElvUI WrathArmory plugin.
345 | - Added flight times.
346 |
347 | 3.0.29 - 12th October 2022
348 | - Added flight times.
349 |
350 | 3.0.28 - 11th October 2022
351 | - For 'Mute game sounds' ('System'), you can now make Netherdrakes quieter.
352 | - Added flight times.
353 | - Removed references to instance chat.
354 |
355 | 3.0.27 - 10th October 2022
356 | - Added flight times.
357 |
358 | 3.0.26 - 9th October 2022
359 | - For 'Sell junk automatically' ('Automation'), white items entered in the exclusions editbox will be sold automatically. Junk items entered in the editbox will not be sold automatically (as before).
360 | - Added flight times.
361 |
362 | 3.0.25 - 8th October 2022
363 | - Added flight times.
364 |
365 | 3.0.24 - 7th October 2022
366 | - Fixed 'Mute travelers' so that it applies to Gnimo too. Gnimo sounds are shared with mechstrider wound sounds so now enabling either mute travelers or mute mechstriders will mute the mechstrider wound sounds.
367 | - Added flight times.
368 |
369 | 3.0.23 - 6th October 2022
370 | - For 'Mute game sounds' ('System'), you can now mute travelers, bikes and mechanical guild vaults.
371 | - For 'Show minimap icon', some of the modified clicks have changed. Read the option tooltip for more details.
372 | - The minimap button control-alt-click to toggle maximised windowed mode now runs much faster.
373 | - Added flight times.
374 |
375 | 3.0.22 - 5th October 2022
376 | - Added flight times.
377 |
378 | 3.0.21 - 4th October 2022
379 | - Added flight times.
380 |
381 | 3.0.20 - 3rd October 2022
382 | - Added flight times.
383 |
384 | 3.0.19 - 2nd October 2022
385 | - Added flight times.
386 |
387 | 3.0.18 - 2nd October 2022
388 | - Added flight times.
389 |
390 | 3.0.17 - 1st October 2022
391 | - For 'Show flight times', you can now set the flight progress bar background to fill instead of drain.
392 | - Added flight times. There are more addon releases at the moment to make sure that everyone gets the latest flight times that have been submitted by thousands of Leatrix Plus users since Wrath's release. Thank you everyone for your contributions.
393 |
394 | 3.0.16 - 1st October 2022
395 | - If 'Automate gossip' ('Automation') is enabled, the gossip from the engineer auction house robots in Dalaran will be skipped.
396 | - Added flight times.
397 |
398 | 3.0.15 - 30th September 2022
399 | - Added flight times.
400 |
401 | 3.0.14 - 30th September 2022
402 | - Changes to file structure.
403 | - Added flight times.
404 |
405 | 3.0.13 - 29th September 2022
406 | - Added lots and lots of flight times. There are now 4378 flight times stored in Leatrix Plus for Wrath Classic.
407 |
408 | 3.0.12 - 28th September 2022
409 | - Added lots and lots of flight times. There are now 4275 flight times stored in Leatrix Plus for Wrath Classic.
410 |
411 | 3.0.11 - 28th September 2022
412 | - Added lots and lots of flight times. There are now 4122 flight times stored in Leatrix Plus for Wrath Classic.
413 |
414 | 3.0.10 - 28th September 2022
415 | - For 'Automate gossip' ('Automation'), stable master gossip will now be skipped as long as there are no other gossip options. You can hold shift to prevent this.
416 | - Added Wrath of the Lich King main title music (media > various > main titles).
417 | - Added lots and lots of flight times. There are now 4003 flight times stored in Leatrix Plus for Wrath Classic.
418 |
419 | 3.0.09 - 27th September 2022
420 | - Added a new option 'Restore chat messages' ('Chat'). If enabled, you can reload your UI without losing your recent chat messages.
421 | - Added a new option 'Block shared quests' ('Social'). If enabled, shared quests will be automatically declined unless the player sharing the quest is a friend.
422 | - Added lots and lots of flight times. There are now 3845 flight times stored in Leatrix Plus for Wrath Classic.
423 |
424 | 3.0.08 - 21st September 2022
425 | - For 'Mute game sounds' ('System'), you can now mute the Arcanite Ripper guitar sound.
426 | - For 'Manage buffs' ('Frames'), fixed an issue which caused buff icons to overlap each other at higher scale values. The scale slider will now affect the consolidated buff icon too.
427 | - Added lots of flight times. There are now 3726 flight times stored in Leatrix Plus for Wrath Classic.
428 |
429 | 3.0.07 - 14th September 2022
430 | - For 'Enhance tooltip' ('Interface'), if you use the setting to hide tooltips for world units during combat, you can now specify whether holding shift should override that setting or not. Also some locale updates.
431 | - For 'Show flight times' ('Interface'), the flight progress bar will no longer show if you are in combat when you attempt to take a flight. A few seconds into the flight, there is an additional check to make sure you are actually on the taxi. If you are not, the flight progress bar will be hidden.
432 | - For 'Enhance quest log' ('Interface'), fixed a minor issue which quest list alignment.
433 | - Added lots of flight times. There are now 3657 flight times stored in Leatrix Plus for Wrath Classic.
434 |
435 | 3.0.05 - 7th September 2022
436 | - For 'Enhance questlog', there is a new setting in the configuration panel to show a toggle headers button. It's enabled by default. The toggle headers button allows you to collapse and expand all quest headers in one click.
437 | - Added lots of flight times. There are now 3590 flight times stored in Leatrix Plus for Wrath Classic.
438 |
439 | 3.0.04 - 4th September 2022
440 | - For 'Automate quests', Darkmoon Faire repeatable quests will now be automated for English game clients if you have the required materials in your bag.
441 | - For 'Enhance minimap', fix for the square minimap mail icon overlapping the tracking icon.
442 |
443 | 3.0.03 - 4th September 2022
444 | - For 'Enhance quest log', quest level and difficulty will now be shown in the quest detail pane once again for quests that are in your quest log. Added a new setting to show the quest difficulty in the quest log list.
445 | - For 'Enhance professions', fix for intermittent horizontal bar showing in the professions frame.
446 | - For 'Enhance minimap', fix for the heroic instance difficulty badge to not move with the minimap.
447 | - Added lots of flight times. There are now 3572 flight times stored in Leatrix Plus for Wrath Classic.
448 |
449 | 3.0.02 - 31st August 2022
450 | - For 'Enhance quest log' ('Interface'), there is a new setting that lets you choose whether the quest log should be taller or not.
451 | - Fix for 'Enhance minimap' calendar button #2.
452 | - Added some flight times. There are now 3541 flight times stored in Leatrix Plus for Wrath Classic.
453 |
454 | 3.0.01 - 31st August 2022
455 | - Added a new option 'Manage durability' ('Frames'). If enabled, you can move and scale the armored man durability frame.
456 | - Fixed an issue with 'Enhance minimap' missing calendar button.
457 | - Fixed an issue with 'Enhance quest log' incorrect quest title.
458 | - Added some flight times. There are now 3539 flight times stored in Leatrix Plus for Wrath Classic.
459 |
460 | 3.0.00 - 29th August 2022
461 | - Welcome to Wrath of the Lich King!
462 | - Removed BCC transitional code.
463 | - Added a new option called 'Manage timer' ('Frames'). The 'Manage frames' option no longer includes the timer bar.
464 | - For 'Enhance minimap' ('Interface'), added a new setting that will allow you to adjust the minimap cluster scale without it affecting the minimap scale.
465 | - Removed the 'Hide the clock' setting (part of 'Enhance minimap') and the 'Show free bag slots' setting as they are now both included in the default UI (interface settings display menu).
466 | - Tweaks to the ElvUI locks.
467 | - Added a few flight times. There are now 3521 flight times stored in Leatrix Plus for Wrath Classic.
468 |
469 | 2.5.120 - 24th August 2022
470 | - The 'Hide action button text' option ('Text') is now two options - 'Hide keybind text' and 'Hide macro text'.
471 | - For 'Show flight times' ('Interface'), you can now control whether flight report windows are shown using a new setting in the configuration panel.
472 | - If you use ElvUI, Leatrix Plus options which are not compatible with specific ElvUI modules will now be disabled and locked while those ElvUI modules are enabled. The option tooltip will tell you which ElvUI module is causing the Leatrix Plus option to be disabled. Disabling the relevant ElvUI module will make the Leatrix Plus setting available again.
473 | - Fixed a minor issue with 'Show Wowhead links' ('Interface') and Questie which sometimes caused the quest link to not be updated.
474 | - Wrath: For 'Enhance quest log' ('Interface'), quest levels are now shown in the quest details pane in addition to the quest list.
475 | - Added lots and lots of flight times. There are now 3515 flight times stored in Leatrix Plus for BCC and Wrath Classic.
476 |
477 | 2.5.119 - 17th August 2022
478 | - Wrath: Added a new option 'Hide alerts' ('Frames'). If checked, alert frames will not be shown. When you earn an achievement, a message will be shown in chat instead (just like retail).
479 | - Wrath: Added Northrend zone and dungeon music to the media section. Most of the dungeon tracks are sound kits.
480 | - Wrath: For 'Accept resurrection' ('Automation'), you will not accept a resurrection request automatically if you are in the Thaddius fight (Naxxramas) and someone in your group has a positive or negative charge.
481 | - For 'Show flight times' ('Interface'), the flight progress bar will now always hide when you land. The flight report window will now show if the minimum time difference between the saved flight time and the actual flight time is 15 seconds (up from 5).
482 | - Fixed a minor issue with 'Show cooldowns' ('Interface') which caused buff spell IDs to show in debuff tooltips.
483 | - Added a massive number of flight times. There are now 3476 flight times stored in Leatrix Plus for BCC and Wrath Classic.
484 |
485 | 2.5.118 - 10th August 2022
486 | - Updated for Wrath of the Lich King Beta.
487 | - Wrath: Show Wowhead links (Interface) will now show achievement links as well as quest links.
488 | - Wrath: Show druid power bar is disabled since it's now included in the default UI.
489 | - If you use 'Automate quests' ('Automation') with a Mac, you can now assign the override key to be the command key if you wish.
490 | - Added a massive number of flight times. There are now 2959 flight times stored in Leatrix Plus for BCC and Wrath Classic.
491 | - For Wrath, it's best to use alpha releases during Wrath Beta/prepatch so that you get the latest development updates. To switch to alpha releases, open CurseForge App, right-click Leatrix Plus in your Wrath addon listing and change release type to Alpha. You can change it back to Release once Wrath is live for everyone.
492 |
493 | 2.5.117 - 3rd August 2022
494 | - Added lots of flight times. There are now 2551 flight times stored in Leatrix Plus for BCC.
495 | - Leatrix Plus will be updated for Wrath Classic in August 2022.
496 |
497 | 2.5.116 - 27th July 2022
498 | - For 'Enhance minimap', added a new setting to hide the tracking button while the pointer is not over the minimap. The clock and zone text bar will now be shown on top when using square minimap. Some structural changes to the code.
499 | - Added lots of flight times. There are now 2530 flight times stored in Leatrix Plus for BCC.
500 | - Leatrix Plus will now attempt to run on Wrath Beta but note that it hasn't been tested and there are no Wrath features yet.
501 |
502 | 2.5.115 - 20th July 2022
503 | - Removed the compatibility fix for ClassicCodex addon.
504 | - Added lots of flight times. There are now 2507 flight times stored in Leatrix Plus for BCC.
505 |
506 | 2.5.114 - 13th July 2022
507 | - For 'Enhance tooltip' ('Interface'), there is a new setting to hide the health bar. It's disabled by default.
508 | - Added lots of flight times. There are now 2492 flight times stored in Leatrix Plus for BCC.
509 |
510 | 2.5.113 - 6th July 2022
511 | - Added lots of flight times. There are now 2473 flight times stored in Leatrix Plus for BCC.
512 |
513 | 2.5.112 - 29th June 2022
514 | - In the game key bindings menu, you can now set a keybind to announce rares in chat. Target a creature, press your keybind and detals of your target will be shown in the General chat channel. These details include your target's name, health percentage remaining and coordinates.
515 | - Added lots of flight times. There are now 2455 flight times stored in Leatrix Plus for BCC.
516 |
517 | 2.5.111 - 22nd June 2022
518 | - In the game key bindings menu, you can now set a keybind to show a web link for whatever your pointer is over (as long as it has a tooltip). For example, if you have an item in your bag and you don't know what it's for, hover your pointer over it so the item tooltip shows up then press your defined hotkey. A Wowhead link will show in a copy-friendly window. Press CTRL/C to copy the link then paste it into your browser.
519 | - In the game key bindings menu, you can now set a keybind to toggle Leatrix Plus. The 'Enable hotkey' command has been removed.
520 | - The '/ltp id' command now runs the web link keybind function so if you don't want to set a keybind, you can enter '/ltp id' instead.
521 | - Added lots of flight times. There are now 2431 flight times stored in Leatrix Plus for BCC.
522 |
523 | 2.5.110 - 15th June 2022
524 | - The 'Manage frames', 'Manage buffs', 'Manage widget' and 'Manage focus' settings ('Frames') now show a frame alignment grid when moving the frames (there is a button in each configuration panel to toggle the grid). You can now drag the frames with the left button to position them freely or with the right button to position them using snap-to-grid.
525 | - Added lots of flight times. There are now 2416 flight times stored in Leatrix Plus for BCC.
526 |
527 | 2.5.109 - 8th June 2022
528 | - For 'Show flight times', you can now change the position, scale and width of the flight progress bar using a new configuration panel. You can also remove the progress bar background and destination text if you wish. The flight progress bar is now noninteractive so you can click through it as if it isn't there. If you wish to manually close the flight progress bar, there is a new Close Bar button in the configuration panel. These new settings give you a lot of customisation options. For example, if you set the width to the minimum, hide the background and hide the destination, you can have a neat little flight timer in the corner of the screen.
529 | - Added lots of flight times. There are now 2402 flight times stored in Leatrix Plus for BCC.
530 |
531 | 2.5.108 - 1st June 2022
532 | - For 'Enhance trainers' ('Interface'), you can now disable the train all skills button if you wish using a new setting in the configuration panel.
533 | - For 'Mute game sounds' ('System'), the 'Mechstriders' setting will no longer mute mechanostrider footstep sounds. There is a new setting called 'Mechsteps' that will mute footstep sounds for mechanical mounts.
534 | - Added lots of flight times. There are now 2383 flight times stored in Leatrix Plus for BCC.
535 |
536 | 2.5.107 - 25th May 2022
537 | - Added a new option 'Show ready timer' ('Interface'). If checked, a timer will be shown under the PvP encounter ready frame so that you know how long you have left to click the enter button.
538 | - Added lots of flight times. There are now 2359 flight times stored in Leatrix Plus for BCC.
539 |
540 | 2.5.106 - 18th May 2022
541 | - For 'Enhance quest log' ('Interface'), the quest log is now taller.
542 | - For 'Enhance professions' ('Interface'), the professions frame is now taller.
543 | - For 'Enhance trainers' ('Interface'), the skill trainer frame is now taller. Also fixed a cosmetic bug with the position of the beast training cost column.
544 | - If you are using ElvUI, 'Show Wowhead links' ('Interface') will now place links inside the quest log frame rather than above it. For 'Enhance professions', the search box and 'Have materials' checkbox have been repositioned.
545 | - Added some flight times. There are now 2340 flight times stored in Leatrix Plus for BCC.
546 |
547 | 2.5.105 - 12th May 2022
548 | - Corrected a very popular flight route and added flight times. There are now 2329 flight times stored in Leatrix Plus for BCC.
549 |
550 | 2.5.104 - 12th May 2022
551 | - For 'Sell junk automatically' ('Automation'), you can now exclude specific junk items from being sold. There is an exclusion list in the configuration panel where you can enter your own item IDs (separated by commas). Item IDs will be shown in item tooltips while the configuration panel is showing.
552 | - If you try to use 'Enhance minimap' ('Interface') and ElvUI Minimap together, you will now be asked which one you want to use on startup. Choosing Leatrix Plus will disable ElvUI's minimap module. Choosing ElvUI will disable Leatrix Plus 'Enhance minimap' setting.
553 | - Added flight times. There are now 2326 flight times stored in Leatrix Plus for BCC.
554 |
555 | 2.5.103 - 5th May 2022
556 | - Added lots of flight times. There are now 2314 flight times stored in Leatrix Plus for BCC.
557 |
558 | 2.5.102 - 27th April 2022
559 | - Added lots of flight times. There are now 2295 flight times stored in Leatrix Plus for BCC.
560 |
561 | 2.5.101 - 20th April 2022
562 | - Added lots of flight times. There are now 2265 flight times stored in Leatrix Plus for BCC.
563 |
564 | 2.5.100 - 13th April 2022
565 | - For 'Enhance minimap' ('Interface'), if you use 'Hide addon buttons' or 'Combine addon buttons' but you want some buttons to remain visible around the minimap, you can now do so using a new 'Buttons' editor in the configuration panel. Just follow the directions in the Help button. The 'Exclude BugSack' setting has been removed since you can now add BugSack to the 'Buttons' panel to achieve the same result.
566 | - Added lots of flight times. There are now 2180 flight times stored in Leatrix Plus for BCC.
567 |
568 | 2.5.99 - 6th April 2022
569 | - Fixed an issue with 'Show bag search box' ('Interface') which caused a Lua error after opening a guild bank.
570 | - Added flight times. There are now 2105 flight times stored in Leatrix Plus for BCC.
571 |
572 | 2.5.98 - 6th April 2022
573 | - The 'Enhance dressup' option now shows a toggle stats button in the character frame. You can either click this button or middle-click the character frame to toggle stats. For new Leatrix Plus users, stats are now shown by default.
574 | - Added lots of flight times. There are now 2102 flight times stored in Leatrix Plus for BCC.
575 |
576 | 2.5.97 - 30th March 2022
577 | - If 'Enhance trainers' ('Interface') is enabled, a 'Train All' button will now be shown in the skill trainer frame allowing you to train all available skills in one click.
578 | - Added a compatibility fix for 'Enhance professions' when using the 'Classic Profession Filter' addon so that you can see the search box in the craft frame (used for enchanting).
579 | - Added lots of flight times. There are now 2075 flight times stored in Leatrix Plus for BCC.
580 |
581 | 2.5.96 - 25th March 2022
582 | - Fixed an issue with 'Enhance professions' ('Interface') which caused the new search box to be obscured.
583 | - Added lots of flight times. There are now 2051 flight times stored in Leatrix Plus for BCC.
584 |
585 | 2.5.95 - 23rd March 2022
586 | - Updated for game version 2.5.4.
587 | - Added lots of flight times. There are now 2035 flight times stored in Leatrix Plus for BCC.
588 | - Updated the '/ltp ra' rare announcement message.
589 | - For 'Enhance tooltip', removed the setting to show tooltip backdrops in faction color.
590 |
591 | 2.5.94 - 9th March 2022
592 | - Added lots of flight times. There are now 1994 flight times stored in Leatrix Plus for BCC.
593 |
594 | 2.5.93 - 2nd March 2022
595 | - Added lots of flight times. There are now 1980 flight times stored in Leatrix Plus for BCC.
596 |
597 | 2.5.92 - 23rd February 2022
598 | - Added lots of flight times. There are now 1971 flight times stored in Leatrix Plus for BCC.
599 |
600 | 2.5.91 - 16th February 2022
601 | - For 'Enhance minimap' ('Interface'), added a new setting 'Show who pinged'. It's enabled by default. Note that this does not apply to your own pings.
602 | - For 'Mute game sounds' ('System'), added 'A'dal'. This will mute A'dal in Shattrath City.
603 | - Added lots of flight times. There are now 1952 flight times stored in Leatrix Plus for BCC.
604 |
605 | 2.5.90 - 9th February 2022
606 | - Added lots of flight times. There are now 1932 flight times stored in Leatrix Plus for BCC.
607 | - Added 'Screech' to 'Mute game sounds' ('System'). This is a spell used by some flying pets.
608 | - Added 'Striders' to 'Mute game sounds' ('System'). This will mute mechanostrider mounts.
609 |
610 | 2.5.89 - 3rd February 2022
611 | - Added lots of flight times. There are now 1908 flight times stored in Leatrix Plus for BCC.
612 |
613 | 2.5.88 - 26th January 2022
614 | - If 'Automate quests' ('Automation') is enabled, you can now set the override key to be shift, alt or control. Repeatable battlemaster and cloth quartermaster quests no longer require the alt key to be held.
615 | - For 'Mute game sounds' ('System'), the 'Chimes' setting will now mute the Ironforge hourly horn sound.
616 | - Added lots of flight times. Keep reporting. Every little helps. There are now 1868 flight times stored in Leatrix Plus for BCC.
617 | - If you use ItemRack alongside 'Enhance minimap' with 'Combine addon buttons' enabled, the ItemRack minimap button flyout menu will remain visible as long as the mouse pointer is over it.
618 |
619 | 2.5.87 - 19th January 2022
620 | - Fixed the textures for 'Enhance professions' ('Interface').
621 |
622 | 2.5.86 - 19th January 2022
623 | - Updated for game version 2.5.3.
624 | - Added lots more flight times. Keep reporting until there's none left to report. Some minor tweaks to flight point tooltips.
625 | - The 'Faster auto loot' option ('System') will now work with TSM Destroy. You no longer need to disable faster auto loot when using TSM Destroy.
626 |
627 | 2.5.85 - 13th January 2022
628 | - Added lots of flight times. Keep reporting.
629 |
630 | 2.5.84 - 9th January 2022
631 | - For 'Show flight times' ('Interface'), added support for odd flight points (such as Altar of Sha'tar).
632 |
633 | 2.5.83 - 9th January 2022
634 | - The 'Show flight times' option ('Interface') now supports multiple flight routes to the same destination. So it will work regardless of how many flight points you have unlocked. Due to the code changes to include multiple routes, some of the submitted flight times could not be added. Please report them again when you see the report window again.
635 |
636 | 2.5.82 - 5th January 2022
637 | - The 'Show flight times' option ('Interface') now works with any locale without the need for translations. Added lots of missing flight times. After landing from a flight, if the saved flight time is missing or incorrect (greater than 5 second difference), you will see a copy-friendly window with the flight details making it very easy for you to report the missing or incorrect flight time.
638 |
639 | 2.5.81 - 29th December 2021
640 | - You need to restart your game client after installing this update.
641 | - Added a new setting 'Show flight times' ('Interface'). If enabled, a flight time progress bar will be shown when you take a flight and travel times will be shown in the flight point tooltips (note: only for flight times that Leatrix Plus is aware of). For the time being, all flight times (whether known or not) will be printed in chat when you land so that you can report inaccurate or missing flight times (there are some of both). If a flight ends before or after the progress bar has reached zero, report the flight time printed in chat and the times will be updated. You can report at github.com/leatrix or you can send a message to leatrix on CurseForge. If you're using the 'Recent chat window' setting ('Chat'), you can use that to copy the text from chat for your report. For non-English locales, the progress bar and flight times will only be shown if the start and end flight point names have been translated. If you want to help with translations to solve that issue, send a message to leatrix on CurseForge.
642 |
643 | 2.5.80 - 22nd December 2021
644 | - If 'Combine addon buttons' ('Enhance minimap)' is enabled, addon button tooltips will no longer show together with game tooltips. Non-standard buttons are now always replaced with LibDBIcon buttons so the 'Replace non-standard buttons' setting has been removed. In addition, non-standard buttons will (for now) be described as such in the button tooltips. You can now set the cluster scale again (note that adjusting the cluster scale has side effects so read the setting tooltip). Method Raid Tools now has a standard LibDBIcon (though it's based on the original, custom icon). If you come across any addons that aren't given a standard icon, please let me know.
645 |
646 | 2.5.79 - 18th December 2021
647 | - If 'Combine addon buttons' ('Enhance minimap)' is enabled, addon button tooltips will now be shown under the minimap so as not to clutter the button frame. This should work with most addon buttons. Fixed a potential issue with addons using non-standard buttons that may have been caused Leatrix Plus not to load correctly.
648 |
649 | 2.5.78 - 17th December 2021
650 | - Fixed a situational issue with 'Replace non-standard buttons' for 'Enhance minimap' ('Interface') creating unintended minimap buttons.
651 |
652 | 2.5.75 - 16th December 2021
653 | - Added a new option 'Filter chat messages' ('Chat'). If enabled, you can block spell links in combat (useful for blocking spell interrupt spam), drunken spam (if it doesn't apply to your character) and duel victory and retreat spam (if you didn't take part in the duel).
654 | - Added a new slider setting to 'Enhance minimap' ('Interface') called 'Square size' which allows you to adjust the square minimap size (up to 400%). While the scale slider makes the minimap and all the elements bigger, the size slider makes the minimap bigger but keeps all the elements the same size. If you are using the square minimap, you can use either the scale slider or the size slider or a combination of the two. The configuration panel reset button will not reset settings that require a UI reload. Added a new setting called 'Replace non-standard buttons'. Most addons use the standard LibDBIcon library for their minimap buttons but a few addons use non-standard buttons instead. The new setting will replace any non-standard buttons with LibDBIcon buttons. This will solve the problems with non-standard buttons such as not hiding automatically, not following the minimap shape and not being placed in the button frame. Added a new setting called 'Exclude BugSack' which will keep the BugSack addon minimap button visible if you have BugSack installed and the minimap button enabled.
655 | - Updated the tooltips for 'Manage frames' and 'Manage focus' ('Frames').
656 |
657 | 2.5.74 - 9th December 2021
658 | - Fixed a few default UI minimap button locations for 'Enhance minimap' ('Interface') when using the square minimap.
659 |
660 | 2.5.73 - 8th December 2021
661 | - Added some new settings to 'Enhance minimap' ('Interface'). 'Square minimap' changes the minimap shape to a square. 'Hide addon buttons' hides addon buttons automatically while the pointer is not over the minimap. 'Combine addon buttons' combines all addon buttons into a single frame which you can toggle by right-clicking the minimap (cannot be used with 'Hide addon buttons'). You can always drag the minimap now while holding the alt key so the 'Unlock map frame' setting has been removed. The maximum minimap scale is now 400% (was 200%).
662 | - If 'Automate gossip' ('Automation') is enabled, battlemaster gossip will now be automatically selected and the battleground name will be shown at the top of the battlefield instance window.
663 | - Fixed a minor issue with 'Enhance professions' ('Interface') which caused the craft frame filter dropdown menu to not be positioned correctly. Also fixed a minor issue with the default UI which caused beast training points to show in profession frames (this bug is not caused by addons).
664 |
665 | 2.5.72 - 2nd December 2021
666 | - If 'Enhance minimap' is enabled, you can toggle the zone text bar once again using a setting in the configuration panel. Added a new setting to toggle the world map button.
667 |
668 | 2.5.71 - 2nd December 2021
669 | - To address issues with minimap cluster scaling (which have been present for a long time), the 'Scale entire cluster' setting has been removed. Also fixed a minor issue causing the minimap toggle button to be visible.
670 |
671 | 2.5.70 - 1st December 2021
672 | - The 'Enhance minimap' option ('Interface') has two new settings. The first is 'Unlock minimap'. If enabled, you can hold alt and drag the minimap to move it. The second is 'Scale entire cluster'. If checked, the scale slider will apply to the entire minimap cluster. If unchecked, the scale slider will only apply to the minimap. The zone text bar is now always hidden.
673 | - The 'Show Wowhead links' option ('Interface') now has a configuration panel allowing you to choose whether links should go directly to the comments section.
674 | - The 'Recent chat window' option ('Chat') will now use the same font and size as the chat window. Fixed an issue which sometimes caused the recent chat window font size to be smaller than intended.
675 | - Fixed a minor issue with 'Automate quests' ('Automation') which caused Mark of Sargeras and Sunfury Signet reputation turn-ins to not be automated if you only had one of either item in your bags.
676 |
677 | 2.5.68 - 17th November 2021
678 | - Added an animation slider setting to 'Enhance dressup' ('Interface').
679 | - Added a configuration panel for 'Accept resurrection' ('Automation') so you can choose whether combat resurrection should be excluded.
680 | - Added a 'Guild' setting to the 'Social' page which allows you to choose whether the 'Social' page options should apply to guild members or not (previously, 'Social' page options always applied to guild members).
681 | - Added 'Yawns' to 'Mute game sounds' ('System'). If checked, yawns from hunter pet cats will be muted.
682 |
683 | 2.5.67 - 10th November 2021
684 | - Added a new setting to 'Enhance dressup' ('Interface') which adds item buttons to the dressing room. Added a 'Show me' button and a button to toggle the new item buttons. The 'Remove tabard' button is now part of the new item buttons.
685 | - Added a new option 'Hide action button text' ('Text'). If enabled, macro and keybind text will not be shown on action buttons.
686 |
687 | 2.5.66 - 4th November 2021
688 | - Added a new option 'More font sizes' ('Chat'). If enabled, additional font sizes will be available in the chat frame font size menu.
689 |
690 | 2.5.64 - 29th October 2021
691 | - Replaced packager to fix CurseForge App issue.
692 |
693 | 2.5.62 - 28th October 2021
694 | - The 'Enhance dressup' option ('Interface') now shows a 'Show target model' button in the dressup window. Requires a player target. The nude and tabard buttons will apply whichever model is showing. Transformations such as shapeshift and appearance change will be removed. The 'Reset' button will set the model back to your character. Dressup window buttons are now much smaller and have tooltips.
695 | - The 'Enhance quest log' option now has a setting to toggle showing quest levels.
696 | - The 'Sell junk automatically' option now has a setting to toggle the vendor summary in chat.
697 | - The 'Repair automatically' option now has a setting to toggle the repair summary in chat.
698 | - Fixed a minor issue with 'Recent chat window' ('Chat') which caused the title bar text to overlap the message count for some locales.
699 | - Fixed a few locale issues with invalid translations.
700 |
701 | 2.5.61 - 21st October 2021
702 | - Added 'Chimes' to 'Mute game sounds' ('System'). If enabled, clock hourly chimes will be muted.
703 | - The 'Enhance tooltip' option ('Interface') now scales Total RP 3 tooltips.
704 |
705 | 2.5.59 - 13th October 2021
706 | - Added a new option called 'Show druid power bar' ('Interface'). If enabled, a druid power bar will be shown in the player frame while you are playing as a shapeshifted druid.
707 | - The 'Dismount me' option ('System') will now work with Shaman Ghost Wolf. Added a new setting to show an unshift buton on the flight map when playing as a shapeshifted druid or shaman. The setting to dismount when selecting a flight destination has been removed as that functionality is now built into the game.
708 | - The 'Release in PvP' option ('Automation') now has a configuration panel that lets you exclude Alterac Valley. You can also set a delay timer before you are automatically released. You can hold shift as the timer is ending to cancel automatic release.
709 |
710 | 2.5.58 - 29th September 2021
711 | - Quests from Soridormi and Arazmodu (The Scale of the Sands, Caverns of Time) will no longer be selected, accepted or turned-in automatically.
712 |
713 | 2.5.55 - 16th September 2021
714 | - Updated for World of Warcraft patch 2.5.2.
715 | - Added guild bank support for 'Repair automatically ('Automation').
716 |
717 | 2.5.53 - 19th August 2021
718 | - Fixed issue caused by malformed translations.
719 |
720 | 2.5.48 - 5th August 2021
721 | - Added 'Gyrocopters' to 'Mute game sounds' ('System'). This includes the engineering flying machine mounts.
722 |
723 | 2.5.47 - 22nd July 2021
724 | - Improved quest automation for Aldor/Scryer repeatable reputation quests in Shattrath.
725 |
726 | 2.5.44 - 30th June 2021
727 | - Updated Classic Profession Filter addon compatibility fixes for 'Enhance professions' ('Interface') to support the latest version. If you use Classic Profession Filter addon, you need to update it.
728 |
729 | 2.5.43 - 24th June 2021
730 | - Fixed a situational issue with 'Faster auto loot' ('System') which caused the loot window to sometimes remain open if you are in a group using free for all loot with multiple group members looting the same corpse at the same time.
731 | - Updated the tooltip for 'Invite from whispers' ('Social') to highlight that an invite will not sent in response a Battle.net message if the invite recipient is showing offline.
732 |
733 | 2.5.42 - 16th June 2021
734 | - Added a new setting to 'Enhance tooltip' ('Interface') which allows you to see guild ranks for players who are not in your guild. It's disabled by default.
735 | - Fixed a compatibility issue with 'Enhance professions' and 'Classic profession filter' addon.
736 | - Removed the 'Class icon portraits' option ('Frames') due to performance issues. There are standalone addons available for this feature if you need it.
737 |
738 | 2.5.41 - 9th June 2021
739 | - Added a note about framerate to the 'Class icon portraits' ('Frames') tooltip.
740 |
741 | 2.5.39 - 6th June 2021
742 | - For 'Automate quests' ('Automation'), quests from Archmage Leryda (The Violet Eye) will no longer be selected, accepted or turned-in automatically.
743 | - Updated translations.
744 |
745 | 2.5.38 - 2nd June 2021
746 | - For 'Automate quests' ('Automation'), quests from BCC escort quest givers will not be accepted automatically. This applies to Ranger Lilatha (Escape from the Catacombs), Anchorite Truuen (Tomb of the Lightbringer), Magwin (A Cry For Help), Fhwoor (Fhwoor Smash!), Kayra Longmane (Escape from Umbrafen), Mag'har Captive (The Totem of Kar'dash, Horde), Kurenai Captive (The Totem of Kar'dash, Alliance), Isla Starmane (Escape from Firewing Point!), Maxx A. Million Mk. V (Mark V is Alive!), Cryo-Engineer Sha'heen (Someone Else's Hard Work Pays Off), Drijya (Sabotage the Warp-Gate!), Bessy (When the Cows Come Home), Image of Commander Ameer (Delivering the Message), Captured Protectorate Vanguard (Escape from the Staging Grounds), Earthmender Wilda (Escape from Coilskar Cistern), Skywing (Skywing), Chief Archaeologist Letoll (Digging Through Bones) and Skyguard Prisoner (Escape from Skettis). Many thanks to drejjmit for supplying the list.
747 | - The 'Manage widget' configuration panel will now show a warning if Titan Panel is preventing the widget frame position from saving correctly..
748 | - The 'Dismount automatically' option ('System') is now called 'Dismount me'. It now has a configuration panel so you can set which dismount rules you want to use including a new setting that dismounts you when you instruct a flight master to open the flight map.
749 |
750 | 2.5.37 - 26th May 2021
751 | - The 'Dismount automatically' option ('System') will no longer dismount you if you are out of range of your target.
752 |
753 | 2.5.33 - 21st May 2021
754 | - The 'Stand and dismount' option is now called 'Dismount automatically'. Most of what it did before is now included in the game. So now, the option will dismount you when you select a flight location or when you attempt to cast a spell regardless of whether you have enough resource to cast or whether the target is in range.
755 | - Fixed an issue with 'Recent chat window' which caused a Lua error when attempting to show Battle.net messages.
756 | - The 'Remove raid restrictions' option has been removed since it's not needed for Burning Crusade Classic.
757 |
758 | 2.5.32 - 17th May 2021
759 | - Welcome to Burning Crusade Classic.
760 | - Leatrix Plus for Burning Crusade Classic is based on Leatrix Plus for World of Warcraft Classic with the changes listed below.
761 | - Added 'Manage focus' ('Frames'), 'Mute game sounds' ('System') and 'Remove raid restrictions' ('System').
762 | - Added Burning Crusade zones, dungeons, titles, credits, narration tracks and movies to 'Media'.
763 | - The 'Enhance dressup' option ('Interface') now adds pan (right-button), zoom (mousewheel) and toggle attributes (middle-click) functionality to the character frame, dressup frame and inspect frame.
764 | - The 'Class colored frames' option ('Frames') now colors the focus frame too.
765 | - Thanks to Blizzard for the day one beta access allowing me to bring Leatrix addons to the BCC community.
766 |
--------------------------------------------------------------------------------
/Changelog and Notes.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | --------------------------------------------------------------------------------
4 | -- What needs to be tested:
5 | --------------------------------------------------------------------------------
6 |
7 | Manage Debuffs - if debuff button reset working fine.
8 |
9 | ElvUI - Need to see what disabled for when ElvUI is loaded. May be something is not made in 3.3.5 ElvUI backport, but still disabled.
10 |
11 | Auto-Ress in BG - With option "Exclude Alterac" will it not auto-res in Alterac BG ?
12 |
13 | Accept Ress - Will it ever accept ressurection if ressurecter in combat ?
14 |
15 | QueueTimer - Need to test if timer .After 0.1 is fine in SetupBar func (was 1 second before)
16 |
17 | LibCandyBar - Will it be conflicting with other addons that use 3.3.5 version of CandyBar lib? Like with BigWigs?
18 |
19 | ShowFlightPaths - Will StopLandingEvent work correctly on summon, BG/arena accept
20 |
21 | Manage Debuffs - Test IT in fight.
22 |
23 | --------------------------------------------------------------------------------
24 | -- What i plan to do in near future list from easy to hard
25 | --------------------------------------------------------------------------------
26 |
27 | Auto-Spirit Res. - Add function to auto ressurect once interracted with Spirit Healer
28 |
29 | ManageBuff - Fix the weapon enhancements not scale correct!
30 |
31 | Faster Loot - Make the config option to change size of error frame with test messages
32 |
33 | World Map - Reveal Map
34 |
35 | Ready Timer - for RDF/BG/ Arena
36 |
37 | Action Bar - Make buttons full red if out of range (instead of blizzard red text only)
38 |
39 | MinimapEnhance - Replace non-standard buttons option! Aka those that don't use LibDBIcon, or may be just set size of them ?
40 |
41 | --------------------------------------------------------------------------------
42 | -- Stuff to-do:
43 | --------------------------------------------------------------------------------
44 |
45 |
46 | Manage Debuffs - make it somehow to be movable even if there are no debuffs shown.
47 |
48 | Media - Need to fix Movies, to be playable.
49 |
50 | Chat History - (needs fixing) make sure it can be working with more than 128 lines correctly.
51 |
52 | CombineAddonButtons - Make the toggle-mode button, that appears on frame TOPRIGHT hover to change the mode of frame
53 | (Modes: 1 - Always Shown, 2 - Shown only on Minimap or this frame HOVER)
54 |
55 | MinimapEnhance - MinimapNoScale and MinimapSize options needs fixing. For now moved them 10000px out of the GUI.
56 |
57 | HideErrorMessages - Add locales for "Requires", for herbalism,mining,lockpicking skill requirements.
58 |
59 | RestoreChatHistory - Fix Channel colors.
60 |
61 | RecentChatWindow - Need to fix channel colors.
62 |
63 | AutomateQuests - Improve Quest Accept conditions (aka skip welcome window and block shared aka escort quests)
64 |
65 | Chat - Copy chat links
66 |
67 | NPCLink - need to retrieve mob id by mouseover tooltip
68 |
69 | AutoGossip - Add https://wowhead.com/wotlk/search?q=Surristrasz to ignore the dialog auto-confirm.
70 |
71 |
72 | --------------------------------------------------------------------------------
73 | -- Not sure if need to do yet:
74 | --------------------------------------------------------------------------------
75 |
76 | HideMiniButtons - Make a copy edit box with a link to "guide" for Frame Stack
77 |
78 | AutoGossip - Add user defined NPCID to automate gossipX (any option)
79 |
80 | AutoGossip - Add ability to automate gossip cycles. (if after first gossip need to select more gossips)
81 |
82 | AutoGossip - Need to make user option to automate inn / trainer .
83 |
84 | Minimap enhance - Add NPC specific tracking icons on minimap.
85 |
86 | Automation - Remove transforms ( Jack-o'-Lantern, Hallowed Wand, Witch, Turkey and Spraybot transformations? etc. )
87 |
88 | Interraction bindings - Add Quest / Vendor Bindings ( e.g. keyboard keys for Inkeepeer: 1 - Set Home, 2 - Vendor; )
89 |
90 | Tooltip - If player has tooltip hidden in combat, make an option to toggle tooltip if "Alt" key is hold.
91 |
92 | HideChatButtons - Add Configuration to enable certain chat buttons.
93 |
94 | GUI - FIX the width for long strings https://cdn.discordapp.com/attachments/766767499987386402/1119067878885437531/wow_otLn4jPKyo.jpg
95 |
96 | Tooltip - Add a number of skill requirement to the tooltip to gather herb,mine node, when mouseovering it.
97 |
98 | Minimap - Zone Text Bar should toggle the minimap.
99 |
100 | Minimap - Tracking button animation fade-in-out.
101 |
102 | Minimap - distance of buttons OnEnter alpha trigger user option?
103 |
104 |
105 |
106 | Minimap - add alt+shift click to reset position of minimap.
107 |
108 | EnhanceDressup - Make clicks, wheel, to rotate, pan, zoom.
109 |
110 | AutomateGossip - Give options to enable hearthstone/ trainer automation
111 |
112 | DurabilityStatus - change color of button based on total durability status.
113 |
114 | CopyChat - Clear Chat Button.
115 |
116 | BagSearch - add highlight item.
117 |
118 | BagSearch - improve look of close button.
119 |
120 | BagSearch - add bank frame.
121 |
122 | BagSearch - position tooltip nicely.
123 |
124 | BagSearch - add button with menu to highlight certain item categories: herb, quest items, grey items etc...
125 |
126 | BagSearch - add slash commands.
127 |
128 | QueueTimer - add user option to disable sound.
129 |
130 | Core - Fix ToggleZygor function
131 |
132 | BagSearch - if item is clicked then remove focus from search
133 |
134 | FasterLoot - add "lightweight" version of faster loot, in case someone doesn't want fastest version or it doesnt work properly on his server.
135 |
136 |
137 |
138 | A feature that would automatically show you the location of the nearest flight master.
139 | A feature that would automatically show you the location of the nearest mailbox.
140 |
141 | Right Click totems to destroy them
142 |
143 | Show best gold value for quest reward.
144 |
145 | Open all bag user config option at merchant.
146 |
147 |
148 | UnitFrames - make them movable with Shift+Ctrl held.
149 |
150 | UnitFrames - make them auto-hidden while not in combat (but full mana or health, conditions)
151 |
152 | Chat - Clear Chat button / dropdown.
153 |
154 | Hide Error text, permit user to enter partial to not un-hide some error text.
155 | -(For example arena ready crystal red error shows how many players ready.)
156 | - You are in shapeshift,
157 |
158 |
159 | AutoSellJunk - Make user able to shift click the item to add to "keep list"
160 |
161 | AutoSellJunk - Make user be able to alt-ctrl (or other bind) click on an item in bag to sell the item on next vendor visit.
162 |
163 | AutoSellJunk - The editbox must be scrollable.
164 |
165 | MinimapEnhance - Add Timer module, just like ShaguTweaks.
166 |
--------------------------------------------------------------------------------
/Leatrix_InputScrollFrameTemplate.xml:
--------------------------------------------------------------------------------
1 |
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 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Leatrix_Plus.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sattva-108/Leatrix_Plus/225cdf45940381c43ac31f348f1600c56eca8fc2/Leatrix_Plus.blp
--------------------------------------------------------------------------------
/Leatrix_Plus.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 30300
2 |
3 | ## Title: |cff33ffccLeatrix |cffffffffPlus |cffff5555[ALPHA]
4 | ## Title-zhCN: Leatrix Plus [|cffeda55f功能百宝箱|r]
5 | ## Title-zhTW: Leatrix Plus [|cffeda55f功能百寶箱|r]
6 |
7 | ## Notes: Quality of life addon.
8 | ## Notes-zhCN: 哆啦A梦的百宝袋
9 | ## Notes-zhTW: 哆啦A夢的百寶袋
10 |
11 | ## Version: 3.3.5 (3.0.131)
12 | ## Author: Leatrix
13 | ## SavedVariables: LeaPlusDB
14 |
15 | ## OptionalDeps: Leatrix_enUS, ElvUI, Glass
16 |
17 | ## X-Curse-Project-ID: 463863
18 |
19 | Leatrix_InputScrollFrameTemplate.xml
20 |
21 | # Leatrix_Plus_Library.lua
22 | libs\LibStub\LibStub.lua
23 | libs\LibCompat-1.0\lib.xml
24 | Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
25 | Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua
26 | Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua
27 |
28 |
29 | Leatrix_Plus_Library.lua
30 | Leatrix_Plus_Locale.lua
31 | Leatrix_Plus_Media.lua
32 | Leatrix_Plus_Flight_Alliance.lua
33 | Leatrix_Plus_Flight_Horde.lua
34 | Leatrix_Plus.lua
35 |
--------------------------------------------------------------------------------
/Leatrix_Plus_Wrath.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 30300
2 |
3 | ## Title: |cff33ffccLeatrix |cffffffffPlus |cffff5555[ALPHA]
4 | --## Title-zhCN: Leatrix Plus [|cffeda55f功能百宝箱|r]
5 | --## Title-zhTW: Leatrix Plus [|cffeda55f功能百寶箱|r]
6 |
7 | ## Notes: Quality of life addon.
8 | --## Notes-zhCN: 哆啦A梦的百宝袋
9 | --## Notes-zhTW: 哆啦A夢的百寶袋
10 |
11 | ## Version: 3.0.131
12 | ## Author: Leatrix
13 | ## SavedVariables: LeaPlusDB
14 |
15 | ## OptionalDeps: Leatrix_enUS, ElvUI, Glass
16 |
17 | ## X-Curse-Project-ID: 463863
18 |
19 | libs\LibStub\LibStub.lua
20 | libs\LibCompat-1.0\lib.xml
21 | Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua
22 | Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua
23 | Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua
24 |
25 |
26 | Leatrix_Plus_Library.lua
27 | Leatrix_Plus_Locale.lua
28 | Leatrix_Plus_Media.lua
29 | Leatrix_Plus_Flight_Alliance.lua
30 | Leatrix_Plus_Flight_Horde.lua
31 | Leatrix_Plus.lua
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LeatrixPlus 3.3.5 WoTLK
2 | **Leatrix Plus** includes a ton of features and QoL stuff.
3 |
4 | 
5 |
6 | ## Credit
7 | - Credit to **[Leatrix](https://github.com/leatrix/leatrix)**
8 | - Code from **[LeatrixPlusWoTLK](https://www.curseforge.com/wow/addons/leatrix-plus-cata)**
9 |
10 |
11 | ## Download & Installation
12 |
13 | 1. **[Download](https://github.com/Sattva-108/Leatrix_Plus/releases/latest)** Leatrix_Plus.rar from latest release.
14 | 2. Copy the `"Leatrix_Plus"` folder inside the .zip to `"\Interface\AddOns\"`.
15 | 3. Replace/overwrite any existing files when copying.
16 |
17 |
18 | ## Usage
19 | `/ltp` - chat command to open configure menu for Leatrix Plus.
20 | 1. Enable the mod(s) by selecting them in the Leatrix Plus menu (`/ltp` command).
21 | 2. Note that some options will require a reload to take effect. You will see the `"Reload"` button glowing if you need to reload, simply click it.
22 | 3. Minimap Button `Click` to open configuration menu
23 | 4. Minimap Button `Shift-Click` to reload user interface
24 | 5. Minimap Button `Alt-Click` to toggle error messages visibility
25 |
--------------------------------------------------------------------------------
/assets/ROTATING-MINIMAPARROW.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sattva-108/Leatrix_Plus/225cdf45940381c43ac31f348f1600c56eca8fc2/assets/ROTATING-MINIMAPARROW.blp
--------------------------------------------------------------------------------
/assets/minimapicon.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sattva-108/Leatrix_Plus/225cdf45940381c43ac31f348f1600c56eca8fc2/assets/minimapicon.tga
--------------------------------------------------------------------------------
/assets/ui-guildachievement-parchment-horizontal-desaturated.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sattva-108/Leatrix_Plus/225cdf45940381c43ac31f348f1600c56eca8fc2/assets/ui-guildachievement-parchment-horizontal-desaturated.blp
--------------------------------------------------------------------------------
/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[ $Id: CallbackHandler-1.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ ]]
2 | local MAJOR, MINOR = "CallbackHandler-1.0", 5
3 | local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
4 |
5 | if not CallbackHandler then return end -- No upgrade needed
6 |
7 | local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
8 |
9 | -- Lua APIs
10 | local tconcat = table.concat
11 | local assert, error, loadstring = assert, error, loadstring
12 | local setmetatable, rawset, rawget = setmetatable, rawset, rawget
13 | local next, select, pairs, type, tostring = next, select, pairs, type, tostring
14 |
15 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
16 | -- List them here for Mikk's FindGlobals script
17 | -- GLOBALS: geterrorhandler
18 |
19 | local xpcall = xpcall
20 |
21 | local function errorhandler(err)
22 | return geterrorhandler()(err)
23 | end
24 |
25 | local function CreateDispatcher(argCount)
26 | local code = [[
27 | local next, xpcall, eh = ...
28 |
29 | local method, ARGS
30 | local function call() method(ARGS) end
31 |
32 | local function dispatch(handlers, ...)
33 | local index
34 | index, method = next(handlers)
35 | if not method then return end
36 | local OLD_ARGS = ARGS
37 | ARGS = ...
38 | repeat
39 | xpcall(call, eh)
40 | index, method = next(handlers, index)
41 | until not method
42 | ARGS = OLD_ARGS
43 | end
44 |
45 | return dispatch
46 | ]]
47 |
48 | local ARGS, OLD_ARGS = {}, {}
49 | for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
50 | code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
51 | return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
52 | end
53 |
54 | local Dispatchers = setmetatable({}, {__index=function(self, argCount)
55 | local dispatcher = CreateDispatcher(argCount)
56 | rawset(self, argCount, dispatcher)
57 | return dispatcher
58 | end})
59 |
60 | --------------------------------------------------------------------------
61 | -- CallbackHandler:New
62 | --
63 | -- target - target object to embed public APIs in
64 | -- RegisterName - name of the callback registration API, default "RegisterCallback"
65 | -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
66 | -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
67 |
68 | function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
69 | -- TODO: Remove this after beta has gone out
70 | assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
71 |
72 | RegisterName = RegisterName or "RegisterCallback"
73 | UnregisterName = UnregisterName or "UnregisterCallback"
74 | if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
75 | UnregisterAllName = "UnregisterAllCallbacks"
76 | end
77 |
78 | -- we declare all objects and exported APIs inside this closure to quickly gain access
79 | -- to e.g. function names, the "target" parameter, etc
80 |
81 |
82 | -- Create the registry object
83 | local events = setmetatable({}, meta)
84 | local registry = { recurse=0, events=events }
85 |
86 | -- registry:Fire() - fires the given event/message into the registry
87 | function registry:Fire(eventname, ...)
88 | if not rawget(events, eventname) or not next(events[eventname]) then return end
89 | local oldrecurse = registry.recurse
90 | registry.recurse = oldrecurse + 1
91 |
92 | Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
93 |
94 | registry.recurse = oldrecurse
95 |
96 | if registry.insertQueue and oldrecurse==0 then
97 | -- Something in one of our callbacks wanted to register more callbacks; they got queued
98 | for eventname,callbacks in pairs(registry.insertQueue) do
99 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
100 | for self,func in pairs(callbacks) do
101 | events[eventname][self] = func
102 | -- fire OnUsed callback?
103 | if first and registry.OnUsed then
104 | registry.OnUsed(registry, target, eventname)
105 | first = nil
106 | end
107 | end
108 | end
109 | registry.insertQueue = nil
110 | end
111 | end
112 |
113 | -- Registration of a callback, handles:
114 | -- self["method"], leads to self["method"](self, ...)
115 | -- self with function ref, leads to functionref(...)
116 | -- "addonId" (instead of self) with function ref, leads to functionref(...)
117 | -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
118 | target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
119 | if type(eventname) ~= "string" then
120 | error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
121 | end
122 |
123 | method = method or eventname
124 |
125 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
126 |
127 | if type(method) ~= "string" and type(method) ~= "function" then
128 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
129 | end
130 |
131 | local regfunc
132 |
133 | if type(method) == "string" then
134 | -- self["method"] calling style
135 | if type(self) ~= "table" then
136 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
137 | elseif self==target then
138 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
139 | elseif type(self[method]) ~= "function" then
140 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
141 | end
142 |
143 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
144 | local arg=select(1,...)
145 | regfunc = function(...) self[method](self,arg,...) end
146 | else
147 | regfunc = function(...) self[method](self,...) end
148 | end
149 | else
150 | -- function ref with self=object or self="addonId"
151 | if type(self)~="table" and type(self)~="string" then
152 | error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2)
153 | end
154 |
155 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
156 | local arg=select(1,...)
157 | regfunc = function(...) method(arg,...) end
158 | else
159 | regfunc = method
160 | end
161 | end
162 |
163 |
164 | if events[eventname][self] or registry.recurse<1 then
165 | -- if registry.recurse<1 then
166 | -- we're overwriting an existing entry, or not currently recursing. just set it.
167 | events[eventname][self] = regfunc
168 | -- fire OnUsed callback?
169 | if registry.OnUsed and first then
170 | registry.OnUsed(registry, target, eventname)
171 | end
172 | else
173 | -- we're currently processing a callback in this registry, so delay the registration of this new entry!
174 | -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
175 | registry.insertQueue = registry.insertQueue or setmetatable({},meta)
176 | registry.insertQueue[eventname][self] = regfunc
177 | end
178 | end
179 |
180 | -- Unregister a callback
181 | target[UnregisterName] = function(self, eventname)
182 | if not self or self==target then
183 | error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
184 | end
185 | if type(eventname) ~= "string" then
186 | error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
187 | end
188 | if rawget(events, eventname) and events[eventname][self] then
189 | events[eventname][self] = nil
190 | -- Fire OnUnused callback?
191 | if registry.OnUnused and not next(events[eventname]) then
192 | registry.OnUnused(registry, target, eventname)
193 | end
194 | end
195 | if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
196 | registry.insertQueue[eventname][self] = nil
197 | end
198 | end
199 |
200 | -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
201 | if UnregisterAllName then
202 | target[UnregisterAllName] = function(...)
203 | if select("#",...)<1 then
204 | error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
205 | end
206 | if select("#",...)==1 and ...==target then
207 | error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
208 | end
209 |
210 |
211 | for i=1,select("#",...) do
212 | local self = select(i,...)
213 | if registry.insertQueue then
214 | for eventname, callbacks in pairs(registry.insertQueue) do
215 | if callbacks[self] then
216 | callbacks[self] = nil
217 | end
218 | end
219 | end
220 | for eventname, callbacks in pairs(events) do
221 | if callbacks[self] then
222 | callbacks[self] = nil
223 | -- Fire OnUnused callback?
224 | if registry.OnUnused and not next(callbacks) then
225 | registry.OnUnused(registry, target, eventname)
226 | end
227 | end
228 | end
229 | end
230 | end
231 | end
232 |
233 | return registry
234 | end
235 |
236 |
237 | -- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
238 | -- try to upgrade old implicit embeds since the system is selfcontained and
239 | -- relies on closures to work.
240 |
241 |
--------------------------------------------------------------------------------
/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Backdrop/Backdrop.lua:
--------------------------------------------------------------------------------
1 | BACKDROP_TOOLTIP_8_8_1111 = {
2 | bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
3 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
4 | tile = true,
5 | tileEdge = true,
6 | tileSize = 8,
7 | edgeSize = 8,
8 | insets = {left = 1, right = 1, top = 1, bottom = 1}
9 | }
10 | BACKDROP_TOOLTIP_8_12_1111 = {
11 | bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
12 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
13 | tile = true,
14 | tileEdge = true,
15 | tileSize = 8,
16 | edgeSize = 12,
17 | insets = {left = 1, right = 1, top = 1, bottom = 1}
18 | }
19 | BACKDROP_TOOLTIP_16_16_5555 = {
20 | bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
21 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
22 | tile = true,
23 | tileEdge = true,
24 | tileSize = 16,
25 | edgeSize = 16,
26 | insets = {left = 5, right = 5, top = 5, bottom = 5}
27 | }
28 | BACKDROP_TOOLTIP_12_12_4444 = {
29 | bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
30 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
31 | tile = true,
32 | tileEdge = true,
33 | tileSize = 12,
34 | edgeSize = 12,
35 | insets = {left = 4, right = 4, top = 4, bottom = 4}
36 | }
37 | BACKDROP_TOOLTIP_0_16 = {
38 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
39 | edgeSize = 16,
40 | tileEdge = true
41 | }
42 | BACKDROP_TOOLTIP_0_12_0055 = {
43 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
44 | edgeSize = 12,
45 | tileEdge = true,
46 | insets = {left = 0, right = 0, top = 5, bottom = 5}
47 | }
48 | BACKDROP_TOOLTIP_0_16_5555 = {
49 | edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
50 | edgeSize = 16,
51 | tileEdge = true,
52 | insets = {left = 5, right = 5, top = 5, bottom = 5}
53 | }
54 | BACKDROP_ACHIEVEMENTS_0_64 = {
55 | edgeFile = "Interface\\AchievementFrame\\UI-Achievement-WoodBorder",
56 | edgeSize = 64,
57 | tileEdge = true
58 | }
59 | BACKDROP_ARENA_32_32 = {
60 | bgFile = "Interface\\CharacterFrame\\UI-Party-Background",
61 | edgeFile = "Interface\\ArenaEnemyFrame\\UI-Arena-Border",
62 | tile = true,
63 | tileEdge = true,
64 | tileSize = 32,
65 | edgeSize = 32,
66 | insets = {left = 32, right = 32, top = 32, bottom = 32}
67 | }
68 | BACKDROP_DIALOG_32_32 = {
69 | bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
70 | edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
71 | tile = true,
72 | tileEdge = true,
73 | tileSize = 32,
74 | edgeSize = 32,
75 | insets = {left = 11, right = 12, top = 12, bottom = 11}
76 | }
77 | BACKDROP_GOLD_DIALOG_32_32 = {
78 | bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
79 | edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
80 | tile = true,
81 | tileEdge = true,
82 | tileSize = 32,
83 | edgeSize = 32,
84 | insets = {left = 11, right = 12, top = 12, bottom = 11}
85 | }
86 | BACKDROP_WATERMARK_DIALOG_0_16 = {
87 | edgeFile = "Interface\\DialogFrame\\UI-DialogBox-TestWatermark-Border",
88 | tileEdge = true,
89 | edgeSize = 16
90 | }
91 | BACKDROP_SLIDER_8_8 = {
92 | bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
93 | edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
94 | tile = true,
95 | tileEdge = true,
96 | tileSize = 8,
97 | edgeSize = 8,
98 | insets = {left = 3, right = 3, top = 6, bottom = 6}
99 | }
100 | BACKDROP_PARTY_32_32 = {
101 | bgFile = "Interface\\CharacterFrame\\UI-Party-Background",
102 | edgeFile = "Interface\\CharacterFrame\\UI-Party-Border",
103 | tile = true,
104 | tileEdge = true,
105 | tileSize = 32,
106 | edgeSize = 32,
107 | insets = {left = 32, right = 32, top = 32, bottom = 32}
108 | }
109 | BACKDROP_TOAST_12_12 = {
110 | bgFile = "Interface\\FriendsFrame\\UI-Toast-Background",
111 | edgeFile = "Interface\\FriendsFrame\\UI-Toast-Border",
112 | tile = true,
113 | tileEdge = true,
114 | tileSize = 12,
115 | edgeSize = 12,
116 | insets = {left = 5, right = 5, top = 5, bottom = 5}
117 | }
118 | BACKDROP_CALLOUT_GLOW_0_16 = {
119 | edgeFile = "Interface\\TutorialFrame\\UI-TutorialFrame-CalloutGlow",
120 | edgeSize = 16,
121 | tileEdge = true
122 | }
123 | BACKDROP_CALLOUT_GLOW_0_20 = {
124 | edgeFile = "Interface\\TutorialFrame\\UI-TutorialFrame-CalloutGlow",
125 | edgeSize = 20,
126 | tileEdge = true
127 | }
128 | BACKDROP_GLUE_TOOLTIP_16_16 = {
129 | bgFile = "Interface\\Glues\\Common\\Glue-Tooltip-Background",
130 | edgeFile = "Interface\\Glues\\Common\\Glue-Tooltip-Border",
131 | tile = true,
132 | tileEdge = true,
133 | tileSize = 16,
134 | edgeSize = 16,
135 | insets = {left = 10, right = 5, top = 4, bottom = 9}
136 | }
137 | BACKDROP_GLUE_TOOLTIP_0_16 = {
138 | edgeFile = "Interface\\Glues\\Common\\Glue-Tooltip-Border",
139 | tileEdge = true,
140 | edgeSize = 16
141 | }
142 | BACKDROP_MIXED_TOOLTIP_16_16 = {
143 | bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
144 | edgeFile = "Interface\\Glues\\Common\\Glue-Tooltip-Border",
145 | tile = true,
146 | tileEdge = true,
147 | tileSize = 16,
148 | edgeSize = 16,
149 | insets = {left = 10, right = 5, top = 4, bottom = 9}
150 | }
151 | BACKDROP_TEXT_PANEL_0_16 = {
152 | edgeFile = "Interface\\Glues\\Common\\TextPanel-Border",
153 | tileEdge = true,
154 | edgeSize = 16
155 | }
156 | BackdropTemplateMixin = {}
157 | local coordStart = 0.0625
158 | local coordEnd = 1 - coordStart
159 | local textureUVs = {
160 | -- keys have to match pieceNames in nineSliceSetup table
161 | TopLeftCorner = {
162 | setWidth = true,
163 | setHeight = true,
164 | ULx = 0.5078125,
165 | ULy = coordStart,
166 | LLx = 0.5078125,
167 | LLy = coordEnd,
168 | URx = 0.6171875,
169 | URy = coordStart,
170 | LRx = 0.6171875,
171 | LRy = coordEnd
172 | },
173 | TopRightCorner = {
174 | setWidth = true,
175 | setHeight = true,
176 | ULx = 0.6328125,
177 | ULy = coordStart,
178 | LLx = 0.6328125,
179 | LLy = coordEnd,
180 | URx = 0.7421875,
181 | URy = coordStart,
182 | LRx = 0.7421875,
183 | LRy = coordEnd
184 | },
185 | BottomLeftCorner = {
186 | setWidth = true,
187 | setHeight = true,
188 | ULx = 0.7578125,
189 | ULy = coordStart,
190 | LLx = 0.7578125,
191 | LLy = coordEnd,
192 | URx = 0.8671875,
193 | URy = coordStart,
194 | LRx = 0.8671875,
195 | LRy = coordEnd
196 | },
197 | BottomRightCorner = {
198 | setWidth = true,
199 | setHeight = true,
200 | ULx = 0.8828125,
201 | ULy = coordStart,
202 | LLx = 0.8828125,
203 | LLy = coordEnd,
204 | URx = 0.9921875,
205 | URy = coordStart,
206 | LRx = 0.9921875,
207 | LRy = coordEnd
208 | },
209 | TopEdge = {
210 | setHeight = true,
211 | ULx = 0.2578125,
212 | ULy = "repeatX",
213 | LLx = 0.3671875,
214 | LLy = "repeatX",
215 | URx = 0.2578125,
216 | URy = coordStart,
217 | LRx = 0.3671875,
218 | LRy = coordStart
219 | },
220 | BottomEdge = {
221 | setHeight = true,
222 | ULx = 0.3828125,
223 | ULy = "repeatX",
224 | LLx = 0.4921875,
225 | LLy = "repeatX",
226 | URx = 0.3828125,
227 | URy = coordStart,
228 | LRx = 0.4921875,
229 | LRy = coordStart
230 | },
231 | LeftEdge = {
232 | setWidth = true,
233 | ULx = 0.0078125,
234 | ULy = coordStart,
235 | LLx = 0.0078125,
236 | LLy = "repeatY",
237 | URx = 0.1171875,
238 | URy = coordStart,
239 | LRx = 0.1171875,
240 | LRy = "repeatY"
241 | },
242 | RightEdge = {
243 | setWidth = true,
244 | ULx = 0.1328125,
245 | ULy = coordStart,
246 | LLx = 0.1328125,
247 | LLy = "repeatY",
248 | URx = 0.2421875,
249 | URy = coordStart,
250 | LRx = 0.2421875,
251 | LRy = "repeatY"
252 | },
253 | Center = {ULx = 0, ULy = 0, LLx = 0, LLy = "repeatY", URx = "repeatX", URy = 0, LRx = "repeatX", LRy = "repeatY"}
254 | }
255 | local defaultEdgeSize = 39 -- the old default
256 | function BackdropTemplateMixin:OnBackdropLoaded()
257 | if self.backdropInfo then
258 | -- check for invalid info
259 | if not self.backdropInfo.edgeFile and not self.backdropInfo.bgFile then
260 | self.backdropInfo = nil
261 | return
262 | end
263 | self:ApplyBackdrop()
264 | do
265 | local r, g, b = 1, 1, 1
266 | if self.backdropColor then
267 | r, g, b = self.backdropColor:GetRGB()
268 | end
269 | local a = self.backdropColorAlpha or 1
270 | self:SetBackdropColor(r, g, b, a)
271 | end
272 | do
273 | local r, g, b = 1, 1, 1
274 | if self.backdropBorderColor then
275 | r, g, b = self.backdropBorderColor:GetRGB()
276 | end
277 | local a = self.backdropBorderColorAlpha or 1
278 | self:SetBackdropBorderColor(r, g, b, a)
279 | end
280 | if self.backdropBorderBlendMode then
281 | self:SetBorderBlendMode(self.backdropBorderBlendMode)
282 | end
283 | end
284 | end
285 | function BackdropTemplateMixin:OnBackdropSizeChanged()
286 | if self.backdropInfo then
287 | self:SetupTextureCoordinates()
288 | end
289 | end
290 | function BackdropTemplateMixin:GetEdgeSize()
291 | if self.backdropInfo.edgeSize and self.backdropInfo.edgeSize > 0 then
292 | return self.backdropInfo.edgeSize
293 | else
294 | return defaultEdgeSize
295 | end
296 | end
297 | local function GetBackdropCoordValue(coord, pieceSetup, repeatX, repeatY)
298 | local value = pieceSetup[coord]
299 | if value == "repeatX" then
300 | return repeatX
301 | elseif value == "repeatY" then
302 | return repeatY
303 | else
304 | return value
305 | end
306 | end
307 | local function SetupBackdropTextureCoordinates(region, pieceSetup, repeatX, repeatY)
308 | region:SetTexCoord(
309 | GetBackdropCoordValue("ULx", pieceSetup, repeatX, repeatY),
310 | GetBackdropCoordValue("ULy", pieceSetup, repeatX, repeatY),
311 | GetBackdropCoordValue("LLx", pieceSetup, repeatX, repeatY),
312 | GetBackdropCoordValue("LLy", pieceSetup, repeatX, repeatY),
313 | GetBackdropCoordValue("URx", pieceSetup, repeatX, repeatY),
314 | GetBackdropCoordValue("URy", pieceSetup, repeatX, repeatY),
315 | GetBackdropCoordValue("LRx", pieceSetup, repeatX, repeatY),
316 | GetBackdropCoordValue("LRy", pieceSetup, repeatX, repeatY)
317 | )
318 | end
319 | function BackdropTemplateMixin:SetupTextureCoordinates()
320 | local width = self:GetWidth()
321 | local height = self:GetHeight()
322 | local effectiveScale = self:GetEffectiveScale()
323 | local edgeSize = self:GetEdgeSize()
324 | local edgeRepeatX = max(0, (width / edgeSize) * effectiveScale - 2 - coordStart)
325 | local edgeRepeatY = max(0, (height / edgeSize) * effectiveScale - 2 - coordStart)
326 | for pieceName, pieceSetup in pairs(textureUVs) do
327 | local region = self[pieceName]
328 | if region then
329 | if pieceName == "Center" then
330 | local repeatX = 1
331 | local repeatY = 1
332 | if self.backdropInfo.tile then
333 | local divisor = self.backdropInfo.tileSize
334 | if not divisor or divisor == 0 then
335 | divisor = edgeSize
336 | end
337 | if divisor ~= 0 then
338 | repeatX = (width / divisor) * effectiveScale
339 | repeatY = (height / divisor) * effectiveScale
340 | end
341 | end
342 | SetupBackdropTextureCoordinates(region, pieceSetup, repeatX, repeatY)
343 | else
344 | SetupBackdropTextureCoordinates(region, pieceSetup, edgeRepeatX, edgeRepeatY)
345 | end
346 | end
347 | end
348 | end
349 | function BackdropTemplateMixin:SetupPieceVisuals(piece, setupInfo, pieceLayout)
350 | local textureInfo = textureUVs[setupInfo.pieceName]
351 | local tileVerts = false
352 | local file
353 | if setupInfo.pieceName == "Center" then
354 | file = self.backdropInfo.bgFile
355 | tileVerts = self.backdropInfo.tile
356 | else
357 | if self.backdropInfo.tileEdge ~= false then
358 | tileVerts = true
359 | end
360 | file = self.backdropInfo.edgeFile
361 | end
362 | piece:SetTexture(file, tileVerts, tileVerts)
363 | local cornerWidth = textureInfo.setWidth and self:GetEdgeSize() or 0
364 | local cornerHeight = textureInfo.setHeight and self:GetEdgeSize() or 0
365 | piece:SetSize(cornerWidth, cornerHeight)
366 | end
367 | function BackdropTemplateMixin:SetBorderBlendMode(blendMode)
368 | if not self.backdropInfo then
369 | return
370 | end
371 | for pieceName in pairs(textureUVs) do
372 | local region = self[pieceName]
373 | if region and pieceName ~= "Center" then
374 | region:SetBlendMode(blendMode)
375 | end
376 | end
377 | end
378 | function BackdropTemplateMixin:HasBackdropInfo(backdropInfo)
379 | return self.backdropInfo == backdropInfo
380 | end
381 | function BackdropTemplateMixin:ClearBackdrop()
382 | if self.backdropInfo then
383 | for pieceName in pairs(textureUVs) do
384 | local region = self[pieceName]
385 | if region then
386 | region:SetTexture(nil)
387 | end
388 | end
389 | self.backdropInfo = nil
390 | end
391 | end
392 | function BackdropTemplateMixin:ApplyBackdrop()
393 | local x, y, x1, y1 = 0, 0, 0, 0
394 | if self.backdropInfo.bgFile then
395 | local edgeSize = self:GetEdgeSize()
396 | x = -edgeSize
397 | y = edgeSize
398 | x1 = edgeSize
399 | y1 = -edgeSize
400 | local insets = self.backdropInfo.insets
401 | if insets then
402 | x = x + (insets.left or 0)
403 | y = y - (insets.top or 0)
404 | x1 = x1 - (insets.right or 0)
405 | y1 = y1 + (insets.bottom or 0)
406 | end
407 | end
408 | local layout = {
409 | TopLeftCorner = {},
410 | TopRightCorner = {},
411 | BottomLeftCorner = {},
412 | BottomRightCorner = {},
413 | TopEdge = {},
414 | BottomEdge = {},
415 | LeftEdge = {},
416 | RightEdge = {},
417 | Center = {layer = "BACKGROUND", x = x, y = y, x1 = x1, y1 = y1},
418 | setupPieceVisualsFunction = BackdropTemplateMixin.SetupPieceVisuals
419 | }
420 | NineSliceUtil.ApplyLayout(self, layout)
421 | self:SetBackdropColor(1, 1, 1, 1)
422 | self:SetBackdropBorderColor(1, 1, 1, 1)
423 | self:SetupTextureCoordinates()
424 | end
425 | -- backwards compatibility API starts here
426 | function BackdropTemplateMixin:SetBackdrop(backdropInfo)
427 | if backdropInfo then
428 | if self:HasBackdropInfo(backdropInfo) then
429 | return
430 | end
431 | if not backdropInfo.edgeFile and not backdropInfo.bgFile then
432 | self:ClearBackdrop()
433 | return
434 | end
435 | self.backdropInfo = backdropInfo
436 | self:ApplyBackdrop()
437 | else
438 | self:ClearBackdrop()
439 | end
440 | end
441 | function BackdropTemplateMixin:GetBackdrop()
442 | if self.backdropInfo then
443 | -- make a copy because it will be altered to match old API output
444 | local backdropInfo = CopyTable(self.backdropInfo)
445 | -- fill in defaults
446 | if not backdropInfo.bgFile then
447 | backdropInfo.bgFile = ""
448 | end
449 | if not backdropInfo.edgeFile then
450 | backdropInfo.edgeFile = ""
451 | end
452 | if backdropInfo.tile == nil then
453 | backdropInfo.tile = false
454 | end
455 | if backdropInfo.tileSize == nil then
456 | backdropInfo.tileSize = 0
457 | end
458 | if backdropInfo.tileEdge == nil then
459 | backdropInfo.tileEdge = true
460 | end
461 | if not backdropInfo.edgeSize then
462 | backdropInfo.edgeSize = self:GetEdgeSize()
463 | end
464 | if not backdropInfo.insets then
465 | backdropInfo.insets = {}
466 | end
467 | if not backdropInfo.insets.left then
468 | backdropInfo.insets.left = 0
469 | end
470 | if not backdropInfo.insets.right then
471 | backdropInfo.insets.right = 0
472 | end
473 | if not backdropInfo.insets.top then
474 | backdropInfo.insets.top = 0
475 | end
476 | if not backdropInfo.insets.bottom then
477 | backdropInfo.insets.bottom = 0
478 | end
479 | return backdropInfo
480 | end
481 | return nil
482 | end
483 | function BackdropTemplateMixin:GetBackdropColor()
484 | if not self.backdropInfo then
485 | return
486 | end
487 | if self.Center then
488 | return self.Center:GetVertexColor()
489 | end
490 | end
491 | function BackdropTemplateMixin:SetBackdropColor(r, g, b, a)
492 | if not self.backdropInfo then
493 | -- Ideally this would throw an error here but the old API just failed silently
494 | return
495 | end
496 | if self.Center then
497 | self.Center:SetVertexColor(r, g, b, a or 1)
498 | end
499 | end
500 | function BackdropTemplateMixin:GetBackdropBorderColor()
501 | if not self.backdropInfo then
502 | return
503 | end
504 | -- return the vertex color of any valid region
505 | for pieceName in pairs(textureUVs) do
506 | local region = self[pieceName]
507 | if region and pieceName ~= "Center" then
508 | return region:GetVertexColor()
509 | end
510 | end
511 | end
512 | function BackdropTemplateMixin:SetBackdropBorderColor(r, g, b, a)
513 | if not self.backdropInfo then
514 | -- Ideally this would throw an error here but the old API just failed silently
515 | return
516 | end
517 | for pieceName in pairs(textureUVs) do
518 | local region = self[pieceName]
519 | if region and pieceName ~= "Center" then
520 | region:SetVertexColor(r, g, b, a or 1)
521 | end
522 | end
523 | end
524 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Backdrop/Backdrop.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/LibCompat-1.0.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- **LibCompat-1.0** provided few handy functions that can be embed to addons.
3 | -- This library was originally created for Skada as of 1.8.50.
4 | -- @author: Kader B (https://github.com/bkader)
5 | --
6 |
7 | local MAJOR, MINOR = "LibCompat-1.0", 16
8 | local LibCompat, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
9 | if not LibCompat then return end
10 |
11 | LibCompat.embeds = LibCompat.embeds or {}
12 |
13 | local pairs, ipairs, select, type = pairs, ipairs, select, type
14 | local tinsert, tremove, tconcat, wipe = table.insert, table.remove, table.concat, wipe
15 | local floor, ceil, max = math.floor, math.ceil, math.max
16 | local setmetatable, format = setmetatable, string.format
17 | local CreateFrame = CreateFrame
18 |
19 | -------------------------------------------------------------------------------
20 |
21 | do
22 | local tostring = tostring
23 |
24 | local tmp = {}
25 | local function Print(self, frame, ...)
26 | local n = 0
27 | if self ~= LibCompat then
28 | n = n + 1
29 | tmp[n] = "|cff33ff99" .. tostring(self) .. "|r:"
30 | end
31 | for i = 1, select("#", ...) do
32 | n = n + 1
33 | tmp[n] = tostring(select(i, ...))
34 | end
35 | frame:AddMessage(tconcat(tmp, " ", 1, n))
36 | end
37 |
38 | function LibCompat:Print(...)
39 | local frame = ...
40 | if type(frame) == "table" and frame.AddMessage then
41 | return Print(self, frame, select(2, ...))
42 | end
43 | return Print(self, DEFAULT_CHAT_FRAME, ...)
44 | end
45 |
46 | function LibCompat:Printf(...)
47 | local frame = ...
48 | if type(frame) == "table" and frame.AddMessage then
49 | return Print(self, frame, format(select(2, ...)))
50 | else
51 | return Print(self, DEFAULT_CHAT_FRAME, format(...))
52 | end
53 | end
54 | end
55 |
56 | -------------------------------------------------------------------------------
57 |
58 | do
59 | local pcall = pcall
60 |
61 | local function DispatchError(err)
62 | print("|cffff9900Error|r:" .. (err or ""))
63 | end
64 |
65 | function LibCompat.QuickDispatch(func, ...)
66 | if type(func) ~= "function" then
67 | return
68 | end
69 | local ok, err = pcall(func, ...)
70 | if not ok then
71 | DispatchError(err)
72 | return
73 | end
74 | return true
75 | end
76 | end
77 |
78 | -------------------------------------------------------------------------------
79 |
80 | do
81 | local function SafePack(...)
82 | local tbl = {...}
83 | tbl.n = select("#", ...)
84 | return tbl
85 | end
86 |
87 | local function SafeUnpack(tbl)
88 | return unpack(tbl, 1, tbl.n)
89 | end
90 |
91 | local function tLength(tbl)
92 | local len = 0
93 | for _ in pairs(tbl) do
94 | len = len + 1
95 | end
96 | return len
97 | end
98 |
99 | -- copies a table from another
100 | local function tCopy(to, from, ...)
101 | for k, v in pairs(from) do
102 | local skip = false
103 | if ... then
104 | for i, j in ipairs(...) do
105 | if j == k then
106 | skip = true
107 | break
108 | end
109 | end
110 | end
111 | if not skip then
112 | if type(v) == "table" then
113 | to[k] = {}
114 | tCopy(to[k], v, ...)
115 | else
116 | to[k] = v
117 | end
118 | end
119 | end
120 | end
121 |
122 | local function tAppendAll(tbl, elems)
123 | for _, elem in ipairs(elems) do
124 | tinsert(tbl, elem)
125 | end
126 | end
127 |
128 | local weaktable = {__mode = "v"}
129 | local function WeakTable(t)
130 | return setmetatable(wipe(t or {}), weaktable)
131 | end
132 |
133 | -- Shamelessly copied from Omen - thanks!
134 | local tablePool = setmetatable({}, {__mode = "kv"})
135 |
136 | -- get a new table
137 | local function newTable(...)
138 | local t = next(tablePool)
139 | if t then
140 | tablePool[t] = nil
141 | for i = 1, select("#", ...) do
142 | t[i] = select(i, ...)
143 | end
144 | return t
145 | else
146 | return {...}
147 | end
148 | end
149 |
150 | -- delete table and return to pool
151 | local function delTable(t, recursive)
152 | if type(t) == "table" then
153 | for k, v in pairs(t) do
154 | if recursive and type(v) == "table" then
155 | delTable(v, recursive)
156 | end
157 | t[k] = nil
158 | end
159 | t[true] = true
160 | t[true] = nil
161 | setmetatable(t, nil)
162 | tablePool[t] = true
163 | end
164 | return nil
165 | end
166 |
167 | LibCompat.SafePack = SafePack
168 | LibCompat.SafeUnpack = SafeUnpack
169 | LibCompat.tLength = tLength
170 | LibCompat.tCopy = tCopy
171 | LibCompat.tAppendAll = tAppendAll
172 | LibCompat.WeakTable = WeakTable
173 | LibCompat.newTable = newTable
174 | LibCompat.delTable = delTable
175 | end
176 |
177 | -------------------------------------------------------------------------------
178 |
179 | do
180 | local function Round(val)
181 | return (val < 0.0) and ceil(val - 0.5) or floor(val + 0.5)
182 | end
183 |
184 | local function Square(val)
185 | return val * val
186 | end
187 |
188 | local function Clamp(val, minval, maxval)
189 | return (val > maxval) and maxval or (val < minval) and minval or val
190 | end
191 |
192 | local function WithinRange(val, minval, maxval)
193 | return val >= minval and val <= maxval
194 | end
195 |
196 | local function WithinRangeExclusive(val, minval, maxval)
197 | return val > minval and val < maxval
198 | end
199 |
200 | LibCompat.Round = Round
201 | LibCompat.Square = Square
202 | LibCompat.Clamp = Clamp
203 | LibCompat.WithinRange = WithinRange
204 | LibCompat.WithinRangeExclusive = WithinRangeExclusive
205 | end
206 |
207 | -------------------------------------------------------------------------------
208 |
209 | do
210 | local GetNumRaidMembers, GetNumPartyMembers = GetNumRaidMembers, GetNumPartyMembers
211 | local UnitExists, UnitAffectingCombat, UnitIsDeadOrGhost = UnitExists, UnitAffectingCombat, UnitIsDeadOrGhost
212 | local UnitHealth, UnitHealthMax = UnitHealth, UnitHealthMax
213 | local UnitPower, UnitPowerMax = UnitPower, UnitPowerMax
214 |
215 | local function IsInRaid()
216 | return (GetNumRaidMembers() > 0)
217 | end
218 |
219 | local function IsInGroup()
220 | return (GetNumRaidMembers() > 0 or GetNumPartyMembers() > 0)
221 | end
222 |
223 | local function GetNumGroupMembers()
224 | return IsInRaid() and GetNumRaidMembers() or GetNumPartyMembers()
225 | end
226 |
227 | local function GetNumSubgroupMembers()
228 | return GetNumPartyMembers()
229 | end
230 |
231 | local function GetGroupTypeAndCount()
232 | if IsInRaid() then
233 | return "raid", 1, GetNumRaidMembers()
234 | elseif IsInGroup() then
235 | return "party", 0, GetNumPartyMembers()
236 | else
237 | return nil, 0, 0
238 | end
239 | end
240 |
241 | local UnitIterator, roster, _
242 | do
243 | local rmem, pmem, step, count
244 |
245 | local function SelfIterator()
246 | while step do
247 | local unit, owner
248 | if step == 1 then
249 | unit, owner, step = "player", nil, 2
250 | elseif step == 2 then
251 | unit, owner, step = "playerpet", "player", nil
252 | end
253 | if unit and UnitExists(unit) then
254 | return unit, owner
255 | end
256 | end
257 | end
258 |
259 | local function PartyIterator()
260 | while step do
261 | local unit, owner
262 | if step <= 2 then
263 | unit, owner = SelfIterator()
264 | step = step or 3
265 | elseif step == 3 then
266 | unit, owner, step = format("party%d", count), nil, 4
267 | elseif step == 4 then
268 | unit, owner = format("partypet%d", count), format("party%d", count)
269 | count = count + 1
270 | step = count <= pmem and 3 or nil
271 | end
272 | if unit and UnitExists(unit) then
273 | return unit, owner
274 | end
275 | end
276 | end
277 |
278 | local function RaidIterator()
279 | while step do
280 | local unit, owner
281 | if step == 1 then
282 | unit, owner, step = format("raid%d", count), nil, 2
283 | elseif step == 2 then
284 | unit, owner = format("raidpet%d", count), format("raid%d", count)
285 | count = count + 1
286 | step = count <= rmem and 1 or nil
287 | end
288 | if unit and UnitExists(unit) then
289 | return unit, owner
290 | end
291 | end
292 | end
293 |
294 | function UnitIterator()
295 | rmem, step = GetNumRaidMembers(), 1
296 | if rmem == 0 then
297 | pmem = GetNumPartyMembers()
298 | if pmem == 0 then
299 | return SelfIterator, false
300 | end
301 | count = 1
302 | return PartyIterator, false
303 | end
304 | count = 1
305 | return RaidIterator, true
306 | end
307 | end
308 |
309 | local function IsGroupDead()
310 | roster, _ = UnitIterator()
311 | for unit in roster do
312 | if not UnitIsDeadOrGhost(unit) then
313 | return false
314 | end
315 | end
316 | return true
317 | end
318 |
319 | local function IsGroupInCombat()
320 | roster, _ = UnitIterator()
321 | for unit in roster do
322 | if UnitAffectingCombat(unit) then
323 | return true
324 | end
325 | end
326 | return false
327 | end
328 |
329 | local function GroupIterator(func, ...)
330 | roster, _ = UnitIterator()
331 | for unit, owner in roster do
332 | LibCompat.QuickDispatch(func, unit, owner, ...)
333 | end
334 | end
335 |
336 | local function GetUnitIdFromGUID(guid, specific)
337 | if specific == nil or specific == "boss" then
338 | for i = 1, 4 do
339 | if UnitExists("boss" .. i) and UnitGUID("boss" .. i) == guid then
340 | return "boss" .. i
341 | end
342 | end
343 | end
344 |
345 | if specific == nil or specific == "player" then
346 | if UnitExists("target") and UnitGUID("target") == guid then
347 | return "target"
348 | elseif UnitExists("focus") and UnitGUID("focus") == guid then
349 | return "focus"
350 | elseif UnitExists("targettarget") and UnitGUID("targettarget") == guid then
351 | return "targettarget"
352 | elseif UnitExists("focustarget") and UnitGUID("focustarget") == guid then
353 | return "focustarget"
354 | elseif UnitExists("mouseover") and UnitGUID("mouseover") == guid then
355 | return "mouseover"
356 | end
357 | end
358 |
359 | if specific == nil or specific == "group" then
360 | roster, _ = UnitIterator()
361 | for unit in roster do
362 | if UnitGUID(unit) == guid then
363 | return unit
364 | elseif UnitExists(unit .. "target") and UnitGUID(unit .. "target") == guid then
365 | return unit .. "target"
366 | end
367 | end
368 | end
369 | end
370 |
371 | local function GetClassFromGUID(guid)
372 | local unit = GetUnitIdFromGUID(guid)
373 | local class
374 | if unit and unit:find("pet") then
375 | class = "PET"
376 | elseif unit and unit:find("boss") then
377 | class = "BOSS"
378 | elseif unit then
379 | class = select(2, UnitClass(unit))
380 | end
381 | return class, unit
382 | end
383 |
384 | local function GetCreatureId(guid)
385 | return guid and tonumber(guid:sub(9, 12), 16) or 0
386 | end
387 |
388 | local function GetUnitCreatureId(unit)
389 | return GetCreatureId(UnitGUID(unit))
390 | end
391 |
392 | local function UnitHealthInfo(unit, guid)
393 | unit = unit or guid and GetUnitIdFromGUID(guid)
394 | local percent, health, maxhealth
395 | if unit and UnitExists(unit) then
396 | health, maxhealth = UnitHealth(unit), UnitHealthMax(unit)
397 | if health and maxhealth then
398 | percent = 100 * health / max(1, maxhealth)
399 | end
400 | end
401 | return percent, health, maxhealth
402 | end
403 |
404 | local function UnitPowerInfo(unit, guid, powerType)
405 | unit = unit or guid and GetUnitIdFromGUID(guid)
406 | local percent, power, maxpower
407 | if unit and UnitExists(unit) then
408 | power, maxpower = UnitPower(unit, powerType), UnitPowerMax(unit, powerType)
409 | if power and maxpower then
410 | percent = 100 * power / max(1, maxpower)
411 | end
412 | end
413 | return percent, power, maxpower
414 | end
415 |
416 | local function UnitFullName(unit)
417 | local name, realm = UnitName(unit)
418 | local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
419 | return namerealm
420 | end
421 |
422 | LibCompat.IsInRaid = IsInRaid
423 | LibCompat.IsInGroup = IsInGroup
424 | LibCompat.GetNumGroupMembers = GetNumGroupMembers
425 | LibCompat.GetNumSubgroupMembers = GetNumSubgroupMembers
426 | LibCompat.GetGroupTypeAndCount = GetGroupTypeAndCount
427 | LibCompat.IsGroupDead = IsGroupDead
428 | LibCompat.IsGroupInCombat = IsGroupInCombat
429 | LibCompat.GroupIterator = GroupIterator
430 | LibCompat.UnitIterator = UnitIterator
431 | LibCompat.GetUnitIdFromGUID = GetUnitIdFromGUID
432 | LibCompat.GetClassFromGUID = GetClassFromGUID
433 | LibCompat.GetCreatureId = GetCreatureId
434 | LibCompat.GetUnitCreatureId = GetUnitCreatureId
435 | LibCompat.UnitHealthInfo = UnitHealthInfo
436 | LibCompat.UnitHealthPercent = UnitHealthInfo -- backward compatibility
437 | LibCompat.UnitPowerInfo = UnitPowerInfo
438 | LibCompat.UnitFullName = UnitFullName
439 | end
440 |
441 | -------------------------------------------------------------------------------
442 |
443 | do
444 | local IsRaidLeader, GetPartyLeaderIndex = IsRaidLeader, GetPartyLeaderIndex
445 | local GetRealNumRaidMembers, GetRaidRosterInfo = GetRealNumRaidMembers, GetRaidRosterInfo
446 |
447 | local function UnitIsGroupLeader(unit)
448 | if LibCompat.IsInRaid() then
449 | if unit == "player" then
450 | return IsRaidLeader()
451 | end
452 |
453 | local rank = select(2, GetRaidRosterInfo(unit:match("%d+")))
454 | return (rank and rank == 2)
455 | end
456 |
457 | if unit == "player" then
458 | return (GetPartyLeaderIndex() == 0)
459 | end
460 | local index = unit:match("%d+")
461 | return (index and index == GetPartyLeaderIndex())
462 | end
463 |
464 | local function UnitIsGroupAssistant(unit)
465 | for i = 1, GetRealNumRaidMembers() do
466 | local name, rank = GetRaidRosterInfo(i)
467 | if name == UnitName(unit) then
468 | return (rank == 1)
469 | end
470 | end
471 | return false
472 | end
473 |
474 | LibCompat.UnitIsGroupLeader = UnitIsGroupLeader
475 | LibCompat.UnitIsGroupAssistant = UnitIsGroupAssistant
476 | end
477 |
478 | -------------------------------------------------------------------------------
479 | -- Class Colors
480 |
481 | do
482 | local classColorsTable
483 | local colors = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS
484 |
485 | local function GetClassColorsTable()
486 | if not classColorsTable then
487 | -- add missing class color strings
488 | colors.DEATHKNIGHT.colorStr = "ffc41f3b"
489 | colors.DRUID.colorStr = "ffff7d0a"
490 | colors.HUNTER.colorStr = "ffabd473"
491 | colors.MAGE.colorStr = "ff3fc7eb"
492 | colors.PALADIN.colorStr = "fff58cba"
493 | colors.PRIEST.colorStr = "ffffffff"
494 | colors.ROGUE.colorStr = "fffff569"
495 | colors.SHAMAN.colorStr = "ff0070de"
496 | colors.WARLOCK.colorStr = "ff8788ee"
497 | colors.WARRIOR.colorStr = "ffc79c6e"
498 |
499 | -- cache it once and for all.
500 | classColorsTable = {}
501 | for class, tbl in pairs(colors) do
502 | classColorsTable[class] = tbl
503 | end
504 | end
505 |
506 | return classColorsTable
507 | end
508 |
509 | local function GetClassColorObj(class)
510 | classColorsTable = classColorsTable or GetClassColorsTable()
511 | return class and classColorsTable[class]
512 | end
513 |
514 | local function GetClassColor(class)
515 | local obj = GetClassColorObj(class)
516 | if obj then
517 | return obj.r, obj.g, obj.b, obj.colorStr
518 | end
519 | return 1, 1, 1, "ffffffff"
520 | end
521 |
522 | LibCompat.GetClassColorsTable = GetClassColorsTable
523 | LibCompat.GetClassColorObj = GetClassColorObj
524 | LibCompat.GetClassColor = GetClassColor
525 | end
526 |
527 | -------------------------------------------------------------------------------
528 | -- C_Timer mimic
529 |
530 | do
531 | local TickerPrototype, waitTable = {}, {}
532 | local TickerMetatable = {__index = TickerPrototype, __metatable = true}
533 |
534 | local waitFrame = LibCompat_TimerFrame or CreateFrame("Frame", "LibCompat_TimerFrame", UIParent)
535 | waitFrame:SetScript("OnUpdate", function(self, elapsed)
536 | local total = #waitTable
537 | for i = 1, total do
538 | local ticker = waitTable[i]
539 |
540 | if ticker then
541 | if ticker._cancelled then
542 | tremove(waitTable, i)
543 | elseif ticker._delay > elapsed then
544 | ticker._delay = ticker._delay - elapsed
545 | i = i + 1
546 | else
547 | ticker._callback(ticker, LibCompat.SafeUnpack(ticker._args))
548 |
549 | if ticker._iterations == -1 then
550 | ticker._delay = ticker._duration
551 | i = i + 1
552 | elseif ticker._iterations > 1 then
553 | ticker._iterations = ticker._iterations - 1
554 | ticker._delay = ticker._duration
555 | i = i + 1
556 | elseif ticker._iterations == 1 then
557 | tremove(waitTable, i)
558 | total = total - 1
559 | end
560 | end
561 | end
562 | end
563 |
564 | if #waitTable == 0 then
565 | self:Hide()
566 | end
567 | end)
568 |
569 | local function AddDelayedCall(ticker, oldTicker)
570 | if oldTicker and type(oldTicker) == "table" then
571 | ticker = oldTicker
572 | end
573 |
574 | tinsert(waitTable, ticker)
575 | waitFrame:Show()
576 | end
577 |
578 | local function CreateTicker(duration, callback, iterations, ...)
579 | local ticker = setmetatable({}, TickerMetatable)
580 |
581 | ticker._iterations = iterations or -1
582 | ticker._duration = duration
583 | ticker._delay = duration
584 | ticker._callback = callback
585 | ticker._args = LibCompat.SafePack(...)
586 |
587 | AddDelayedCall(ticker)
588 | return ticker
589 | end
590 |
591 | function TickerPrototype:IsCancelled()
592 | return self._cancelled
593 | end
594 |
595 | function TickerPrototype:Cancel()
596 | self._cancelled = true
597 | end
598 |
599 | local function After(duration, callback, ...)
600 | AddDelayedCall({
601 | _iterations = 1,
602 | _delay = duration,
603 | _callback = callback,
604 | _args = LibCompat.SafePack(...)
605 | })
606 | end
607 |
608 | local function NewTimer(duration, callback, ...)
609 | return CreateTicker(duration, callback, 1, ...)
610 | end
611 |
612 | local function NewTicker(duration, callback, iterations, ...)
613 | return CreateTicker(duration, callback, iterations, ...)
614 | end
615 |
616 | local function CancelTimer(ticker)
617 | if ticker and type(ticker.Cancel) == "function" then
618 | ticker:Cancel()
619 | end
620 | return nil -- return nil to assign input reference
621 | end
622 |
623 | LibCompat.After = After
624 | LibCompat.NewTimer = NewTimer
625 | LibCompat.NewTicker = NewTicker
626 | LibCompat.CancelTimer = CancelTimer
627 | end
628 |
629 | -------------------------------------------------------------------------------
630 |
631 | do
632 | local GetSpellInfo, GetSpellLink = GetSpellInfo, GetSpellLink
633 |
634 | local custom = {
635 | [3] = {ACTION_ENVIRONMENTAL_DAMAGE_FALLING, "Interface\\Icons\\ability_rogue_quickrecovery"},
636 | [4] = {ACTION_ENVIRONMENTAL_DAMAGE_DROWNING, "Interface\\Icons\\spell_shadow_demonbreath"},
637 | [5] = {ACTION_ENVIRONMENTAL_DAMAGE_FATIGUE, "Interface\\Icons\\ability_creature_cursed_05"},
638 | [6] = {ACTION_ENVIRONMENTAL_DAMAGE_FIRE, "Interface\\Icons\\spell_fire_fire"},
639 | [7] = {ACTION_ENVIRONMENTAL_DAMAGE_LAVA, "Interface\\Icons\\spell_shaman_lavaflow"},
640 | [8] = {ACTION_ENVIRONMENTAL_DAMAGE_SLIME, "Interface\\Icons\\inv_misc_slime_01"}
641 | }
642 |
643 | local function _GetSpellInfo(spellid)
644 | local res1, res2, res3, res4, res5, res6, res7, res8, res9
645 | if spellid then
646 | if custom[spellid] then
647 | res1, res3 = custom[spellid][1], custom[spellid][2]
648 | else
649 | res1, res2, res3, res4, res5, res6, res7, res8, res9 = GetSpellInfo(spellid)
650 | if spellid == 75 then
651 | res3 = "Interface\\Icons\\INV_Weapon_Bow_07"
652 | elseif spellid == 6603 then
653 | res1, res3 = MELEE, "Interface\\Icons\\INV_Sword_04"
654 | end
655 | end
656 | end
657 | return res1, res2, res3, res4, res5, res6, res7, res8, res9
658 | end
659 |
660 | local function _GetSpellLink(spellid)
661 | if not custom[spellid] then
662 | return GetSpellLink(spellid)
663 | end
664 | end
665 |
666 | LibCompat.GetSpellInfo = _GetSpellInfo
667 | LibCompat.GetSpellLink = _GetSpellLink
668 | end
669 |
670 | -------------------------------------------------------------------------------
671 |
672 | do
673 | local band, rshift, lshift = bit.band, bit.rshift, bit.lshift
674 | local byte, char = string.byte, string.char
675 |
676 | local function HexEncode(str, title)
677 | local hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}
678 | local t = (title and title ~= "") and {format("[=== %s ===]", title)} or {}
679 | local j = 0
680 | for i = 1, #str do
681 | if j <= 0 then
682 | t[#t + 1], j = "\n", 32
683 | end
684 | j = j - 1
685 |
686 | local b = byte(str, i)
687 | t[#t + 1] = hex[band(b, 15) + 1]
688 | t[#t + 1] = hex[band(rshift(b, 4), 15) + 1]
689 | end
690 | if title and title ~= "" then
691 | t[#t + 1] = "\n" .. t[1]
692 | end
693 | return tconcat(t)
694 | end
695 |
696 | local function HexDecode(str)
697 | str = str:gsub("%[.-%]", ""):gsub("[^0123456789ABCDEF]", "")
698 | if (#str == 0) or (#str % 2 ~= 0) then
699 | return false, "Invalid Hex string"
700 | end
701 |
702 | local t, bl, bh = {}
703 | local i = 1
704 | repeat
705 | bl = byte(str, i)
706 | bl = bl >= 65 and bl - 55 or bl - 48
707 | i = i + 1
708 | bh = byte(str, i)
709 | bh = bh >= 65 and bh - 55 or bh - 48
710 | i = i + 1
711 | t[#t + 1] = char(lshift(bh, 4) + bl)
712 | until i >= #str
713 | return tconcat(t)
714 | end
715 |
716 | local function EscapeStr(str)
717 | local res = ""
718 | for i = 1, str:len() do
719 | local n = str:sub(i, i)
720 | res = res .. n
721 | if n == "|" then
722 | res = res .. "\124"
723 | end
724 | end
725 | return (res ~= "") and res or str
726 | end
727 |
728 | LibCompat.HexEncode = HexEncode
729 | LibCompat.HexDecode = HexDecode
730 | LibCompat.EscapeStr = EscapeStr
731 | end
732 |
733 | -------------------------------------------------------------------------------
734 |
735 | do
736 | local LGT = LibStub("LibGroupTalents-1.0")
737 | local UnitClass, MAX_TALENT_TABS = UnitClass, MAX_TALENT_TABS or 3
738 | local GetActiveTalentGroup, GetTalentTabInfo = GetActiveTalentGroup, GetTalentTabInfo
739 | local LGTRoleTable = {melee = "DAMAGER", caster = "DAMAGER", healer = "HEALER", tank = "TANK"}
740 |
741 | -- list of class to specs
742 | local specsTable = {
743 | ["MAGE"] = {62, 63, 64},
744 | ["PRIEST"] = {256, 257, 258},
745 | ["ROGUE"] = {259, 260, 261},
746 | ["WARLOCK"] = {265, 266, 267},
747 | ["WARRIOR"] = {71, 72, 73},
748 | ["PALADIN"] = {65, 66, 70},
749 | ["DEATHKNIGHT"] = {250, 251, 252},
750 | ["DRUID"] = {102, 103, 104, 105},
751 | ["HUNTER"] = {253, 254, 255},
752 | ["SHAMAN"] = {262, 263, 264}
753 | }
754 |
755 | local function GetSpecialization(isInspect, isPet, specGroup)
756 | local currentSpecGroup = GetActiveTalentGroup(isInspect, isPet) or (specGroup or 1)
757 | local points, specname, specid = 0, nil, nil
758 |
759 | for i = 1, MAX_TALENT_TABS do
760 | local name, _, pointsSpent = GetTalentTabInfo(i, isInspect, isPet, currentSpecGroup)
761 | if points <= pointsSpent then
762 | points = pointsSpent
763 | specname = name
764 | specid = i
765 | end
766 | end
767 | return specid, specname, points
768 | end
769 |
770 | -- checks if the feral druid is a cat or tank spec
771 | local function GetDruidSpec(unit)
772 | -- 57881 : Natural Reaction -- used by druid tanks
773 | local points = LGT:UnitHasTalent(unit, LibCompat.GetSpellInfo(57881), LGT:GetActiveTalentGroup(unit))
774 | return (points and points > 0) and 3 or 2
775 | end
776 |
777 | local function GetInspectSpecialization(unit, class)
778 | local spec -- start with nil
779 |
780 | if unit and UnitExists(unit) then
781 | class = class or select(2, UnitClass(unit))
782 | if class and specsTable[class] then
783 | local talentGroup = LGT:GetActiveTalentGroup(unit)
784 | local maxPoints, index = 0, 0
785 |
786 | for i = 1, MAX_TALENT_TABS do
787 | local _, _, pointsSpent = LGT:GetTalentTabInfo(unit, i, talentGroup)
788 | if pointsSpent ~= nil then
789 | if maxPoints < pointsSpent then
790 | maxPoints = pointsSpent
791 | if class == "DRUID" and i >= 2 then
792 | if i == 3 then
793 | index = 4
794 | elseif i == 2 then
795 | index = GetDruidSpec(unit)
796 | end
797 | else
798 | index = i
799 | end
800 | end
801 | end
802 | end
803 | spec = specsTable[class][index]
804 | end
805 | end
806 |
807 | return spec
808 | end
809 |
810 | local function GetSpecializationRole(unit)
811 | return LGTRoleTable[LGT:GetUnitRole(unit or "player")] or "NONE"
812 | end
813 |
814 | local function GetSpecializationInfo(specIndex, isInspect, isPet, specGroup)
815 | local name, icon, _, background = GetTalentTabInfo(specIndex, isInspect, isPet, specGroup)
816 | local id, role
817 | if isInspect and UnitExists("target") then
818 | id, role = GetInspectSpecialization("target"), GetSpecializationRole("target")
819 | else
820 | id, role = GetInspectSpecialization("player"), GetSpecializationRole("player")
821 | end
822 | return id, name, nil, icon, background, role
823 | end
824 |
825 | local function UnitGroupRolesAssigned(unit)
826 | return LGTRoleTable[LGT:GetUnitRole(unit or "player")] or "NONE"
827 | end
828 |
829 | local function GetUnitRole(unit)
830 | return LGTRoleTable[LGT:GetUnitRole(unit or "player")] or "NONE"
831 | end
832 |
833 | local function GetGUIDRole(guid)
834 | return LGTRoleTable[LGT:GetGUIDRole(guid)] or "NONE"
835 | end
836 |
837 | LibCompat.GetSpecialization = GetSpecialization
838 | LibCompat.GetInspectSpecialization = GetInspectSpecialization
839 | LibCompat.GetSpecializationRole = GetSpecializationRole
840 | LibCompat.GetSpecializationInfo = GetSpecializationInfo
841 |
842 | LibCompat.UnitGroupRolesAssigned = UnitGroupRolesAssigned
843 | LibCompat.GetUnitRole = UnitGroupRolesAssigned
844 | LibCompat.GetGUIDRole = GetGUIDRole
845 | LibCompat.GetUnitSpec = GetInspectSpecialization
846 |
847 | -- functions that simply replaced other api functions
848 | LibCompat.GetNumSpecializations = GetNumTalentTabs
849 | LibCompat.GetNumSpecGroups = GetNumTalentGroups
850 | LibCompat.GetNumUnspentTalents = GetUnspentTalentPoints
851 | LibCompat.GetActiveSpecGroup = GetActiveTalentGroup
852 | LibCompat.SetActiveSpecGroup = SetActiveTalentGroup
853 | end
854 |
855 | -------------------------------------------------------------------------------
856 |
857 | do
858 | local C_PvP = {}
859 | local IsInInstance, instanceType = IsInInstance, nil
860 |
861 | function C_PvP.IsPvPMap()
862 | instanceType = select(2, IsInInstance())
863 | return (instanceType == "pvp" or instanceType == "arena")
864 | end
865 |
866 | function C_PvP.IsBattleground()
867 | instanceType = select(2, IsInInstance())
868 | return (instanceType == "pvp")
869 | end
870 |
871 | function C_PvP.IsArena()
872 | instanceType = select(2, IsInInstance())
873 | return (instanceType == "arena")
874 | end
875 |
876 | LibCompat.IsInPvP = C_PvP.IsPvPMap
877 | LibCompat.C_PvP = C_PvP
878 | end
879 |
880 | -------------------------------------------------------------------------------
881 |
882 | local mixins = {
883 | "QuickDispatch",
884 | -- table util
885 | "SafePack",
886 | "SafeUnpack",
887 | "tLength",
888 | "tCopy",
889 | "tAppendAll",
890 | "WeakTable",
891 | "newTable",
892 | "delTable",
893 | -- math util
894 | "Round",
895 | "Square",
896 | "Clamp",
897 | "WithinRange",
898 | "WithinRangeExclusive",
899 | -- roster util
900 | "IsInRaid",
901 | "IsInGroup",
902 | "IsInPvP",
903 | "GetNumGroupMembers",
904 | "GetNumSubgroupMembers",
905 | "GetGroupTypeAndCount",
906 | "IsGroupDead",
907 | "IsGroupInCombat",
908 | "GroupIterator",
909 | "UnitIterator",
910 | "UnitFullName",
911 | "C_PvP",
912 | -- unit util
913 | "GetUnitIdFromGUID",
914 | "GetClassFromGUID",
915 | "GetCreatureId",
916 | "GetUnitCreatureId",
917 | "UnitHealthInfo",
918 | "UnitHealthPercent", -- backward compatibility
919 | "UnitPowerInfo",
920 | "UnitIsGroupLeader",
921 | "UnitIsGroupAssistant",
922 | "GetUnitSpec", -- backward compatibility
923 | "GetSpecialization",
924 | "GetInspectSpecialization",
925 | "GetSpecializationRole",
926 | "GetNumSpecializations",
927 | "GetSpecializationInfo",
928 | "UnitGroupRolesAssigned",
929 | "GetNumSpecGroups",
930 | "GetNumUnspentTalents",
931 | "GetActiveSpecGroup",
932 | "SetActiveSpecGroup",
933 | "GetUnitRole",
934 | "GetGUIDRole",
935 | -- timer util
936 | "After",
937 | "NewTimer",
938 | "NewTicker",
939 | "CancelTimer",
940 | -- spell util
941 | "GetSpellInfo",
942 | "GetSpellLink",
943 | -- misc util
944 | "HexEncode",
945 | "HexDecode",
946 | "EscapeStr",
947 | "GetClassColorsTable",
948 | "GetClassColorObj",
949 | "GetClassColor",
950 | "Print",
951 | "Printf"
952 | }
953 |
954 | function LibCompat:Embed(target)
955 | for _, v in pairs(mixins) do
956 | target[v] = self[v]
957 | end
958 | target.locale = target.locale or GetLocale()
959 | self.embeds[target] = true
960 | return target
961 | end
962 |
963 | for addon in pairs(LibCompat.embeds) do
964 | LibCompat:Embed(addon)
965 | end
966 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/LibCompat-1.0.toc:
--------------------------------------------------------------------------------
1 | ## Title: Lib: Compat-1.0
2 | ## Notes: Brings extra useful functions, some from later expansions and others as utilties.
3 |
4 | ## Author: Kader
5 | ## X-Email: bkader@mail.com
6 | ## X-Donate: bkader@mail.com
7 |
8 | ## Interface: 30300
9 | ## Version: 16
10 |
11 | ## X-Category: Library
12 | ## X-Website: https://github.com/bkader/LibCompat-1.0
13 |
14 | ## Dependencies:
15 | ## X-Embeds: LibStub, CallbackHandler-1.0, LibTalentQuery-1.0, LibGroupTalents-1.0
16 | ## OptionalDeps: LibStub, CallbackHandler-1.0
17 | ## DefaultState: Enabled
18 | ## LoadOnDemand: 0
19 |
20 | lib.xml
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[ $Id: CallbackHandler-1.0.lua 18 2014-10-16 02:52:20Z mikk $ ]]
2 | local MAJOR, MINOR = "CallbackHandler-1.0", 6
3 | local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
4 |
5 | if not CallbackHandler then return end -- No upgrade needed
6 |
7 | local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
8 |
9 | -- Lua APIs
10 | local tconcat = table.concat
11 | local assert, error, loadstring = assert, error, loadstring
12 | local setmetatable, rawset, rawget = setmetatable, rawset, rawget
13 | local next, select, pairs, type, tostring = next, select, pairs, type, tostring
14 |
15 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
16 | -- List them here for Mikk's FindGlobals script
17 | -- GLOBALS: geterrorhandler
18 |
19 | local xpcall = xpcall
20 |
21 | local function errorhandler(err)
22 | return geterrorhandler()(err)
23 | end
24 |
25 | local function CreateDispatcher(argCount)
26 | local code = [[
27 | local next, xpcall, eh = ...
28 |
29 | local method, ARGS
30 | local function call() method(ARGS) end
31 |
32 | local function dispatch(handlers, ...)
33 | local index
34 | index, method = next(handlers)
35 | if not method then return end
36 | local OLD_ARGS = ARGS
37 | ARGS = ...
38 | repeat
39 | xpcall(call, eh)
40 | index, method = next(handlers, index)
41 | until not method
42 | ARGS = OLD_ARGS
43 | end
44 |
45 | return dispatch
46 | ]]
47 |
48 | local ARGS, OLD_ARGS = {}, {}
49 | for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
50 | code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
51 | return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
52 | end
53 |
54 | local Dispatchers = setmetatable({}, {__index=function(self, argCount)
55 | local dispatcher = CreateDispatcher(argCount)
56 | rawset(self, argCount, dispatcher)
57 | return dispatcher
58 | end})
59 |
60 | --------------------------------------------------------------------------
61 | -- CallbackHandler:New
62 | --
63 | -- target - target object to embed public APIs in
64 | -- RegisterName - name of the callback registration API, default "RegisterCallback"
65 | -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
66 | -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
67 |
68 | function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
69 |
70 | RegisterName = RegisterName or "RegisterCallback"
71 | UnregisterName = UnregisterName or "UnregisterCallback"
72 | if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
73 | UnregisterAllName = "UnregisterAllCallbacks"
74 | end
75 |
76 | -- we declare all objects and exported APIs inside this closure to quickly gain access
77 | -- to e.g. function names, the "target" parameter, etc
78 |
79 |
80 | -- Create the registry object
81 | local events = setmetatable({}, meta)
82 | local registry = { recurse=0, events=events }
83 |
84 | -- registry:Fire() - fires the given event/message into the registry
85 | function registry:Fire(eventname, ...)
86 | if not rawget(events, eventname) or not next(events[eventname]) then return end
87 | local oldrecurse = registry.recurse
88 | registry.recurse = oldrecurse + 1
89 |
90 | Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
91 |
92 | registry.recurse = oldrecurse
93 |
94 | if registry.insertQueue and oldrecurse==0 then
95 | -- Something in one of our callbacks wanted to register more callbacks; they got queued
96 | for eventname,callbacks in pairs(registry.insertQueue) do
97 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
98 | for self,func in pairs(callbacks) do
99 | events[eventname][self] = func
100 | -- fire OnUsed callback?
101 | if first and registry.OnUsed then
102 | registry.OnUsed(registry, target, eventname)
103 | first = nil
104 | end
105 | end
106 | end
107 | registry.insertQueue = nil
108 | end
109 | end
110 |
111 | -- Registration of a callback, handles:
112 | -- self["method"], leads to self["method"](self, ...)
113 | -- self with function ref, leads to functionref(...)
114 | -- "addonId" (instead of self) with function ref, leads to functionref(...)
115 | -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
116 | target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
117 | if type(eventname) ~= "string" then
118 | error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
119 | end
120 |
121 | method = method or eventname
122 |
123 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
124 |
125 | if type(method) ~= "string" and type(method) ~= "function" then
126 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
127 | end
128 |
129 | local regfunc
130 |
131 | if type(method) == "string" then
132 | -- self["method"] calling style
133 | if type(self) ~= "table" then
134 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
135 | elseif self==target then
136 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
137 | elseif type(self[method]) ~= "function" then
138 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
139 | end
140 |
141 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
142 | local arg=select(1,...)
143 | regfunc = function(...) self[method](self,arg,...) end
144 | else
145 | regfunc = function(...) self[method](self,...) end
146 | end
147 | else
148 | -- function ref with self=object or self="addonId" or self=thread
149 | if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
150 | error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
151 | end
152 |
153 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
154 | local arg=select(1,...)
155 | regfunc = function(...) method(arg,...) end
156 | else
157 | regfunc = method
158 | end
159 | end
160 |
161 |
162 | if events[eventname][self] or registry.recurse<1 then
163 | -- if registry.recurse<1 then
164 | -- we're overwriting an existing entry, or not currently recursing. just set it.
165 | events[eventname][self] = regfunc
166 | -- fire OnUsed callback?
167 | if registry.OnUsed and first then
168 | registry.OnUsed(registry, target, eventname)
169 | end
170 | else
171 | -- we're currently processing a callback in this registry, so delay the registration of this new entry!
172 | -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
173 | registry.insertQueue = registry.insertQueue or setmetatable({},meta)
174 | registry.insertQueue[eventname][self] = regfunc
175 | end
176 | end
177 |
178 | -- Unregister a callback
179 | target[UnregisterName] = function(self, eventname)
180 | if not self or self==target then
181 | error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
182 | end
183 | if type(eventname) ~= "string" then
184 | error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
185 | end
186 | if rawget(events, eventname) and events[eventname][self] then
187 | events[eventname][self] = nil
188 | -- Fire OnUnused callback?
189 | if registry.OnUnused and not next(events[eventname]) then
190 | registry.OnUnused(registry, target, eventname)
191 | end
192 | end
193 | if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
194 | registry.insertQueue[eventname][self] = nil
195 | end
196 | end
197 |
198 | -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
199 | if UnregisterAllName then
200 | target[UnregisterAllName] = function(...)
201 | if select("#",...)<1 then
202 | error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
203 | end
204 | if select("#",...)==1 and ...==target then
205 | error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
206 | end
207 |
208 |
209 | for i=1,select("#",...) do
210 | local self = select(i,...)
211 | if registry.insertQueue then
212 | for eventname, callbacks in pairs(registry.insertQueue) do
213 | if callbacks[self] then
214 | callbacks[self] = nil
215 | end
216 | end
217 | end
218 | for eventname, callbacks in pairs(events) do
219 | if callbacks[self] then
220 | callbacks[self] = nil
221 | -- Fire OnUnused callback?
222 | if registry.OnUnused and not next(callbacks) then
223 | registry.OnUnused(registry, target, eventname)
224 | end
225 | end
226 | end
227 | end
228 | end
229 | end
230 |
231 | return registry
232 | end
233 |
234 |
235 | -- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
236 | -- try to upgrade old implicit embeds since the system is selfcontained and
237 | -- relies on closures to work.
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Libs/LibGroupTalents-1.0/LibGroupTalents-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Name: LibGroupTalents-1.0
3 | Revision: $Rev: 55 $
4 | Author: Zek
5 | Documentation: http://wowace.com/wiki/LibGroupTalents-1.0
6 | SVN: svn://svn.wowace.com/wow/libgrouptalents-1-0/mainline/trunk
7 | Description: Talent Library abstraction layer to provide easy interface to the lower level system
8 | Dependancies: LibStub, CallbackHandler-1.0, LibTalentQuery-1.0
9 | License: GPL v3
10 |
11 | Purpose:
12 | LibGroupTalents-1.0 is intended to do the following basic functions usually handled at the mod level.
13 |
14 | - Maintain a raid wide table of talents, automatically updated on roster changes, notifying you on talent receipts.
15 | - Provide easy access to talent queries (spec weight, spec name, specific talent presence)
16 | - Monitor talent changes in players, and notify of changes (respec, talent swap, update after out of sight, level up)
17 | - Monitor player roles, and notify of changes (melee, tank, healer, caster)
18 | - Communicate directly with itself to other users to update talents via addon channel when possible
19 |
20 | Notes:
21 | The LibTalentQuery-1.0 dependancy must be included before LibGroupTalents-1.0 in any lib.xml or mod side TOC declarations.
22 |
23 | Functions:
24 | UnitHasTalent(unit, talentName[, group])-- Returns: Points spent in talent or nil
25 | GUIDHasTalent(guid, talentName[, group])-- As UnitHasTalent
26 | GetUnitTalentSpec(unitid[, group]) -- Returns: Dominant Tree, spent1, spent2, spent3
27 | GetGUIDTalentSpec(guid[, group]) -- As GetUnitTalentSpec
28 | GetUnitTalents(unit, refresh) -- Returns: Raw talent information in form of table of 3 strings of points spent. The refresh arg will force a re-query of the unit's talents
29 | GetGUIDTalents(guid, refresh) -- As GetUnitTalents
30 | GetUnitRole(unit) -- Returns one of: "melee", "caster", "healer", "tank"
31 | GetGUIDRole(guid) -- As GetUnitRole
32 | RefreshTalentsByUnit(unit) -- Force a refresh of talents for the specific unit
33 | RefreshTalentsByGUID(guid) -- Force a refresh of talents for the specific player GUID
34 | GetTreeNames(class) -- Returns: The three talent tree names for that class (Note: These return values are only valid after a player of that class has been inspected)
35 | GetTreeIcons(class) -- Returns: The three talent tree icons for that class (Note: As above)
36 | GetTalentCount() -- Returns: Talent info got, Talent info missing
37 | GetTalentMissingNames() -- Returns: Comma delimited list of player names we're missing talents for
38 | GetClassTalentInfo(class, talentName) -- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
39 | GetUnitStorageString(unit) -- Returns: An encoded data string containing talent information for the player which can be stored by mods to set in later sessions using SetStorageString()
40 | GetGUIDStorageString(guid) -- As GetUnitStorageString
41 | SetStorageString(talentString) -- Returns: true on success (applicable). Any second return value indicates the data was invalid and should not be kept
42 | GetUnitGlyphs(unit[, group]) -- Returns: Up to 6 spell IDs for the currently assigned Glyphs (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
43 | GetGUIDGlyphs(guid[, group]) -- As GetUnitGlyphs
44 | UnitHasGlyph(unit, glyph [, group]) -- Returns: true if the player has the glyph associated with spellID or spellName (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
45 | GUIDHasGlyph(unit, glyph [, group]) -- As UnitHasGlyph
46 | PurgeAndRescanTalents() -- Wipe current roster of all talents and rescan from start
47 |
48 | Convenience Functions (Similar to Blizzard API functions, but callable with a unit ID):
49 | GetActiveTalentGroup(unit) -- Returns: Active talent group for unit
50 | GetNumTalentGroups(unit) -- Returns: Number of talent groups for unit
51 | GetNumTalentTabs(unit) -- Returns: Number of talent tabs. Here's a clue; it's going to be 3...
52 | GetTalentTabInfo(unit, tab[, group]) -- Returns: Tree Name, Tree Icon, Points Spent, Tree Background
53 | GetNumTalents(unit, tab) -- Returns: Number of talents for specified tree
54 | GetTalentInfo(unit, tab, index[, group])-- Returns: Talent Name, Icon, Tier, Column, Points Spent, Max Rank (Note that preview return values are not given unless called with "player")
55 | GetUnspentTalentPoints(unit[, group]) -- Returns: Number of un-spent talent points for the unit
56 |
57 | Events:
58 | LibGroupTalents_Update(guid, unit, newSpec, n1, n2, n3 [, oldSpec, o1, o2, o3]) -- Received updated talents. If it's a respec, or old set is know, it passes the old info also (this is not sent if new talent scan is same as previous)
59 | LibGroupTalents_UpdateComplete(guid1, guid2[, ...]) -- Sent when there are no more pending talent reads due (passes all GUIDs that were updated since last time this event was fired)
60 | LibGroupTalents_Add(guid, unit, name, realm) -- Unit added to talent roster (Talents not necessarily available yet, but this is the mod's chance to feed talents using SetStorageString)
61 | LibGroupTalents_Remove(guid, name, realm) -- Unit removed from talent roster (This is your last chance to store talents if required using GetUnitStorageString)
62 | LibGroupTalents_RoleChange(guid, unit, newrole, oldrole) -- Roles are: "melee", "caster", "healer", "tank"
63 | LibGroupTalents_GlyphUpdate(guid, unit) -- Fired when a player's glyphs change (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
64 |
65 | ]]
66 |
67 | local TalentQuery = LibStub("LibTalentQuery-1.0")
68 |
69 | local MAJOR, MINOR = "LibGroupTalents-1.0", tonumber(("$Rev: 55 $"):match("(%d+)"))
70 | local lib = LibStub:NewLibrary(MAJOR, MINOR)
71 | if not lib then return end
72 |
73 | local ChatThrottleLib = _G.ChatThrottleLib
74 |
75 | lib.roster = lib.roster or {}
76 | lib.classTalentData = lib.classTalentData or {}
77 | lib.batch = lib.batch or {}
78 | lib.pendingStorageStrings = lib.pendingStorageStrings or {}
79 |
80 | local function UnitFullName(unit)
81 | local name, realm = UnitName(unit)
82 | local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
83 | return namerealm
84 | end
85 |
86 | local function RosterInfoFullName(info)
87 | local name, realm = info.name, info.realm
88 | local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
89 | return namerealm
90 | end
91 |
92 | local specChangers = {}
93 | for index,spellid in ipairs(_G.TALENT_ACTIVATION_SPELLS) do
94 | specChangers[GetSpellInfo(spellid)] = index
95 | end
96 |
97 | local frame = lib.frame
98 | if (not frame) then
99 | frame = CreateFrame("Frame", "LibGroupTalents_Frame")
100 | lib.frame = frame
101 | end
102 | frame:UnregisterAllEvents()
103 | frame:RegisterEvent("RAID_ROSTER_UPDATE")
104 | frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
105 | frame:RegisterEvent("UNIT_NAME_UPDATE")
106 | frame:RegisterEvent("PLAYER_TALENT_UPDATE")
107 | frame:RegisterEvent("UNIT_LEVEL")
108 | frame:RegisterEvent("UNIT_AURA") -- Always get a UNIT_AURA when a unit's UnitIsVisible() changes
109 | frame:RegisterEvent("CHAT_MSG_ADDON")
110 | frame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
111 | frame:RegisterEvent("PLAYER_LOGIN")
112 | frame:RegisterEvent("GLYPH_ADDED")
113 | frame:RegisterEvent("GLYPH_REMOVED")
114 | frame:RegisterEvent("GLYPH_UPDATED")
115 |
116 | frame:SetScript("OnEvent", function(self, event, ...)
117 | return lib[event](lib, ...)
118 | end)
119 |
120 | if not lib.events then
121 | lib.events = LibStub("CallbackHandler-1.0"):New(lib)
122 | end
123 |
124 | local next, select, pairs, type = next, select, pairs, type
125 | local new, del, deepDel
126 | do
127 | local list = setmetatable({},{__mode='k'})
128 | function new(...)
129 | local t = next(list)
130 | if t then
131 | list[t] = nil
132 | for i = 1, select('#', ...) do
133 | t[i] = select(i, ...)
134 | end
135 | return t
136 | else
137 | return {...}
138 | end
139 | end
140 | function del(t)
141 | if (t) then
142 | wipe(t)
143 | t[''] = true
144 | t[''] = nil
145 | list[t] = true
146 | end
147 | end
148 | function deepDel(t)
149 | if (t) then
150 | for k,v in pairs(t) do
151 | if type(v) == "table" then
152 | deepDel(v)
153 | end
154 | t[k] = nil
155 | end
156 | t[''] = true
157 | t[''] = nil
158 | list[t] = true
159 | end
160 | end
161 | end
162 |
163 | do
164 | local delay = 0
165 | frame:SetScript("OnUpdate", function(self, elapsed)
166 | if (lib.raidRosterUpdate) then
167 | lib.raidRosterUpdate = nil
168 | lib:OnRaidRosterUpdate()
169 | end
170 |
171 | if (lib.refreshCheckTimer) then
172 | lib.refreshCheckTimer = lib.refreshCheckTimer - elapsed
173 | if (lib.refreshCheckTimer < 0) then
174 | lib.refreshCheckTimer = nil
175 | lib:CheckForMissingTalents()
176 | end
177 | end
178 |
179 | if (lib.talentTimers) then
180 | delay = delay + elapsed
181 | if (delay > 1) then
182 | delay = 0
183 | local now = GetTime()
184 | local triggers
185 | for guid,when in pairs(lib.talentTimers) do
186 | if (now > when) then
187 | -- Pass to second table to process, because RefreshTimers can affect this talentTimers table
188 | -- So it's important we're not still iterating it at the time
189 | if (not triggers) then
190 | triggers = new()
191 | end
192 | triggers[guid] = true
193 | lib.talentTimers[guid] = nil
194 | if (not next(lib.talentTimers)) then
195 | lib.talentTimers = del(lib.talentTimers)
196 | break
197 | end
198 | end
199 | end
200 |
201 | if (triggers) then
202 | for guid in pairs(triggers) do
203 | lib:RefreshTalentsByGUID(guid)
204 | end
205 | del(triggers)
206 | end
207 | end
208 | end
209 |
210 | if (not lib.talentTimers and not lib.refreshCheckTimer) then
211 | self:Hide()
212 | end
213 | end)
214 | end
215 | frame:Show()
216 | lib.raidRosterUpdate = true
217 |
218 | -- GetGUIDTalentsRaw
219 | local function GetGUIDTalentsRaw(guid, group)
220 | local r = guid and lib.roster[guid]
221 | return r and r.talents and r.talents[group or r.active], r
222 | end
223 |
224 | -- PLAYER_LOGIN
225 | function lib:PLAYER_LOGIN()
226 | ChatThrottleLib = _G.ChatThrottleLib
227 | lib.PLAYER_LOGIN = nil
228 | end
229 |
230 | -- RAID_ROSTER_UPDATE
231 | function lib:RAID_ROSTER_UPDATE()
232 | self.raidRosterUpdate = true
233 | frame:Show()
234 | end
235 | lib.PARTY_MEMBERS_CHANGED = lib.RAID_ROSTER_UPDATE
236 |
237 | -- UNIT_NAME_UPDATE
238 | function lib:UNIT_NAME_UPDATE(unit)
239 | local guid = unit and UnitGUID(unit)
240 | local r = guid and self.roster[guid]
241 | if (r) then
242 | local needsAdd = r.name == UNKNOWN
243 | r.name, r.realm = UnitName(unit)
244 | if (r.realm == "") then
245 | r.realm = nil
246 | end
247 | r.class = select(2, UnitClass(unit))
248 | r.level = UnitLevel(unit)
249 | if (not r.talents) then
250 | if (needsAdd) then
251 | self.events:Fire("LibGroupTalents_Add", guid, unit, r.name, r.realm)
252 | end
253 |
254 | self:CheckForMissingTalents()
255 | end
256 | end
257 | end
258 |
259 | -- AnyPending
260 | local function AnyPending()
261 | local checkUpdate
262 | for guid,info in pairs(lib.roster) do
263 | local namerealm = RosterInfoFullName(info)
264 | if (UnitIsConnected(namerealm)) then
265 | if (lib.wasOffline) then
266 | lib.wasOffline[guid] = nil
267 | end
268 | if (not info.talents or info.refresh) then
269 | return true
270 | end
271 | else
272 | if (not lib.wasOffline) then
273 | lib.wasOffline = new()
274 | end
275 | lib.wasOffline[guid] = true
276 | end
277 | end
278 | if (lib.wasOffline and not next(lib.wasOffline)) then
279 | lib.wasOffline = del(lib.wasOffline)
280 | end
281 | end
282 |
283 | -- CheckForUpdateComplete
284 | local function CheckForUpdateComplete()
285 | -- When all pending updates are complete, send an event to notify nothing else is due
286 | if (next(lib.batch)) then
287 | if (not AnyPending()) then
288 | lib.events:Fire("LibGroupTalents_UpdateComplete", unpack(lib.batch))
289 | wipe(lib.batch)
290 | end
291 | end
292 | end
293 |
294 | -- UNIT_LEVEL
295 | function lib:UNIT_LEVEL(unit)
296 | if (UnitInParty(unit) or UnitInRaid(unit)) then
297 | local guid = UnitGUID(unit)
298 | local r = guid and self.roster[guid]
299 | if (r) then
300 | r.level = UnitLevel(unit)
301 | self:RefreshTalentsByUnit(unit)
302 | end
303 | end
304 | end
305 |
306 | -- UNIT_AURA
307 | function lib:UNIT_AURA(unit)
308 | local guid = UnitGUID(unit)
309 | if (not UnitIsVisible(unit) or (self.wasOffline and self.wasOffline[guid])) then
310 | if (not self.outOfSight) then
311 | self.outOfSight = {}
312 | end
313 | self.outOfSight[guid] = true
314 | self:RefreshTalentsByGUID(guid)
315 | end
316 | end
317 |
318 | -- OnRaidRosterUpdate
319 | function lib:OnRaidRosterUpdate()
320 | local instanceType = select(2, IsInInstance())
321 | if (instanceType == "pvp" or instanceType == "arena") then
322 | self.distribution = "BATTLEGROUND"
323 | else
324 | if (GetNumRaidMembers() > 0) then
325 | self.distribution = "RAID"
326 | elseif (GetNumPartyMembers() > 0) then
327 | self.distribution = "PARTY"
328 | else
329 | self.distribution = nil
330 | end
331 | end
332 | if (self.distribution) then
333 | if (self.sentHello ~= self.distribution) then
334 | self.sentHello = self.distribution
335 | self:SendCommMessage("HELLO "..MINOR, nil, self.distribution)
336 | end
337 | else
338 | self.sentHello = nil
339 | self.talentThrottle = del(self.talentThrottle)
340 | self.wasOffline = del(self.wasOffline)
341 | self.outOfSight = del(self.outOfSight)
342 | wipe(self.pendingStorageStrings)
343 | end
344 |
345 | -- Now check for roster changes
346 | local subtractions = new()
347 | local additions = new()
348 | local changes = new()
349 |
350 | if (self.roster) then
351 | for guid,info in pairs(self.roster) do
352 | subtractions[guid] = info.level or 0
353 | end
354 | end
355 |
356 | for unit in self:IterateRoster() do
357 | local guid = UnitGUID(unit)
358 | if (guid) then
359 | local n = self.roster[guid]
360 | if (not n) then
361 | n = new()
362 | self.roster[guid] = n
363 | end
364 |
365 | n.name, n.realm = UnitName(unit)
366 | if (n.realm == "") then
367 | n.realm = nil -- Fix this already..
368 | end
369 | n.level = UnitLevel(unit)
370 | n.class = select(2, UnitClass(unit))
371 | n.unit = unit
372 |
373 | if (subtractions[guid]) then
374 | if (subtractions[guid] ~= n.level) then
375 | changes[guid] = unit -- Level changed, needs a rescan
376 | end
377 |
378 | subtractions[guid] = nil
379 | else
380 | if (n.name ~= UNKNOWN) then
381 | self.events:Fire("LibGroupTalents_Add", guid, unit, n.name, n.realm)
382 | end
383 | additions[guid] = unit
384 | end
385 | end
386 | end
387 |
388 | if (next(additions)) then
389 | for guid,unit in pairs(additions) do
390 | self:GetUnitTalents(unit)
391 | end
392 | end
393 |
394 | if (next(changes)) then
395 | for guid,unit in pairs(changes) do
396 | self:GetUnitTalents(unit)
397 | end
398 | end
399 |
400 | if (next(subtractions)) then
401 | for guid in pairs(subtractions) do
402 | local r = self.roster[guid]
403 | if (r) then
404 | self.events:Fire("LibGroupTalents_Remove", guid, r.name, r.realm)
405 | self.roster[guid] = deepDel(r)
406 |
407 | local classStorageStrings = self.pendingStorageStrings[r.class]
408 | if (classStorageStrings) then
409 | classStorageStrings[guid] = del(classStorageStrings[guid])
410 | if (not next(classStorageStrings)) then
411 | self.pendingStorageStrings[r.class] = del(self.pendingStorageStrings[r.class])
412 | end
413 | end
414 | end
415 | end
416 |
417 | CheckForUpdateComplete()
418 | end
419 |
420 | del(additions)
421 | del(subtractions)
422 | del(changes)
423 |
424 | self:CheckForMissingTalents()
425 | end
426 |
427 | -- ValidateUnit
428 | local function ValidateUnit(r, guid)
429 | local unit = r.unit
430 | if (UnitGUID(unit) ~= guid) then
431 | local name = r.name .. (r.realm and "-" or "") .. (r.realm or "")
432 | local index = UnitInRaid(name)
433 | if (index) then
434 | r.unit = "raid"..index
435 | return true
436 | else
437 | if (UnitGUID("player") == guid) then
438 | r.unit = "player"
439 | return true
440 |
441 | elseif (UnitInParty(name)) then
442 | for i = 1,4 do
443 | if (UnitGUID("party"..i) == guid) then
444 | r.unit = "party"..i
445 | return true
446 | end
447 | end
448 | end
449 | end
450 | return
451 | end
452 |
453 | return true
454 | end
455 |
456 | -- CountTree
457 | local function CountTree(branch)
458 | local count = 0
459 | for i = 1,#branch do
460 | count = count + branch:byte(i) - 48
461 | end
462 | return count
463 | end
464 |
465 | -- TalentWeight
466 | local function TalentWeight(talents, class)
467 | if (talents and #talents == 3 and class) then
468 | local c1, c2, c3 = CountTree(talents[1]), CountTree(talents[2]), CountTree(talents[3])
469 |
470 | local weight = 1
471 | if (c2 > c1 and c2 > c3) then
472 | weight = 2
473 | elseif (c3 > c1 and c3 > c2) then
474 | weight = 3
475 | end
476 |
477 | local data = lib.classTalentData[class]
478 | if (data and data[weight]) then
479 | return data[weight].name, c1, c2, c3
480 | end
481 |
482 | return weight, c1, c2, c3
483 | end
484 | return nil, 0, 0, 0
485 | end
486 |
487 | do
488 | -- First segment: Player ID (from GUID), Name, level, class, activePage, TalentString
489 | -- Subsequent: spec number, talentString()
490 |
491 | -- crc
492 | local function crc32(str)
493 | local val = tonumber((select(2, GetBuildInfo()))) -- Use WoW build as CRC base
494 | for i = 1,#str do
495 | val = bit.band(val * 2 + str:byte(i), 0xFFFF)
496 | end
497 | return val
498 | end
499 |
500 | -- GetUnitStorageString
501 | function lib:GetUnitStorageString(unit)
502 | return self:GetGUIDStorageString(UnitGUID(unit))
503 | end
504 |
505 | -- GetGUIDStorageString
506 | -- Make a storage string for mods to store talents.
507 | -- Rules: 1) Your own realm only 2) Their talents are complete (nothing unspent)
508 | function lib:GetGUIDStorageString(guid)
509 | local r = self.roster[guid]
510 | if (r) then
511 | local id
512 | local playerGUID = UnitGUID("player")
513 | if (playerGUID:sub(1, 6) == guid:sub(1, 6)) then
514 | -- Same realm code, so just trim it off. This is likely always true from what I've seen
515 | id = format("%X", tonumber(guid:sub(7), 16))
516 | else
517 | id = guid:sub(4)
518 | end
519 |
520 | if (r.talents and r.active and not r.realm and (not r.unspent or not r.unspent[r.active])) then
521 | if (r.level < 1) then
522 | r.level = UnitLevel(r.name) or 0
523 | end
524 | local str = format("%s,%d,%s,%d,%d", id, r.level, r.class, r.active, r.numActive)
525 | for i = 1,r.numActive do
526 | local t = r.talents[i]
527 | if (t) then
528 | str = format("%s;%d,%s", str, i, table.concat(t, "-"))
529 | end
530 | end
531 | return format("%s;%d", str, crc32(str))
532 | end
533 | end
534 | end
535 |
536 | -- SetStorageString
537 | function lib:SetStorageString(str, comms)
538 | local ret, retInfo
539 | if (str) then
540 | local parts = new(strsplit(";", str))
541 | if (#parts >= 2) then
542 | local strCRC = tonumber(parts[#parts])
543 | local temp = table.concat(parts, ";", 1, #parts - 1)
544 | if (crc32(temp) == strCRC) then
545 | local part1 = new(strsplit(",", parts[1]))
546 |
547 | while true do
548 | local guid
549 | local id = part1[1]
550 | if (id:len() < 12) then
551 | -- Trimmed GUID, we'll prefix it with our own GUID's realm code
552 | guid = format("%s%012X", UnitGUID("player"):sub(1, 6), tonumber(id, 16))
553 | else
554 | guid = format("0x0%015s", id)
555 | end
556 |
557 | local r = self.roster[guid]
558 | if (not r) then
559 | retInfo = format("Unexpected SetStorageString for ID %s", guid)
560 | ret = true -- Still return true, we just didn't want this string yet
561 | break
562 | elseif (r.name == UNKNOWN) then
563 | retInfo = format("Premature SetStorageString for ID %s", guid)
564 | ret = true -- Still return true, we just didn't want this string yet
565 | break
566 | end
567 | if (r.talents) then
568 | -- We've already received talents for this player
569 | ret = true -- Still return true, we just didn't want this string because we have their talents
570 | break
571 | end
572 |
573 | if (not self.classTalentData[r.class]) then
574 | -- Received a storage string for a class that we've not yet been able to scan
575 | -- the talent trees for. We store this until we have that data
576 | local classStorageStrings = self.pendingStorageStrings[r.class]
577 | if (not classStorageStrings) then
578 | classStorageStrings = new()
579 | self.pendingStorageStrings[r.class] = classStorageStrings
580 | end
581 | classStorageStrings[guid] = str
582 | ret = true
583 | break
584 | end
585 |
586 | local level = tonumber(part1[2])
587 | local class = part1[3]
588 | local active = tonumber(part1[4])
589 | local numActive = tonumber(part1[5])
590 |
591 | if (r.level < 1) then
592 | r.level = UnitLevel(r.name) or 0
593 | end
594 | if (level ~= r.level and r.level > 1) then
595 | -- Won't accept talents for mismatched levels (but ignore errors reading the UnitLevel early)
596 | retInfo = "Wrong level"
597 | break
598 | end
599 | if (not r.class and class) then
600 | -- If we don't have the class, but the storage string does, we'll take it
601 | r.class = class
602 | end
603 | if (class ~= r.class) then
604 | -- Class doesn't match, probably a char delete/remake or xrealm
605 | retInfo = format("Wrong class: expected %q, got %q", tostring(r.class), tostring(class))
606 | break
607 | end
608 |
609 | -- Now the talent trees
610 | local talents = new()
611 | for i = 2,#parts - 1 do
612 | local partN = new(strsplit(",", parts[i]))
613 | if (#partN == 2) then
614 | local specNumber = tonumber(partN[1])
615 | local specTalents = new(strsplit("-", partN[2]))
616 |
617 | if (specNumber and #specTalents >= 3) then
618 | talents[specNumber] = specTalents
619 | else
620 | del(specTalents)
621 | talents = del(talents)
622 | retInfo = "Invalid talent specs in tree "..i
623 | break
624 | end
625 | end
626 | end
627 |
628 | if (talents) then
629 | r.talents = talents
630 | r.active = active
631 | r.numActive = numActive
632 | if (comms ~= r.name) then
633 | -- If comms part sends player name along with packet, then we'll skip the refresh later
634 | -- which we'd normally do when Storage is set via app startup
635 | r.refresh = true
636 | else
637 | r.refresh = nil
638 | end
639 |
640 | ValidateUnit(r, guid)
641 | local newSpec, n1, n2, n3 = TalentWeight(r.talents[r.active], r.class)
642 | self.events:Fire("LibGroupTalents_Update", guid, r.unit, newSpec, n1, n2, n3)
643 | self:GetGUIDRole(guid, true)
644 | ret = true
645 | end
646 | break
647 | end
648 |
649 | del(part1)
650 | else
651 | retInfo = "Invalid string"
652 | end
653 | end
654 | del(parts)
655 | end
656 |
657 | return ret, retInfo
658 | end
659 | end
660 |
661 | -- GetClassTalentData
662 | -- Builds an internal table for talent name -> tree/index lookups.
663 | function GetClassTalentData(unit)
664 | local _, class = UnitClass(unit)
665 | if (class) then
666 | local data = lib.classTalentData[class]
667 | if (not data) then
668 | local isnotplayer = not UnitIsUnit("player", unit)
669 | if (GetNumTalentTabs(isnotplayer) > 0) then
670 | data = new()
671 |
672 | for tab = 1, GetNumTalentTabs(isnotplayer) do
673 | local tree = new()
674 | local _
675 | tree.name, tree.icon, _, tree.background = GetTalentTabInfo(tab, isnotplayer)
676 | tinsert(data, tree)
677 |
678 | tree.list = new()
679 | for i = 1,GetNumTalents(tab, isnotplayer) do
680 | local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer)
681 | if (name) then
682 | local entry = new()
683 | entry.name = name
684 | entry.icon = icon
685 | entry.tier = tier
686 | entry.column = column
687 | entry.maxRank = maxRank
688 | entry.index = i
689 | entry.treeIndex = tab
690 | tinsert(tree.list, entry)
691 | if (not data.list) then
692 | data.list = new()
693 | end
694 | data.list[name] = entry
695 | end
696 | end
697 | end
698 |
699 | if (next(data)) then
700 | lib.classTalentData[class] = data
701 |
702 | --for guid,r in pairs(lib.roster) do
703 | -- if (r.class == class and r.talents) then
704 | -- -- We picked up class talent data for a class after receiving talents for them via comms
705 | -- -- So, we fire an Update event for any members of the class we already have so that
706 | -- -- talents can now be interpreted correctly.
707 | -- local spec, n1, n2, n3 = TalentWeight(r.talents[r.active], class)
708 | -- lib.events:Fire("LibGroupTalents_Update", guid, unit, spec, n1, n2, n3)
709 | -- end
710 | --end
711 |
712 | local classStorageStrings = lib.pendingStorageStrings[class]
713 | if (classStorageStrings) then
714 | local unitGUID = UnitGUID(unit)
715 | for guid, str in pairs(classStorageStrings) do
716 | if (guid ~= unitGUID) then
717 | lib:SetStorageString(str)
718 | end
719 | end
720 | lib.pendingStorageStrings[class] = del(lib.pendingStorageStrings[class])
721 | end
722 | else
723 | deepDel(data)
724 | end
725 | end
726 | end
727 | end
728 | end
729 |
730 | -- GetTreeNames
731 | function lib:GetTreeNames(class)
732 | local info = self.classTalentData[class]
733 | if (info) then
734 | return info[1].name, info[2].name, info[3].name
735 | end
736 | end
737 |
738 | -- GetTreeIcons
739 | function lib:GetTreeIcons(class)
740 | local info = self.classTalentData[class]
741 | if (info) then
742 | return info[1].icon, info[2].icon, info[3].icon
743 | end
744 | end
745 |
746 | -- ReadTalentGroup
747 | local function ReadTalentGroup(isnotplayer, group, class)
748 | local numTabs = GetNumTalentTabs(isnotplayer)
749 | if (numTabs and numTabs >= 3 and GetNumTalents(1, isnotplayer) > 0) then
750 | local ctd = lib.classTalentData[class]
751 | --[===[@debug@
752 | assert(ctd and ctd[1] and ctd[2] and ctd[3])
753 | assert(ctd[1].list and ctd[2].list and ctd[3].list)
754 | --@end-debug@]===]
755 |
756 | local n = new()
757 | for tab = 1, numTabs do
758 | local branchLength = GetNumTalents(tab, isnotplayer, nil, group)
759 | if (branchLength ~= #ctd[tab].list) then
760 | -- Tab tree size is not what we expected for this class
761 | del(n)
762 | return
763 | end
764 |
765 | local t = new()
766 | local trim
767 | for i = 1,branchLength do
768 | local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer, nil, group)
769 | tinsert(t, currentRank)
770 | if (currentRank > 0) then
771 | trim = i -- We strip off trailing zeros from talent strings to save storage space
772 | end
773 | end
774 |
775 | tinsert(n, table.concat(t, nil, 1, trim or 0))
776 | del(t)
777 | end
778 |
779 | return n
780 | end
781 | end
782 |
783 | -- TalentQuery_Ready
784 | function lib:TalentQuery_Ready_Outsider(e, name, realm, unit)
785 | self:TalentQuery_Ready(e, name, realm, unit)
786 | end
787 |
788 | -- TalentQuery_Ready
789 | function lib:TalentQuery_Ready(e, name, realm, unit)
790 | GetClassTalentData(unit)
791 |
792 | local guid = unit and UnitGUID(unit)
793 | local r = guid and self.roster[guid]
794 | if (r) then
795 | local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
796 | local isnotplayer = not UnitIsUnit(unit, "player")
797 |
798 | if (GetTalentTabInfo(1, isnotplayer)) then
799 | local active = GetActiveTalentGroup(isnotplayer)
800 | local numActive = GetNumTalentGroups(isnotplayer)
801 | local listUnspent, invalid
802 | local talents = new()
803 |
804 | for group = 1,numActive do
805 | local n = ReadTalentGroup(isnotplayer, group, r.class)
806 | if (n and #n >= 3) then
807 | talents[group] = n
808 | else
809 | invalid = true
810 | break
811 | end
812 |
813 | local unspent = GetUnspentTalentPoints(isnotplayer, nil, group)
814 | if (unspent and unspent > 0) then
815 | if (not listUnspent) then
816 | listUnspent = new()
817 | end
818 | listUnspent[group] = unspent
819 | end
820 | end
821 |
822 | if (isnotplayer and (invalid or (listUnspent and listUnspent[active] or 0) > 0)) then
823 | -- Unit didn't have all their points spent in active group, so we'll have another look in 10 seconds
824 | -- Don't need to check when it's "player" because we get PLAYER_TALENT_UPDATE event on changes
825 | self:TriggerRefreshTalents(guid, 10)
826 | end
827 |
828 | if (not invalid) then
829 | if (active > numActive) then
830 | -- May be better to discard instead? We'll see
831 | active = 1
832 | end
833 | self:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
834 | end
835 | end
836 | end
837 | end
838 | TalentQuery.RegisterCallback(lib, "TalentQuery_Ready")
839 | TalentQuery.RegisterCallback(lib, "TalentQuery_Ready_Outsider")
840 |
841 | -- GetUnitTalentSpec
842 | function lib:GetUnitTalentSpec(unit, group)
843 | return self:GetGUIDTalentSpec(UnitGUID(unit), group)
844 | end
845 |
846 | -- GetGUIDTalentSpec
847 | function lib:GetGUIDTalentSpec(guid, group)
848 | local talents, r = GetGUIDTalentsRaw(guid, group)
849 | if (talents) then
850 | return TalentWeight(talents, r.class)
851 | end
852 | end
853 |
854 | -- CompareTalents
855 | local function CompareTalents(tree1, tree2)
856 | if ((tree1 ~= nil) ~= (tree2 ~= nil)) then
857 | return
858 | end
859 | if (tree1 and tree2 and #tree1 == #tree2) then
860 | for i = 1,#tree1 do
861 | if (tree1[i] ~= tree2[i]) then
862 | return
863 | end
864 | end
865 | return true
866 | end
867 | end
868 |
869 | -- OnReceiveTalents
870 | function lib:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
871 | local r = self.roster[guid]
872 | if (r) then
873 | if (active ~= r.active or numActive ~= r.numActive or not CompareTalents(talents and talents[active], r.talents and r.talents[r.active])) then
874 | local oldTalents
875 | if (r.talents) then
876 | oldTalents = r.talents[r.active]
877 | end
878 | del(r.unspent)
879 |
880 | r.talents = talents
881 | r.active = active
882 | r.numActive = numActive
883 | r.unspent = listUnspent
884 |
885 | local newSpec, n1, n2, n3 = TalentWeight(r.talents[active], r.class)
886 |
887 | local fired
888 | if (oldTalents) then
889 | -- For those cases when we didn't have the alternate talents for a player for whatever reason.
890 | -- Maybe they just picked up dual talent spec, or gated to trainer to respec.
891 | local oldSpec, o1, o2, o3 = TalentWeight(oldTalents, r.class)
892 |
893 | if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
894 | self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
895 | fired = true
896 | end
897 | end
898 |
899 | if (not fired) then
900 | self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
901 | end
902 | self:GetGUIDRole(guid, true)
903 |
904 | tinsert(self.batch, guid)
905 | CheckForUpdateComplete()
906 |
907 | oldTalents = del(oldTalents)
908 | return
909 | end
910 | end
911 | del(talents)
912 | end
913 |
914 | -- OnReceiveGlyphs
915 | function lib:OnReceiveGlyphs(guid, sender, glyphs)
916 | local r = self.roster[guid]
917 | if (r) then
918 | if (ValidateUnit(r, guid)) then
919 | local oldGlyphs
920 | if (r.glyphs) then
921 | oldGlyphs = r.glyphs[r.active]
922 | r.glyphs = del(r.glyphs)
923 | end
924 |
925 | r.glyphs = glyphs
926 |
927 | local newGlyphs = r.glyphs and r.glyphs[r.active]
928 | if (newGlyphs ~= oldGlyphs) then
929 | self.events:Fire("LibGroupTalents_GlyphUpdate", guid, r.unit)
930 | end
931 | return
932 | end
933 | end
934 |
935 | del(glyphs)
936 | end
937 |
938 | -- GetUnitGlyphs
939 | function lib:GetUnitGlyphs(unit, group)
940 | return self:GetGUIDGlyphs(UnitGUID(unit), group)
941 | end
942 |
943 | -- GetGUIDGlyphs
944 | function lib:GetGUIDGlyphs(guid, group)
945 | local r = self.roster[guid]
946 | if (r) then
947 | local g = r.glyphs and r.glyphs[group or r.active]
948 | if (g) then
949 | local temp = new(strsplit(",", g))
950 | for i,str in ipairs(temp) do
951 | temp[i] = tonumber(str)
952 | end
953 | local a, b, c, d, e, f = unpack(temp)
954 | del(temp)
955 | return a, b, c, d, e, f
956 | end
957 | end
958 | end
959 |
960 | -- UnitHasGlyph
961 | function lib:UnitHasGlyph(unit, glyphID, group)
962 | return lib:GUIDHasGlyph(UnitGUID(unit), glyphID, group)
963 | end
964 |
965 | -- GUIDHasGlyph
966 | function lib:GUIDHasGlyph(guid, glyphID, group)
967 | local ret
968 | local r = self.roster[guid]
969 | if (r) then
970 | local g = r.glyphs and r.glyphs[group or r.active]
971 | if (g) then
972 | local temp = new(strsplit(",", g))
973 | for i,str in ipairs(temp) do
974 | local id = tonumber(str)
975 | if (type(glyphID) == "number") then
976 | if (glyphID == id) then
977 | ret = true
978 | break
979 | end
980 | else
981 | if (glyphID == GetSpellInfo(id)) then
982 | ret = true
983 | break
984 | end
985 | end
986 | end
987 | del(temp)
988 | end
989 | end
990 | return ret
991 | end
992 |
993 | -- GLYPH_ADDED
994 | function lib:GLYPH_ADDED(index, a, b, c)
995 | self:RefreshPlayerGlyphs()
996 | end
997 |
998 | -- GLYPH_REMOVED
999 | function lib:GLYPH_REMOVED(index, a, b, c)
1000 | self:RefreshPlayerGlyphs()
1001 | end
1002 |
1003 | -- GLYPH_UPDATED
1004 | function lib:GLYPH_UPDATED(index, a, b, c)
1005 | self:RefreshPlayerGlyphs()
1006 | end
1007 |
1008 | -- RefreshPlayerGlyphs
1009 | function lib:RefreshPlayerGlyphs()
1010 | local guid = UnitGUID("player")
1011 | local r = self.roster[guid]
1012 | if (not r) then
1013 | return
1014 | end
1015 |
1016 | local glyphs = new()
1017 | local any
1018 | for talentGroup = 1,GetNumTalentGroups() do
1019 | local list = new()
1020 | for i = 1,GetNumGlyphSockets() do
1021 | local enabled, glyphType, glyphSpell, icon = GetGlyphSocketInfo(i, talentGroup)
1022 | if (enabled and glyphType and glyphSpell) then
1023 | tinsert(list, glyphSpell)
1024 | any = true
1025 | end
1026 | end
1027 | glyphs[talentGroup] = table.concat(list, ",")
1028 | del(list)
1029 | end
1030 |
1031 | local oldGlyphs = r.glyphs
1032 | if (any) then
1033 | r.glyphs = glyphs
1034 | else
1035 | del(glyphs)
1036 | end
1037 |
1038 | local change = (oldGlyphs and oldGlyphs[r.active]) ~= (r.glyphs and r.glyphs[r.active])
1039 | if (change) then
1040 | self:SendMyGlyphs()
1041 | self.events:Fire("LibGroupTalents_GlyphUpdate", guid, "player")
1042 | end
1043 |
1044 | del(oldGlyphs)
1045 | end
1046 |
1047 | -- PLAYER_TALENT_UPDATE
1048 | function lib:PLAYER_TALENT_UPDATE()
1049 | self:TriggerRefreshTalents(UnitGUID("player"), 2)
1050 | end
1051 |
1052 | -- UNIT_SPELLCAST_SUCCEEDED
1053 | function lib:UNIT_SPELLCAST_SUCCEEDED(unit, spell)
1054 | local newActiveGroup = specChangers[spell]
1055 | if (newActiveGroup) then
1056 | local guid = UnitGUID(unit)
1057 | local r = guid and self.roster[guid]
1058 | if (r) then
1059 | if (newActiveGroup == r.active) then
1060 | -- We obviously didn't see them switch from this set
1061 | self:GetGUIDRole(guid, true)
1062 | return
1063 | end
1064 |
1065 | if (r.talents) then
1066 | local oldSet = r.talents[r.active]
1067 | local newSet = r.talents[newActiveGroup]
1068 | if (oldSet and newSet) then
1069 | -- We have the other talent set, so no need to refresh anything. Just compare and notify
1070 | r.active = newActiveGroup
1071 |
1072 | local oldSpec, o1, o2, o3 = TalentWeight(oldSet, r.class)
1073 | local newSpec, n1, n2, n3 = TalentWeight(newSet, r.class)
1074 |
1075 | if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
1076 | self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
1077 | else
1078 | self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
1079 | end
1080 | self:GetGUIDRole(guid, true)
1081 | return
1082 | end
1083 | end
1084 |
1085 | -- If we get this far, then someone probably gated to respec
1086 | self:RefreshTalentsByGUID(guid)
1087 | end
1088 | end
1089 | end
1090 |
1091 | -- TriggerRefreshTalents
1092 | function lib:TriggerRefreshTalents(guid, delay)
1093 | if (not self.talentTimers) then
1094 | self.talentTimers = new()
1095 | end
1096 | if (guid) then
1097 | self.talentTimers[guid] = GetTime() + delay
1098 | frame:Show()
1099 | end
1100 | end
1101 |
1102 | -- RefreshTalentsByUnit
1103 | function lib:RefreshTalentsByUnit(unit)
1104 | local guid = UnitGUID(unit)
1105 | if (guid) then
1106 | self:RefreshTalentsByGUID(guid)
1107 | end
1108 | end
1109 |
1110 | -- RefreshTalentsByGUID
1111 | function lib:RefreshTalentsByGUID(guid)
1112 | local r = self.roster[guid]
1113 | if (not r) then
1114 | return
1115 | end
1116 | if (not ValidateUnit(r, guid)) then
1117 | return
1118 | end
1119 |
1120 | if (self.talentTimers) then
1121 | self.talentTimers[guid] = nil
1122 | end
1123 |
1124 | if (not self.talentThrottle) then
1125 | self.talentThrottle = {}
1126 | end
1127 | for guidThrottle,when in pairs(self.talentThrottle) do
1128 | if (when < GetTime() - 5) then
1129 | self.talentThrottle[guidThrottle] = nil
1130 | elseif (guid == guidThrottle) then
1131 | return
1132 | end
1133 | end
1134 | self.talentThrottle[guid] = GetTime()
1135 |
1136 | if (self.commQueried) then
1137 | self.commQueried[guid] = nil
1138 | if (not next(self.commQueried)) then
1139 | self.commQueried = del(self.commQueried)
1140 | end
1141 | end
1142 |
1143 | r.refresh = true
1144 | self:CheckForMissingTalents()
1145 |
1146 | if (UnitGUID("player") == guid) then
1147 | self:SendMyTalents()
1148 | end
1149 | end
1150 |
1151 | -- CheckForMissingTalents
1152 | function lib:CheckForMissingTalents()
1153 | local any
1154 | for guid,info in pairs(self.roster) do
1155 | local namerealm = RosterInfoFullName(info)
1156 | if (not info.talents or (not UnitIsVisible(namerealm) and UnitExists(namerealm)) or info.refresh) then
1157 | any = true
1158 | info.refresh = nil
1159 | self:GetUnitTalents(info.unit, true)
1160 | end
1161 | end
1162 |
1163 | if (any) then
1164 | lib.refreshCheckTimer = 15
1165 | frame:Show()
1166 | end
1167 | end
1168 |
1169 | do
1170 | local survivalOfTheFittest = GetSpellInfo(33853) -- Survival of the Fittest
1171 | local protectorOfThePack = GetSpellInfo(57873) -- Protector of the Pack
1172 | local dkBladeBarrier = GetSpellInfo(49182) -- Blade Barrier
1173 | local dkToughness = GetSpellInfo(49042) -- Toughness
1174 | local dkAnticipation = GetSpellInfo(55129) -- Anticipation
1175 |
1176 | -- GetUnitRole
1177 | function lib:GetUnitRole(unit, reset)
1178 | local guid = UnitGUID(unit)
1179 | if (guid) then
1180 | return self:GetGUIDRole(guid, reset)
1181 | end
1182 | end
1183 |
1184 | -- GetGUIDRole
1185 | function lib:GetGUIDRole(guid, reset)
1186 | local r = guid and self.roster[guid]
1187 | if (not r) then
1188 | return
1189 | end
1190 | if (r.role and not reset) then
1191 | return r.role
1192 | end
1193 | if (not ValidateUnit(r, guid)) then
1194 | return
1195 | end
1196 |
1197 | local class = r.class
1198 | local role
1199 |
1200 | local unit = r.unit
1201 | if (class == "ROGUE" or class == "HUNTER") then
1202 | role = "melee"
1203 | elseif (class == "MAGE" or class == "WARLOCK") then
1204 | role = "caster"
1205 | elseif (r.talents and r.talents[r.active]) then
1206 | if (class == "DEATHKNIGHT") then
1207 | local score = self:GUIDHasTalent(guid, dkBladeBarrier) and 1 or 0
1208 | score = score + (self:GUIDHasTalent(guid, dkToughness) and 1 or 0)
1209 | score = score + (self:GUIDHasTalent(guid, dkAnticipation) and 1 or 0)
1210 | role = score >= 2 and "tank" or "melee" -- Has 2 of the 3 tanking talents at least
1211 |
1212 | else
1213 | local specName, t1, t2, t3 = TalentWeight(r.talents[r.active], class)
1214 |
1215 | if (class == "PRIEST") then
1216 | role = ((t1 + t2) > t3) and "healer" or "caster"
1217 | elseif (class == "WARRIOR") then
1218 | role = ((t1 + t2) > t3) and "melee" or "tank"
1219 | else
1220 | local heavy = (t1 > t2 and t1 > t3 and 1) or (t2 > t1 and t2 > t3 and 2) or (t3 > t1 and t3 > t2 and 3) or 0
1221 | if (class == "PALADIN") then
1222 | role = heavy == 1 and "healer" or heavy == 2 and "tank" or heavy == 3 and "melee"
1223 |
1224 | elseif (class == "DRUID") then
1225 | if (heavy == 2) then
1226 | if (self:GUIDHasTalent(guid, survivalOfTheFittest) and self:GUIDHasTalent(guid, protectorOfThePack)) then
1227 | role = "tank"
1228 | else
1229 | role = "melee"
1230 | end
1231 | else
1232 | role = heavy == 1 and "caster" or "healer"
1233 | end
1234 |
1235 | elseif (class == "SHAMAN") then
1236 | role = heavy == 1 and "caster" or heavy == 2 and "melee" or heavy == 3 and "healer"
1237 | end
1238 | end
1239 | end
1240 | end
1241 |
1242 | local oldrole = r.role
1243 | r.role = role
1244 |
1245 | if (role and role ~= oldrole) then
1246 | self.events:Fire("LibGroupTalents_RoleChange", guid, unit, role, oldrole)
1247 | end
1248 | return role
1249 | end
1250 | end
1251 |
1252 | -- GetUnitTalents
1253 | function lib:GetUnitTalents(unit, refetch)
1254 | local guid = UnitGUID(unit)
1255 | if (not guid) then
1256 | return
1257 | end
1258 | return self:GetGUIDTalents(guid, refetch)
1259 | end
1260 |
1261 | -- CanCommQuery
1262 | local function CanCommQuery(guid)
1263 | if (not lib.commQueried or not lib.commQueried[guid]) then
1264 | if (not lib.commQueried) then
1265 | lib.commQueried = new()
1266 | end
1267 | lib.commQueried[guid] = true
1268 | return true
1269 | end
1270 | end
1271 |
1272 | -- GetGUIDTalents
1273 | function lib:GetGUIDTalents(guid, refetch)
1274 | local r = self.roster[guid]
1275 | if (not r) then
1276 | return
1277 | end
1278 | if (not ValidateUnit(r, guid)) then
1279 | return
1280 | end
1281 |
1282 | local unit = r.unit
1283 | local name, realm = UnitName(unit)
1284 | local activeTalents = r.talents and r.talents[r.active]
1285 |
1286 | if (activeTalents) then
1287 | -- If someone is out of sight, we won't catch their talent swap spell cast, so we'll invalidate them here and recheck talents
1288 | if ((not UnitIsVisible(unit) and UnitIsConnected(unit)) or (self.outOfSight and self.outOfSight[guid])) then
1289 | if (not r.version) then
1290 | refetch = true
1291 | end
1292 | end
1293 | end
1294 |
1295 | if (not activeTalents or refetch) then
1296 | if (UnitIsUnit("player", unit)) then
1297 | self:RefreshPlayerGlyphs()
1298 | self:TalentQuery_Ready(nil, name, nil, unit)
1299 |
1300 | elseif ((UnitInRaid(unit) or UnitInParty(unit)) and UnitIsConnected(unit)) then
1301 | TalentQuery:Query(unit)
1302 |
1303 | local namerealm = UnitFullName(unit)
1304 | if (not r.talents and not r.requested) then
1305 | -- Don't need to query on a 'refetch' because they'll send changes anyway via comms
1306 | local skipGlyphs
1307 | if (not UnitIsVisible(unit) or not CanInspect(unit)) then
1308 | if (r.version) then
1309 | if (CanCommQuery(guid)) then
1310 | -- We request talents via comms for anyone that may be out of inspect range
1311 | self:SendCommMessage("REQUESTTALENTS", namerealm)
1312 | r.requested = true
1313 | skipGlyphs = true
1314 | end
1315 | end
1316 | end
1317 | end
1318 |
1319 | if (not r.glyphs and not skipGlyphs) then
1320 | if (r.version and r.version >= 15) then
1321 | if (CanCommQuery(guid)) then
1322 | -- They're in range to inspect, but we'll still want to ask for their glyphs
1323 | self:SendCommMessage("REQUESTGLYPHS", namerealm)
1324 | end
1325 | end
1326 | end
1327 | end
1328 |
1329 | if (self.outOfSight) then
1330 | self.outOfSight[guid] = nil
1331 | end
1332 | end
1333 |
1334 | return activeTalents
1335 | end
1336 |
1337 | -- SendCommMessage
1338 | function lib:SendCommMessage(msg, target, channel)
1339 | if (msg) then
1340 | if (ChatThrottleLib) then
1341 | ChatThrottleLib:SendAddonMessage("NORMAL", MAJOR, msg, channel or "WHISPER", target)
1342 | else
1343 | SendAddonMessage(MAJOR, msg, channel or "WHISPER", target)
1344 | end
1345 | end
1346 | end
1347 |
1348 | -- Throttle - Purposely local to here
1349 | -- Abuse prevention. Yes, who would abuse addon comms? Noone would make a macro to crash a mod user would they. Right?
1350 | -- Well, this one time, at band camp. Someone thought it was super funny to make a macro that DCd PallyPower users
1351 | local throttle
1352 | local function Throttle(sender, key)
1353 | if (not throttle) then
1354 | throttle = {}
1355 | end
1356 | local s = throttle[sender]
1357 | if (not s) then
1358 | s = {}
1359 | throttle[sender] = s
1360 | end
1361 |
1362 | if ((s[key] or 0) < GetTime() - 4.5) then
1363 | -- Same message key only allowable once every 4.5 secs from 1 person (Respec cast time is 5 seconds)
1364 | s[key] = GetTime()
1365 | return true
1366 | end
1367 | end
1368 |
1369 | -- CHAT_MSG_ADDON
1370 | function lib:CHAT_MSG_ADDON(prefix, msg, channel, sender)
1371 | if (prefix == MAJOR) then
1372 | if (sender == UnitName("player")) then
1373 | return
1374 | elseif (not UnitInRaid(sender) and not UnitInParty(sender)) then
1375 | return
1376 | end
1377 |
1378 | local guid = UnitGUID(sender)
1379 | if (not guid) then
1380 | return
1381 | end
1382 | local r = self.roster[guid]
1383 | if (not r) then
1384 | return
1385 | end
1386 |
1387 | local cmd, str = msg:match("^(%a+) *(.*)$")
1388 | if (not cmd) then
1389 | return
1390 | end
1391 |
1392 | if (cmd == "TALENTS") then
1393 | -- Talents come in form of:
1394 | local t = r.talents
1395 | r.talents = nil -- SetStorageString won't overwrite talents usually, but we want it to here, without providing a means to do it easily with an arg from a mod
1396 | if (not self:SetStorageString(str, sender)) then
1397 | r.talents = t
1398 | else
1399 | deepDel(t)
1400 | end
1401 |
1402 | elseif (cmd == "GLYPHS") then
1403 | local invalid
1404 | local pages = new(strsplit(";", str))
1405 | local glyphs = new()
1406 | for page,info in ipairs(pages) do
1407 | local list = new(strsplit(",", info))
1408 | local tab = tonumber(tremove(list, 1))
1409 | if (tab) then
1410 | glyphs[tab] = table.concat(list, ",")
1411 | del(list)
1412 | else
1413 | invalid = true
1414 | del(glyphs)
1415 | del(list)
1416 | break
1417 | end
1418 | end
1419 | if (not invalid) then
1420 | self:OnReceiveGlyphs(guid, sender, glyphs)
1421 | end
1422 | del(pages)
1423 |
1424 | elseif (cmd == "REQUESTTALENTS") then
1425 | if (Throttle(sender, "REQUESTTALENTS")) then
1426 | if ((r.version or 0) < 39) then
1427 | if (lib.sentToOld and lib.sentToOld[guid]) then
1428 | return
1429 | end
1430 | if (not lib.sentToOld) then
1431 | lib.sentToOld = new()
1432 | end
1433 | lib.sentToOld[guid] = time()
1434 | end
1435 |
1436 | self:SendMyTalents(sender)
1437 | self:SendMyGlyphs(sender)
1438 | end
1439 |
1440 | elseif (cmd == "REQUESTGLYPHS") then
1441 | if (Throttle(sender, "REQUESTGLYPHS")) then
1442 | self:SendMyGlyphs(sender)
1443 | end
1444 |
1445 | elseif (cmd == "HELLO") then
1446 | r.version = tonumber(str)
1447 | if (channel ~= "WHISPER") then
1448 | if (lib.sentToOld) then
1449 | lib.sentToOld[guid] = nil
1450 | end
1451 | if (UnitIsConnected(sender) and Throttle(sender, "HELLO")) then
1452 | self:SendCommMessage("HELLO "..MINOR, sender)
1453 | self:SendMyGlyphs(sender)
1454 | end
1455 | end
1456 | end
1457 | end
1458 | end
1459 |
1460 | -- SendMy
1461 | local function SendMy(sender, str)
1462 | if (sender) then
1463 | if (UnitIsConnected(sender)) then
1464 | lib:SendCommMessage(str, sender)
1465 | end
1466 | else
1467 | for guid,info in pairs(lib.roster) do
1468 | if (info.version) then
1469 | local namerealm = RosterInfoFullName(info)
1470 | if (UnitIsConnected(namerealm)) then
1471 | lib:SendCommMessage(str, namerealm)
1472 | end
1473 | end
1474 | end
1475 | end
1476 | end
1477 |
1478 | -- SendMyTalents
1479 | function lib:SendMyTalents(sender)
1480 | if (sender or self:UserCount() > 0) then
1481 | local str = self:GetGUIDStorageString(UnitGUID("player"))
1482 | if (str) then
1483 | SendMy(sender, "TALENTS "..str)
1484 | end
1485 | end
1486 | end
1487 |
1488 | -- SendMyGlyphs
1489 | function lib:SendMyGlyphs(sender)
1490 | if (sender or self:UserCount() > 0) then
1491 | local r = self.roster[UnitGUID("player")]
1492 | if (r and r.glyphs) then
1493 | local str = "GLYPHS "
1494 | local i = 1
1495 | for tab,g in pairs(r.glyphs) do
1496 | local temp = format("%d,%s", tab, g)
1497 | str = str .. (i > 1 and ";" or "") .. temp
1498 | i = i + 1
1499 | end
1500 | SendMy(sender, str)
1501 | end
1502 | end
1503 | end
1504 |
1505 | -- UserCount
1506 | function lib:UserCount()
1507 | local count = 0
1508 | for guid,info in pairs(self.roster) do
1509 | if (info.version and not UnitIsUnit("player", RosterInfoFullName(info))) then
1510 | count = count + 1
1511 | end
1512 | end
1513 | return count
1514 | end
1515 |
1516 | -- UnitHasTalent
1517 | -- eg: lib:UnitHasTalent("player", GetSpellInfo(talentSpellID))
1518 | -- Returns: nil, or number of points spent into talent
1519 | -- If the talent group is not specified, then the active talent group is used
1520 | function lib:UnitHasTalent(unit, talentName, group)
1521 | return unit and self:GUIDHasTalent(UnitGUID(unit), talentName, group)
1522 | end
1523 |
1524 | -- GUIDHasTalent
1525 | -- Returns: nil, or number of points spent into talent
1526 | function lib:GUIDHasTalent(guid, talentName, group)
1527 | local talents, r = GetGUIDTalentsRaw(guid, group)
1528 | if (talents and r.class) then
1529 | local data = self.classTalentData[r.class]
1530 | if (data) then
1531 | local info = data.list and data.list[talentName]
1532 | if (info) then
1533 | local str = talents[info.treeIndex]
1534 | if (str) then
1535 | local amount = (str:byte(info.index) or 48) - 48
1536 | return (amount or 0) > 0 and amount or nil
1537 | end
1538 | end
1539 | end
1540 | end
1541 | end
1542 |
1543 | -- GetClassTalentInfo
1544 | function lib:GetClassTalentInfo(class, talentName)
1545 | -- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
1546 | local data = self.classTalentData[class]
1547 | if (data) then
1548 | local info = data.list and data.list[talentName]
1549 | if (info) then
1550 | return info.maxRank, info.icon, info.treeIndex, info.column, info.tier, info.index
1551 | end
1552 | end
1553 | end
1554 |
1555 | -- GetActiveTalentGroup
1556 | function lib:GetActiveTalentGroup(unit)
1557 | if (UnitIsUnit(unit, "player")) then
1558 | return GetActiveTalentGroup()
1559 | else
1560 | local guid = unit and UnitGUID(unit)
1561 | local r = guid and self.roster[guid]
1562 | return r and r.active or nil
1563 | end
1564 | end
1565 |
1566 | -- GetNumTalentGroups
1567 | function lib:GetNumTalentGroups(unit)
1568 | if (UnitIsUnit(unit, "player")) then
1569 | return GetNumTalentGroups()
1570 | else
1571 | local guid = unit and UnitGUID(unit)
1572 | local r = guid and self.roster[guid]
1573 | return r and r.numActive or nil
1574 | end
1575 | end
1576 |
1577 | -- GetTalentTabInfo
1578 | function lib:GetTalentTabInfo(unit, tab, group)
1579 | if (UnitIsUnit(unit, "player")) then
1580 | return GetTalentTabInfo(tab, nil, nil, group or GetActiveTalentGroup())
1581 | else
1582 | local guid = unit and UnitGUID(unit)
1583 | local r = guid and self.roster[guid]
1584 | if (r and r.class) then
1585 | local ctd = self.classTalentData[r.class]
1586 | if (ctd and tab >= 1 and tab <= #ctd) then
1587 | local spec, c1, c2, c3 = self:GetGUIDTalentSpec(guid, group)
1588 | return ctd[tab].name, ctd[tab].icon, tab == 1 and c1 or tab == 2 and c2 or c3, ctd[tab].background, 0
1589 | end
1590 | end
1591 | end
1592 | end
1593 |
1594 | -- GetNumTalents
1595 | function lib:GetNumTalents(unit, tab)
1596 | if (UnitIsUnit(unit, "player")) then
1597 | return GetNumTalents(tab)
1598 | else
1599 | local _, class = UnitClass(unit)
1600 | if (class) then
1601 | local ctd = self.classTalentData[class]
1602 | if (ctd and tab >= 1 and tab <= #ctd) then
1603 | return #ctd[tab].list
1604 | end
1605 | end
1606 | end
1607 | end
1608 |
1609 | -- GetTalentInfo
1610 | function lib:GetTalentInfo(unit, tab, index, group)
1611 | if (UnitIsUnit(unit, "player")) then
1612 | return GetTalentInfo(tab, index, nil, nil, group or GetActiveTalentGroup())
1613 | else
1614 | local _, class = UnitClass(unit)
1615 | if (class) then
1616 | local ctd = self.classTalentData[class]
1617 | if (ctd and tab >= 1 and tab <= #ctd) then
1618 | local info = ctd[tab].list[index]
1619 | if (info) then
1620 | local spent = self:UnitHasTalent(unit, info.name, group)
1621 | return info.name, info.icon, info.tier, info.column, spent or 0, info.maxRank
1622 | end
1623 | end
1624 | end
1625 | end
1626 | end
1627 |
1628 | -- GetNumTalentTabs
1629 | function lib:GetNumTalentTabs()
1630 | return GetNumTalentTabs()
1631 | end
1632 |
1633 | -- GetNumTalentTabs
1634 | function lib:GetUnspentTalentPoints(unit, group)
1635 | if (UnitIsUnit(unit, "player")) then
1636 | return GetUnspentTalentPoints(nil, nil, group)
1637 | else
1638 | local guid = unit and UnitGUID(unit)
1639 | local r = guid and self.roster[guid]
1640 | if (r) then
1641 | return r.unspent and r.unspent[group or r.active or 1]
1642 | end
1643 | end
1644 | end
1645 |
1646 | -- GetTalentCount
1647 | function lib:GetTalentCount()
1648 | local count, missing = 0, 0
1649 | for guid,info in pairs(self.roster) do
1650 | if (info.talents) then
1651 | count = count + 1
1652 | else
1653 | missing = missing + 1
1654 | end
1655 | end
1656 | return count, missing
1657 | end
1658 |
1659 | -- GetTalentMissingNames
1660 | function lib:GetTalentMissingNames()
1661 | local list = new()
1662 | for unit in self:IterateRoster() do
1663 | local guid = UnitGUID(unit)
1664 | local r = guid and self.roster[guid]
1665 | if (not r or not r.talents) then
1666 | tinsert(list, UnitFullName(unit))
1667 | end
1668 | end
1669 | local ret
1670 | if (next(list)) then
1671 | ret = table.concat(list, ",")
1672 | end
1673 | del(list)
1674 | return ret
1675 | end
1676 |
1677 | -- PurgeAndRescanTalents
1678 | function lib:PurgeAndRescanTalents()
1679 | if (self.roster) then
1680 | wipe(self.pendingStorageStrings)
1681 | for guid,info in pairs(self.roster) do
1682 | info.talents = del(info.talents)
1683 | info.active = nil
1684 | info.numActive = nil
1685 | info.requested = nil
1686 | end
1687 | end
1688 | self:CheckForMissingTalents()
1689 | end
1690 |
1691 | -- Roster iterator
1692 | do
1693 | local function iter(t)
1694 | local key = t.id
1695 | local ret
1696 | if (t.mode == "raid") then
1697 | if (key > t.r) then
1698 | del(t)
1699 | return nil
1700 | end
1701 | ret = "raid"..key
1702 | else
1703 | if (key > t.p) then
1704 | del(t)
1705 | return nil
1706 | end
1707 | ret = key == 0 and "player" or "party"..key
1708 | end
1709 | t.id = key + 1
1710 | return ret
1711 | end
1712 |
1713 | -- IterateRoster
1714 | function lib:IterateRoster()
1715 | local t = new()
1716 | if (GetNumRaidMembers() > 0) then
1717 | t.mode = "raid"
1718 | t.id = 1
1719 | t.r = GetNumRaidMembers()
1720 | else
1721 | t.mode = "party"
1722 | t.id = 0
1723 | t.p = GetNumPartyMembers()
1724 | end
1725 | return iter, t
1726 | end
1727 | end
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Libs/LibGroupTalents-1.0/LibTalentQuery-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Name: LibTalentQuery-1.0
3 | Revision: $Rev: 84 $
4 | Author: Rich Martel (richmartel@gmail.com)
5 | Documentation: http://wowace.com/wiki/LibTalentQuery-1.0
6 | SVN: svn://svn.wowace.com/wow/libtalentquery-1-0/mainline/trunk
7 | Description: Library to help with querying unit talents
8 | Dependancies: LibStub, CallbackHandler-1.0
9 | License: LGPL v2.1
10 |
11 | Example Usage:
12 | local TalentQuery = LibStub:GetLibrary("LibTalentQuery-1.0")
13 | TalentQuery.RegisterCallback(self, "TalentQuery_Ready")
14 |
15 | local raidTalents = {}
16 | ...
17 | TalentQuery:Query(unit)
18 | ...
19 | function MyAddon:TalentQuery_Ready(e, name, realm, unitid)
20 | local isnotplayer = not UnitIsUnit(unitid, "player")
21 | local spec = {}
22 | for tab = 1, GetNumTalentTabs(isnotplayer) do
23 | local treename, _, pointsspent = GetTalentTabInfo(tab, isnotplayer)
24 | tinsert(spec, pointsspent)
25 | end
26 | raidTalents[UnitGUID(unitid)] = spec
27 | end
28 | ]]
29 |
30 | local MAJOR, MINOR = "LibTalentQuery-1.0", 90000 + tonumber(("$Rev: 84 $"):match("(%d+)"))
31 |
32 | local lib = LibStub:NewLibrary(MAJOR, MINOR)
33 | if not lib then return end
34 |
35 | local INSPECTDELAY = 1
36 | local INSPECTTIMEOUT = 5
37 | if not lib.events then
38 | lib.events = LibStub("CallbackHandler-1.0"):New(lib)
39 | end
40 |
41 | local validateTrees
42 | local enteredWorld = IsLoggedIn()
43 | local frame = lib.frame
44 | if not frame then
45 | frame = CreateFrame("Frame", MAJOR .. "_Frame")
46 | lib.frame = frame
47 | end
48 | frame:UnregisterAllEvents()
49 | frame:RegisterEvent("INSPECT_TALENT_READY")
50 | frame:RegisterEvent("PLAYER_ENTERING_WORLD")
51 | frame:RegisterEvent("PLAYER_LEAVING_WORLD")
52 | frame:RegisterEvent("PLAYER_LOGIN")
53 | frame:SetScript("OnEvent", function(this, event, ...)
54 | return lib[event](lib, ...)
55 | end)
56 |
57 | do
58 | local lastUpdateTime = 0
59 | frame:SetScript("OnUpdate", function(this, elapsed)
60 | lastUpdateTime = lastUpdateTime + elapsed
61 | if lastUpdateTime > INSPECTDELAY then
62 | lib:CheckInspectQueue()
63 | lastUpdateTime = 0
64 | end
65 | end)
66 | frame:Hide()
67 | end
68 |
69 | local inspectQueue = lib.inspectQueue or {}
70 | lib.inspectQueue = inspectQueue
71 | local garbageQueue = lib.garbageQueue or {} -- Added a second queue to things. Inspects that initially fail are now
72 | lib.garbageQueue = garbageQueue -- thrown into second queue will will be processed once main queue is empty
73 |
74 | if next(inspectQueue) then
75 | frame:Show()
76 | end
77 |
78 | local UnitIsPlayer = _G.UnitIsPlayer
79 | local UnitName = _G.UnitName
80 | local UnitExists = _G.UnitExists
81 | local UnitGUID = _G.UnitGUID
82 | local GetNumRaidMembers = _G.GetNumRaidMembers
83 | local GetNumPartyMembers = _G.GetNumPartyMembers
84 | local UnitIsVisible = _G.UnitIsVisible
85 | local UnitIsConnected = _G.UnitIsConnected
86 | local UnitCanAttack = _G.UnitCanAttack
87 | local CanInspect = _G.CanInspect
88 |
89 | local function UnitFullName(unit)
90 | local name, realm = UnitName(unit)
91 | local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
92 | return namerealm
93 | end
94 |
95 | -- GuidToUnitID
96 | local function GuidToUnitID(guid)
97 | local prefix, min, max = "raid", 1, GetNumRaidMembers()
98 | if max == 0 then
99 | prefix, min, max = "party", 0, GetNumPartyMembers()
100 | end
101 |
102 | -- Prioritise getting direct units first because other players targets
103 | -- can change between notify and event which can bugger things up
104 | for i = min, max do
105 | local unit = i == 0 and "player" or prefix .. i
106 | if (UnitGUID(unit) == guid) then
107 | return unit
108 | end
109 | end
110 |
111 | -- This properly detects target units
112 | if (UnitGUID("target") == guid) then
113 | return "target"
114 | elseif (UnitGUID("focus") == guid) then
115 | return "focus"
116 | elseif (UnitGUID("mouseover") == guid) then
117 | return "mouseover"
118 | end
119 |
120 | for i = min, max + 3 do
121 | local unit
122 | if i == 0 then
123 | unit = "player"
124 | elseif i == max + 1 then
125 | unit = "target"
126 | elseif i == max + 2 then
127 | unit = "focus"
128 | elseif i == max + 3 then
129 | unit = "mouseover"
130 | else
131 | unit = prefix .. i
132 | end
133 | if (UnitGUID(unit .. "target") == guid) then
134 | return unit .. "target"
135 | elseif (i <= max and UnitGUID(unit.."pettarget") == guid) then
136 | return unit .. "pettarget"
137 | end
138 | end
139 | return nil
140 | end
141 |
142 | -- Query
143 | function lib:Query(unit)
144 | if (UnitLevel(unit) < 10 or UnitName(unit) == UNKNOWN) then
145 | return
146 | end
147 |
148 | self.lastQueuedInspectReceived = nil
149 | if UnitIsUnit(unit, "player") then
150 | self.events:Fire("TalentQuery_Ready", UnitName("player"), nil, "player")
151 | else
152 | if type(unit) ~= "string" then
153 | error(("Bad argument #2 to 'Query'. Expected %q, received %q (%s)"):format("string", type(unit), tostring(unit)), 2)
154 | elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
155 | error(("Bad argument #2 to 'Query'. %q is not a valid player unit"):format(tostring(unit)), 2)
156 | elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
157 | error(("Bad argument #2 to 'Query'. %q does not require a server query before reading talents"):format("player"), 2)
158 | else
159 | local name = UnitFullName(unit)
160 | if (not inspectQueue[name]) then
161 | inspectQueue[name] = UnitGUID(unit)
162 | garbageQueue[name] = nil
163 | end
164 | frame:Show()
165 | end
166 | end
167 | end
168 |
169 | -- CheckInspectQueue
170 | -- Originally, it would wait until no pending NotifyInspect() were expected, and then do it's own.
171 | -- It was also only bother looking at ready results if it had triggered the Notify for that occasion.
172 | -- For the changes I've done, no assumption is made about which mod is performing NotifyInspect().
173 | -- We note the name, unit, time of any inspects done whether from this queue or any other source,
174 | -- we remove from our queue any we were expecting, and use a seperate event in case extra talent
175 | -- info is any time wanted (opportunistic refreshes etc) - Zeksie, 20th May 2009
176 | function lib:CheckInspectQueue()
177 | if (_G.InspectFrame and _G.InspectFrame:IsShown()) then
178 | return
179 | end
180 |
181 | if (not self.lastInspectTime or self.lastInspectTime < GetTime() - INSPECTTIMEOUT) then
182 | self.lastInspectPending = 0
183 | end
184 |
185 | if (self.lastInspectPending > 0 or not enteredWorld) then
186 | return
187 | end
188 |
189 | if (self.lastQueuedInspectReceived and self.lastQueuedInspectReceived < GetTime() - 60) then
190 | -- No queued results received for a minute, so purge the queue as invalid and move on with our lives
191 | self.lastQueuedInspectReceived = nil
192 | inspectQueue = {}
193 | lib.inspectQueue = inspectQueue
194 | garbageQueue = {}
195 | lib.garbageQueue = garbageQueue
196 | frame:Hide()
197 | return
198 | end
199 |
200 | for name,guid in pairs(inspectQueue) do
201 | local unit = GuidToUnitID(guid)
202 | if (not unit) then
203 | inspectQueue[name] = nil
204 | else
205 | if (UnitIsVisible(unit) and UnitIsConnected(unit) and not UnitCanAttack("player", unit) and not UnitCanAttack(unit, "player") and CanInspect(unit) and UnitClass(unit)) then
206 | NotifyInspect(unit)
207 | break
208 | else
209 | garbageQueue[name] = guid -- Not available, throw into secondary queue and continue
210 | inspectQueue[name] = nil
211 | end
212 | end
213 | end
214 |
215 | if (not next(inspectQueue)) then
216 | if (next(garbageQueue)) then
217 | -- Retry initially failed inspects
218 | lib.inspectQueue = garbageQueue
219 | inspectQueue = lib.inspectQueue
220 | lib.garbageQueue = {}
221 | garbageQueue = lib.garbageQueue
222 | else
223 | frame:Hide()
224 | end
225 | end
226 | end
227 |
228 | -- NotifyInspect
229 | if not lib.NotifyInspect then -- don't hook twice
230 | hooksecurefunc("NotifyInspect", function(...) return lib:NotifyInspect(...) end)
231 | end
232 | function lib:NotifyInspect(unit)
233 | if (not (UnitExists(unit) and UnitIsVisible(unit) and UnitIsConnected(unit) and CheckInteractDistance(unit, 4))) then
234 | return
235 | end
236 | self.lastInspectUnit = unit
237 | self.lastInspectGUID = UnitGUID(unit)
238 | self.lastInspectTime = GetTime()
239 | self.lastInspectName = UnitFullName(unit)
240 | self.lastInspectPending = self.lastInspectPending + 1
241 | local isnotplayer = not UnitIsUnit("player", unit)
242 | self.lastInspectTree = GetTalentTabInfo(1, isnotplayer) -- Talent tree names are available immediately
243 | end
244 |
245 | -- Reset
246 | function lib:Reset()
247 | self.lastInspectPending = 0
248 | self.lastInspectUnit = nil
249 | self.lastInspectTime = nil
250 | self.lastInspectName = nil
251 | self.lastInspectGUID = nil
252 | self.lastInspectTree = nil
253 | end
254 |
255 | -- INSPECT_TALENT_READY
256 | function lib:INSPECT_TALENT_READY()
257 | self.lastInspectPending = self.lastInspectPending - 1
258 |
259 | -- Results are valid only when we have received as many events as we have posted notifies
260 | if (self.lastInspectName and self.lastInspectPending == 0) then
261 | -- Check unit ID is still pointing to same actual unit
262 | if (UnitGUID(self.lastInspectUnit) == self.lastInspectGUID) then
263 | local guid = inspectQueue[self.lastInspectName]
264 | inspectQueue[self.lastInspectName] = nil
265 |
266 | local name, realm = strsplit("-", self.lastInspectName)
267 |
268 | self.lastQueuedInspectReceived = GetTime()
269 |
270 | -- Notify of expected talent results
271 | local isnotplayer = not UnitIsUnit("player", self.lastInspectName)
272 | local group = GetActiveTalentGroup(isnotplayer)
273 | local tree1, _, spent1 = GetTalentTabInfo(1, isnotplayer, nil, group)
274 | if (tree1 ~= self.lastInspectTree) then
275 | -- Expected talent tree name to be the same as it was when we triggered the NotifyInspect()
276 | garbageQueue[self.lastInspectName] = self.lastInspectGUID
277 | self:Reset()
278 | self:CheckInspectQueue()
279 | return
280 |
281 | elseif (validateTrees) then
282 | -- Double checking here. Check the tree name matches what we expect for this class
283 | local _, class = UnitClass(self.lastInspectUnit)
284 | if (tree1 ~= validateTrees[class]) then
285 | garbageQueue[self.lastInspectName] = self.lastInspectGUID
286 | self:Reset()
287 | self:CheckInspectQueue()
288 | return
289 | end
290 | end
291 |
292 | local tree2, _, spent2 = GetTalentTabInfo(2, isnotplayer, nil, group)
293 | local tree3, _, spent3 = GetTalentTabInfo(3, isnotplayer, nil, group)
294 | if ((spent1 or 0) + (spent2 or 0) + (spent3 or 0) > 0) then
295 | if (guid) then
296 | -- It was in our queue
297 | self.events:Fire("TalentQuery_Ready", name, realm, self.lastInspectUnit)
298 | else
299 | -- Also notify of non-expected ones, as it's entirely useful to refresh them if they're there
300 | -- It is up to the receiving applicating to determine whether they want to receive the information
301 | self.events:Fire("TalentQuery_Ready_Outsider", name, realm, self.lastInspectUnit)
302 | end
303 | else
304 | -- Tree came back with zero points spent, probably an issue while logging in
305 | garbageQueue[self.lastInspectName] = guid
306 | end
307 | end
308 |
309 | self:Reset()
310 | self:CheckInspectQueue()
311 | end
312 | end
313 |
314 | function lib:PLAYER_ENTERING_WORLD()
315 | -- We can't inspect other's talents until now
316 | -- We just get 0/0/0 back even though we get an INSPECT_TALENT_READY event
317 | enteredWorld = true
318 | end
319 |
320 | function lib:PLAYER_LEAVING_WORLD()
321 | enteredWorld = nil
322 | end
323 |
324 | function lib:PLAYER_LOGIN()
325 | validateTrees = {
326 | DRUID = "Balance",
327 | PRIEST = "Discipline",
328 | ROGUE = "Assassination",
329 | HUNTER = "Beast Mastery",
330 | WARLOCK = "Affliction",
331 | WARRIOR = "Arms",
332 | DEATHKNIGHT = "Blood",
333 | PALADIN = "Holy",
334 | SHAMAN = "Elemental",
335 | MAGE = "Arcane",
336 | }
337 |
338 | if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") then
339 | -- LibBabble-TalentTree-3.0 only loaded if present and not enUS
340 | local LBT = LibStub("LibBabble-TalentTree-3.0", true)
341 | if (not LBT) then
342 | LoadAddOn("LibBabble-TalentTree-3.0")
343 | LBT = LibStub("LibBabble-TalentTree-3.0", true)
344 | end
345 | LBT = LBT and LBT:GetLookupTable()
346 | if (LBT) then
347 | for class,tree1 in pairs(validateTrees) do
348 | validateTrees[class] = LBT[tree1]
349 | end
350 | else
351 | validateTrees = nil
352 | end
353 | end
354 |
355 | self.PLAYER_LOGIN = nil
356 | end
357 |
358 | lib:Reset()
359 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Libs/LibGroupTalents-1.0/lib.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Libs/LibStub/LibStub.lua:
--------------------------------------------------------------------------------
1 | -- $Id: LibStub.lua 103 2014-10-16 03:02:50Z mikk $
2 | -- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/addons/libstub/ for more info
3 | -- LibStub is hereby placed in the Public Domain
4 | -- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
5 | local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
6 | local LibStub = _G[LIBSTUB_MAJOR]
7 |
8 | -- Check to see is this version of the stub is obsolete
9 | if not LibStub or LibStub.minor < LIBSTUB_MINOR then
10 | LibStub = LibStub or {libs = {}, minors = {} }
11 | _G[LIBSTUB_MAJOR] = LibStub
12 | LibStub.minor = LIBSTUB_MINOR
13 |
14 | -- LibStub:NewLibrary(major, minor)
15 | -- major (string) - the major version of the library
16 | -- minor (string or number ) - the minor version of the library
17 | --
18 | -- returns nil if a newer or same version of the lib is already present
19 | -- returns empty library object or old library object if upgrade is needed
20 | function LibStub:NewLibrary(major, minor)
21 | assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
22 | minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
23 |
24 | local oldminor = self.minors[major]
25 | if oldminor and oldminor >= minor then return nil end
26 | self.minors[major], self.libs[major] = minor, self.libs[major] or {}
27 | return self.libs[major], oldminor
28 | end
29 |
30 | -- LibStub:GetLibrary(major, [silent])
31 | -- major (string) - the major version of the library
32 | -- silent (boolean) - if true, library is optional, silently return nil if its not found
33 | --
34 | -- throws an error if the library can not be found (except silent is set)
35 | -- returns the library object if found
36 | function LibStub:GetLibrary(major, silent)
37 | if not self.libs[major] and not silent then
38 | error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
39 | end
40 | return self.libs[major], self.minors[major]
41 | end
42 |
43 | -- LibStub:IterateLibraries()
44 | --
45 | -- Returns an iterator for the currently registered libraries
46 | function LibStub:IterateLibraries()
47 | return pairs(self.libs)
48 | end
49 |
50 | setmetatable(LibStub, { __call = LibStub.GetLibrary })
51 | end
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/Templates.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/libs/LibCompat-1.0/lib.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/libs/LibDBIcon-1.0/LibDBIcon-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Name: DBIcon-1.0
3 | Revision: $Rev: 13 $
4 | Author(s): Rabbit (rabbit.magtheridon@gmail.com)
5 | Description: Allows addons to register to recieve a lightweight minimap icon as an alternative to more heavy LDB displays.
6 | Dependencies: LibStub
7 | License: GPL v2 or later.
8 | ]]
9 |
10 | --[[
11 | Copyright (C) 2008-2010 Rabbit
12 |
13 | This program is free software; you can redistribute it and/or
14 | modify it under the terms of the GNU General Public License
15 | as published by the Free Software Foundation; either version 2
16 | of the License, or (at your option) any later version.
17 |
18 | This program is distributed in the hope that it will be useful,
19 | but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 | GNU General Public License for more details.
22 |
23 | You should have received a copy of the GNU General Public License
24 | along with this program; if not, write to the Free Software
25 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 | ]]
27 |
28 | -----------------------------------------------------------------------
29 | -- DBIcon-1.0
30 | --
31 | -- Disclaimer: Most of this code was ripped from Barrel but fixed, streamlined
32 | -- and cleaned up a lot so that it no longer sucks.
33 | --
34 |
35 | local DBICON10 = "LibDBIcon-1.0"
36 | local DBICON10_MINOR = tonumber(("$Rev: 13 $"):match("(%d+)"))
37 | if not LibStub then error(DBICON10 .. " requires LibStub.") end
38 | local ldb = LibStub("LibDataBroker-1.1", true)
39 | if not ldb then error(DBICON10 .. " requires LibDataBroker-1.1.") end
40 | local lib = LibStub:NewLibrary(DBICON10, DBICON10_MINOR)
41 | if not lib then return end
42 |
43 | lib.disabled = lib.disabled or nil
44 | lib.objects = lib.objects or {}
45 | lib.callbackRegistered = lib.callbackRegistered or nil
46 | lib.notCreated = lib.notCreated or {}
47 |
48 | function lib:IconCallback(event, name, key, value, dataobj)
49 | if lib.objects[name] then
50 | lib.objects[name].icon:SetTexture(dataobj.icon)
51 | end
52 | end
53 | if not lib.callbackRegistered then
54 | ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__icon", "IconCallback")
55 | lib.callbackRegistered = true
56 | end
57 |
58 | -- Tooltip code ripped from StatBlockCore by Funkydude
59 | local function getAnchors(frame)
60 | local x,y = frame:GetCenter()
61 | if not x or not y then return "TOPLEFT", "BOTTOMLEFT" end
62 | local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
63 | local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
64 | return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
65 | end
66 |
67 | local function onEnter(self)
68 | if self.isMoving then return end
69 | local obj = self.dataObject
70 | if obj.OnTooltipShow then
71 | GameTooltip:SetOwner(self, "ANCHOR_NONE")
72 | GameTooltip:SetPoint(getAnchors(self))
73 | obj.OnTooltipShow(GameTooltip)
74 | GameTooltip:Show()
75 | elseif obj.OnEnter then
76 | obj.OnEnter(self)
77 | end
78 | end
79 |
80 | local function onLeave(self)
81 | local obj = self.dataObject
82 | GameTooltip:Hide()
83 | if obj.OnLeave then obj.OnLeave(self) end
84 | end
85 |
86 | --------------------------------------------------------------------------------
87 |
88 | local minimapShapes = {
89 | ["ROUND"] = {true, true, true, true},
90 | ["SQUARE"] = {false, false, false, false},
91 | ["CORNER-TOPLEFT"] = {true, false, false, false},
92 | ["CORNER-TOPRIGHT"] = {false, false, true, false},
93 | ["CORNER-BOTTOMLEFT"] = {false, true, false, false},
94 | ["CORNER-BOTTOMRIGHT"] = {false, false, false, true},
95 | ["SIDE-LEFT"] = {true, true, false, false},
96 | ["SIDE-RIGHT"] = {false, false, true, true},
97 | ["SIDE-TOP"] = {true, false, true, false},
98 | ["SIDE-BOTTOM"] = {false, true, false, true},
99 | ["TRICORNER-TOPLEFT"] = {true, true, true, false},
100 | ["TRICORNER-TOPRIGHT"] = {true, false, true, true},
101 | ["TRICORNER-BOTTOMLEFT"] = {true, true, false, true},
102 | ["TRICORNER-BOTTOMRIGHT"] = {false, true, true, true},
103 | }
104 |
105 | local function updatePosition(button)
106 | local angle = math.rad(button.db.minimapPos or 225)
107 | local x, y, q = math.cos(angle), math.sin(angle), 1
108 | if x < 0 then q = q + 1 end
109 | if y > 0 then q = q + 2 end
110 | local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND"
111 | local quadTable = minimapShapes[minimapShape]
112 | if quadTable[q] then
113 | x, y = x*80, y*80
114 | else
115 | local diagRadius = 103.13708498985 --math.sqrt(2*(80)^2)-10
116 | x = math.max(-80, math.min(x*diagRadius, 80))
117 | y = math.max(-80, math.min(y*diagRadius, 80))
118 | end
119 | button:SetPoint("CENTER", Minimap, "CENTER", x, y)
120 | end
121 |
122 | local function onClick(self, b) if self.dataObject.OnClick then self.dataObject.OnClick(self, b) end end
123 | local function onMouseDown(self) self.icon:SetTexCoord(0, 1, 0, 1) end
124 | local function onMouseUp(self) self.icon:SetTexCoord(0.05, 0.95, 0.05, 0.95) end
125 |
126 | local function onUpdate(self)
127 | local mx, my = Minimap:GetCenter()
128 | local px, py = GetCursorPosition()
129 | local scale = Minimap:GetEffectiveScale()
130 | px, py = px / scale, py / scale
131 | self.db.minimapPos = math.deg(math.atan2(py - my, px - mx)) % 360
132 | updatePosition(self)
133 | end
134 |
135 | local function onDragStart(self)
136 | self:LockHighlight()
137 | self.icon:SetTexCoord(0, 1, 0, 1)
138 | self:SetScript("OnUpdate", onUpdate)
139 | self.isMoving = true
140 | GameTooltip:Hide()
141 | end
142 |
143 | local function onDragStop(self)
144 | self:SetScript("OnUpdate", nil)
145 | self.icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
146 | self:UnlockHighlight()
147 | self.isMoving = nil
148 | end
149 |
150 | local function createButton(name, object, db)
151 | local button = CreateFrame("Button", "LibDBIcon10_"..name, Minimap)
152 | button.dataObject = object
153 | button.db = db
154 | button:SetFrameStrata("MEDIUM")
155 | button:SetWidth(31); button:SetHeight(31)
156 | button:SetFrameLevel(8)
157 | button:RegisterForClicks("anyUp")
158 | button:RegisterForDrag("LeftButton")
159 | button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
160 | local overlay = button:CreateTexture(nil, "OVERLAY")
161 | overlay:SetWidth(53); overlay:SetHeight(53)
162 | overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
163 | overlay:SetPoint("TOPLEFT")
164 | local icon = button:CreateTexture(nil, "BACKGROUND")
165 | icon:SetWidth(20); icon:SetHeight(20)
166 | icon:SetTexture(object.icon)
167 | icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
168 | icon:SetPoint("TOPLEFT", 7, -5)
169 | button.icon = icon
170 |
171 | button:SetScript("OnEnter", onEnter)
172 | button:SetScript("OnLeave", onLeave)
173 | button:SetScript("OnClick", onClick)
174 | button:SetScript("OnDragStart", onDragStart)
175 | button:SetScript("OnDragStop", onDragStop)
176 | button:SetScript("OnMouseDown", onMouseDown)
177 | button:SetScript("OnMouseUp", onMouseUp)
178 |
179 | lib.objects[name] = button
180 |
181 | if lib.loggedIn then
182 | updatePosition(button)
183 | if not db.hide then button:Show()
184 | else button:Hide() end
185 | end
186 | end
187 |
188 | -- We could use a metatable.__index on lib.objects, but then we'd create
189 | -- the icons when checking things like :IsRegistered, which is not necessary.
190 | local function check(name)
191 | if lib.notCreated[name] then
192 | createButton(name, lib.notCreated[name][1], lib.notCreated[name][2])
193 | lib.notCreated[name] = nil
194 | end
195 | end
196 |
197 | lib.loggedIn = lib.loggedIn or false
198 | -- Wait a bit with the initial positioning to let any GetMinimapShape addons
199 | -- load up.
200 | if not lib.loggedIn then
201 | local f = CreateFrame("Frame")
202 | f:SetScript("OnEvent", function()
203 | for _, object in pairs(lib.objects) do
204 | updatePosition(object)
205 | if not lib.disabled and not object.db.hide then object:Show()
206 | else object:Hide() end
207 | end
208 | lib.loggedIn = true
209 | f:SetScript("OnEvent", nil)
210 | f = nil
211 | end)
212 | f:RegisterEvent("PLAYER_LOGIN")
213 | end
214 |
215 | function lib:Register(name, object, db)
216 | if lib.disabled then return end
217 | if not object.icon then error("Can't register LDB objects without icons set!") end
218 | if lib.objects[name] or lib.notCreated[name] then error("Already registered, nubcake.") end
219 | if not db or not db.hide then
220 | createButton(name, object, db)
221 | else
222 | lib.notCreated[name] = {object, db}
223 | end
224 | end
225 |
226 | function lib:Hide(name)
227 | if not lib.objects[name] then return end
228 | lib.objects[name]:Hide()
229 | end
230 | function lib:Show(name)
231 | if lib.disabled then return end
232 | check(name)
233 | lib.objects[name]:Show()
234 | updatePosition(lib.objects[name])
235 | end
236 | function lib:IsRegistered(name)
237 | return (lib.objects[name] or lib.notCreated[name]) and true or false
238 | end
239 | function lib:Refresh(name, db)
240 | if lib.disabled then return end
241 | check(name)
242 | local button = lib.objects[name]
243 | if db then button.db = db end
244 | updatePosition(button)
245 | end
246 |
247 | function lib:EnableLibrary()
248 | lib.disabled = nil
249 | for name, object in pairs(lib.objects) do
250 | if not object.db or (object.db and not object.db.hide) then
251 | object:Show()
252 | updatePosition(object)
253 | end
254 | end
255 | end
256 |
257 | function lib:DisableLibrary()
258 | lib.disabled = true
259 | for name, object in pairs(lib.objects) do
260 | object:Hide()
261 | end
262 | end
263 |
264 |
--------------------------------------------------------------------------------
/libs/LibDataBroker-1.1/LibDataBroker-1.1.lua:
--------------------------------------------------------------------------------
1 |
2 | assert(LibStub, "LibDataBroker-1.1 requires LibStub")
3 | assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
4 |
5 | local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
6 | if not lib then return end
7 | oldminor = oldminor or 0
8 |
9 |
10 | lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
11 | lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
12 | local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
13 |
14 | if oldminor < 2 then
15 | lib.domt = {
16 | __metatable = "access denied",
17 | __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
18 | }
19 | end
20 |
21 | if oldminor < 3 then
22 | lib.domt.__newindex = function(self, key, value)
23 | if not attributestorage[self] then attributestorage[self] = {} end
24 | if attributestorage[self][key] == value then return end
25 | attributestorage[self][key] = value
26 | local name = namestorage[self]
27 | if not name then return end
28 | callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
29 | callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
30 | callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
31 | callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
32 | end
33 | end
34 |
35 | if oldminor < 2 then
36 | function lib:NewDataObject(name, dataobj)
37 | if self.proxystorage[name] then return end
38 |
39 | if dataobj then
40 | assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
41 | self.attributestorage[dataobj] = {}
42 | for i,v in pairs(dataobj) do
43 | self.attributestorage[dataobj][i] = v
44 | dataobj[i] = nil
45 | end
46 | end
47 | dataobj = setmetatable(dataobj or {}, self.domt)
48 | self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
49 | self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
50 | return dataobj
51 | end
52 | end
53 |
54 | if oldminor < 1 then
55 | function lib:DataObjectIterator()
56 | return pairs(self.proxystorage)
57 | end
58 |
59 | function lib:GetDataObjectByName(dataobjectname)
60 | return self.proxystorage[dataobjectname]
61 | end
62 |
63 | function lib:GetNameByDataObject(dataobject)
64 | return self.namestorage[dataobject]
65 | end
66 | end
67 |
68 | if oldminor < 4 then
69 | local next = pairs(attributestorage)
70 | function lib:pairs(dataobject_or_name)
71 | local t = type(dataobject_or_name)
72 | assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)")
73 |
74 | local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
75 | assert(attributestorage[dataobj], "Data object not found")
76 |
77 | return next, attributestorage[dataobj], nil
78 | end
79 |
80 | local ipairs_iter = ipairs(attributestorage)
81 | function lib:ipairs(dataobject_or_name)
82 | local t = type(dataobject_or_name)
83 | assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)")
84 |
85 | local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
86 | assert(attributestorage[dataobj], "Data object not found")
87 |
88 | return ipairs_iter, attributestorage[dataobj], 0
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/libs/LibDataBroker-1.1/README.textile:
--------------------------------------------------------------------------------
1 | LibDataBroker is a small WoW addon library designed to provide a "MVC":http://en.wikipedia.org/wiki/Model-view-controller interface for use in various addons.
2 | LDB's primary goal is to "detach" plugins for TitanPanel and FuBar from the display addon.
3 | Plugins can provide data into a simple table, and display addons can receive callbacks to refresh their display of this data.
4 | LDB also provides a place for addons to register "quicklaunch" functions, removing the need for authors to embed many large libraries to create minimap buttons.
5 | Users who do not wish to be "plagued" by these buttons simply do not install an addon to render them.
6 |
7 | Due to it's simple generic design, LDB can be used for any design where you wish to have an addon notified of changes to a table.
8 |
9 | h2. Links
10 |
11 | * "API documentation":http://github.com/tekkub/libdatabroker-1-1/wikis/api
12 | * "Data specifications":http://github.com/tekkub/libdatabroker-1-1/wikis/data-specifications
13 | * "Addons using LDB":http://github.com/tekkub/libdatabroker-1-1/wikis/addons-using-ldb
14 |
--------------------------------------------------------------------------------
/libs/LibStub/LibStub.lua:
--------------------------------------------------------------------------------
1 | -- $Id: LibStub.lua 76 2007-09-03 01:50:17Z mikk $
2 | -- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
3 | -- LibStub is hereby placed in the Public Domain
4 | -- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
5 | local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
6 | local LibStub = _G[LIBSTUB_MAJOR]
7 |
8 | -- Check to see is this version of the stub is obsolete
9 | if not LibStub or LibStub.minor < LIBSTUB_MINOR then
10 | LibStub = LibStub or {libs = {}, minors = {} }
11 | _G[LIBSTUB_MAJOR] = LibStub
12 | LibStub.minor = LIBSTUB_MINOR
13 |
14 | -- LibStub:NewLibrary(major, minor)
15 | -- major (string) - the major version of the library
16 | -- minor (string or number ) - the minor version of the library
17 | --
18 | -- returns nil if a newer or same version of the lib is already present
19 | -- returns empty library object or old library object if upgrade is needed
20 | function LibStub:NewLibrary(major, minor)
21 | assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
22 | minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
23 |
24 | local oldminor = self.minors[major]
25 | if oldminor and oldminor >= minor then return nil end
26 | self.minors[major], self.libs[major] = minor, self.libs[major] or {}
27 | return self.libs[major], oldminor
28 | end
29 |
30 | -- LibStub:GetLibrary(major, [silent])
31 | -- major (string) - the major version of the library
32 | -- silent (boolean) - if true, library is optional, silently return nil if its not found
33 | --
34 | -- throws an error if the library can not be found (except silent is set)
35 | -- returns the library object if found
36 | function LibStub:GetLibrary(major, silent)
37 | if not self.libs[major] and not silent then
38 | error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
39 | end
40 | return self.libs[major], self.minors[major]
41 | end
42 |
43 | -- LibStub:IterateLibraries()
44 | --
45 | -- Returns an iterator for the currently registered libraries
46 | function LibStub:IterateLibraries()
47 | return pairs(self.libs)
48 | end
49 |
50 | setmetatable(LibStub, { __call = LibStub.GetLibrary })
51 | end
52 |
--------------------------------------------------------------------------------
/libs/LibStub/LibStub.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 40200
2 | ## Title: Lib: LibStub
3 | ## Notes: Universal Library Stub
4 | ## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
5 | ## X-Website: http://www.wowace.com/addons/libstub/
6 | ## X-Category: Library
7 | ## X-License: Public Domain
8 | ## X-Curse-Packaged-Version: 1.0.2-40200
9 | ## X-Curse-Project-Name: LibStub
10 | ## X-Curse-Project-ID: libstub
11 | ## X-Curse-Repository-ID: wow/libstub/mainline
12 |
13 | LibStub.lua
14 |
--------------------------------------------------------------------------------
/libs/LibStub/tests/test.lua:
--------------------------------------------------------------------------------
1 | debugstack = debug.traceback
2 | strmatch = string.match
3 |
4 | loadfile("../LibStub.lua")()
5 |
6 | local lib, oldMinor = LibStub:NewLibrary("Pants", 1) -- make a new thingy
7 | assert(lib) -- should return the library table
8 | assert(not oldMinor) -- should not return the old minor, since it didn't exist
9 |
10 | -- the following is to create data and then be able to check if the same data exists after the fact
11 | function lib:MyMethod()
12 | end
13 | local MyMethod = lib.MyMethod
14 | lib.MyTable = {}
15 | local MyTable = lib.MyTable
16 |
17 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", 1) -- try to register a library with the same version, should silently fail
18 | assert(not newLib) -- should not return since out of date
19 |
20 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", 0) -- try to register a library with a previous, should silently fail
21 | assert(not newLib) -- should not return since out of date
22 |
23 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", 2) -- register a new version
24 | assert(newLib) -- library table
25 | assert(rawequal(newLib, lib)) -- should be the same reference as the previous
26 | assert(newOldMinor == 1) -- should return the minor version of the previous version
27 |
28 | assert(rawequal(lib.MyMethod, MyMethod)) -- verify that values were saved
29 | assert(rawequal(lib.MyTable, MyTable)) -- verify that values were saved
30 |
31 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 3 Blah") -- register a new version with a string minor version (instead of a number)
32 | assert(newLib) -- library table
33 | assert(newOldMinor == 2) -- previous version was 2
34 |
35 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 4 and please ignore 15 Blah") -- register a new version with a string minor version (instead of a number)
36 | assert(newLib)
37 | assert(newOldMinor == 3) -- previous version was 3 (even though it gave a string)
38 |
39 | local newLib, newOldMinor = LibStub:NewLibrary("Pants", 5) -- register a new library, using a normal number instead of a string
40 | assert(newLib)
41 | assert(newOldMinor == 4) -- previous version was 4 (even though it gave a string)
--------------------------------------------------------------------------------
/libs/LibStub/tests/test2.lua:
--------------------------------------------------------------------------------
1 | debugstack = debug.traceback
2 | strmatch = string.match
3 |
4 | loadfile("../LibStub.lua")()
5 |
6 | for major, library in LibStub:IterateLibraries() do
7 | -- check that MyLib doesn't exist yet, by iterating through all the libraries
8 | assert(major ~= "MyLib")
9 | end
10 |
11 | assert(not LibStub:GetLibrary("MyLib", true)) -- check that MyLib doesn't exist yet by direct checking
12 | assert(not pcall(LibStub.GetLibrary, LibStub, "MyLib")) -- don't silently fail, thus it should raise an error.
13 | local lib = LibStub:NewLibrary("MyLib", 1) -- create the lib
14 | assert(lib) -- check it exists
15 | assert(rawequal(LibStub:GetLibrary("MyLib"), lib)) -- verify that :GetLibrary("MyLib") properly equals the lib reference
16 |
17 | assert(LibStub:NewLibrary("MyLib", 2)) -- create a new version
18 |
19 | local count=0
20 | for major, library in LibStub:IterateLibraries() do
21 | -- check that MyLib exists somewhere in the libraries, by iterating through all the libraries
22 | if major == "MyLib" then -- we found it!
23 | count = count +1
24 | assert(rawequal(library, lib)) -- verify that the references are equal
25 | end
26 | end
27 | assert(count == 1) -- verify that we actually found it, and only once
28 |
--------------------------------------------------------------------------------
/libs/LibStub/tests/test3.lua:
--------------------------------------------------------------------------------
1 | debugstack = debug.traceback
2 | strmatch = string.match
3 |
4 | loadfile("../LibStub.lua")()
5 |
6 | local proxy = newproxy() -- non-string
7 |
8 | assert(not pcall(LibStub.NewLibrary, LibStub, proxy, 1)) -- should error, proxy is not a string, it's userdata
9 | local success, ret = pcall(LibStub.GetLibrary, proxy, true)
10 | assert(not success or not ret) -- either error because proxy is not a string or because it's not actually registered.
11 |
12 | assert(not pcall(LibStub.NewLibrary, LibStub, "Something", "No number in here")) -- should error, minor has no string in it.
13 |
14 | assert(not LibStub:GetLibrary("Something", true)) -- shouldn't've created it from the above statement
--------------------------------------------------------------------------------
/libs/LibStub/tests/test4.lua:
--------------------------------------------------------------------------------
1 | debugstack = debug.traceback
2 | strmatch = string.match
3 |
4 | loadfile("../LibStub.lua")()
5 |
6 |
7 | -- Pretend like loaded libstub is old and doesn't have :IterateLibraries
8 | assert(LibStub.minor)
9 | LibStub.minor = LibStub.minor - 0.0001
10 | LibStub.IterateLibraries = nil
11 |
12 | loadfile("../LibStub.lua")()
13 |
14 | assert(type(LibStub.IterateLibraries)=="function")
15 |
16 |
17 | -- Now pretend that we're the same version -- :IterateLibraries should NOT be re-created
18 | LibStub.IterateLibraries = 123
19 |
20 | loadfile("../LibStub.lua")()
21 |
22 | assert(LibStub.IterateLibraries == 123)
23 |
24 |
25 | -- Now pretend that a newer version is loaded -- :IterateLibraries should NOT be re-created
26 | LibStub.minor = LibStub.minor + 0.0001
27 |
28 | loadfile("../LibStub.lua")()
29 |
30 | assert(LibStub.IterateLibraries == 123)
31 |
32 |
33 | -- Again with a huge number
34 | LibStub.minor = LibStub.minor + 1234567890
35 |
36 | loadfile("../LibStub.lua")()
37 |
38 | assert(LibStub.IterateLibraries == 123)
39 |
40 |
41 | print("OK")
--------------------------------------------------------------------------------