-
Notifications
You must be signed in to change notification settings - Fork 1
/
PersonalLootHelper-Core.lua
2867 lines (2514 loc) · 118 KB
/
PersonalLootHelper-Core.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--[[
TODOs:
Add legacy mode to show system alerts only?
Add legacy coordinate rolls mode back?
Remove whisper/ms/os/xmog prompts right away when players leave group? (ex: end of lfr)
When offering loot, show how many people are eligible and keep track of who still may roll?
Bug - whisper message doesn't allow special characters
Don't show PLH UI if group is using RCLootCouncil
Do something special when multiple of same item drop? i.e. tell looter that [name] offered a duplicate of this item to [name]?
Localization
Known Limitations:
PLH assumes everyone in the group is eligible to receive tradeable loot; it doesn't check whether everyone
tagged the mob or whether anyone was already loot-locked
PLH assumes that any loot received is part of the Personal Loot system, if Personal Loot is enabled. However,
items can be obtained other ways - for example, someone using a baleful token to create a baleful item. Those
items are not part of the Personal Loot system, but PLH cannot distinguish them from regular loot.
PLH determines whether an item is tradeable by comparing against the equipped item's ilvl, but the actual logic
for personal loot determines this based on the highest ilvl ever equipped in that slot. If you want to trade
an item that is tradeable, but for which PLH did not notify you, you can do "/plh trade [item]".
Ways I've tried to determine true tradeability:
Look for _G.BIND_TRADE_TIME_REMAINING in tooltips for the itemlinks generated by CHAT_MSG_LOOT and for tooltips
found by GetContainerItemInfo() and GetContainerItemLink()...none of those links show this text
Check attributes of SHOW_LOOT_TOAST event...lessAwesome attribute sounded promising, but was always false
Created https://us.battle.net/forums/en/wow/topic/20764076267#1 to discuss
Known Bugs:
Taint issues on ACTIVE_CHAT_EDIT_BOX and LAST_ACTIVE_CHAT_EDIT_BOX when doing /plh repeatedly...however, this
issue is also reproducable by doing /dbm repeatedly, so it's not specific to PLH. I'm not going to worry about it.
Created https://us.battle.net/forums/en/wow/topic/20764046364#1 to discuss
Sometimes when clicking resize, window will double in size
CHANGELOG:
20210316 - 2.27
9.0.5 version update
Fix checkboxes on config screen
Fix saving of "announce trades" configuration option
20201117 - 2.25
Fix issue of PLH not prompting when items can be traded. Thank you to RubioTwitch for identifying the fix!!
20181214 - 2.19
8.1 version update
20181211 - 2.18
Added patch 8.1 trinkets
20180903 - 2.17
Fixed issue that caused PLH to not work for players whose realms have spaces in their names
20180902 - 2.16
Fixed several more bugs that affected loot trading
20180830 - 2.15
Fixed issue some player were having where they were shown pass/whisper buttons instead of keep/offer buttons for their own loot
20180828 - 2.14
Fixed notifications for rings and trinkets where the loot is an upgrade for one slot but not the other
Fixed trinket recommendations to pay attention to primary attributes
20180828 - 2.13
Fixed version notification
20180828 - 2.12
Added azerite armor back into evaluations since Blizzard is allowing Azerite armor to be traded
20180826 - 2.11
Fixed announce trades to work when traded item is not something the group leader could have used
20180826 - 2.10
Added option to announce trades in guild groups
20180825 - 2.09
Fixed item caching issue that was resulting in missed or incorrect recommendations
Added alert to show when a new version of PLH is available
Fixed a few taint issues:
function GetItemInfoReceivedEvent()
variables inside RestoreMainWindowPosition()
20180725 - 2.08
Increased time between inspections for slower computers & connections
20180724 - 2.07
Removed "Automatically hide PLH when there is no loot to trade" as an option; instead, PLH is always auto-hidden
Updated to never show Azerite armor as being tradeable
Added BfA trinkets
Fixed LUA error in IsAnUpgradeForCharacter
Changed button back from "OFFER TO PLH USERS" to "OFFER TO GROUP". I can't decide on the best way to label this button - I'm open
to suggestions!
20180723 - 2.06
Fixed bug that could cause upgrades to not be identified if player's items weren't cached
(Added player items to cache instead of using GetInventoryItemLink() since player items are guaranteed to be cached post-8.0)
Fixed bug that could cause users to not be prompted for items if items weren't cached
(in PLH_ProcessTradeItemMessage)
Fixed bug that could cause trinkets with primary stats to not be evaluated correctly
(Removed trinkets from isEquippableItemForCharacter primary attribute check)
Changed groupInfoCache to cache FullItemInfo(s) instead of items
20180720 - 2.05
Fixed bug that was causing some gear to not be identified as equippable (leather gear looted by int-specced druid not identified as
equippable by rogues, for example)
20180719 - 2.04
Added option to notify individual of loot they can trade to others in the group (instead of only notifying in UI for other PLH users).
Renamed "offer to group" button to "offer to plh users" to make it clearer; if you want to offer an item to non-PLH users,
do so manually via whispers or instance chat.
Made ilvl descriptions in options clearer
Fixed bug that prevented requests from working cross-realm
Fixed bug that caused prior items to be shown when doing "/plh show" (by moving ClearLootedItemsDisplay() earlier in UpdateLootedItemsDisplay())
Increased time between inspections to allow cache to populate correctly
Default whisper message if it is ''
Removed some debug statements
20180718 - 2.03
Hopefully fixed bug reported by many players of preferences not saving (config was only setting values in OnShow, not during creation)
20180718 - 2.02
Fixed bug in which whisper message was incorrectly showing %item
20180718 - 2.01
Updated to WoW version 8.0
Fixed bug reported by many players of preferences not saving (issue was same name used for prior version preferences)
Fixed bug reported by pro100tehb re: wrong buttons shown for looted item (I think problem was SHOW_LOOT_TOAST event)
20180427 - 2.00
Updated for Battle for Azeroth - First revision to include a window for trading loot!
Removed all relic logic since relics are being removed with BfA
Removed Coordinate Rolls mode
Removed chat notifications
Removed raid frame highlight
]]--
local GetItemInfo = GetItemInfo or C_Item.GetItemInfo
-- Constants to control inspection process
local DELAY_BETWEEN_INSPECTIONS_LONG = 12 -- in seconds
local DELAY_BETWEEN_INSPECTIONS_SHORT = 0.2 -- in seconds
local MIN_DELAY_BETWEEN_CACHE_REFRESHES = 10 -- in seconds
local MAX_INSPECT_LOOPS = 4 -- maximum # of times to retry calling NotifyInspect on all members in the roster for whom we've cached fewer than the expected number of items
-- Colors for display in the looted items frame
local COLOR_PLAYER_LOOTED_ITEM = _G.LIGHTYELLOW_FONT_COLOR_CODE
local COLOR_NON_PLAYER_LOOTED_ITEM = _G.YELLOW_FONT_COLOR_CODE
local COLOR_HIGHER_ILVL = _G.GREEN_FONT_COLOR_CODE
local COLOR_LOWER_ILVL = _G.RED_FONT_COLOR_CODE
local COLOR_BOE = _G.ORANGE_FONT_COLOR_CODE
local COLOR_BUTTON_TEXT = _G.YELLOW_FONT_COLOR_CODE
-- Keys for the array returned by GetFullItemInfo()
local FII_ITEM = 'ITEM' -- item link
--local FII_NAME = 'NAME' -- return value 1 of Blizzard API call GetItemInfo()
--local FII_LINK = 'LINK' -- return value 2 of Blizzard API call GetItemInfo()
local FII_QUALITY = 'QUALITY' -- return value 3 of Blizzard API call GetItemInfo()
local FII_BASE_ILVL = 'BASE_ILVL' -- return value 4 of Blizzard API call GetItemInfo()
local FII_REQUIRED_LEVEL = 'REQUIRED_LEVEL' -- return value 5 of Blizzard API call GetItemInfo()
--local FII_TYPE = 'TYPE' -- return value 6 of Blizzard API call GetItemInfo()
--local FII_SUB_TYPE = 'SUB_TYPE' -- return value 7 of Blizzard API call GetItemInfo()
--local FII_MAX_STACK = 'MAX_STACK' -- return value 8 of Blizzard API call GetItemInfo()
local FII_ITEM_EQUIP_LOC = 'ITEM_EQUIP_LOC' -- return value 9 of Blizzard API call GetItemInfo()
--local FII_TEXTURE = 'TEXTURE' -- return value 10 of Blizzard API call GetItemInfo()
--local FII_VENDOR_PRICE = 'VENDOR_PRICE' -- return value 11 of Blizzard API call GetItemInfo()
local FII_CLASS = 'CLASS' -- return value 12 of Blizzard API call GetItemInfo()
local FII_SUB_CLASS = 'SUB_CLASS' -- return value 13 of Blizzard API call GetItemInfo()
local FII_BIND_TYPE = 'BIND_TYPE' -- return value 14 of Blizzard API call GetItemInfo()
--local FII_EXPAC_ID = 'EXPAC_ID' -- return value 15 of Blizzard API call GetItemInfo()
--local FII_ITEM_SET_ID = 'ITEM_SET_ID' -- return value 16 of Blizzard API call GetItemInfo()
--local FII_IS_CRAFTING_REAGENT = 'IS_CRAFTING_REAGENT' -- return value 17 of Blizzard API call GetItemInfo()
local FII_IS_EQUIPPABLE = 'IS_EQUIPPABLE' -- true if the item is equippable, false otherwise
local FII_REAL_ILVL = 'REAL_ILVL' -- real ilvl, derived from tooltip
local FII_CLASSES = 'CLASSES' -- uppercase string of classes that can use the item (ex: tier); nil if item is not class-restricted
local FII_TRADE_TIME_WARNING_SHOWN = 'TRADE_TIME_WARNING_SHOWN' -- true if the 'You may trade this item...' text is in the tooltip
local FII_HAS_SOCKET = 'HAS_SOCKET' -- true if the item has a socket
local FII_HAS_AVOIDANCE = 'HAS_AVOIDANCE' -- true if the item has avoidance
local FII_HAS_INDESTRUCTIBLE = 'HAS_INDESTRUCTIBLE' -- true if the item has indestructible
local FII_HAS_LEECH = 'HAS_LEECH' -- true if the item has leech
local FII_HAS_SPEED = 'HAS_SPEED' -- true if the item has speed
local FII_XMOGGABLE = 'XMOGGABLE' -- true if the player needs this item for xmog
local FII_IS_AZERITE_ITEM = 'IS_AZERITE_ITEM' -- true if the item is an Azerite item
-- Keys for the groupInfoCache
local CLASS_NAME = 'CLASS_NAME'
local SPEC = 'SPEC'
local LEVEL = 'LEVEL'
local FORCE_REFRESH = 'FORCE_REFRESH'
-- Keys for the lootedItems array
local LOOTER_NAME = 'LOOTER_NAME'
local FULL_ITEM_INFO = 'FULL_ITEM_INFO'
local STATUS = 'STATUS'
local SELECTED_REQUESTOR_INDEX = 'SELECTED_REQUESTOR_INDEX'
local DEFAULT_REQUESTOR_INDEX = 'DEFAULT_REQUESTOR_INDEX'
local CONFIRMATION_MESSAGE = 'CONFIRMATION_MESSAGE'
local REQUESTORS = 'REQUESTORS'
local REQUESTOR_NAME = 'REQUESTOR_NAME'
local REQUESTOR_ROLL = 'REQUESTOR_ROLL'
local REQUESTOR_REQUEST_TYPE = 'REQUESTOR_REQUEST_TYPE'
local REQUESTOR_SORT_ORDER = 'REQUESTOR_SORT_ORDER'
-- Allowed values for lootedItems[STATUS]
local STATUS_DEFAULT = 'STATUS_DEFAULT'
local STATUS_HIDDEN = 'STATUS_HIDDEN'
local STATUS_OFFERED = 'STATUS_OFFERED'
local STATUS_AVAILABLE = 'STATUS_AVAILABLE'
local STATUS_KEPT = 'STATUS_KEPT'
local STATUS_REQUESTED = 'STATUS_REQUESTED'
local STATUS_REQUESTED_VIA_WHISPER = 'STATUS_REQUESTED_VIA_WHISPER'
-- Allowed values for lootedItems[REQUESTORS][requestorIndex][REQUESTOR_REQUEST_TYPE]
local REQUEST_TYPE_MAIN_SPEC = 'MAIN SPEC'
local REQUEST_TYPE_OFF_SPEC = 'OFF SPEC'
local REQUEST_TYPE_XMOG = 'XMOG'
local REQUEST_TYPE_SHARD = 'SHARD'
-- Localization-independent class names
local DEATH_KNIGHT = select(2, GetClassInfo(6))
local DEMON_HUNTER = select(2, GetClassInfo(12))
local DRUID = select(2, GetClassInfo(11))
local EVOKER = select(2, GetClassInfo(13))
local HUNTER = select(2, GetClassInfo(3))
local MAGE = select(2, GetClassInfo(8))
local MONK = select(2, GetClassInfo(10))
local PALADIN = select(2, GetClassInfo(2))
local PRIEST = select(2, GetClassInfo(5))
local ROGUE = select(2, GetClassInfo(4))
local SHAMAN = select(2, GetClassInfo(7))
local WARLOCK = select(2, GetClassInfo(9))
local WARRIOR = select(2, GetClassInfo(1))
-- Specialization IDs from http://wow.gamepedia.com/API_GetInspectSpecialization
local SPECS = {
DK_BLOOD = 250,
DK_FROST = 251,
DK_UNHOLY = 252,
DH_HAVOC = 577,
DH_VENGEANCE = 581,
DRUID_BALANCE = 102,
DRUID_FERAL = 103,
DRUID_GUARDIAN = 104,
DRUID_RESTO = 105,
EVOKER_DEVA = 1467,
EVOKER_PRES = 1468,
EVOKER_AUG = 1473,
HUNTER_BM = 253,
HUNTER_MARKS = 254,
HUNTER_SURVIVAL = 255,
MAGE_ARCANE = 62,
MAGE_FIRE = 63,
MAGE_FROST = 64,
MONK_BM = 268,
MONK_MW = 270,
MONK_WW = 269,
PALADIN_HOLY = 65,
PALADIN_PROT = 66,
PALADIN_RET = 70,
PRIEST_DISC = 256,
PRIEST_HOLY = 257,
PRIEST_SHADOW = 258,
ROGUE_ASS = 259,
ROGUE_OUTLAW = 260,
ROGUE_SUB = 261,
SHAMAN_ELE = 262,
SHAMAN_ENH = 263,
SHAMAN_RESTO = 264,
WARLOCK_AFF = 256,
WARLOCK_DEMO = 266,
WARLOCK_DESTRO = 267,
WARRIOR_ARMS = 71,
WARRIOR_FURY = 72,
WARRIOR_PROT = 73
}
local SPEC_BY_CLASS = {
[DEATH_KNIGHT] = { SPECS.DK_BLOOD, SPECS.DK_FROST, SPECS.DK_UNHOLY },
[DEMON_HUNTER] = { SPECS.DH_HAVOC, SPECS.DH_VENGEANCE },
[DRUID] = { SPECS.DRUID_BALANCE, SPECS.DRUID_FERAL, SPECS.DRUID_GUARDIAN, SPECS.DRUID_RESTO },
[EVOKER] = { SPECS.EVOKER_DEVA, SPECS.EVOKER_PRES, SPECS.EVOKER_AUG },
[HUNTER] = { SPECS.HUNTER_BM, SPECS.HUNTER_MARKS, SPECS.HUNTER_SURVIVAL },
[MAGE] = { SPECS.MAGE_ARCANE, SPECS.MAGE_FIRE, SPECS.MAGE_FROST },
[MONK] = { SPECS.MONK_BM, SPECS.MONK_MW, SPECS.MONK_WW },
[PALADIN] = { SPECS.PALADIN_HOLY, SPECS.PALADIN_PROT, SPECS.PALADIN_RET },
[PRIEST] = { SPECS.PRIEST_DISC, SPECS.PRIEST_HOLY, SPECS.PRIEST_SHADOW },
[ROGUE] = { SPECS.ROGUE_ASS, SPECS.ROGUE_OUTLAW, SPECS.ROGUE_SUB },
[SHAMAN] = { SPECS.SHAMAN_ELE, SPECS.SHAMAN_ENH, SPECS.SHAMAN_RESTO },
[WARLOCK] = { SPECS.WARLOCK_AFF, SPECS.WARLOCK_DEMO, SPECS.WARLOCK_DESTRO },
[WARRIOR] = { SPECS.WARRIOR_ARMS, SPECS.WARRIOR_FURY, SPECS.WARRIOR_PROT }
}
-- Mapping of specs to roles
local ROLE_BY_SPEC = {
[SPECS.DK_BLOOD] = PLH_ROLE_TANK,
[SPECS.DK_FROST] = PLH_ROLE_STRENGTH_DPS,
[SPECS.DK_UNHOLY] = PLH_ROLE_STRENGTH_DPS,
[SPECS.DH_HAVOC] = PLH_ROLE_AGILITY_DPS,
[SPECS.DH_VENGEANCE] = PLH_ROLE_TANK,
[SPECS.DRUID_BALANCE] = PLH_ROLE_INTELLECT_DPS,
[SPECS.DRUID_FERAL] = PLH_ROLE_AGILITY_DPS,
[SPECS.DRUID_GUARDIAN] = PLH_ROLE_TANK,
[SPECS.DRUID_RESTO] = PLH_ROLE_HEALER,
[SPECS.EVOKER_DEVA] = PLH_ROLE_INTELLECT_DPS,
[SPECS.EVOKER_PRES] = PLH_ROLE_HEALER,
[SPECS.EVOKER_AUG] = PLH_ROLE_INTELLECT_DPS,
[SPECS.HUNTER_BM] = PLH_ROLE_AGILITY_DPS,
[SPECS.HUNTER_MARKS] = PLH_ROLE_AGILITY_DPS,
[SPECS.HUNTER_SURVIVAL] = PLH_ROLE_AGILITY_DPS,
[SPECS.MAGE_ARCANE] = PLH_ROLE_INTELLECT_DPS,
[SPECS.MAGE_FIRE] = PLH_ROLE_INTELLECT_DPS,
[SPECS.MAGE_FROST] = PLH_ROLE_INTELLECT_DPS,
[SPECS.MONK_BM] = PLH_ROLE_TANK,
[SPECS.MONK_MW] = PLH_ROLE_HEALER,
[SPECS.MONK_WW] = PLH_ROLE_AGILITY_DPS,
[SPECS.PALADIN_HOLY] = PLH_ROLE_HEALER,
[SPECS.PALADIN_PROT] = PLH_ROLE_TANK,
[SPECS.PALADIN_RET] = PLH_ROLE_STRENGTH_DPS,
[SPECS.PRIEST_DISC] = PLH_ROLE_HEALER,
[SPECS.PRIEST_HOLY] = PLH_ROLE_HEALER,
[SPECS.PRIEST_SHADOW] = PLH_ROLE_INTELLECT_DPS,
[SPECS.ROGUE_ASS] = PLH_ROLE_AGILITY_DPS,
[SPECS.ROGUE_OUTLAW] = PLH_ROLE_AGILITY_DPS,
[SPECS.ROGUE_SUB] = PLH_ROLE_AGILITY_DPS,
[SPECS.SHAMAN_ELE] = PLH_ROLE_INTELLECT_DPS,
[SPECS.SHAMAN_ENH] = PLH_ROLE_AGILITY_DPS,
[SPECS.SHAMAN_RESTO] = PLH_ROLE_HEALER,
[SPECS.WARLOCK_AFF] = PLH_ROLE_INTELLECT_DPS,
[SPECS.WARLOCK_DEMO] = PLH_ROLE_INTELLECT_DPS,
[SPECS.WARLOCK_DESTRO] = PLH_ROLE_INTELLECT_DPS,
[SPECS.WARRIOR_ARMS] = PLH_ROLE_STRENGTH_DPS,
[SPECS.WARRIOR_FURY] = PLH_ROLE_STRENGTH_DPS,
[SPECS.WARRIOR_PROT] = PLH_ROLE_TANK
}
local PRIMARY_ATTRIBUTE_BY_SPEC = {
[SPECS.DK_BLOOD] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.DK_FROST] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.DK_UNHOLY] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.DH_HAVOC] = ITEM_MOD_AGILITY_SHORT,
[SPECS.DH_VENGEANCE] = ITEM_MOD_AGILITY_SHORT,
[SPECS.DRUID_BALANCE] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.DRUID_FERAL] = ITEM_MOD_AGILITY_SHORT,
[SPECS.DRUID_GUARDIAN] = ITEM_MOD_AGILITY_SHORT,
[SPECS.DRUID_RESTO] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.EVOKER_DEVA] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.EVOKER_PRES] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.EVOKER_AUG] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.HUNTER_BM] = ITEM_MOD_AGILITY_SHORT,
[SPECS.HUNTER_MARKS] = ITEM_MOD_AGILITY_SHORT,
[SPECS.HUNTER_SURVIVAL] = ITEM_MOD_AGILITY_SHORT,
[SPECS.MAGE_ARCANE] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.MAGE_FIRE] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.MAGE_FROST] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.MONK_BM] = ITEM_MOD_AGILITY_SHORT,
[SPECS.MONK_MW] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.MONK_WW] = ITEM_MOD_AGILITY_SHORT,
[SPECS.PALADIN_HOLY] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.PALADIN_PROT] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.PALADIN_RET] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.PRIEST_DISC] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.PRIEST_HOLY] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.PRIEST_SHADOW] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.ROGUE_ASS] = ITEM_MOD_AGILITY_SHORT,
[SPECS.ROGUE_OUTLAW] = ITEM_MOD_AGILITY_SHORT,
[SPECS.ROGUE_SUB] = ITEM_MOD_AGILITY_SHORT,
[SPECS.SHAMAN_ELE] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.SHAMAN_ENH] = ITEM_MOD_AGILITY_SHORT,
[SPECS.SHAMAN_RESTO] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.WARLOCK_AFF] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.WARLOCK_DEMO] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.WARLOCK_DESTRO] = ITEM_MOD_INTELLECT_SHORT,
[SPECS.WARRIOR_ARMS] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.WARRIOR_FURY] = ITEM_MOD_STRENGTH_SHORT,
[SPECS.WARRIOR_PROT] = ITEM_MOD_STRENGTH_SHORT
}
local ItemClass = Enum.ItemClass
local ItemArmorSubclass = Enum.ItemArmorSubclass
local ItemWeaponSubclass = Enum.ItemWeaponSubclass
local EQUIPPABLE_ARMOR_BY_SPEC = {
[SPECS.DK_BLOOD] = { ItemArmorSubclass.Plate },
[SPECS.DK_FROST] = { ItemArmorSubclass.Plate },
[SPECS.DK_UNHOLY] = { ItemArmorSubclass.Plate },
[SPECS.DH_HAVOC] = { ItemArmorSubclass.Leather },
[SPECS.DH_VENGEANCE] = { ItemArmorSubclass.Leather },
[SPECS.DRUID_BALANCE] = { ItemArmorSubclass.Leather, ItemArmorSubclass.Generic },
[SPECS.DRUID_FERAL] = { ItemArmorSubclass.Leather },
[SPECS.DRUID_GUARDIAN] = { ItemArmorSubclass.Leather },
[SPECS.DRUID_RESTO] = { ItemArmorSubclass.Leather, ItemArmorSubclass.Generic },
[SPECS.EVOKER_DEVA] = { ItemArmorSubclass.Mail, ItemArmorSubclass.Generic },
[SPECS.EVOKER_PRES] = { ItemArmorSubclass.Mail, ItemArmorSubclass.Generic },
[SPECS.EVOKER_AUG] = { ItemArmorSubclass.Mail, ItemArmorSubclass.Generic },
[SPECS.HUNTER_BM] = { ItemArmorSubclass.Mail },
[SPECS.HUNTER_MARKS] = { ItemArmorSubclass.Mail },
[SPECS.HUNTER_SURVIVAL] = { ItemArmorSubclass.Mail },
[SPECS.MAGE_ARCANE] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.MAGE_FIRE] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.MAGE_FROST] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.MONK_BM] = { ItemArmorSubclass.Leather },
[SPECS.MONK_MW] = { ItemArmorSubclass.Leather, ItemArmorSubclass.Generic },
[SPECS.MONK_WW] = { ItemArmorSubclass.Leather },
[SPECS.PALADIN_HOLY] = { ItemArmorSubclass.Plate, ItemArmorSubclass.Generic, ItemArmorSubclass.Shield },
[SPECS.PALADIN_PROT] = { ItemArmorSubclass.Plate, ItemArmorSubclass.Shield },
[SPECS.PALADIN_RET] = { ItemArmorSubclass.Plate },
[SPECS.PRIEST_DISC] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.PRIEST_HOLY] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.PRIEST_SHADOW] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.ROGUE_ASS] = { ItemArmorSubclass.Leather },
[SPECS.ROGUE_OUTLAW] = { ItemArmorSubclass.Leather },
[SPECS.ROGUE_SUB] = { ItemArmorSubclass.Leather },
[SPECS.SHAMAN_ELE] = { ItemArmorSubclass.Mail, ItemArmorSubclass.Generic, ItemArmorSubclass.Shield },
[SPECS.SHAMAN_ENH] = { ItemArmorSubclass.Mail },
[SPECS.SHAMAN_RESTO] = { ItemArmorSubclass.Mail, ItemArmorSubclass.Generic, ItemArmorSubclass.Shield },
[SPECS.WARLOCK_AFF] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.WARLOCK_DEMO] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.WARLOCK_DESTRO] = { ItemArmorSubclass.Cloth, ItemArmorSubclass.Generic },
[SPECS.WARRIOR_ARMS] = { ItemArmorSubclass.Plate },
[SPECS.WARRIOR_FURY] = { ItemArmorSubclass.Plate },
[SPECS.WARRIOR_PROT] = { ItemArmorSubclass.Plate, ItemArmorSubclass.Shield }
}
local EQUIPPABLE_WEAPON_BY_SPEC = {
[SPECS.DK_BLOOD] = { ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword2H },
[SPECS.DK_FROST] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Sword1H },
[SPECS.DK_UNHOLY] = { ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword2H },
[SPECS.DH_HAVOC] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Unarmed, ItemWeaponSubclass.Warglaive },
[SPECS.DH_VENGEANCE] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Unarmed, ItemWeaponSubclass.Warglaive },
[SPECS.DRUID_BALANCE] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Unarmed },
[SPECS.DRUID_FERAL] = { ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff },
[SPECS.DRUID_GUARDIAN] = { ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff },
[SPECS.DRUID_RESTO] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Unarmed },
[SPECS.EVOKER_DEVA] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Sword2H, ItemWeaponSubclass.Unarmed },
[SPECS.EVOKER_PRES] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Sword2H, ItemWeaponSubclass.Unarmed },
[SPECS.EVOKER_AUG] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Sword2H, ItemWeaponSubclass.Unarmed },
[SPECS.HUNTER_BM] = { ItemWeaponSubclass.Bows, ItemWeaponSubclass.Crossbow, ItemWeaponSubclass.Guns },
[SPECS.HUNTER_MARKS] = { ItemWeaponSubclass.Bows, ItemWeaponSubclass.Crossbow, ItemWeaponSubclass.Guns },
[SPECS.HUNTER_SURVIVAL] = { ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff },
[SPECS.MAGE_ARCANE] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.MAGE_FIRE] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.MAGE_FROST] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.MONK_BM] = { ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff },
[SPECS.MONK_MW] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Unarmed },
[SPECS.MONK_WW] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Unarmed },
[SPECS.PALADIN_HOLY] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Sword2H },
[SPECS.PALADIN_PROT] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Sword1H },
[SPECS.PALADIN_RET] = { ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword2H },
[SPECS.PRIEST_DISC] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Wand },
[SPECS.PRIEST_HOLY] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Wand },
[SPECS.PRIEST_SHADOW] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Wand },
[SPECS.ROGUE_ASS] = { ItemWeaponSubclass.Dagger },
[SPECS.ROGUE_OUTLAW] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Unarmed },
[SPECS.ROGUE_SUB] = { ItemWeaponSubclass.Dagger },
[SPECS.SHAMAN_ELE] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Unarmed },
[SPECS.SHAMAN_ENH] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Unarmed },
[SPECS.SHAMAN_RESTO] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Unarmed },
[SPECS.WARLOCK_AFF] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.WARLOCK_DEMO] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.WARLOCK_DESTRO] = { ItemWeaponSubclass.Dagger, ItemWeaponSubclass.Staff, ItemWeaponSubclass.Sword1H, ItemWeaponSubclass.Wand },
[SPECS.WARRIOR_ARMS] = { ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword2H },
[SPECS.WARRIOR_FURY] = { ItemWeaponSubclass.Axe2H, ItemWeaponSubclass.Mace2H, ItemWeaponSubclass.Polearm, ItemWeaponSubclass.Sword2H },
[SPECS.WARRIOR_PROT] = { ItemWeaponSubclass.Axe1H, ItemWeaponSubclass.Mace1H, ItemWeaponSubclass.Sword1H }
}
local SPECS_EXPECTED_TO_HAVE_OFFHAND = {
[SPECS.DK_FROST] = true,
[SPECS.DH_VENGEANCE] = true,
[SPECS.DH_HAVOC] = true,
[SPECS.MONK_WW] = true,
[SPECS.PALADIN_PROT] = true,
[SPECS.ROGUE_ASS] = true,
[SPECS.ROGUE_OUTLAW] = true,
[SPECS.ROGUE_SUB] = true,
[SPECS.SHAMAN_ENH] = true,
[SPECS.WARRIOR_PROT] = true
}
-- Event listener frames
local eventHandlerFrame
local enableOrDisableEventFrame
-- Variables to control addon's status
local isEnabled = false
local priorCacheRefreshTime = 0
local showedVersionAlert = false
-- Variables to control inspection process
local inspectLoop = 0
local inspectIndex = 0 -- index of the character we're currently inspecting
local maxInspectIndex = 0 -- the index of the last character in GetRaidRosterInfo()
local notifyInspectName = nil -- valued if we sent a request to inspect someone, nil otherwise
-- Display widgets
local lootedItemsFrame
local scrollFrame
local scrollbar
local contentFrame -- content frame for dislaying looted items
local welcomeLabel
local radioButtons = {} -- indexed by lootedItemID and requestorIndex
local labels = {}
local labelIndex = 0 -- index of the mos recently created label
local buttons = {}
local buttonIndex = 0 -- index of the most recently created button
local itemFrames = {}
local itemFrameIndex = 0 -- index of the most recently created item frame
local tooltipLong -- tooltip with the first 30 lines of the tooltip (for getting ilvl)
local plhUsers = {} -- array of PLH users; keyed by name-realm of user, valued with version
local itemCache = {} -- keeps track of items that we're waiting to be loaded into the cache so they can be processed
local playerItemCache = {} -- contains FullItemInfos of the players' items
local groupInfoCache = {} -- array of items equipped by group members; keyed by name-realm of group member
--[[ structure is as follows, for each group member:
groupInfoCache[name-realm][CLASS_NAME] group member's class name from UnitClass(), in english
groupInfoCache[name-realm][SPEC] group member's spec from GetInspectSpecialization()
groupInfoCache[name-realm][LEVEL] group member's character level
groupInfoCache[name-realm][FORCE_REFRESH] boolean for whether to force a refresh of this member's data during next cache refresh
groupInfoCache[name-realm][INVSLOT_HEAD] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_NECK] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_SHOULDER] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_BACK] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_CHEST] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_WRIST] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_HAND] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_WAIST] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_LEGS] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_FEET] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_FINGER1] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_FINGER2] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_TRINKET1] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_TRINKET2] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_MAINHAND] item equipped in this slot
groupInfoCache[name-realm][INVSLOT_OFFHAND] item equipped in this slot
]]--
local lootedItems = {} -- array of items looted by player; keyed by name-realm of looter
--[[ structure is as follows, for each looted item:
lootedItems[lootedItemIndex][LOOTER_NAME] looter's full name (name-realm)
lootedItems[lootedItemIndex][FULL_ITEM_INFO] full item info
lootedItems[lootedItemIndex][STATUS] one of the STATUS_ options from below
lootedItems[lootedItemIndex][SELECTED_REQUESTOR_INDEX] which requestor has been selected via radio button
lootedItems[lootedItemIndex][DEFAULT_REQUESTOR_INDEX] which requestor is default if player hasn't clicked a radio button yet
lootedItems[lootedItemIndex][CONFIRMATION_MESSAGE] confirmation message to show after users hits OFFER TO GROUP or REQUEST
lootedItems[lootedItemIndex][REQUESTORS][requestorIndex][REQUESTOR_NAME] requestor's full name (name-realm)
lootedItems[lootedItemIndex][REQUESTORS][requestorIndex][REQUESTOR_ROLL] 1-100 roll result
lootedItems[lootedItemIndex][REQUESTORS][requestorIndex][REQUESTOR_REQUEST_TYPE] one of the REQUEST_TYPE_ options from below
lootedItems[lootedItemIndex][REQUESTORS][requestorIndex][REQUESTOR_SORT_ORDER] order to be displayed
lootedItems[STATUS] values are as follows:
If you are the looter:
STATUS_DEFAULT default value for items you looted
STATUS_HIDDEN you clicked OK (after OFFERing the item to someone) or KEEP
STATUS_OFFERED you clicked OFFER TO SELECTED PLAYER to offer the item to the person identified by lootedItem[SELECTED_REQUESTOR_INDEX]
STATUS_AVAILABLE you clicked OFFER TO GROUP to make the item available for requests
STATUS_KEPT N/A
STATUS_REQUESTED at least one person has requested this item
STATUS_REQUESTED_VIA_WHISPER N/A
If you are not the looter:
STATUS_DEFAULT default value for items looted by other players
STATUS_HIDDEN you clicked OK (after the item was OFFERed or KEEPed) or PASS
STATUS_OFFERED the looter clicked OFFER TO SELECTED PLAYER to offer the item to someone; if lootedItem[SELECTED_REQUESTOR_INDEX] == 1 then the winner is you!
STATUS_AVAILABLE the looter clicked OFFER TO GROUP to make the item available for requests
STATUS_KEPT the looter clicked KEEP
STATUS_REQUESTED you clicked MS/OS/XMOG/SHARD to request this item from a looter who uses PLH
STATUS_REQUESTED_VIA_WHISPER you clicked WHISPER to whisper the looter to request this item from a looter who does not use PLH
]]--
--[[ UTILITY FUNCTIONS ]]--
local function GetItemPrimaryAttribute(item)
local stats = C_Item.GetItemStats(item)
if stats ~= nil then
for stat, value in pairs(stats) do
if _G[stat] == ITEM_MOD_STRENGTH_SHORT or _G[stat] == ITEM_MOD_INTELLECT_SHORT or _G[stat] == ITEM_MOD_AGILITY_SHORT then
return _G[stat]
end
end
end
return nil
end
local function IsPlayer(characterName)
return characterName == 'player'
or characterName == PLH_GetFullName('player')
or characterName == UnitName('player')
end
local function hasBonus(fullItemInfo)
return fullItemInfo[FII_HAS_SOCKET]
or fullItemInfo[FII_HAS_SPEED]
or fullItemInfo[FII_HAS_LEECH]
or fullItemInfo[FII_HAS_AVOIDANCE]
or fullItemInfo[FII_HAS_INDESTRUCTIBLE]
end
local function GetILVLFromTooltip(tooltip)
local ITEM_LEVEL_PATTERN = _G.ITEM_LEVEL:gsub('%%d', '(%%d+)') -- Item Level (%d+)
local ilvl = nil
local text = tooltip.leftside[2]:GetText()
if text ~= nil then
ilvl = text:match(ITEM_LEVEL_PATTERN)
end
if ilvl == nil then -- ilvl can be in the 2nd or 3rd line dependng on the tooltip; if we didn't find it in 2nd, try 3rd
text = tooltip.leftside[3]:GetText()
if text ~= nil then
ilvl = text:match(ITEM_LEVEL_PATTERN)
end
end
return ilvl
end
local function GetFullItemInfo(item)
local ITEM_CLASSES_ALLOWED_PATTERN = _G.ITEM_CLASSES_ALLOWED:gsub('%%s', '(.+)') -- Classes: (.+)
local BIND_TRADE_TIME_REMAINING_PATTERN = _G.BIND_TRADE_TIME_REMAINING:gsub('%%s', '(.+)') -- You may trade this item with players that were also eligible to loot this item for the next (.+).
local TRANSMOGRIFY_TOOLTIP_APPEARANCE_UNKNOWN_PATTERN = _G.TRANSMOGRIFY_TOOLTIP_APPEARANCE_UNKNOWN:gsub('%%s', '(.+)') -- You haven't collected this appearance
local TRANSMOGRIFY_TOOLTIP_ITEM_UNKNOWN_APPEARANCE_KNOWN_PATTERN = _G.TRANSMOGRIFY_TOOLTIP_ITEM_UNKNOWN_APPEARANCE_KNOWN:gsub('%%s', '(.+)') -- You've collected this appearance, but not from this item
local TOOLTIP_AZERITE_UNLOCK_LEVELS_PATTERN = _G.TOOLTIP_AZERITE_UNLOCK_LEVELS:gsub('%(0/%%d%)', '%%(0/%%d%%)') -- Azerite Powers (0/%d):
local CURRENTLY_SELECTED_AZERITE_POWERS_PATTERN = _G.CURRENTLY_SELECTED_AZERITE_POWERS:gsub('%(%%d/%%d%)', '%%(%%d/%%d%%)') -- Active Azerite Powers (%d/%d):
local fullItemInfo = {}
if item ~= nil then
fullItemInfo[FII_ITEM] = item
-- determine the basic values from the Blizzard GetItemInfo() API call
_, _, fullItemInfo[FII_QUALITY], fullItemInfo[FII_BASE_ILVL], fullItemInfo[FII_REQUIRED_LEVEL], _, _, _, fullItemInfo[FII_ITEM_EQUIP_LOC], _, _, fullItemInfo[FII_CLASS], fullItemInfo[FII_SUB_CLASS], fullItemInfo[FII_BIND_TYPE], _, _, _ = GetItemInfo(item)
-- determine whether the item is equippable
fullItemInfo[FII_IS_EQUIPPABLE] = IsEquippableItem(item)
if fullItemInfo[FII_IS_EQUIPPABLE] then
-- set up the tooltip to determine values that aren't returned via GetItemInfo()
tooltipLong = tooltipLong or CreateFrame("GameTooltip", "PLHScanTooltip", nil, "GameTooltipTemplate")
tooltipLong:SetOwner(WorldFrame, "ANCHOR_NONE")
tooltipLong:ClearLines()
tooltipLong:SetHyperlink(item)
tooltipLong.leftside = {}
local i=1
while _G["PLHScanTooltipTextLeft" .. i] do
tooltipLong.leftside[i] = _G["PLHScanTooltipTextLeft" .. i]
i = i + 1
end
-- determine the real iLVL
local realILVL = GetILVLFromTooltip(tooltipLong)
if realILVL == nil then -- if we still couldn't find it (shouldn't happen), just use the base ilvl we got from GetItemInfo()
realILVL = fullItemInfo[FII_BASE_ILVL]
end
fullItemInfo[FII_REAL_ILVL] = tonumber(realILVL)
local classes = nil
local hasBindTradeTimeWarning = nil
local hasSocket = false
local hasAvoidance = false
local hasIndestructible = false
local hasLeech = false
local hasSpeed = false
local xmoggable = false
local isAzeriteItem = false
local text
local index = 6 -- the elements we're looking for are all further down in the tooltip
while tooltipLong.leftside[index] do
text = tooltipLong.leftside[index]:GetText()
if text ~= nil then
hasBindTradeTimeWarning = hasBindTradeTimeWarning or text:match(BIND_TRADE_TIME_REMAINING_PATTERN)
classes = classes or text:match(ITEM_CLASSES_ALLOWED_PATTERN)
hasSocket = hasSocket or text:find(_G.EMPTY_SOCKET_PRISMATIC) == 1
hasAvoidance = hasAvoidance or text:find(_G.STAT_AVOIDANCE) ~= nil
hasIndestructible = hasIndestructible or text:find(_G.STAT_STURDINESS) == 1
hasLeech = hasLeech or text:find(_G.STAT_LIFESTEAL) ~= nil
hasSpeed = hasSpeed or text:find(_G.STAT_SPEED) ~= nil
xmoggable = xmoggable or text:find(TRANSMOGRIFY_TOOLTIP_APPEARANCE_UNKNOWN_PATTERN) ~= nil or text:find(TRANSMOGRIFY_TOOLTIP_ITEM_UNKNOWN_APPEARANCE_KNOWN_PATTERN) ~= nil
isAzeriteItem = isAzeriteItem or text:match(TOOLTIP_AZERITE_UNLOCK_LEVELS_PATTERN) ~= nil or text:match(CURRENTLY_SELECTED_AZERITE_POWERS_PATTERN) ~= nil
end
index = index + 1
end
if classes ~= nil then
classes = string.upper(classes)
classes = string.gsub(classes, ' ', '') -- remove space for DEMON HUNTER, DEATH KNIGHT
end
-- if hasBindTradeTimeWarning then
-- print("SETTING FII_TRADE_TIME_WARNING_SHOWN TO TRUE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
-- end
fullItemInfo[FII_CLASSES] = classes
fullItemInfo[FII_TRADE_TIME_WARNING_SHOWN] = hasBindTradeTimeWarning
fullItemInfo[FII_HAS_SOCKET] = hasSocket
fullItemInfo[FII_HAS_AVOIDANCE] = hasAvoidance
fullItemInfo[FII_HAS_INDESTRUCTIBLE] = hasIndestructible
fullItemInfo[FII_HAS_LEECH] = hasLeech
fullItemInfo[FII_HAS_SPEED] = hasSpeed
fullItemInfo[FII_XMOGGABLE] = xmoggable
fullItemInfo[FII_IS_AZERITE_ITEM] = isAzeriteItem
end
end
return fullItemInfo
end
--[[ FUNCTIONS TO CHECK IF ITEM IS EQUIPPABLE ]]--
local function IsTrinketUsable(item, role)
--[[
local itemLink = select(2, GetItemInfo(item))
local itemID = string.match(itemLink, 'item:(%d+):')
local trinketList = PLH_GetTrinketList(role)
if itemID ~= nil and trinketList ~= nil then
return trinketList[tonumber(itemID)]
else
return false
end
]]--
return true -- trinkets are technically usable by any role; if a healer wants to use a dps trinket, a dps wants to use a tank trinket, or whatever, that's fine
end
-- Returns false if the character cannot use the item.
local function IsEquippableItemForCharacter(fullItemInfo, characterName)
local characterClass
local characterSpec
local characterLevel
if fullItemInfo ~= nil and characterName ~= nil and fullItemInfo[FII_IS_EQUIPPABLE] then
if IsPlayer(characterName) then
_, characterClass = UnitClass('player')
characterSpec = GetSpecializationInfo(GetSpecialization())
characterLevel = UnitLevel('player')
elseif groupInfoCache[characterName] ~= nil then
characterClass = groupInfoCache[characterName][CLASS_NAME]
characterSpec = groupInfoCache[characterName][SPEC]
characterLevel = groupInfoCache[characterName][LEVEL]
else
PLH_SendDebugMessage('Unable to determine class and spec in InEquippableItemForCharacter()!!!! for ' .. characterName)
return true -- should never reach here, but if we do it means we're not looking up the player or anyone in cache
end
if fullItemInfo[FII_REQUIRED_LEVEL] > characterLevel and not IsPlayer(characterName) then
return false
end
if fullItemInfo[FII_CLASSES] ~= nil then -- check whether to item is a class restricted item (ex: tier)
if not string.find(characterClass, fullItemInfo[FII_CLASSES]) then
return false
end
end
if fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_CLOAK' or fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_FINGER' or fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_NECK' then
return true
end
if fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_WEAPON' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_SHIELD' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_2HWEAPON' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_WEAPONMAINHAND' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_WEAPONOFFHAND' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_HOLDABLE' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_RANGED' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_THROWN' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_RANGEDRIGHT' or
fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_TRINKET' then
local itemPrimaryAttribute = GetItemPrimaryAttribute(fullItemInfo[FII_ITEM])
if itemPrimaryAttribute ~= nil then
local isValidPrimaryAttribute = false
for _, spec in pairs(SPEC_BY_CLASS[characterClass]) do
if characterSpec == spec or not PLH_PREFS[PLH_PREFS_CURRENT_SPEC_ONLY] then
if PRIMARY_ATTRIBUTE_BY_SPEC[spec] == itemPrimaryAttribute then
isValidPrimaryAttribute = true
break;
end
end
end
if not isValidPrimaryAttribute then
return false
end
end
end
if fullItemInfo[FII_ITEM_EQUIP_LOC] == 'INVTYPE_TRINKET' then
for _, spec in pairs(SPEC_BY_CLASS[characterClass]) do
if characterSpec == spec or not PLH_PREFS[PLH_PREFS_CURRENT_SPEC_ONLY] then
if IsTrinketUsable(fullItemInfo[FII_ITEM], ROLE_BY_SPEC[spec]) then
return true
end
end
end
return IsTrinketUsable(fullItemInfo[FII_ITEM], PLH_ROLE_UNKNOWN) == true
else
local subClasses
for _, spec in pairs(SPEC_BY_CLASS[characterClass]) do
if characterSpec == spec or not PLH_PREFS[PLH_PREFS_CURRENT_SPEC_ONLY] then
if fullItemInfo[FII_CLASS] == ItemClass.Armor then
subClasses = EQUIPPABLE_ARMOR_BY_SPEC[spec]
else
subClasses = EQUIPPABLE_WEAPON_BY_SPEC[spec]
end
for _, subClass in pairs(subClasses) do
if subClass == fullItemInfo[FII_SUB_CLASS] then
return true
end
end
end
end
end
end
return false
end
--[[ FUNCTIONS TO CHECK IF ITEM IS AN UPGRADE ]]--
-- returns two variables: true if the item is an upgrade over equippedItem (based on ilvl), equipped ilvl
local function IsAnUpgrade(itemILVL, equippedILVL, threshold)
if equippedILVL == nil then -- this means we couldn't find an equippedItem
return false, 0
else
if threshold == nil then
threshold = 1
end
return itemILVL > equippedILVL + threshold, equippedILVL
end
end
-- Returns an appropriate SlotID for the given itemEquipLoc, or nil if it's not an item
-- if itemEquipLoc is a finger slot or trinket slot, we'll just return the first item
-- if itemEquipLoc is a weapon that can be in either slot (INVTYPE_WEAPON), we'll return the main hand
local function GetSlotID(itemEquipLoc)
if itemEquipLoc == 'INVTYPE_HEAD' then return INVSLOT_HEAD
elseif itemEquipLoc == 'INVTYPE_NECK' then return INVSLOT_NECK
elseif itemEquipLoc == 'INVTYPE_SHOULDER' then return INVSLOT_SHOULDER
elseif itemEquipLoc == 'BODY' then return INVSLOT_BODY
elseif itemEquipLoc == 'INVTYPE_CHEST' then return INVSLOT_CHEST
elseif itemEquipLoc == 'INVTYPE_ROBE' then return INVSLOT_CHEST
elseif itemEquipLoc == 'INVTYPE_WAIST' then return INVSLOT_WAIST
elseif itemEquipLoc == 'INVTYPE_LEGS' then return INVSLOT_LEGS
elseif itemEquipLoc == 'INVTYPE_FEET' then return INVSLOT_FEET
elseif itemEquipLoc == 'INVTYPE_WRIST' then return INVSLOT_WRIST
elseif itemEquipLoc == 'INVTYPE_HAND' then return INVSLOT_HAND
elseif itemEquipLoc == 'INVTYPE_FINGER' then return INVSLOT_FINGER1
elseif itemEquipLoc == 'INVTYPE_TRINKET' then return INVSLOT_TRINKET1
elseif itemEquipLoc == 'INVTYPE_CLOAK' then return INVSLOT_BACK
elseif itemEquipLoc == 'INVTYPE_WEAPON' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_SHIELD' then return INVSLOT_OFFHAND
elseif itemEquipLoc == 'INVTYPE_2HWEAPON' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_WEAPONMAINHAND' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_WEAPONOFFHAND' then return INVSLOT_OFFHAND
elseif itemEquipLoc == 'INVTYPE_HOLDABLE' then return INVSLOT_OFFHAND
elseif itemEquipLoc == 'INVTYPE_RANGED' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_THROWN' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_RANGEDRIGHT' then return INVSLOT_MAINHAND
elseif itemEquipLoc == 'INVTYPE_TABARD' then return INVSLOT_TABARD
else return nil
end
end
-- Returns the FULL_ITEM_INFO that character has equipped in slotID
local function GetEquippedItem(characterName, slotID)
local item = nil
if IsPlayer(characterName) then
item = playerItemCache[slotID]
-- item = GetInventoryItemLink('player', slotID)
-- if item ~= nil then
-- item = GetFullItemInfo(item)
-- end
else
local characterDetails = groupInfoCache[characterName]
if characterDetails ~= nil then
item = GetFullItemInfo(characterDetails[slotID])
end
end
return item
end
local function LoadPlayerItems()
PLH_SendDebugMessage("Loading Player Items")
local item
for slotID = 1, 17 do
item = GetInventoryItemLink('player', slotID)
if item ~= nil then
playerItemCache[slotID] = GetFullItemInfo(item)
else
playerItemCache[slotID] = nil
end
end
end
-- returns two variables: true if the item is an upgrade over equippedItem (based on ilvl), equipped ilvl
-- note: doesn't check if item is equippable, so make sure you do that check beforehand
-- both parameter: if true, return true for rings/trinkets only if it's an upgrade for both slots
local function IsAnUpgradeForCharacter(fullItemInfo, characterName, threshold, both)
local itemEquipLoc = fullItemInfo[FII_ITEM_EQUIP_LOC]
local itemRealILVL = fullItemInfo[FII_REAL_ILVL]
local equippedItem1 = nil
local isAnUpgrade1 = false
local equippedILVL1 = 0
local equippedItem2 = nil
local isAnUpgrade2 = false
local equippedILVL2 = 0
local slotID
if itemEquipLoc ~= nil and itemEquipLoc ~= '' then
if itemEquipLoc == 'INVTYPE_FINGER' then
equippedItem1 = GetEquippedItem(characterName, INVSLOT_FINGER1)
equippedItem2 = GetEquippedItem(characterName, INVSLOT_FINGER2)
elseif itemEquipLoc == 'INVTYPE_TRINKET' then
equippedItem1 = GetEquippedItem(characterName, INVSLOT_TRINKET1)
equippedItem2 = GetEquippedItem(characterName, INVSLOT_TRINKET2)
elseif itemEquipLoc == 'INVTYPE_WEAPON' then
equippedItem1 = GetEquippedItem(characterName, INVSLOT_MAINHAND)
equippedItem2 = GetEquippedItem(characterName, INVSLOT_OFFHAND)
if equippedItem2 ~= nil and equippedItem2[FII_ITEM_EQUIP_LOC] == 'INVTYPE_SHIELD' then
equippedItem2 = nil -- ignore this slot if we have a shield equipped in offhand
end
else
slotID = GetSlotID(itemEquipLoc)
equippedItem1 = GetEquippedItem(characterName, slotID)
end
if equippedItem1 ~= nil then
if equippedItem2 ~= nil then
isAnUpgrade1, equippedILVL1 = IsAnUpgrade(itemRealILVL, equippedItem1[FII_REAL_ILVL], threshold)
isAnUpgrade2, equippedILVL2 = IsAnUpgrade(itemRealILVL, equippedItem2[FII_REAL_ILVL], threshold)
if both then
isAnUpgrade1 = isAnUpgrade1 and isAnUpgrade2
else
isAnUpgrade1 = isAnUpgrade1 or isAnUpgrade2
end
equippedILVL1 = min(equippedILVL1, equippedILVL2)
else
isAnUpgrade1, equippedILVL1 = IsAnUpgrade(itemRealILVL, equippedItem1[FII_REAL_ILVL], threshold)
end
end
end
return isAnUpgrade1, equippedILVL1
end
-- returns two variables: first is true or false, second is array of people for whom the item may is an upgrade (by ilvl)
local function IsAnUpgradeForAnyCharacter(fullItemInfo)
local isAnUpgrade, equippedILVL
local isAnUpgradeForAnyCharacterNames = {}
local index = 1
local characterName
while GetRaidRosterInfo(index) ~= nil do
characterName = PLH_GetFullName(select(1, GetRaidRosterInfo(index)))
if IsEquippableItemForCharacter(fullItemInfo, characterName) then
isAnUpgrade, equippedILVL = IsAnUpgradeForCharacter(fullItemInfo, characterName, 0)
if isAnUpgrade then
isAnUpgradeForAnyCharacterNames[#isAnUpgradeForAnyCharacterNames + 1] = Ambiguate(characterName, 'short') .. ' (' .. equippedILVL .. ')'
end
end
index = index + 1
end
return #isAnUpgradeForAnyCharacterNames > 0, isAnUpgradeForAnyCharacterNames
end
--[[ FUNCTIONS FOR PLHUSERS ]]
function PLH_GetNumberOfPLHUsers()
local count = 0
for _ in pairs(plhUsers) do
count = count + 1
end
return count
end
local function IsPLHUser(characterName)
if plhUsers[characterName] ~= nil then
return true
else
return false
end
end
--[[ FUNCTIONS FOR DISPLAYING THE LOOTED ITEMS WINDOW ]]--
local function IsEnchanting(profession)
if profession ~= nil then
return select(7, GetProfessionInfo(profession)) == 333
else
return false
end
end
local function IsEnchanter()
local profession1, profession2 = GetProfessions()
return IsEnchanting(profession1) or IsEnchanting(profession2)
end
--[[
local function CanBeXMogged(itemEquipLoc)