SethAluma

PersonalLootHelper-Core.lua (with Tier 19 class checks)

Mar 23rd, 2017
254
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 114.50 KB | None | 0 0
  1. --[[
  2.  
  3. -----------------------------------------------
  4. Announce logic psuedocode
  5.  
  6. Default isAnnouncer to false
  7.  
  8. In PerformNotify:
  9.     If Notify Group
  10.         If self is group lead
  11.             isAnnouncer = true
  12.         Else   
  13.             Request announce via SendAddonMessage, telling them whether you're the group lead
  14.             Wait 300ms, queue responses
  15.             If no response in 300ms
  16.                 isAnnouncer = true
  17.             else
  18.                 Loop through responses
  19.                 If other addon is already announcer
  20.                     isAnnouncer = false
  21.                 Else
  22.                     If you are character with lowest (!assistant + name) then
  23.                         isAnnouncer = true
  24.                     Else
  25.                         isAnnouncer = false
  26.  
  27. In AddonMessage listener:
  28.     If received request to announce:
  29.         If Notify Group
  30.             If you are group lead tell other addon you're the announcer
  31.             If you are announcer
  32.                 If other addon is not group leader, tell other addon you're the announcer
  33.                 Else isAnnouncer = false
  34.             Else tell other addon your character name
  35.    
  36. When you change your options to turn Notify Group off, set isAnnouncer back to false
  37. When PLH becomes disabled, set isAnnouncer to false
  38. -----------------------------------------------
  39.    
  40. Changelog
  41.  
  42. 20170321 - 1.23
  43.     Added checking of whether or not Tier 19 pieces are equippable by class.
  44.  
  45. 20170312 - 1.22
  46.     Fixed bug that could cause raid frames to be non-responsive after PLH showed loot in them
  47.         note:  hide tooltip in UnhighlightRaidFrames()
  48.  
  49. 20170301 - 1.21
  50.     Added option to display items that can be traded to you directly in the raid frames to make it easier to find people who have loot that
  51.         you may want and to make it easier to compare that loot to what you have equipped (hold shift over item for comparison).
  52.         A future version of PLH will also indicate players who can receive items you've looted directly in the raid frames.
  53.     note:  added PLH_HIGHLIGHT_RAID_FRAMES, HighlightRaidFrames(), and related
  54.        
  55.     Added option to exclude notifications if character level is too low to equip an item
  56.         note: added PLH_CHECK_CHARACTER_LEVEL
  57.  
  58.     Decoupled "coordinate rolls" mode from "notify group" mode, and enabled raid assistants to perform roll coordination
  59.         note: added PLH_COORDINATE_ROLLS and PLH_NOTIFY_GROUP; removed references to PLH_NOTIFY_MODE except to set values of the new global variables
  60.        
  61.     Removed case sensitivity from trade whispers when "coordinate rolls" is active; also permitted "[item] trade" in addition to existing "trade [item]"
  62.         note: in ProcessWhisper()
  63.    
  64. 20170218 - 1.20
  65.     Added check to see if a character is a high enough level to equip an item that drops (to fix recommendations in holiday and TW dungeons)
  66.         note:  added check in IsEquippableItemForCharacter
  67.  
  68. 20170126 - 1.19
  69.     Resolved "script ran too long" error
  70.         note:  Reduced calls to PLH_GetRelicType() in IsAnUpgradeForCharacter()
  71.  
  72. 20170125 - 1.18
  73.     Fixed bug that was cauing PLH to activate even if loot method was set to "Master Looter"
  74.  
  75.     Disabled PLH in BGs & arena
  76.    
  77.     Removed PLH enabled/PLH disabled announcements
  78.  
  79.     Removed PLH whispers (for coordinate rolls mode);
  80.         players can still whisper "trade" or "trade [item]" to initiate rolls, but there will be less whisper spam now
  81.  
  82.     Fixed bug in "coordinate rolls" mode where PLH would sometimes tell players that PLH did not have a record of the item they looted
  83.         note:  fixed by moving the addition of the looted item to whisperedItems[] earlier in PerformNotify()
  84.  
  85.     Fixed bug that was causing trinkets to not be evaluated properly
  86.         note:  moved the trinket eligibility check before the primary attribute check in IsEquippableItemForCharacter
  87.  
  88.     Added missing ToV and Nightbane (Karazhan) trinkets
  89.  
  90.     Fixed bug that was causing rolls for off-spec relics to be incorrectly ignored
  91.         note:  added off-spec relic check back into IsEquippableItemForCharacter
  92.        
  93. 20161025 - 1.17
  94.     Updated for 7.1
  95.    
  96. 20161015 - 1.16
  97.     Public
  98.         Added ability for players to whisper the roll coordinator with 'trade [item]' in Coordinate Rolls mode to initiate rolls for a specific item; for example, when PLH can't determine their item is tradeable because they have lower ilvl equipped but higher ilvl in their bags
  99.         Resolved bug where PLH would ignore 'trade' whispers from looters in Coordinate Rolls mode when PLH only found one other group member for whom the item would be an upgrade
  100.         Resolved bug where PLH would sometimes declare people's rolls as ineligible in Coordinate Rolls mode even though they could equip the item
  101.         Added support for BNET whispers in Coordinate Rolls mode (note: only works for 'trade' whispers, not 'trade [item]' whispers)
  102.         Added support for 7.1 trinkets
  103.     Private
  104.         In WhisperReceivedEvent, add functionality for 'trade [item]'
  105.         In PerformNotify, add looted item to whisperedItems array even if only 1 other person can use the item
  106.         Added 'or groupInfoCache[name] == nil' check for initial class/etc population in UpdateGroupInfoCache
  107.         Added BNWhisperReceivedEvent and related - note that it won't work when the bnet whisper is 'trade [item]' because bnet whispers strip off the color-coding of the item link
  108.  
  109. 20161015 - 1.15
  110.     Public
  111.         Removed group notification from LFR, regardless of whether or not Notify Group is set
  112.         Set default Notify Mode to "self" instead of "group" for new users
  113.         Resolved bug where sometimes PLH would tell someone their loot wasn't found after notifying them in Coordinate Rolls mode
  114.         Resolved bug where sometimes high rolls would be incorrectly ignored in Coordinate Rolls mode
  115.        
  116.     Private
  117.         Added LFR check to PerformNotify
  118.         Changed value of DEFAULT_NOTIFY_MODE to "self"
  119.         Added realm checked to sender name in WhisperReceivedEvent
  120.         Added tonumber in PLH_EndRolls()
  121.    
  122. 20160921 - 1.14
  123.     Public
  124.         Fixed bug that caused options to reset upon login for users who also have the addon Oilvl (or other addons that call the options panel 'okay' behind the scenes) installed
  125.        
  126.     Private
  127.         Create options panel after ADDON_LOADED event
  128.         Set checkboxes on options panel during panel creation vs. waiting for the OnShow event
  129.  
  130. 20160920 - 1.13
  131.     Public
  132.         Fixed bug that was causing PLH to evaluate loot received via bonus roll
  133.    
  134.     Detailed
  135.         Added LOOT_ITEM_SELF_PATTERN and LOOT_ITEM_PATTERN
  136.  
  137. 20160919 - 1.12
  138.     Public
  139.         Limited the maximum number of times that we inspect a character vs. continually trying to inspect them if they're missing a relic or piece of gear
  140.         Redesigned the interface options screen to update checkboxes when the frame is displayed vs. updating for each checkbox
  141.    
  142.     Detailed
  143.         Added InspectCount to groupInfoCache
  144.         Many changes to config
  145.             change OptionsBaseCheckButtonTemplate into InterfaceOptionsCheckButtonTemplate
  146.             set all values in parent frame's onShow event vs. onShow events for each checkbox
  147.             add version to display
  148.  
  149. 20160917 - 1.11
  150.     Public
  151.         Fixed options panel to use unique names for each checkbox frame, hopefully resolving the issue that some users experienced of settings resetting
  152.    
  153.     Detailed
  154.         Changed to expect 2 relics instead of 1 relic at level 110
  155.         Fix taint error on _ in GetEquippedRelic
  156.         Removed test file from .toc
  157.         Call OpenToCategory twice instead of using BlizzBugsSuck work-around
  158.         Fixed frame names in -Config; it was naming many frames the same, which may have caused peoples' settings to reset
  159.  
  160. 20160914 - 1.10
  161.     Public
  162.         Added international client support
  163.    
  164.     Detailed
  165.         Localization in:
  166.             LootReceivedEvent (use arguments vs. string parse)
  167.             RollReceivedEvent (use PLH_RANDOM_ROLL_RESULT_PATTERN)
  168.             util - PLH_GetRealILVL (use PLH_ITEM_LEVEL_PATTERN)
  169.             util - PLH_IsBoundToPlayer (use binding constants from _G)
  170.             util - PLH_GetRelicType (use PLH_RELIC_TOOLTIP_TYPE_PATTERN)
  171.             IsRelic (use item class/subclass)
  172.             ValidGear (use item class/subclass)
  173.             IsTrinketUsable - use item ID instead of item name
  174.            
  175. 20160913 - 1.9
  176.     Public
  177.         Fixed bug that caused relics to be ignored
  178.         Eliminated loading of unit test file - saves memory and may prevent the setting reset issue that some are having
  179.        
  180.     Detailed
  181.         Added relic check to ShouldBeEvaluated
  182.         Added hack to UpdateGroupInfoCache to check item 2nd time to hopefully fully cache it
  183.         Reverted back to setting default options based on ADDON_LOADED instead of PLAYER_ENTERING_WORLD
  184.         Commented out PersonalLootHelper-test.lua in toc file
  185.  
  186. 20160912 - 1.8
  187.     Public
  188.         Added support for trinkets that Wowhead identifies as being usable by "unknown":  Ettin Fingernail, Padawsen's Unlucky Charm, Unstable Arcanocrystal, Thrice-Accursed Compass, Chrono Shard, and Horn of Valor
  189.         Fixed bug when checking eligibility during rolls in Coordinate Rolls mode
  190.         Fixed LUA error reported in ticket #2
  191.    
  192.     Detailed
  193.         Added trinkets that have an "unknown" usable by attribute per wowhead:  http://www.wowhead.com/items/armor/trinkets/role:6?filter=166;7;0
  194.         Check for fullname in RollReceivedEvent
  195.         Added nil check to GroupMemberInfoChangedEvent
  196.  
  197. 20160912 - 1.7
  198.     Public
  199.         Changed timing for saved variables being initialized to hopefully address reports of saved variables being reset
  200.             (note: I have been unable to duplicate this reported bug on my end; if your options still reset after version 1.7,
  201.             please let me know via comment or ticket on curse!)
  202.         Added error checking to resolve reported LUA error
  203.  
  204.     Detailed
  205.         Changed initialize to fire after PLAYER_ENTERING_WORLD instead of ADDON_LOADED since people are reporting variables being reset
  206.         Added nil checking to PLH_GetFullName
  207.         Removed non-localized reference to spec variable in UpdateGroupInfoCache
  208.         Localized itemPrimaryAttribute in IsEquippableItemForCharacter
  209.         Localized _ in LootReceivedEvent
  210.  
  211. 20160910 - 1.6
  212.     Public
  213.         Fixed bugs that could rarely cause LUA errors
  214.         Fixed issue that could result in an excessive number of requests to inspect players who only have 1 relic slotted
  215.    
  216.     Detailed
  217.         Added nil check to PLH_GetUnitNameWithRealm
  218.         Made variable local in PLH_GetUnitGUIDFromFullname
  219.         Reduced NUM_EXPECTED_RELICS from 2 to 1 since many people still seem to only have 1 relic equipped even at level 110!
  220.    
  221. 20160908 - 1.5
  222.     Public
  223.         Fixed bug that could cause addon options to reset (for real this time!)
  224.        
  225.     Detailed
  226.         Added check to Initialize to ensure this addon was the source of the ADDON_LOADED event
  227.  
  228. 20160908 - 1.4
  229.     Public
  230.         Added support for Artifact Relics
  231.         Fixed bug whereby sometimes multiple notifications would be given for the same item
  232.  
  233.     Known Limitations
  234.         Personal Loot Helper cannot determine whether an Artifact Relic is an upgrade for a character's offspec since it has no way of determining what relics are slotted into the offspec Artifact
  235.    
  236.     Detailed
  237.         Added ValidRelics, PLH_GetRelicType, GetEquippedRelic
  238.         Added relic evaluation to IsEquippableItemForCharacter and IsAnUpgradeForCharacter
  239.         Added relics to cache and expected item count
  240.         Checked if frames already exist in Initialize() to prevent creating duplicate listeners
  241.  
  242. 20160903 - 1.3
  243.     Public
  244.         Fixed bug that could result in incorrect recommendations for rings, trinkets, and weapons
  245.         Fixed bug that could cause addon options to reset
  246.         Increased number of group member names that PLH will show a recommendation for from 3 to 4; beyond that, it will state "and others"
  247.         Modified notification text to say "...[item] is an ilvl upgrade..." vs. "...[item] is an upgrade..." to avoid the impression that the addon is doing any type of dps simultation to determine whether an item is truly an upgrade
  248.  
  249.     Detailed
  250.         Fixed slot IDs to use numbers instead of strings in IsAnUpgradeForCharacter
  251.         Changed Initialize() to be called by an event listener on ADDON_LOADED instead of called directly
  252.         Increased MAX_NAMES_TO_SHOW from 3 to 4
  253.         Commented out some debug statements and calls to PLH_PrintCache to reduce debug spam
  254.         Modified text in PerformNotify
  255.    
  256. 20160901 - 1.2
  257.     Public
  258.         Added ability for users to limit evaluations to characters' current spec only (in interface options); by default, PLH will consider an item as a possible upgrade as long as it is usable by ANY of the character's possible specilizations
  259.         Added cache refreshes when players change specs or change equipped gear to ensure evaluations are done with up-to-date spec/gear
  260.         Added support for evaluating whether Legion trinkets are upgrades based on class/spec.  Note that pre-Legion trinkets will not be evaluated.
  261.    
  262.     Detailed
  263.         Added PLH_CURRENT_SPEC_ONLY to IsAnUpgradeForCharacter and interface options screen
  264.         Added PLAYER_SPECIALIZATION_CHANGED listener to update cache when a group member's spec changes
  265.         Added UNIT_INVENTORY_CHANGED listener to update cache when a player's gear changes
  266.         Cleaned up comments to reflect current logic
  267.         Added GetExpectedItemCount to determine how many items should be cached by class/spec instead of assuming 15 for everyone
  268.         Added TRINKET_arrays, IsTrinketUsable, and logic in IsEquippableItemForCharacter to evaluate trinkets
  269.  
  270. 20160830 - 1.1
  271.     Public
  272.         Added logic to limit the upgrade evaluation to gear that is appropriate for the character's possible specs based on the item's primary attribute (str/int/agi)
  273.             Note: will add future enhancement to allow users to select an "evaluate current spec only" option; for now I
  274.             default it to consider items if they're equippable by ANY of the character's specs since players may queue for a spec other than their main spec
  275.         Fixed evaluation of cloaks; previously only considered cloth-wearers as eligible to equip cloaks
  276.         Minor performance improvements, particularly when in a large raid group
  277.         Fixed /plh to open directly to Personal Loot Helper options on first try
  278.        
  279.     Detailed
  280.         In PopulateGroupCache(), only refresh cache if it has been more than 3 seconds since the last refresh
  281.         In IsCharacterInGroup, eliminate redundant call to GetRaidRosterInfo()
  282.         Copy code from BlizzBugsSuck.lua to fix InterfaceOptionsFrame_OpenToCategory not opening to addon
  283.         In UpdateGroupInfoCache(unit), cache the character's Spec, and in PLH_GetItemCountFromCache subtract 2 since we added Spec
  284.         In IsEquippableItemForCharacter, consider the item equippable for every class if it's a cloak
  285.         In IsEquippableItemForCharacter, evaluate the character's spec vs. the primary attribute of the item
  286.         Added PLH_TestItems()
  287.        
  288. The general flow is as follows:
  289. LootReceivedEvent()             triggered when loot is received by anyone in the group
  290.   --> PerformNotify()           if loot is a tradeable and is an upgrade for someone in group, notifies based on users' preferred Notify Mode
  291.                                 if Coordinate Rolls mode then flow continues as follows:
  292.     --> WhisperReceivedEvent()  triggered when we receive a 'TRADE' whisper from a person who looted a tradeable item
  293.       --> AskForRolls()         prompts the group to roll for the item
  294.         --> PLH_EndRolls()          notifies the group who won the roll
  295.  
  296. Known Limitations:
  297.     As of version 1.10, Personal Loot Helper works on non-english clients!  However, the notifications provided by
  298.         PLH are all in english.
  299.     If the user reloads their UI or logs out, all state is lost - the cache will be lost and any pending rolls will remain uncompleted
  300.     If a player receives more than 1 item of personal loot, only their last item will be eligible for rolls when they
  301.        reply to the addon's request to trade the item(s)
  302.     The addon assumes everyone in the group is eligible to receive tradeable loot; it doesn't check whether everyone
  303.         was within range of the kill and not already loot-locked
  304.     The addon doesn't properly check if tier tokens can be used or are upgrades, but that should be a moot point for
  305.        Legion since tier will drop as actual gear vs. tier tokens when loot method is personal
  306.     If multiple people are running the addon in Notify Group mode, then each of them will send a message notifying the
  307.        group of the loot.  There's no straightforward foolproof way around this, so users will have to coordinate
  308.        among themselves to switch to Notify Self mode if it becomes too spammy.
  309.     Players cannot inspect other characters while the player is dead.  If you die & reload, the item cache will be
  310.        empty until the next time an event that triggers a cache refresh is received.
  311.     The addon assumes that any loot received is part of the Personal Loot system, if Personal Loot is enabled.  However,
  312.        items can be obtained other ways - for example, someone using a baleful token to create a baleful item.  Those
  313.        items are not part of the Personal Loot system, but PLHelper cannot distinguish them from regular loot.
  314.     Trinket evaluation is hardcoded for Legion items only; trinkets prior to Legion will not be evaluated
  315.  
  316. TODO Refactoring
  317.     refactor - get constants for slotid, spec, etc. from _G instead of hard-coding
  318.     refactor - make sure we properly use name/character/unit/guid everywhere to avoid confusion...
  319.             don't even use 'character'; it's confusing; just use name, unit, or guid
  320.     refactor - rename all uses of PLHelper to PLH
  321.     refactor - can probably change queuedRollOwner and queuedRollItem now that I understand lua arrays better; search 'key', for example
  322.     refactor - clean up unit test lua
  323.     refactor - clean up comments; reflect current logic
  324.     refactor - since PLH_GetUnitNameWithRealm() operates on units, can we replace all of its calls with UnitFullName(unit)?
  325.             easy way to test this:  change implementation of PLH_GetUnitNameWithRealm(unit) to call UnitFullName(unit) and see
  326.             what happens!
  327.     comments - update to reflect the approach to looping logic; just using outer loop now; no inner retry logic
  328.  
  329. Future enhancement ideas:
  330.     if you have addon installed, get a popup to decide what to do vs. a whisper?
  331.     option for roll timers - auto-finish rolls after [X] seconds or never, timers for roll warnings
  332.     add ability to cancel rolls?
  333.     add localization; see http://lua-users.org/wiki/StringLibraryTutorial for defining string constants
  334.     add a 'Who Else is Running PLHelper?' button to the config screen, which will use SendAddonMessage to query
  335.        for other instances of PLHelper, and report back the selected Notify Mode of each (to help coordinate with
  336.        making sure only 1 person is running in Notify Group mode)
  337.     also check for gem slots & tertiary stats when determining if an item is an upgrade
  338. ]]--
  339.  
  340. -- slash command
  341. SLASH_PLHelperCommand1 = '/plh'
  342.  
  343. local NOTIFY_MODE_SELF = 1
  344. local NOTIFY_MODE_GROUP = 2
  345. local NOTIFY_MODE_COORDINATE_ROLLS = 3
  346.  
  347. local DEFAULT_NOTIFY_MODE = NOTIFY_MODE_SELF
  348. local DEFAULT_INCLUDE_BOE = false
  349. local DEFAULT_MIN_ILVL = 528  -- personal loot was introduced with Siege of Orgrimmar, which started at ilvl 528
  350. local DEFAULT_MIN_QUALITY = 3  -- Rare
  351. local DEFAULT_DEBUG = false
  352. local DEFAULT_CURRENT_SPEC_ONLY = false
  353. local DEFAULT_CHECK_CHARACTER_LEVEL = true
  354. local DEFAULT_HIGHLIGHT_RAID_FRAMES = true
  355. local DEFAULT_HIGHLIGHT_SIZE = 20
  356.  
  357. local TRADE_MESSAGE = 'TRADE'  -- added some hardcording in ProcessWhisper for various way people may offer to trade items; customize text there if needed (ex: foreign languages)
  358. local DELAY_BETWEEN_ROLLS = 4 -- in seconds
  359. local UNHIGHLIGHT_DELAY = 105  -- in seconds
  360. local DELAY_BETWEEN_INSPECTIONS = 3  -- in seconds
  361. local MAX_INSPECT_LOOPS = 3    -- 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
  362. local NUM_EXPECTED_ITEMS = 15 -- number of items we expect each person to have equipped (based on having something in every gear) plus 3 relics
  363.     -- slot; if we've cached fewer than that amount of items for a character, we'll include that character in additional
  364.     -- inspect loops.
  365. --local MAX_INSPECT_RETRIES = 2  -- maximum # of times to retry calling NotifyInspect for a specific character if we don't get an INSPECT_READY
  366. local NUM_EXPECTED_RELICS_110 = 3
  367. local NUM_EXPECTED_RELICS_101 = 1
  368. local MAX_INSPECTS_PER_CHARACTER = 5
  369. local MAX_NAMES_TO_SHOW = 4
  370. local PLH_RELICSLOT = 1000  -- for indexing relics in groupInfoCache
  371.  
  372. local DEATH_KNIGHT = select(2, GetClassInfo(6))
  373. local DEMON_HUNTER = select(2, GetClassInfo(12))
  374. local DRUID = select(2, GetClassInfo(11))
  375. local HUNTER = select(2, GetClassInfo(3))
  376. local MAGE = select(2, GetClassInfo(8))
  377. local MONK = select(2, GetClassInfo(10))
  378. local PALADIN = select(2, GetClassInfo(2))
  379. local PRIEST = select(2, GetClassInfo(5))
  380. local ROGUE = select(2, GetClassInfo(4))
  381. local SHAMAN = select(2, GetClassInfo(7))
  382. local WARLOCK = select(2, GetClassInfo(9))
  383. local WARRIOR = select(2, GetClassInfo(1))
  384.  
  385. local ValidGear = {
  386. --  { DEATH_KNIGHT, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  387. --  { DEATH_KNIGHT, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  388. --  { DEATH_KNIGHT, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_MAIL },
  389.     { DEATH_KNIGHT, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_PLATE },
  390.     { DEATH_KNIGHT, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  391.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  392.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  393.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  394.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  395.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE2H },
  396.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE2H },
  397.     { DEATH_KNIGHT, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD2H },
  398.  
  399. --  { DEMON_HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  400.     { DEMON_HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  401.     { DEMON_HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  402.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  403.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  404.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  405.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  406.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  407.     { DEMON_HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_WARGLAIVE },
  408.  
  409. --  { DRUID, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  410.     { DRUID, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  411.     { DRUID, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  412.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  413.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  414.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  415.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  416.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  417.     { DRUID, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE2H },
  418.  
  419. --  { HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  420. --  { HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  421.     { HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_MAIL },
  422.     { HUNTER, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  423.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  424.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  425.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  426.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  427.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  428.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  429.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE2H },
  430.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD2H },
  431.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_BOWS },
  432.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_CROSSBOW },
  433.     { HUNTER, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_GUNS },
  434.  
  435.     { MAGE, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  436.     { MAGE, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  437.     { MAGE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  438.     { MAGE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  439.     { MAGE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  440.     { MAGE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_WAND },
  441.  
  442. --  { MONK, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  443.     { MONK, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  444.     { MONK, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  445.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  446.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  447.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  448.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  449.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  450.     { MONK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  451.  
  452. --  { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  453. --  { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  454. --  { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_MAIL },
  455.     { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_PLATE },
  456.     { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  457.     { PALADIN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_SHIELD },
  458.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  459.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  460.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  461.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  462.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE2H },
  463.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE2H },
  464.     { PALADIN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD2H },
  465.  
  466.     { PRIEST, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  467.     { PRIEST, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  468.     { PRIEST, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  469.     { PRIEST, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  470.     { PRIEST, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  471.     { PRIEST, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_WAND },
  472.  
  473. --  { ROGUE, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  474.     { ROGUE, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  475.     { ROGUE, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  476.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  477.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  478.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  479.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  480.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  481.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_BOWS },
  482.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_CROSSBOW },
  483.     { ROGUE, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_GUNS },
  484.  
  485. --  { SHAMAN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  486. --  { SHAMAN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  487.     { SHAMAN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_MAIL },
  488.     { SHAMAN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  489.     { SHAMAN, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_SHIELD },
  490.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  491.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  492.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  493.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  494.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  495.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE2H },
  496.     { SHAMAN, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE2H },
  497.  
  498.     { WARLOCK, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  499.     { WARLOCK, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  500.     { WARLOCK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  501.     { WARLOCK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  502.     { WARLOCK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  503.     { WARLOCK, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_WAND },
  504.  
  505. --  { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_CLOTH },
  506. --  { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_LEATHER },
  507. --  { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_MAIL },
  508.     { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_PLATE },
  509.     { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_GENERIC },
  510.     { WARRIOR, LE_ITEM_CLASS_ARMOR, LE_ITEM_ARMOR_SHIELD },
  511.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE1H },
  512.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_DAGGER },
  513.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_UNARMED },
  514.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE1H },
  515.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_POLEARM },
  516.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_STAFF },
  517.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD1H },
  518.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_AXE2H },
  519.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_MACE2H },
  520.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_SWORD2H },
  521.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_BOWS },
  522.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_CROSSBOW },
  523.     { WARRIOR, LE_ITEM_CLASS_WEAPON, LE_ITEM_WEAPON_GUNS }
  524. }
  525.  
  526. local ValidTier = {
  527.     -- Tier 19
  528.     [138355] = DEATH_KNIGHT, -- Dreadwyrm Crown
  529.     [138349] = DEATH_KNIGHT, -- Dreadwyrm Breastplate
  530.     [138361] = DEATH_KNIGHT, -- Dreadwyrm Shoulderguards
  531.     [138352] = DEATH_KNIGHT, -- Dreadwyrm Gauntlets
  532.     [138358] = DEATH_KNIGHT, -- Dreadwyrm Legplates
  533.     [138364] = DEATH_KNIGHT, -- Dreadwyrm Greatcloak
  534.  
  535.     [138378] = DEMON_HUNTER, -- Mask of Second Sight
  536.     [138376] = DEMON_HUNTER, -- Tunic of Second Sight
  537.     [138380] = DEMON_HUNTER, -- Shoulderguards of Second Sight
  538.     [138377] = DEMON_HUNTER, -- Gloves of Second Sight
  539.     [138379] = DEMON_HUNTER, -- Legwraps of Second Sight
  540.     [138375] = DEMON_HUNTER, -- Cape of Second Sight
  541.  
  542.     [138330] = DRUID, -- Hood of the Astral Warden
  543.     [138324] = DRUID, -- Robe of the Astral Warden
  544.     [138336] = DRUID, -- Mantle of the Astral Warden
  545.     [138327] = DRUID, -- Gloves of the Astral Warden
  546.     [138333] = DRUID, -- Leggings of the Astral Warden
  547.     [138366] = DRUID, -- Cloak of the Astral Warden
  548.  
  549.     [138342] = HUNTER, -- Eagletalon Cowl
  550.     [138339] = HUNTER, -- Eagletalon Tunic
  551.     [138347] = HUNTER, -- Eagletalon Spaulders
  552.     [138340] = HUNTER, -- Eagletalon Gauntlets
  553.     [138344] = HUNTER, -- Eagletalon Legchains
  554.     [138368] = HUNTER, -- Eagletalon Cloak
  555.  
  556.     [138312] = MAGE, -- Hood of Everburning Knowledge
  557.     [138318] = MAGE, -- Robe of Everburning Knowledge
  558.     [138321] = MAGE, -- Mantle of Everburning Knowledge
  559.     [138309] = MAGE, -- Gloves of Everburning Knowledge
  560.     [138315] = MAGE, -- Leggings of Everburning Knowledge
  561.     [138365] = MAGE, -- Cloak of Everburning Knowledge
  562.  
  563.     [138331] = MONK, -- Hood of Enveloped Dissonance
  564.     [138325] = MONK, -- Tunic of Enveloped Dissonance
  565.     [138337] = MONK, -- Pauldrons of Enveloped Dissonance
  566.     [138328] = MONK, -- Gloves of Enveloped Dissonance
  567.     [138334] = MONK, -- Leggings of Enveloped Dissonance
  568.     [138367] = MONK, -- Cloak of Enveloped Dissonance
  569.  
  570.     [138356] = PALADIN, -- Helmet of the Highlord
  571.     [138350] = PALADIN, -- Breastplate of the Highlord
  572.     [138362] = PALADIN, -- Pauldrons of the Highlord
  573.     [138353] = PALADIN, -- Gauntlets of the Highlord
  574.     [138359] = PALADIN, -- Legplates of the Highlord
  575.     [138369] = PALADIN, -- Greatmantle of the Highlord
  576.  
  577.     [138313] = PRIEST, -- Purifier's Gorget
  578.     [138319] = PRIEST, -- Purifier's Cassock
  579.     [138322] = PRIEST, -- Purifier's Mantle
  580.     [138310] = PRIEST, -- Purifier's Gloves
  581.     [138316] = PRIEST, -- Purifier's Leggings
  582.     [138370] = PRIEST, -- Purifier's Drape
  583.  
  584.     [138332] = ROGUE, -- Doomblade Cowl
  585.     [138326] = ROGUE, -- Doomblade Tunic
  586.     [138338] = ROGUE, -- Doomblade Spaulders
  587.     [138329] = ROGUE, -- Doomblade Gauntlets
  588.     [138335] = ROGUE, -- Doomblade Pants
  589.     [138371] = ROGUE, -- Doomblade Shadowwrap
  590.  
  591.     [138343] = SHAMAN, -- Helm of Shackled Elements
  592.     [138346] = SHAMAN, -- Raiment of Shackled Elements
  593.     [138348] = SHAMAN, -- Pauldrons of Shackled Elements
  594.     [138341] = SHAMAN, -- Gauntlets of Shackled Elements
  595.     [138345] = SHAMAN, -- Leggings of Shackled Elements
  596.     [138372] = SHAMAN, -- Cloak of Shackled Elements
  597.  
  598.     [138314] = WARLOCK, -- Eyes of Azj'Aqir
  599.     [138320] = WARLOCK, -- Finery of Azj'Aqir
  600.     [138323] = WARLOCK, -- Pauldrons of Azj'Aqir
  601.     [138311] = WARLOCK, -- Clutch of Azj'Aqir
  602.     [138317] = WARLOCK, -- Leggings of Azj'Aqir
  603.     [138373] = WARLOCK, -- Cloak of Azj'Aqir
  604.  
  605.     [138357] = WARRIOR, -- Warhelm of the Obsidian Aspect
  606.     [138351] = WARRIOR, -- Chestplate of the Obsidian Aspect
  607.     [138363] = WARRIOR, -- Shoulderplates of the Obsidian Aspect
  608.     [138354] = WARRIOR, -- Gauntlets of the Obsidian Aspect
  609.     [138360] = WARRIOR, -- Legplates of the Obsidian Aspect
  610.     [138374] = WARRIOR  -- Greatcloak of the Obsidian Aspect
  611. }
  612.  
  613. local ValidRelics = {
  614.     [250] = {RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_IRON}, -- Blood DK
  615.     [251] = {RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_FROST}, -- Frost DK
  616.     [252] = {RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_BLOOD}, -- Unholy DK
  617.  
  618.     [577] = {RELIC_SLOT_TYPE_FEL, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_FEL}, -- Havoc DH
  619.     [581] = {RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_FEL}, -- Vengeance DH
  620.  
  621.     [102] = {RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_ARCANE}, -- Balance Druid
  622.     [103] = {RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_LIFE}, -- Feral Druid
  623.     [104] = {RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_LIFE}, -- Guardian Druid
  624.     [105] = {RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_LIFE}, -- Restoration Druid
  625.  
  626.     [253] = {RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_IRON}, -- Beast Mastery Hunter
  627.     [254] = {RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_LIFE}, -- Marksmanship Hunter
  628.     [255] = {RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_BLOOD}, -- Survival Hunter
  629.  
  630.     [62] = {RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_ARCANE}, -- Arcane Mage
  631.     [63] = {RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_FIRE}, -- Fire Mage
  632.     [64] = {RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_ARCANE, RELIC_SLOT_TYPE_FROST}, -- Frost Mage
  633.  
  634.     [268] = {RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_IRON}, -- Brewmaster Monk
  635.     [270] = {RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_WIND}, -- Mistweaver Monk
  636.     [269] = {RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_WIND}, -- Windwalker Monk
  637.  
  638.     [65] = {RELIC_SLOT_TYPE_HOLY, RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_HOLY}, -- Holy Paladin
  639.     [66] = {RELIC_SLOT_TYPE_HOLY, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_ARCANE}, -- Protection Paladin
  640.     [70] = {RELIC_SLOT_TYPE_HOLY, RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_HOLY}, -- Retribution Paladin
  641.  
  642.     [256] = {RELIC_SLOT_TYPE_HOLY, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_HOLY}, -- Discipline Priest
  643.     [257] = {RELIC_SLOT_TYPE_HOLY, RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_HOLY}, -- Holy Priest
  644.     [258] = {RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_SHADOW}, -- Shadow Priest
  645.  
  646.     [259] = {RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_BLOOD}, -- Assassination Rogue
  647.     [260] = {RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_WIND}, -- Outlaw Rogue
  648.     [261] = {RELIC_SLOT_TYPE_FEL, RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_FEL}, -- Subtlety Rogue
  649.  
  650.     [262] = {RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_WIND}, -- Elemental Shaman
  651.     [263] = {RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_WIND}, -- Enhancement Shaman
  652.     [264] = {RELIC_SLOT_TYPE_LIFE, RELIC_SLOT_TYPE_FROST, RELIC_SLOT_TYPE_LIFE}, -- Restoration Shaman
  653.  
  654.     [265] = {RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_SHADOW}, -- Affliction Warlock
  655.     [266] = {RELIC_SLOT_TYPE_SHADOW, RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_FEL}, -- Demonology Warlock
  656.     [267] = {RELIC_SLOT_TYPE_FEL, RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_FEL}, -- Destruction Warlock
  657.  
  658.     [71] = {RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_SHADOW}, -- Arms Warrior
  659.     [72] = {RELIC_SLOT_TYPE_FIRE, RELIC_SLOT_TYPE_WIND, RELIC_SLOT_TYPE_IRON}, -- Fury Warrior
  660.     [73] = {RELIC_SLOT_TYPE_IRON, RELIC_SLOT_TYPE_BLOOD, RELIC_SLOT_TYPE_FIRE}, -- Protection Warrior
  661. }
  662.  
  663. -- IDs for following are from http://wow.gamepedia.com/API_GetInspectSpecialization
  664. local PrimaryAttributes = {
  665.     { DEATH_KNIGHT, 'Any', ITEM_MOD_STRENGTH_SHORT },
  666.    
  667.     { DEMON_HUNTER, 'Any', ITEM_MOD_AGILITY_SHORT },
  668.    
  669.     { DRUID, 102, ITEM_MOD_INTELLECT_SHORT },           -- balance
  670.     { DRUID, 103, ITEM_MOD_AGILITY_SHORT },         -- feral
  671.     { DRUID, 104, ITEM_MOD_AGILITY_SHORT },         -- guardian
  672.     { DRUID, 105, ITEM_MOD_INTELLECT_SHORT },           -- restoration
  673.    
  674.     { HUNTER, 'Any', ITEM_MOD_AGILITY_SHORT },
  675.    
  676.     { MAGE, 'Any', ITEM_MOD_INTELLECT_SHORT },
  677.  
  678.     { MONK, 268, ITEM_MOD_AGILITY_SHORT },              -- brewmaster
  679.     { MONK, 270, ITEM_MOD_INTELLECT_SHORT },            -- mistweaver
  680.     { MONK, 269, ITEM_MOD_AGILITY_SHORT },              -- windwalker
  681.    
  682.     { PALADIN, 65, ITEM_MOD_INTELLECT_SHORT },          -- holy
  683.     { PALADIN, 66, ITEM_MOD_STRENGTH_SHORT },           -- protection
  684.     { PALADIN, 70, ITEM_MOD_STRENGTH_SHORT },           -- retribution
  685.  
  686.     { PRIEST, 'Any', ITEM_MOD_INTELLECT_SHORT },
  687.    
  688.     { ROGUE, 'Any', ITEM_MOD_AGILITY_SHORT },
  689.  
  690.     { SHAMAN, 262, ITEM_MOD_INTELLECT_SHORT },          -- elemental
  691.     { SHAMAN, 263, ITEM_MOD_AGILITY_SHORT },            -- enhancement
  692.     { SHAMAN, 264, ITEM_MOD_INTELLECT_SHORT },          -- restoration
  693.    
  694.     { WARLOCK, 'Any', ITEM_MOD_INTELLECT_SHORT },
  695.    
  696.     { WARRIOR, 'Any', ITEM_MOD_STRENGTH_SHORT }
  697. }
  698.    
  699. local OffspecAttributes = {
  700.     { DRUID, 102, ITEM_MOD_AGILITY_SHORT },         -- balance
  701.     { DRUID, 103, ITEM_MOD_INTELLECT_SHORT },           -- feral
  702.     { DRUID, 104, ITEM_MOD_INTELLECT_SHORT },           -- guardian
  703.     { DRUID, 105, ITEM_MOD_AGILITY_SHORT },         -- restoration
  704.  
  705.     { MONK, 268, ITEM_MOD_INTELLECT_SHORT },            -- brewmaster
  706.     { MONK, 270, ITEM_MOD_AGILITY_SHORT },              -- mistweaver
  707.     { MONK, 269, ITEM_MOD_INTELLECT_SHORT },            -- windwalker
  708.  
  709.     { PALADIN, 65, ITEM_MOD_STRENGTH_SHORT },           -- holy
  710.     { PALADIN, 66, ITEM_MOD_INTELLECT_SHORT },          -- protection
  711.     { PALADIN, 70, ITEM_MOD_INTELLECT_SHORT },          -- retribution
  712.  
  713.     { SHAMAN, 262, ITEM_MOD_AGILITY_SHORT },            -- elemental
  714.     { SHAMAN, 263, ITEM_MOD_INTELLECT_SHORT },          -- enhancement
  715.     { SHAMAN, 264, ITEM_MOD_AGILITY_SHORT }         -- restoration
  716.    
  717. }
  718.  
  719. -- note that the following is only valid for Legion; previously different weapons were possible (ex: feral staff vs. 2 fist weapons)
  720. -- adding 3 for relics
  721. local ExpectedItemCount = {         -- number of items expected by spec, assuming person has gear in every slot
  722.     { DEATH_KNIGHT, 250, 15 },          -- blood
  723.     { DEATH_KNIGHT, 251, 16 },          -- frost
  724.     { DEATH_KNIGHT, 252, 15 },          -- unholy
  725.    
  726.     { DEMON_HUNTER, 577, 16 },          -- havoc
  727.     { DEMON_HUNTER, 581, 16 },          -- vengeance
  728.    
  729.     { DRUID, 102, 15 },                 -- balance
  730.     { DRUID, 103, 16 },                 -- feral
  731.     { DRUID, 104, 16 },                 -- guardian
  732.     { DRUID, 105, 15 },                 -- restoration
  733.    
  734.     { HUNTER, 253, 15 },                    -- beast mastery
  735.     { HUNTER, 254, 15 },                    -- marksmanship
  736.     { HUNTER, 255, 15 },                    -- survival
  737.    
  738.     { MAGE, 62, 15 },                       -- arcane
  739.     { MAGE, 63, 16 },                       -- fire
  740.     { MAGE, 64, 15 },                       -- frost
  741.  
  742.     { MONK, 268, 15 },                  -- brewmaster
  743.     { MONK, 270, 15 },                  -- mistweaver
  744.     { MONK, 269, 16 },                  -- windwalker
  745.    
  746.     { PALADIN, 65, 15 },                    -- holy
  747.     { PALADIN, 66, 16 },                    -- protection
  748.     { PALADIN, 70, 15 },                    -- retribution
  749.  
  750.     { PRIEST, 256, 15 },                    -- discipline
  751.     { PRIEST, 257, 15 },                    -- holy
  752.     { PRIEST, 258, 16 },                    -- shadow
  753.    
  754.     { ROGUE, 259, 16 },                 -- assassination
  755.     { ROGUE, 260, 16 },                 -- outlaw
  756.     { ROGUE, 261, 16 },                 -- subtlety
  757.  
  758.     { SHAMAN, 262, 16 },                    -- elemental
  759.     { SHAMAN, 263, 16 },                    -- enhancement
  760.     { SHAMAN, 264, 16 },                    -- restoration
  761.    
  762.     { WARLOCK, 256, 15 },                   -- affliction
  763.     { WARLOCK, 266, 16 },                   -- demonology
  764.     { WARLOCK, 267, 15 },                   -- destruction
  765.    
  766.     { WARRIOR, 71, 15 },                    -- arms
  767.     { WARRIOR, 72, 16 },                    -- fury
  768.     { WARRIOR, 73, 16 }                 -- protection
  769. }
  770.  
  771. --[[
  772. to easily populate these arrays:
  773.     wowhead search trinkets -> usable by "whichever" -> added in expansion/patch
  774.     paste into OpenOffice
  775.     =concatenate(b1;", -- ";d1)
  776.     ensure curly quotes are off in tools -> autocorrect options -> localized options
  777.     to obtain IDs: http://www.wowhead.com/items/armor/trinkets/role:1?filter=166:151;7:1;0:0#0-3+2
  778. ]]--   
  779. local TRINKET_AGILITY_DPS = {
  780.  
  781.     -- 7.1.5 and previously missed 7.1 trinkets
  782.     142506, -- Eye of Guarm
  783.     142166, -- Ethereal Urn
  784.    
  785.     -- 7.1 trinkets
  786.     140027, -- Ley Spark
  787.     142164, -- Toe Knee's Promise
  788.     142160, -- Mrrgria's Favor
  789.     142167, -- Eye of Command
  790.     142165, -- Deteriorated Construct Core
  791.     142159, -- Bloodstained Handkerchief
  792.     142157, -- Aran's Relaxing Ruby
  793.  
  794.     137419, -- Chrono Shard
  795.     133642, -- Horn of Valor
  796.     141537, -- Thrice-Accursed Compass
  797.     141482, -- Unstable Arcanocrystal
  798.     140794, -- Arcanogolem Digit
  799.     140806, -- Convergence of Fates
  800.     140808, -- Draught of Souls
  801.     140796, -- Entwined Elemental Foci
  802.     140801, -- Fury of the Burning Sky
  803.     140798, -- Icon of Rot
  804.     140802, -- Nightblooming Frond
  805.     136258, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Conquest
  806.     136145, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Conquest
  807.     139329, -- Bloodthirsty Instinct
  808.     139334, -- Nature's Call
  809.     139320, -- Ravaged Seed Pod
  810.     139325, -- Spontaneous Appendages
  811.     139323, -- Twisting Wind
  812.     138224, -- Unstable Horrorslime
  813.     135806, -- Legion Season 1Vindictive Gladiator's Insignia of Conquest
  814.     135693, -- Legion Season 1Vindictive Gladiator's Insignia of Conquest
  815.     136716, -- Caged Horror
  816.     137459, -- Chaos Talisman
  817.     137446, -- Elementium Bomb Squirrel Generator
  818.     133641, -- Eye of Skovald
  819.     137539, -- Faulty Countermeasure
  820.     137329, -- Figurehead of the Naglfar
  821.     137369, -- Giant Ornamental Pearl
  822.     136975, -- Hunger of the Pack
  823.     137357, -- Mark of Dargrul
  824.     133644, -- Memento of Angerboda
  825.     137541, -- Moonlit Prism
  826.     137349, -- Naraxas' Spiked Tongue
  827.     137312, -- Nightmare Egg Shell
  828.     137306, -- Oakheart's Gnarled Root
  829.     137433, -- Obelisk of the Void
  830.     136715, -- Spiked Counterweight
  831.     137367, -- Stormsinger Fulmination Charge
  832.     137373, -- Tempered Egg of Serpentrix
  833.     137406, -- Terrorbound Nexus
  834.     140026, -- The Devilsaur's Bite
  835.     137439, -- Tiny Oozeling in a Jar
  836.     137537, -- Tirathon's Betrayal
  837.     137486, -- Windscar Whetstone
  838.     135919, -- Legion Season 1Vindictive Combatant's Insignia of Conquest
  839.     136032, -- Legion Season 1Vindictive Combatant's Insignia of Conquest
  840.     139630, -- Etching of SargerasDemon Hunter
  841.     128958, -- Lekos' LeashDemon Hunter
  842.     129044, -- Frothing Helhound's Fury
  843.     131803 -- Spine of Barax
  844. }
  845.  
  846. local TRINKET_INTELLECT_DPS = {
  847.     -- 7.1.5 and previously missed 7.1 trinkets
  848.     142166, -- Ethereal Urn
  849.  
  850.     -- 7.1 trinkets
  851.     140031, -- Mana Spark
  852.     142160, -- Mrrgria's Favor
  853.     142165, -- Deteriorated Construct Core
  854.     142157, -- Aran's Relaxing Ruby
  855.    
  856.     137419, -- Chrono Shard
  857.     133642, -- Horn of Valor
  858.     141482, -- Unstable Arcanocrystal
  859.     141536, -- Padawsen's Unlucky Charm
  860.     132970, -- Runas' Nearly Depleted Ley Crystal
  861.     136038, -- Legion Season 1Vindictive Combatant's Insignia of Dominance
  862.     135925, -- Legion Season 1Vindictive Combatant's Insignia of Dominance
  863.     132895, -- The Watcher's Divine Inspiration
  864.     137367, -- Stormsinger Fulmination Charge
  865.     137398, -- Portable Manacracker
  866.     121810, -- Pocket Void Portal
  867.     137433, -- Obelisk of the Void
  868.     137306, -- Oakheart's Gnarled Root
  869.     137349, -- Naraxas' Spiked Tongue
  870.     137541, -- Moonlit Prism
  871.     137485, -- Infernal Writ
  872.     137329, -- Figurehead of the Naglfar
  873.     133641, -- Eye of Skovald
  874.     137446, -- Elementium Bomb Squirrel Generator
  875.     140030, -- Devilsaur Shock-Baton
  876.     137301, -- Corrupted Starlight
  877.     136716, -- Caged Horror
  878.     121652, -- Ancient Leaf
  879.     139326, -- Wriggling Sinew
  880.     140809, -- Whispers in the Dark
  881.     135699, -- Legion Season 1Vindictive Gladiator's Insignia of Dominance
  882.     136151, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Dominance
  883.     135812, -- Legion Season 1Vindictive Gladiator's Insignia of Dominance
  884.     136264, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Dominance
  885.     138224, -- Unstable Horrorslime
  886.     139323, -- Twisting Wind
  887.     139321, -- Swarming Plaguehive
  888.     140804, -- Star Gate
  889.     140800, -- Pharamere's Forbidden Grimore
  890.     140798, -- Icon of Rot
  891.     140801, -- Fury of the Burning Sky
  892.     140792, -- Erratic Metronome
  893.     139336 -- Bough of Corruption
  894. }
  895.  
  896. local TRINKET_STRENGTH_DPS = {
  897.     -- 7.1.5 and previously missed 7.1 trinkets
  898.     142166, -- Ethereal Urn
  899.     142508, -- Chains of the Valorous
  900.  
  901.     -- 7.1 trinkets
  902.     140035, -- Fluctuating Arc Capacitor
  903.     142164, -- Toe Knee's Promise
  904.     142167, -- Eye of Command
  905.     142159, -- Bloodstained Handkerchief
  906.    
  907.     137419, -- Chrono Shard
  908.     133642, -- Horn of Valor
  909.     141482, -- Unstable Arcanocrystal
  910.     141535, -- Ettin Fingernail
  911.     137486, -- Windscar Whetstone
  912.     136041, -- Legion Season 1Vindictive Combatant's Insignia of Victory
  913.     135928, -- Legion Season 1Vindictive Combatant's Insignia of Victory
  914.     137439, -- Tiny Oozeling in a Jar
  915.     137406, -- Terrorbound Nexus
  916.     129260, -- Tenacity of Cursed Blood
  917.     136715, -- Spiked Counterweight
  918.     137312, -- Nightmare Egg Shell
  919.     121806, -- Mountain Rage Shaker
  920.     121570, -- Might of the Forsaken
  921.     133644, -- Memento of Angerboda
  922.     137357, -- Mark of Dargrul
  923.     140034, -- Impact Tremor
  924.     136975, -- Hunger of the Pack
  925.     137369, -- Giant Ornamental Pearl
  926.     137539, -- Faulty Countermeasure
  927.     137459, -- Chaos Talisman
  928.     135815, -- Legion Season 1Vindictive Gladiator's Insignia of Victory
  929.     135702, -- Legion Season 1Vindictive Gladiator's Insignia of Victory
  930.     136154, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Victory
  931.     136267, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Victory
  932.     139328, -- Ursoc's Rending Paw
  933.     139325, -- Spontaneous Appendages
  934.     139320, -- Ravaged Seed Pod
  935.     139334, -- Nature's Call
  936.     140799, -- Might of Krosus
  937.     140796, -- Entwined Elemental Foci
  938.     140808, -- Draught of Souls
  939.     140806, -- Convergence of Fates
  940.     140790 -- Claw of the Crystalline Scorpid
  941. }
  942.  
  943. local TRINKET_HEALER = {
  944.     -- 7.1.5 and previously missed 7.1 trinkets
  945.     142166, -- Ethereal Urn
  946.     142507, -- Brinewater Slime in a Bottle
  947.  
  948.     -- 7.1 trinkets
  949.     140031, -- Mana Spark
  950.     142160, -- Mrrgria's Favor
  951.     142162, -- Fluctuating Energy
  952.     142158, -- Faith's Crucible
  953.     142165, -- Deteriorated Construct Core
  954.     142157, -- Aran's Relaxing Ruby
  955.    
  956.     137419, -- Chrono Shard
  957.     133642, -- Horn of Valor
  958.     141482, -- Unstable Arcanocrystal
  959.     141536, -- Padawsen's Unlucky Charm
  960.     132970, -- Runas' Nearly Depleted Ley Crystal
  961.     136038, -- Legion Season 1Vindictive Combatant's Insignia of Dominance
  962.     135925, -- Legion Season 1Vindictive Combatant's Insignia of Dominance
  963.     137452, -- Thrumming Gossamer
  964.     132895, -- The Watcher's Divine Inspiration
  965.     137367, -- Stormsinger Fulmination Charge
  966.     137398, -- Portable Manacracker
  967.     121810, -- Pocket Void Portal
  968.     137433, -- Obelisk of the Void
  969.     137306, -- Oakheart's Gnarled Root
  970.     133766, -- Nether Anti-Toxin
  971.     137349, -- Naraxas' Spiked Tongue
  972.     133645, -- Naglfar Fare
  973.     133646, -- Mote of Sanctification
  974.     137541, -- Moonlit Prism
  975.     137462, -- Jewel of Insatiable Desire
  976.     137485, -- Infernal Writ
  977.     137484, -- Flask of the Solemn Night
  978.     137329, -- Figurehead of the Naglfar
  979.     133641, -- Eye of Skovald
  980.     137446, -- Elementium Bomb Squirrel Generator
  981.     140030, -- Devilsaur Shock-Baton
  982.     137301, -- Corrupted Starlight
  983.     137540, -- Concave Reflecting Lens
  984.     136716, -- Caged Horror
  985.     137378, -- Bottled Hurricane
  986.     121652, -- Ancient Leaf
  987.     136714, -- Amalgam's Seventh Spine
  988.     139326, -- Wriggling Sinew
  989.     135812, -- Legion Season 1Vindictive Gladiator's Insignia of Dominance
  990.     136264, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Dominance
  991.     135699, -- Legion Season 1Vindictive Gladiator's Insignia of Dominance
  992.     136151, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Dominance
  993.     138222, -- Vial of Nightmare Fog
  994.     138224, -- Unstable Horrorslime
  995.     139323, -- Twisting Wind
  996.     139321, -- Swarming Plaguehive
  997.     140793, -- Perfectly Preserved Cake
  998.     139333, -- Horn of Cenarius
  999.     139330, -- Heightened Senses
  1000.     140803, -- Etraeus' Celestial Map
  1001.     140805, -- Ephemeral Paradox
  1002.     139322, -- Cocoon of Enforced Solitude
  1003.     139336, -- Bough of Corruption
  1004.     140795 -- Aluriel's Mirror
  1005. }
  1006.  
  1007. local TRINKET_TANK = {
  1008.     -- 7.1.5 and previously missed 7.1 trinkets
  1009.     142506, -- Eye of Guarm
  1010.     142166, -- Ethereal Urn
  1011.  
  1012.     -- 7.1 trinkets
  1013.     140027, -- Ley Spark
  1014.     140035, -- Fluctuating Arc Capacitor
  1015.     142169, -- Raven Eidolon
  1016.     142168, -- Majordomo's Dinner Bell
  1017.     142161, -- Inescapable Dread
  1018.    
  1019.     137419, -- Chrono Shard
  1020.     133642, -- Horn of Valor
  1021.     141482, -- Unstable Arcanocrystal
  1022.     137315, -- Writhing Heart of Darkness
  1023.     136041, -- Legion Season 1Vindictive Combatant's Insignia of Victory
  1024.     135928, -- Legion Season 1Vindictive Combatant's Insignia of Victory
  1025.     136032, -- Legion Season 1Vindictive Combatant's Insignia of Conquest
  1026.     135919, -- Legion Season 1Vindictive Combatant's Insignia of Conquest
  1027.     140026, -- The Devilsaur's Bite
  1028.     129260, -- Tenacity of Cursed Blood
  1029.     137344, -- Talisman of the Cragshaper
  1030.     131803, -- Spine of Barax
  1031.     137440, -- Shivermaw's Jawbone
  1032.     137338, -- Shard of Rokmora
  1033.     137362, -- Parjesh's Medallion
  1034.     137538, -- Orb of Torment
  1035.     121806, -- Mountain Rage Shaker
  1036.     121570, -- Might of the Forsaken
  1037.     128958, -- Lekos' LeashDemon Hunter
  1038.     137430, -- Impenetrable Nerubian Husk
  1039.     140034, -- Impact Tremor
  1040.     133647, -- Gift of Radiance
  1041.     129044, -- Frothing Helhound's Fury
  1042.     136978, -- Ember of Nullification
  1043.     137400, -- Coagulated Nightwell Residue
  1044.     135702, -- Legion Season 1Vindictive Gladiator's Insignia of Victory
  1045.     136267, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Victory
  1046.     135815, -- Legion Season 1Vindictive Gladiator's Insignia of Victory
  1047.     136154, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Victory
  1048.     135693, -- Legion Season 1Vindictive Gladiator's Insignia of Conquest
  1049.     136258, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Conquest
  1050.     135806, -- Legion Season 1Vindictive Gladiator's Insignia of Conquest
  1051.     136145, -- Legion Season 1 EliteVindictive Gladiator's Insignia of Conquest
  1052.     139327, -- Unbridled Fury
  1053.     140791, -- Royal Dagger Haft
  1054.     138225, -- Phantasmal Echo
  1055.     140807, -- Infernal Contract
  1056.     139335, -- Grotesque Statuette
  1057.     139324, -- Goblet of Nightmarish Ichor
  1058.     140797, -- Fang of Tichcondrius
  1059.     139630, -- Etching of SargerasDemon Hunter
  1060.     140789 -- Animated Exoskeleton
  1061. }
  1062.  
  1063. -- set up Frames that will listen for events
  1064. local addonLoadedFrame
  1065. local lootReceivedEventFrame
  1066. local whisperReceivedEventFrame
  1067. local bnWhisperReceivedEventFrame
  1068. local rollReceivedEventFrame
  1069. local inspectReadyEventFrame
  1070. local rosterUpdatedEventFrame
  1071. local combatStatusChangedEventFrame
  1072. local rollDelayFrame
  1073. local highlightDelayFrame
  1074. local groupMemberInfoChangedEventFrame
  1075.  
  1076. -- set up variables that will track addon's status
  1077. local isEnabled = false
  1078. local delay = 0
  1079. local nextRollDelay = 0
  1080. local unhighlightDelay = 0
  1081. local priorCacheRefreshTime = 0
  1082.  
  1083. local whisperedItems = {}  -- list of items we've whispered to people; index is character name-realm, content is item
  1084.  
  1085. local numOfQueuedRollItems = 0;
  1086. local queuedRollOwners = {}
  1087. local queuedRollItems = {}
  1088.  
  1089. local currentRollOwner = nil
  1090. local currentRollItem = nil
  1091. local currentRolls = {}
  1092. local PLH_RANDOM_ROLL_RESULT_PATTERN = _G.RANDOM_ROLL_RESULT
  1093.       PLH_RANDOM_ROLL_RESULT_PATTERN = PLH_RANDOM_ROLL_RESULT_PATTERN:gsub('%%s', '(.+)')
  1094.       PLH_RANDOM_ROLL_RESULT_PATTERN = PLH_RANDOM_ROLL_RESULT_PATTERN:gsub('%%d %(%%d%-%%d%)', '(%%d+) %%((%%d+)%%-(%%d+)%%)')
  1095.       PLH_RANDOM_ROLL_RESULT_PATTERN = '^' .. PLH_RANDOM_ROLL_RESULT_PATTERN .. '$'
  1096. local LOOT_ITEM_SELF_PATTERN = _G.LOOT_ITEM_SELF
  1097.       LOOT_ITEM_SELF_PATTERN = LOOT_ITEM_SELF_PATTERN:gsub('%%s', '(.+)')
  1098. local LOOT_ITEM_PATTERN = _G.LOOT_ITEM
  1099.       LOOT_ITEM_PATTERN = LOOT_ITEM_PATTERN:gsub('%%s', '(.+)')
  1100.  
  1101. -- set up variables that will cache group member's information
  1102. local groupInfoCache = {}  -- array keyed by name-realm paired with a list of slotid-items
  1103. local maxInspectIndex = 0  -- the index of the last character in GetRaidRosterInfo(); must be < inspectIndex to start with so PopulateGroupInfoCache can start inspections
  1104. local inspectIndex = maxInspectIndex + 1    -- the index of the character within GetRaidRosterInfo() that we're currently inspecting
  1105.                                             -- defaulting to a higher value so PopulateGroupCache() knows to start a new inspection loop the first time it is called
  1106. local notifyInspectName = nil -- valued if we sent a request to inspect someone, nil otherwise
  1107. --local inspectRetries = 0  -- tracks how many times we've attempted to inspect a specific character without getting an INSPECT_READY result
  1108. local inspectLoop = MAX_INSPECT_LOOPS + 1 -- defaulting to a higher value so PopulateGroupCache() knows to start a new inspection loop the first time it is called
  1109.  
  1110. local raidFrameTextures = {}  -- array indexed by characterName-realmName, containing texture to be shown in raid frames (for loot identification)
  1111. local raidFrameTooltips = {}  -- array indexed by characterName-realmName, containing tooltip to be shown in raid frames (for loot identification)
  1112.  
  1113. local function GetExpectedRelicCount(level)
  1114.     if level ~= nil then
  1115.         if level == 110 then
  1116.             return NUM_EXPECTED_RELICS_110
  1117.         elseif level >= 101 and level < 110 then
  1118.             return NUM_EXPECTED_RELICS_101
  1119.         end
  1120.     end
  1121.     return 0
  1122. end
  1123.  
  1124. local function GetExpectedItemCount(class, spec, level)
  1125.     if class ~= nil and spec ~= nil and level ~= nil then
  1126.         if level >= 101 then   -- pre-Legion, classes could use different weapons; ex: feral used a staff instead of 2 fist weapons
  1127.                                 -- technically the change happens once the person gets their artifact, but we'll just simplify and
  1128.                                 -- expect Legion counts at 101 and above, and 15 items for 100 and earlier
  1129.             local i = 1
  1130.             while ExpectedItemCount[i] do
  1131.                 if class == ExpectedItemCount[i][1] and spec == ExpectedItemCount[i][2] then
  1132.                     return ExpectedItemCount[i][3]
  1133.                 end
  1134.                 i = i + 1
  1135.             end
  1136.         end
  1137.     end
  1138.     return NUM_EXPECTED_ITEMS
  1139. end
  1140.  
  1141. function PLH_GetRelicCountFromCache(name)
  1142.     local relicCount = 0
  1143.     local characterDetails = groupInfoCache[name]
  1144.     if characterDetails ~= nil then
  1145.         if characterDetails[PLH_RELICSLOT + 1] ~= nil then
  1146.             relicCount = relicCount + 1
  1147.         end
  1148.         if characterDetails[PLH_RELICSLOT + 2] ~= nil then
  1149.             relicCount = relicCount + 1
  1150.         end
  1151.         if characterDetails[PLH_RELICSLOT + 3] ~= nil then
  1152.             relicCount = relicCount + 1
  1153.         end
  1154.     end
  1155.     return relicCount
  1156. end
  1157.  
  1158. -- only returns count of equippable items from cache; excludes relics and other cached info such as ClassName/Spec/Level
  1159. function PLH_GetItemCountFromCache(name)
  1160.     local itemCount = 0
  1161.     if groupInfoCache[name] ~= nil then
  1162.         for slotID, item in pairs (groupInfoCache[name]) do
  1163.             itemCount = itemCount + 1
  1164.         end
  1165.         itemCount = itemCount - 4 -- subtract 3 since everyone has ClassName and Spec and Level and InspectCount elements
  1166.         itemCount = itemCount - PLH_GetRelicCountFromCache(name)
  1167.     else
  1168.         PLH_SendDebugMessage('groupInfoCache[name] is nil for ', name) 
  1169.     end
  1170.     return itemCount
  1171. end
  1172.  
  1173. -- Returns the item that character has equipped in slotID, based on the cache
  1174. local function GetEquippedItem(characterName, slotID)
  1175.     local item = nil
  1176.     if characterName == PLH_GetUnitNameWithRealm('player') then
  1177.         -- we can get the item directly
  1178.         item = GetInventoryItemLink('player', slotID)
  1179.     else
  1180.         -- we have to get the item from the cache
  1181.         local characterDetails = groupInfoCache[characterName]
  1182.         if characterDetails ~= nil then
  1183.             item = characterDetails[slotID]
  1184.         end
  1185.     end
  1186.     return item
  1187. end
  1188.  
  1189. local function GetEquippedRelic(characterName, relicNumber)
  1190.     local _, relic = nil
  1191.     if characterName == PLH_GetUnitNameWithRealm('player') then
  1192.         -- we can get the item directly
  1193.         local weapon = GetInventoryItemLink('player', INVSLOT_MAINHAND)
  1194.         _, relic = GetItemGem(weapon, relicNumber)
  1195.     else
  1196.         -- we have to get the item from the cache
  1197.         local characterDetails = groupInfoCache[characterName]
  1198.         if characterDetails ~= nil then
  1199.             relic = characterDetails[PLH_RELICSLOT + relicNumber]
  1200.         end
  1201.     end
  1202.     return relic
  1203. end
  1204.  
  1205. -- note that this will return a value based on player's class/spec, so it will switch if the primary attribute is mutable!
  1206. -- thus only use for cloaks/rings/necks/trinkets, sine those things are not mutable
  1207. local function GetItemPrimaryAttribute(item)
  1208.     local stats = GetItemStats(item)
  1209.     if stats ~= nil then
  1210.         for stat, value in pairs(stats) do
  1211.             if _G[stat] == ITEM_MOD_STRENGTH_SHORT or _G[stat] == ITEM_MOD_INTELLECT_SHORT or _G[stat] == ITEM_MOD_AGILITY_SHORT then
  1212.                 return _G[stat]
  1213.             end
  1214.         end
  1215.     end
  1216.     return nil
  1217. end
  1218.  
  1219. -- TODO may need to limit this by ilvl?  ex: did cloaks only become mutable in Legion?
  1220. local function IsMutablePrimaryAttribute(itemEquipLoc)
  1221.     return itemEquipLoc == 'INVTYPE_HEAD'
  1222.         or itemEquipLoc == 'INVTYPE_SHOULDER'
  1223.         or itemEquipLoc == 'INVTYPE_CLOAK'
  1224.         or itemEquipLoc == 'INVTYPE_CHEST'
  1225.         or itemEquipLoc == 'INVTYPE_ROBE'
  1226.         or itemEquipLoc == 'INVTYPE_WAIST'
  1227.         or itemEquipLoc == 'INVTYPE_LEGS'
  1228.         or itemEquipLoc == 'INVTYPE_FEET'
  1229.         or itemEquipLoc == 'INVTYPE_WRIST'
  1230.         or itemEquipLoc == 'INVTYPE_HAND'
  1231. end
  1232.  
  1233. local function IsTrinketUsable(item, role)
  1234.     local itemLink = select(2, GetItemInfo(item))
  1235.     local itemID = string.match(itemLink, 'item:(%d+):')
  1236.  
  1237.     local trinketList = nil
  1238.     if role == 'AgilityDPS' then
  1239.         trinketList = TRINKET_AGILITY_DPS
  1240.     elseif role == 'IntellectDPS' then
  1241.         trinketList = TRINKET_INTELLECT_DPS
  1242.     elseif role == 'StrengthDPS' then
  1243.         trinketList = TRINKET_STRENGTH_DPS
  1244.     elseif role == 'Healer' then
  1245.         trinketList = TRINKET_HEALER
  1246.     elseif role == 'Tank' then
  1247.         trinketList = TRINKET_TANK
  1248.     end
  1249.    
  1250.     local i
  1251.     if trinketList ~= nil then
  1252.         for i = 1, #trinketList do
  1253.             if tostring(trinketList[i]) == itemID then
  1254.                 return true
  1255.             end
  1256.         end
  1257.     end
  1258.    
  1259.     return false
  1260. end
  1261.  
  1262. local function IsRelic(item)
  1263.     local _, _, _, _, _, _, _, _, _, _, _, itemClass, itemSubclass = GetItemInfo(item)
  1264.     return itemClass == LE_ITEM_CLASS_GEM and itemSubclass == LE_ITEM_ARMOR_RELIC
  1265. end
  1266.  
  1267. local function IsValidRelicTypeForSpec(relicType, spec)
  1268.     local specRelics = ValidRelics[spec]
  1269.     if specRelics ~= nil then
  1270.         return ValidRelics[spec][1] == relicType or ValidRelics[spec][2] == relicType or ValidRelics[spec][3] == relicType
  1271.     else
  1272.         return false
  1273.     end
  1274. end
  1275.  
  1276. -- Returns true if the item is a tier set for a specific class, or is not a tier set piece.
  1277. local function IsValidTierForClass(itemID, class)
  1278.     local itemClass = ValidTier[itemID]
  1279.     if itemClass ~= nil then
  1280.         return itemClass == class
  1281.     else
  1282.         return true
  1283.     end
  1284. end
  1285.  
  1286. -- Returns false if the character cannot use the item.
  1287. local function IsEquippableItemForCharacter(item, characterName)
  1288.     local isEquippableForClass = false
  1289.     local isEquippableForSpec = false
  1290.     local isEquippableForOffspec = false
  1291.     if item ~= nil and characterName ~= nil then
  1292.         if IsEquippableItem(item) or IsRelic(item) then
  1293.             local _, itemLink, _, _, requiredLevel, _, _, _, itemEquipLoc, _, _, itemClass, itemSubclass = GetItemInfo(item)
  1294.             local itemID = tonumber(string.match(itemLink, 'item:(%d+):'))
  1295.             local class
  1296.             local spec
  1297.             local characterLevel
  1298.  
  1299.             if characterName == PLH_GetUnitNameWithRealm('player') then
  1300.                 _, class = UnitClass('player')
  1301.                 spec = GetSpecializationInfo(GetSpecialization())
  1302.                 characterLevel = UnitLevel('player')
  1303.             elseif groupInfoCache[characterName] ~= nil then
  1304.                 class = groupInfoCache[characterName]['ClassName']
  1305.                 spec = groupInfoCache[characterName]['Spec']
  1306.                 characterLevel = groupInfoCache[characterName]['Level']
  1307.             else
  1308.                 PLH_SendDebugMessage('Unable to determine class and spec in InEquippableItemForCharacter()!!!!')
  1309.                 return false  -- should never reach here, but if we do it means we're not looking up the player or anyone in cache
  1310.             end
  1311.            
  1312.             -- Check if the piece is not equippable due to tier class restraints.
  1313.             if not IsValidTierForClass(itemID, class) then
  1314.                 return false
  1315.             end
  1316.            
  1317.             -- check if character is a high enough level to equip the item
  1318.             if PLH_CHECK_CHARACTER_LEVEL and requiredLevel > characterLevel then
  1319.                 return false
  1320.             end
  1321.            
  1322.             local isRelic = IsRelic(item)
  1323.             isEquippableForClass = itemEquipLoc == 'INVTYPE_CLOAK' -- cloaks show up as type=armor, subtype=cloth, but they're equippable by all, so set to true if cloak
  1324.             local i = 1
  1325.            
  1326.             while not isEquippableForClass and ValidGear[i] do
  1327.                 if class == ValidGear[i][1] and itemClass == ValidGear[i][2] and itemSubclass == ValidGear[i][3] then
  1328.                     isEquippableForClass = true
  1329.                 end
  1330.                 i = i + 1
  1331.             end
  1332.  
  1333.             if isEquippableForClass then
  1334.                 if itemEquipLoc == 'INVTYPE_TRINKET' then
  1335.                     if spec == 105 or spec == 270 or spec == 65 or spec == 256 or spec == 257 or spec == 264 then
  1336.                         isEquippableForSpec = IsTrinketUsable(item, 'Healer')                  
  1337.                     elseif spec == 250 or spec == 581 or spec == 104 or spec == 268 or spec == 66 or spec == 73 then
  1338.                         isEquippableForSpec = IsTrinketUsable(item, 'Tank')
  1339.                     elseif spec == 577 or spec == 103 or spec == 253 or spec == 254 or spec == 255 or spec == 269 or spec == 259 or spec == 260 or spec == 261 or spec == 263 then
  1340.                         isEquippableForSpec = IsTrinketUsable(item, 'AgilityDPS')
  1341.                     elseif spec == 251 or spec == 252 or spec == 70 or spec == 71 or spec == 72 then
  1342.                         isEquippableForSpec = IsTrinketUsable(item, 'StrengthDPS')
  1343.                     elseif spec == 102 or spec == 62 or spec == 63 or spec == 64 or spec == 258 or spec == 262 or spec == 265 or spec == 266 or spec == 267 then
  1344.                         isEquippableForSpec = IsTrinketUsable(item, 'IntellectDPS')
  1345.                     end
  1346.                        
  1347.                     if not PLH_CURRENT_SPEC_ONLY and not isEquippableForSpec then
  1348.                         if class == DEATH_KNIGHT or class == WARRIOR then
  1349.                             isEquippableForOffspec = IsTrinketUsable(item, 'Tank') or IsTrinketUsable(item, 'StrengthDPS')
  1350.                         elseif class == DEMON_HUNTER then
  1351.                             isEquippableForOffspec = IsTrinketUsable(item, 'Tank') or IsTrinketUsable(item, 'AgilityDPS')
  1352.                         elseif class == DRUID then
  1353.                             isEquippableForOffspec = IsTrinketUsable(item, 'Tank') or IsTrinketUsable(item, 'AgilityDPS') or IsTrinketUsable(item, 'Healer') or IsTrinketUsable(item, 'IntellectDPS')
  1354.                         elseif class == MONK then
  1355.                             isEquippableForOffspec = IsTrinketUsable(item, 'Tank') or IsTrinketUsable(item, 'AgilityDPS') or IsTrinketUsable(item, 'Healer')
  1356.                         elseif class == PALADIN then
  1357.                             isEquippableForOffspec = IsTrinketUsable(item, 'Tank') or IsTrinketUsable(item, 'StrengthDPS') or IsTrinketUsable(item, 'Healer')
  1358.                         elseif class == PRIEST then
  1359.                             isEquippableForOffspec = IsTrinketUsable(item, 'Healer') or IsTrinketUsable(item, 'IntellectDPS')
  1360.                         elseif class == SHAMAN then
  1361.                             isEquippableForOffspec = IsTrinketUsable(item, 'Healer') or IsTrinketUsable(item, 'IntellectDPS') or IsTrinketUsable(item, 'AgilityDPS')
  1362.                         end
  1363.                     end
  1364.                 else
  1365.                     local itemPrimaryAttribute = GetItemPrimaryAttribute(item)
  1366.                     if itemPrimaryAttribute == nil then
  1367.                         isEquippableForSpec = true  -- if there's no primary attr (ex: ring/neck), then the item is equippable by everyone
  1368.                     elseif IsMutablePrimaryAttribute(itemEquipLoc) then
  1369.                         isEquippableForSpec = true  -- if the item is a piece of gear that has mutable primary stats then return true
  1370.                     else
  1371.                         -- otherwise we're going to check if the item's primary attribute is applicable for the character's spec
  1372.                         i = 1
  1373.                         while not isEquippableForSpec and PrimaryAttributes[i] do
  1374.                             if class == PrimaryAttributes[i][1] and PrimaryAttributes[i][3] == itemPrimaryAttribute and (PrimaryAttributes[i][2] == 'Any' or PrimaryAttributes[i][2] == spec) then
  1375.                                 isEquippableForSpec = true
  1376.                             end
  1377.                             i = i + 1
  1378.                         end
  1379.  
  1380.                         if not PLH_CURRENT_SPEC_ONLY and not isEquippableForSpec then
  1381.                             -- now check to see if it's usable by an offspec
  1382.                             i = 1
  1383.                             while not isEquippableForSpec and OffspecAttributes[i] do
  1384.                                 if class == OffspecAttributes[i][1] and OffspecAttributes[i][3] == itemPrimaryAttribute and OffspecAttributes[i][2] == spec then
  1385.                                     isEquippableForOffspec = true
  1386.                                 end
  1387.                                 i = i + 1
  1388.                             end
  1389.                         end
  1390.                     end
  1391.                 end
  1392.             elseif isRelic then
  1393.                 local relicType = PLH_GetRelicType(item)
  1394.                 isEquippableForSpec = IsValidRelicTypeForSpec(relicType, spec)
  1395.                 isEquippableForClass = isEquippableForSpec
  1396.                 if not PLH_CURRENT_SPEC_ONLY and not isEquippableForSpec then
  1397.                     if class == DEATH_KNIGHT then
  1398.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 250) or IsValidRelicTypeForSpec(relicType, 251) or IsValidRelicTypeForSpec(relicType, 252)
  1399.                     elseif class == DEMON_HUNTER then
  1400.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 577) or IsValidRelicTypeForSpec(relicType, 581)
  1401.                     elseif class == DRUID then
  1402.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 102) or IsValidRelicTypeForSpec(relicType, 103) or IsValidRelicTypeForSpec(relicType, 104) or IsValidRelicTypeForSpec(relicType, 105)
  1403.                     elseif class == HUNTER then
  1404.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 253) or IsValidRelicTypeForSpec(relicType, 254) or IsValidRelicTypeForSpec(relicType, 255)
  1405.                     elseif class == MAGE then
  1406.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 62) or IsValidRelicTypeForSpec(relicType, 63) or IsValidRelicTypeForSpec(relicType, 64)
  1407.                     elseif class == MONK then
  1408.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 268) or IsValidRelicTypeForSpec(relicType, 270) or IsValidRelicTypeForSpec(relicType, 269)
  1409.                     elseif class == PALADIN then
  1410.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 65) or IsValidRelicTypeForSpec(relicType, 66) or IsValidRelicTypeForSpec(relicType, 70)
  1411.                     elseif class == PRIEST then
  1412.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 256) or IsValidRelicTypeForSpec(relicType, 257) or IsValidRelicTypeForSpec(relicType, 258)
  1413.                     elseif class == ROGUE then
  1414.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 259) or IsValidRelicTypeForSpec(relicType, 260) or IsValidRelicTypeForSpec(relicType, 261)
  1415.                     elseif class == SHAMAN then
  1416.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 262) or IsValidRelicTypeForSpec(relicType, 263) or IsValidRelicTypeForSpec(relicType, 264)
  1417.                     elseif class == WARLOCK then
  1418.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 265) or IsValidRelicTypeForSpec(relicType, 266) or IsValidRelicTypeForSpec(relicType, 267)
  1419.                     elseif class == WARRIOR then
  1420.                         isEquippableForOffspec = IsValidRelicTypeForSpec(relicType, 71) or IsValidRelicTypeForSpec(relicType, 72) or IsValidRelicTypeForSpec(relicType, 73)
  1421.                     end
  1422.                     isEquippableForClass = isEquippableForSpec or isEquippableForOffspec
  1423.                 end
  1424.             end
  1425.         end
  1426.     end
  1427. --  PLH_SendDebugMessage('For ' .. characterName .. ' item ' .. item .. ' isEquippableForClass = ' .. tostring(isEquippableForClass) .. ' and isEquippableForSpec = ' .. tostring(isEquippableForSpec)  .. ' and isEquippableForOffspec = ' .. tostring(isEquippableForOffspec))
  1428.    
  1429.     return isEquippableForClass and (isEquippableForSpec or isEquippableForOffspec)
  1430. end
  1431.  
  1432. -- returns two variables:  true if the item is an upgrade over equippedItem (based on ilvl), equipped ilvl
  1433. local function IsAnUpgrade(item, equippedItem)
  1434.     local equippedILVL = PLH_GetRealILVL(equippedItem)
  1435.     if equippedILVL == 0 then
  1436.         -- this means we couldn't find an equippedItem
  1437.         return false, 0
  1438.     else
  1439.         return PLH_GetRealILVL(item) > PLH_GetRealILVL(equippedItem), equippedILVL
  1440.     end
  1441. end
  1442.  
  1443. -- returns an appropriate SlotID for the given itemEquipLoc, or nil if it's not an item
  1444. -- if itemEquipLoc is a finger slot or trinket slot, we'll just return the first item
  1445. -- if itemEquipLoc is a weapon that can be in either slot (INVTYPE_WEAPON), we'll return the main hand
  1446. local function GetSlotID(itemEquipLoc)
  1447.     if itemEquipLoc == 'INVTYPE_HEAD' then return INVSLOT_HEAD
  1448.     elseif itemEquipLoc == 'INVTYPE_NECK' then return INVSLOT_NECK
  1449.     elseif itemEquipLoc == 'INVTYPE_SHOULDER' then return INVSLOT_SHOULDER
  1450.     elseif itemEquipLoc == 'BODY' then return INVSLOT_BODY
  1451.     elseif itemEquipLoc == 'INVTYPE_CHEST' then return INVSLOT_CHEST
  1452.     elseif itemEquipLoc == 'INVTYPE_ROBE' then return INVSLOT_CHEST
  1453.     elseif itemEquipLoc == 'INVTYPE_WAIST' then return INVSLOT_WAIST
  1454.     elseif itemEquipLoc == 'INVTYPE_LEGS' then return INVSLOT_LEGS
  1455.     elseif itemEquipLoc == 'INVTYPE_FEET' then return INVSLOT_FEET
  1456.     elseif itemEquipLoc == 'INVTYPE_WRIST' then return INVSLOT_WRIST
  1457.     elseif itemEquipLoc == 'INVTYPE_HAND' then return INVSLOT_HAND
  1458.     elseif itemEquipLoc == 'INVTYPE_FINGER' then return INVSLOT_FINGER1
  1459.     elseif itemEquipLoc == 'INVTYPE_TRINKET' then return INVSLOT_TRINKET1
  1460.     elseif itemEquipLoc == 'INVTYPE_CLOAK' then return INVSLOT_BACK
  1461.     elseif itemEquipLoc == 'INVTYPE_WEAPON' then return INVSLOT_MAINHAND
  1462.     elseif itemEquipLoc == 'INVTYPE_SHIELD' then return INVSLOT_OFFHAND
  1463.     elseif itemEquipLoc == 'INVTYPE_2HWEAPON' then return INVSLOT_MAINHAND
  1464.     elseif itemEquipLoc == 'INVTYPE_WEAPONMAINHAND' then return INVSLOT_MAINHAND
  1465.     elseif itemEquipLoc == 'INVTYPE_WEAPONOFFHAND' then return INVSLOT_OFFHAND
  1466.     elseif itemEquipLoc == 'INVTYPE_HOLDABLE' then return INVSLOT_OFFHAND
  1467.     elseif itemEquipLoc == 'INVTYPE_RANGED' then return INVSLOT_MAINHAND
  1468.     elseif itemEquipLoc == 'INVTYPE_THROWN' then return INVSLOT_MAINHAND
  1469.     elseif itemEquipLoc == 'INVTYPE_RANGEDRIGHT' then return INVSLOT_MAINHAND
  1470.     elseif itemEquipLoc == 'INVTYPE_RELIC' then return INVSLOT_MAINHAND
  1471.     elseif itemEquipLoc == 'INVTYPE_TABARD' then return INVSLOT_TABARD
  1472.     else return nil
  1473.     end
  1474. end
  1475.  
  1476. -- returns two variables:  true if the item is an upgrade over equippedItem (based on ilvl), equipped ilvl
  1477. -- note: doesn't check if item is equippable, so make sure you do that check beforehand
  1478. local function IsAnUpgradeForCharacter(item, characterName)
  1479.     local isAnUpgrade = false
  1480.     local equippedILVL = 0
  1481.     local _, _, _, _, _, _, _, _, itemEquipLoc, _, _ = GetItemInfo(item)
  1482.     if itemEquipLoc ~= nil and itemEquipLoc ~= '' then
  1483.         if itemEquipLoc == 'INVTYPE_FINGER' then
  1484.             local equippedItem0 = GetEquippedItem(characterName, 11)    -- 1st ring
  1485.             local equippedItem1 = GetEquippedItem(characterName, 12)    -- 2nd ring
  1486.             isAnUpgrade = IsAnUpgrade(item, equippedItem0) or IsAnUpgrade(item, equippedItem1)
  1487.             equippedILVL = min(PLH_GetRealILVL(equippedItem0), PLH_GetRealILVL(equippedItem1))
  1488.         elseif itemEquipLoc == 'INVTYPE_TRINKET' then
  1489.             local equippedItem0 = GetEquippedItem(characterName, 13)    -- 1st trinket
  1490.             local equippedItem1 = GetEquippedItem(characterName, 14)    -- 2nd trinket
  1491.             isAnUpgrade = IsAnUpgrade(item, equippedItem0) or IsAnUpgrade(item, equippedItem1)
  1492.             equippedILVL = min(PLH_GetRealILVL(equippedItem0), PLH_GetRealILVL(equippedItem1))
  1493.         elseif itemEquipLoc == 'INVTYPE_WEAPON' then
  1494.             local equippedItem0 = GetEquippedItem(characterName, 16)        -- main hand
  1495.             local equippedItem1 = GetEquippedItem(characterName, 17)        -- off hand
  1496.             isAnUpgrade = IsAnUpgrade(item, equippedItem0) or IsAnUpgrade(item, equippedItem1)
  1497.             equippedILVL = min(PLH_GetRealILVL(equippedItem0), PLH_GetRealILVL(equippedItem1))
  1498.         else
  1499.             local slotID = GetSlotID(itemEquipLoc)
  1500.             local equippedItem =  GetEquippedItem(characterName, slotID)
  1501.             isAnUpgrade, equippedILVL = IsAnUpgrade(item, equippedItem)
  1502.         end
  1503.     elseif IsRelic(item) then
  1504.         local relicType = PLH_GetRelicType(item)
  1505.  
  1506.         local relic1 = GetEquippedRelic(characterName, 1)
  1507.         local relic1ILVL = PLH_GetRealILVL(relic1)
  1508.         local relic1Type = PLH_GetRelicType(relic1)
  1509.         local relic2 = GetEquippedRelic(characterName, 2)
  1510.         local relic2ILVL = PLH_GetRealILVL(relic2)
  1511.         local relic2Type = PLH_GetRelicType(relic2)
  1512.         local relic3 = GetEquippedRelic(characterName, 3)
  1513.         local relic3ILVL = PLH_GetRealILVL(relic3)
  1514.         local relic3Type = PLH_GetRelicType(relic3)
  1515.         isAnUpgrade = (relicType == relic1Type and IsAnUpgrade(item, relic1))
  1516.             or (relicType == relic2Type and IsAnUpgrade(item, relic2))
  1517.             or (relicType == relic3Type and IsAnUpgrade(item, relic3))
  1518.         if relicType == relic1Type then
  1519.             equippedILVL = relic1ILVL
  1520.         end
  1521.         if relicType == relic2Type and (equippedILVL == 0 or relic2ILVL < equippedILVL) then
  1522.             equippedILVL = relic2ILVL
  1523.         end
  1524.         if relicType == relic3Type and (equippedILVL == 0 or relic3ILVL < equippedILVL) then
  1525.             equippedILVL = relic3ILVL
  1526.         end
  1527.     end
  1528.     return isAnUpgrade, equippedILVL
  1529. end
  1530.  
  1531. -- returns two variables:  first is true or false, second is list of people for whom the item may is an upgrade (by ilvl)
  1532. local function IsAnUpgradeForAnyCharacter(item)
  1533.     local isAnUpgrade, equippedILVL
  1534.     local isAnUpgradeForAnyCharacterNames = {}
  1535.  
  1536.     local index = 1
  1537.     local characterName
  1538.     while GetRaidRosterInfo(index) ~= nil do
  1539.         characterName = select(1, GetRaidRosterInfo(index))
  1540.         -- this characterName may be a full name-realm or it may just be a name, but the functions we're calling expect name-realm
  1541.         if not string.find(characterName, '-') then
  1542.             characterName = PLH_GetFullName(characterName, GetRealmName())
  1543.         end
  1544.         if IsEquippableItemForCharacter(item, characterName) then
  1545.             isAnUpgrade, equippedILVL = IsAnUpgradeForCharacter(item, characterName)
  1546.             if isAnUpgrade then
  1547. --              PLH_SendDebugMessage(item .. ' is an ilvl upgrade for ' .. characterName)
  1548.                 isAnUpgradeForAnyCharacterNames[#isAnUpgradeForAnyCharacterNames + 1] = PLH_GetNameWithoutRealm(characterName) .. ' (' .. equippedILVL .. ')'
  1549.             end
  1550.         end
  1551.         index = index + 1
  1552.     end
  1553.  
  1554.     return #isAnUpgradeForAnyCharacterNames > 0, isAnUpgradeForAnyCharacterNames
  1555. end
  1556.  
  1557. local function IsPlayerInUpgradeList(list)
  1558.     if list ~= nil then
  1559.         local playerName = UnitName('player')
  1560.         for i = 1, #list do
  1561.             if string.sub(list[i], 1, string.len(playerName)) == playerName then  -- see if string starts with 'playername '; ex: 'Madone (690)'
  1562.                 return true
  1563.             end
  1564.         end
  1565.     end
  1566.     return false
  1567. end
  1568.  
  1569. -- returns the names from the given array, with 'and others' if array size > limit
  1570. local function GetNames(namelist, limit)
  1571.     local names = ''
  1572.     if namelist ~= nil then
  1573.         if limit == nil then  -- no limit; show all names
  1574.             limit = #namelist
  1575.         end
  1576.         if namelist[1] ~= nil then
  1577.             names = namelist[1]
  1578.             local maxnames = min(#namelist, limit)
  1579.             for i = 2, maxnames do
  1580.                 if #namelist == 2 then
  1581.                     names = names .. ' '
  1582.                 else
  1583.                     names = names .. ', '
  1584.                 end
  1585.                 if i == #namelist then -- last person
  1586.                     names = names .. 'and '
  1587.                 end
  1588.                 names = names .. namelist[i]
  1589.             end
  1590.             if #namelist > limit then
  1591.                 names = names .. ', and others'
  1592.             end
  1593.         end
  1594.     end
  1595.     return names
  1596. end
  1597.  
  1598. local function PlayerCanCoordinateRolls()
  1599.     return (UnitIsGroupLeader('player') or UnitIsGroupAssistant('player')) and not PLH_IsInLFR()
  1600. end
  1601.  
  1602. local function UnhighlightRaidFrames()
  1603.     for characterName, texture in pairs(raidFrameTextures) do
  1604.         texture:Hide()
  1605.     end
  1606.     for characterName, tooltip in pairs(raidFrameTooltips) do
  1607.         tooltip:SetScript("OnEnter", nil)
  1608.         tooltip:SetScript("OnEvent", nil)
  1609.         tooltip:UnregisterAllEvents()
  1610.         tooltip:Hide()
  1611.     end
  1612. end
  1613.  
  1614. local function StartUnhighlightRaidFramesTimer()
  1615.     highlightDelayFrame:SetScript('OnUpdate', function(self, elapsed)
  1616.         unhighlightDelay = unhighlightDelay + elapsed
  1617.         if unhighlightDelay >= UNHIGHLIGHT_DELAY then
  1618.             UnhighlightRaidFrames()
  1619.             highlightDelayFrame:SetScript('OnUpdate', nil)
  1620.             unhighlightDelay = 0
  1621.         end
  1622.     end)
  1623. end
  1624.  
  1625. function PLH_ResizeHighlights()
  1626.     for characterName, texture in pairs(raidFrameTextures) do
  1627.         raidFrameTextures[characterName]:SetWidth(PLH_HIGHLIGHT_SIZE)
  1628.         raidFrameTextures[characterName]:SetHeight(PLH_HIGHLIGHT_SIZE)
  1629.     end
  1630.     for characterName, tooltip in pairs(raidFrameTooltips) do
  1631.         raidFrameTooltips[characterName]:SetWidth(PLH_HIGHLIGHT_SIZE)
  1632.         raidFrameTooltips[characterName]:SetHeight(PLH_HIGHLIGHT_SIZE)
  1633.     end
  1634. end
  1635.  
  1636. function PLH_ApplyFrameTexture(frame, characterName, item)
  1637.     local unitName = PLH_GetUnitNameWithRealm(frame.unit)
  1638.  
  1639.     if characterName == unitName then
  1640.         -- create the texture to display in the raid frames
  1641.         if not raidFrameTextures[characterName] then
  1642.             raidFrameTextures[characterName] = frame:CreateTexture(nil, "OVERLAY")
  1643.             raidFrameTextures[characterName]:SetPoint("BOTTOM", 0, 2)
  1644.             raidFrameTextures[characterName]:SetWidth(PLH_HIGHLIGHT_SIZE)
  1645.             raidFrameTextures[characterName]:SetHeight(PLH_HIGHLIGHT_SIZE)
  1646.         end
  1647. --      local file_id = GetSpellTexture(60650)  -- spell id for "titanium seal of dalaran"
  1648.         local itemTexture = select(10, GetItemInfo(item))
  1649.         raidFrameTextures[characterName]:SetTexture(itemTexture)
  1650.  
  1651.         -- create the tooltip to display when the cursor is over the texture
  1652.         if not raidFrameTooltips[characterName] then
  1653.             raidFrameTooltips[characterName] = CreateFrame("Frame", frame:GetName() .. "itemTooltip", frame)
  1654.             raidFrameTooltips[characterName]:SetPoint("BOTTOM", 0, 2)
  1655.             raidFrameTooltips[characterName]:SetWidth(PLH_HIGHLIGHT_SIZE)
  1656.             raidFrameTooltips[characterName]:SetHeight(PLH_HIGHLIGHT_SIZE)
  1657.         end
  1658.  
  1659.         raidFrameTooltips[characterName]:SetScript("OnEnter", function(self)
  1660.             GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
  1661.             GameTooltip:SetHyperlink(item)
  1662.             GameTooltip:Show()
  1663.             -- the following sets up the listener for the shift key to toggle display of the item comparison
  1664.             raidFrameTooltips[characterName]:SetScript("OnEvent", function(self, event, arg, ...)
  1665.                 if raidFrameTooltips[characterName]:IsShown() and event == "MODIFIER_STATE_CHANGED" and (arg == "LSHIFT" or arg == "RSHIFT") then
  1666.                     GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
  1667.                     GameTooltip:SetHyperlink(item)
  1668.                     GameTooltip:Show()
  1669.                 end
  1670.             end)
  1671.             raidFrameTooltips[characterName]:RegisterEvent("MODIFIER_STATE_CHANGED")
  1672.         end)       
  1673.  
  1674.         raidFrameTooltips[characterName]:SetScript("OnLeave", function(self)
  1675.             raidFrameTooltips[characterName]:SetScript("OnEvent", nil)
  1676.             raidFrameTooltips[characterName]:UnregisterAllEvents()
  1677.             GameTooltip:Hide()
  1678.         end)
  1679.  
  1680.         raidFrameTextures[characterName]:Show()
  1681.         raidFrameTooltips[characterName]:Show()
  1682.        
  1683.     end
  1684. end
  1685.  
  1686. -- TODO implement for players to whom you can trade as well (keep in mind someone may both trade and receive an item)
  1687. -- TODO what if a single person loots multiple useful items?  (ex: end of m+)
  1688. local function HighlightRaidFrames(characterName, item)
  1689.     if PLH_HIGHLIGHT_RAID_FRAMES then
  1690.         CompactRaidFrameContainer_ApplyToFrames(CompactRaidFrameContainer, "normal", PLH_ApplyFrameTexture, characterName, item)
  1691.         StartUnhighlightRaidFramesTimer()
  1692.     end
  1693. end
  1694.  
  1695. -- Determines whether item is not an upgrade for the person who looted the item, and is an upgrade for someone else in the group
  1696. -- If that's the case, performs the action based on the users' selected Notify Mode
  1697. local function PerformNotify(item, characterName)
  1698.     local isAnUpgradeForLooter, equippedILVL = IsAnUpgradeForCharacter(item, characterName)
  1699.     if PLH_COORDINATE_ROLLS and PlayerCanCoordinateRolls() then
  1700.         whisperedItems[characterName] = item  -- use full name-realm since that what we'll get when we look it up from the whisper
  1701. --      if #isAnUpgradeForAnyCharacterNames > 1 then  -- more than 1 person can use the item
  1702. --                  PLH_SendWhisper('You can trade ' .. item .. ', which is an ilvl upgrade for ' .. names .. '. Reply \'' .. TRADE_MESSAGE .. '\' to initiate rolls for this item.', characterName)
  1703. --      end
  1704.     end
  1705.     if equippedILVL > 0 and not isAnUpgradeForLooter then
  1706.         -- we now know the item can be traded by the person who received it, so let's check to see if anyone can actually
  1707.         --    use the item as an upgrade
  1708.         local isAnUpgradeForAnyCharacter, isAnUpgradeForAnyCharacterNames = IsAnUpgradeForAnyCharacter(item)
  1709.         if isAnUpgradeForAnyCharacter then
  1710.             local names = GetNames(isAnUpgradeForAnyCharacterNames, MAX_NAMES_TO_SHOW)
  1711.  
  1712.             if PLH_NOTIFY_GROUP then
  1713.                 if not PLH_IsInLFR() then
  1714.                     PLH_SendBroadcast(PLH_GetNameWithoutRealm(characterName) .. ' can trade ' .. item .. ', which is an ilvl upgrade for ' .. names)
  1715.                 end
  1716.             end
  1717.  
  1718.             if characterName == PLH_GetUnitNameWithRealm('player') then  -- player can trade an item
  1719.                 PLH_SendAlert('You can trade ' .. item .. ', which is an ilvl upgrade for ' .. names)
  1720.                 PlaySound('GLUECREATECHARACTERBUTTON')
  1721.             elseif IsPlayerInUpgradeList(isAnUpgradeForAnyCharacterNames) then  -- player can receive an item
  1722.                 PLH_SendAlert(PLH_GetNameWithoutRealm(characterName) .. ' can trade ' .. item .. ', which is an ilvl upgrade for you!')
  1723.                 PlaySound('LEVELUP')
  1724.                 HighlightRaidFrames(characterName, item)
  1725.             end
  1726.         end
  1727.     end
  1728. end
  1729.  
  1730. -- returns true if the item should be evaluated for potential trades based on the following criteria:
  1731. --   1. item is equippable
  1732. --   2. ilvl is >= min ilvl from preferences
  1733. --   3. quality is >= min quality from preferences
  1734. --   4. item is BoP, or user specified to include BoE items in preferences
  1735. local function ShouldBeEvaluated(item)
  1736.     if not IsEquippableItem(item) and not IsRelic(item) then
  1737.         return false
  1738.     else
  1739.         local ilvl = PLH_GetRealILVL(item)
  1740.         if ilvl < PLH_MIN_ILVL then
  1741.             return false
  1742.         else
  1743.             local quality = select(3, GetItemInfo(item))
  1744.             if quality < PLH_MIN_QUALITY then
  1745.                 return false
  1746.             else
  1747.                 if not PLH_INCLUDE_BOE and not PLH_IsBoundToPlayer(item) then
  1748.                     return false
  1749.                 else
  1750.                     return true
  1751.                 end
  1752.             end
  1753.         end
  1754.     end
  1755. end
  1756.  
  1757. local function LootReceivedEvent(self, event, ...)
  1758.     local message, _, _, _, looter = ...
  1759.    
  1760. --  local _, _, lootedItem = string.find(message, '(|.+|r)')
  1761.    
  1762.     local lootedItem = message:match(LOOT_ITEM_SELF_PATTERN)
  1763.     if lootedItem == nil then
  1764.         _, lootedItem = message:match(LOOT_ITEM_PATTERN)
  1765.     end
  1766.    
  1767.     if lootedItem then
  1768.         if ShouldBeEvaluated(lootedItem) then
  1769.             if not string.find(looter, '-') then
  1770.                 looter = PLH_GetUnitNameWithRealm(looter)
  1771.             end
  1772.             PerformNotify(lootedItem, looter)
  1773.         end
  1774.     end
  1775.            
  1776. --[[           
  1777.     local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 = ...
  1778.     local owner
  1779.  
  1780.     local message = arg1           
  1781.     local _, _, lootedItem = string.find(message, 'You receive loot: (|.+|r)')
  1782.     if lootedItem then
  1783.         if ShouldBeEvaluated(lootedItem) then
  1784.             PerformNotify(lootedItem, PLH_GetUnitNameWithRealm('player'))
  1785.         end
  1786.     else
  1787.         local _, _, owner, lootedItem = string.find(message, '(.+) receives loot: (|.+|r)')
  1788.  
  1789.         if lootedItem then
  1790.             if ShouldBeEvaluated(lootedItem) then
  1791.                 owner = PLH_GetUnitNameWithRealm(owner)
  1792.                 PerformNotify(lootedItem, owner)
  1793.             end
  1794.         end
  1795.     end
  1796. ]]--   
  1797. end
  1798.  
  1799. local function QueueItem(sender, item)
  1800.     queuedRollOwners[numOfQueuedRollItems] = sender
  1801.     queuedRollItems[numOfQueuedRollItems] = item
  1802.     numOfQueuedRollItems = numOfQueuedRollItems + 1
  1803. end
  1804.  
  1805. local function AskForRolls()
  1806.     if currentRollItem == nil and numOfQueuedRollItems > 0 then
  1807.         currentRollOwner = queuedRollOwners[numOfQueuedRollItems - 1]
  1808.         currentRollItem = queuedRollItems[numOfQueuedRollItems - 1]
  1809.         numOfQueuedRollItems = numOfQueuedRollItems - 1
  1810.         PLH_SendBroadcast('Roll for ' .. currentRollItem .. ' from ' .. PLH_GetNameWithoutRealm(currentRollOwner), true)
  1811.  
  1812.         local FiveSecondWarningDisplayed = false
  1813.         local FifteenSecondWarningDisplayed = false
  1814.        
  1815.         rollDelayFrame:SetScript('OnUpdate', function(self, elapsed)
  1816.             delay = delay + elapsed
  1817. --          if currentRollItem == nil then
  1818.                 -- that means the user forced rolls to end, so we can stop the countdown; relying on PLH_EndRolls() to clean us up
  1819. --          elseif delay >= 15 and not FifteenSecondWarningDisplayed then
  1820.             if delay >= 15 and not FifteenSecondWarningDisplayed then
  1821.                 PLH_SendBroadcast('15 seconds remaining to roll for ' .. currentRollItem, false)
  1822.                 FifteenSecondWarningDisplayed = true
  1823.             elseif delay >= 25 and not FiveSecondWarningDisplayed then
  1824.                 PLH_SendBroadcast('5 seconds remaining to roll for ' .. currentRollItem, false)
  1825.                 FiveSecondWarningDisplayed = true
  1826.             elseif delay >= 30 then
  1827.                 -- force an end to the rolls
  1828.                 PLH_EndRolls()
  1829.             end
  1830.         end)
  1831.     end
  1832. end
  1833.  
  1834. local function ClearRolls()
  1835.     currentRollOwner = nil
  1836.     currentRollItem = nil
  1837.     currentRolls = {}
  1838.     queuedRollOwners[numOfQueuedRollItems] = nil
  1839.     queuedRollItems[numOfQueuedRollItems] = nil
  1840. end
  1841.  
  1842. -- not local, because it has to be callable by the global slash command
  1843. function PLH_EndRolls()
  1844.     -- clear the countdown for the existing roll
  1845.     rollDelayFrame:SetScript('OnUpdate', nil)
  1846.     delay = 0
  1847.    
  1848.     -- determine the winner(s)
  1849.     local winners = nil
  1850.     local highRoll = 0
  1851.     for name, roll in pairs(currentRolls) do
  1852.         roll = tonumber(roll)
  1853.         if winners == nil or roll > highRoll then
  1854.             winners = { name }
  1855.             highRoll = roll
  1856.         elseif winners ~= nil and roll == highRoll then
  1857.             table.insert(winners, name)
  1858.         end
  1859.     end
  1860.    
  1861.     -- notify everyone of the results of the rolls
  1862.     if winners ~= nil then
  1863.         if #winners > 1 then
  1864.             PLH_SendBroadcast(GetNames(winners) .. ' tied for ' .. currentRollItem .. ' from ' .. PLH_GetNameWithoutRealm(currentRollOwner) .. ' with a ' .. highRoll, true)
  1865.         else
  1866.             PLH_SendBroadcast(winners[1] .. ' won ' .. currentRollItem .. ' from ' .. PLH_GetNameWithoutRealm(currentRollOwner) .. ' with a ' .. highRoll, true)
  1867.         end
  1868.     else
  1869.         PLH_SendBroadcast('Nobody rolled for ' .. currentRollItem .. ' from ' .. PLH_GetNameWithoutRealm(currentRollOwner), true)
  1870.     end
  1871.  
  1872.     -- if there's another roll to do, let's delay a few seconds before starting the next roll
  1873.     if (numOfQueuedRollItems > 0) then
  1874.         rollDelayFrame:SetScript('OnUpdate', function(self, elapsed)
  1875.             nextRollDelay = nextRollDelay + elapsed
  1876.             if nextRollDelay >= DELAY_BETWEEN_ROLLS then
  1877.                 rollDelayFrame:SetScript('OnUpdate', nil)
  1878.                 nextRollDelay = 0
  1879.                 ClearRolls()
  1880.                 AskForRolls()
  1881.             end
  1882.         end)
  1883.     else           
  1884.         ClearRolls()
  1885.     end
  1886. end
  1887.  
  1888. local function GetItemFromQueueByPlayer(player)
  1889.     for key, value in pairs(queuedRollOwners) do
  1890.         if value == player then
  1891.             return queuedRollItems[key]
  1892.         end
  1893.     end
  1894.     return nil
  1895. end
  1896.  
  1897. local function ProcessWhisper(message, sender)
  1898.     if PLH_COORDINATE_ROLLS and PlayerCanCoordinateRolls() then
  1899.         if not string.find(sender, '-') then
  1900.             sender = PLH_GetUnitNameWithRealm(sender)
  1901.         end
  1902.  
  1903.         -- if the person whispered 'trade [item]' or '[item] trade', then add the item to the array so we can process it
  1904.         local _, _, whisperedItem = string.find(message, 'trade  (|.+|r)')
  1905.         if whisperedItem == nil then
  1906.             _, _, whisperedItem = string.find(message, 'Trade  (|.+|r)')
  1907.         end
  1908.         if whisperedItem == nil then
  1909.             _, _, whisperedItem = string.find(message, 'TRADE  (|.+|r)')
  1910.         end
  1911.         if whisperedItem == nil then
  1912.             _, _, whisperedItem = string.find(message, '(|.+|r) trade')
  1913.         end
  1914.         if whisperedItem == nil then
  1915.             _, _, whisperedItem = string.find(message, '(|.+|r) Trade')
  1916.         end
  1917.         if whisperedItem == nil then
  1918.             _, _, whisperedItem = string.find(message, '(|.+|r) TRADE')
  1919.         end
  1920.         if whisperedItem ~= nil then
  1921.             whisperedItems[sender] = whisperedItem
  1922.         end
  1923.  
  1924.         message = string.upper(message)
  1925.         if whisperedItem ~= nil or message == TRADE_MESSAGE or message == '\'' .. TRADE_MESSAGE .. '\'' then
  1926.             if whisperedItems[sender] ~= nil then
  1927.                 local item = whisperedItems[sender]
  1928.                 whisperedItems[sender] = nil
  1929.                 QueueItem(sender, item)
  1930.                 -- if we're still rolling for another item, let the person know their item is queued
  1931.                 if currentRollItem ~= nil then
  1932.                     PLH_SendWhisper('Thank you! ' .. item .. ' will be rolled for after current rolls are done.', sender)
  1933.                 else
  1934.                     AskForRolls()
  1935.                 end
  1936.             elseif currentRollOwner == sender then
  1937.                 PLH_SendWhisper('Your ' .. currentRollItem .. ' is currently being rolled for', sender)
  1938.             else
  1939.                 local item = GetItemFromQueueByPlayer(sender)
  1940.                 if item then
  1941.                     PLH_SendWhisper('Your ' .. item .. ' is already in queue to be rolled for', sender)
  1942.                 else
  1943.                     PLH_SendWhisper('No record of which item you looted! Whisper \'trade [item]\' to ' .. UnitName('player') .. ' if you would still like to trade the item.', sender)
  1944.                 end
  1945.             end
  1946.         end
  1947.     end
  1948. end
  1949.  
  1950. local function WhisperReceivedEvent(self, event, ...)
  1951.     local message, sender = ...
  1952.  
  1953.     ProcessWhisper(message, sender)
  1954. end
  1955.  
  1956. local function BNWhisperReceivedEvent(self, event, ...)
  1957.     local message = ...
  1958.  
  1959.     local bnetIDAccount = select(13, ...)
  1960.     local bnetIDGameAccount = select(6, BNGetFriendInfoByID(bnetIDAccount));
  1961.     local _, sender, _, realmName = BNGetGameAccountInfo(bnetIDGameAccount)
  1962.  
  1963.     if realmName ~= nil then
  1964.         sender = sender .. '-' .. realmName
  1965.     end
  1966.    
  1967.     ProcessWhisper(message, sender)
  1968. end
  1969.  
  1970. local function RollReceivedEvent(self, event, ...)
  1971.     if currentRollItem ~= nil then
  1972.         local message = select(1, ...)
  1973.         if message then
  1974. --          local name, roll, minRoll, maxRoll = message:match('^(.+) rolls (%d+) %((%d+)%-(%d+)%)$')
  1975.             local name, roll, minRoll, maxRoll = message:match(PLH_RANDOM_ROLL_RESULT_PATTERN)
  1976.             if name then
  1977.                 local fullname = PLH_GetUnitNameWithRealm(name)
  1978.                 if minRoll ~= '1' or maxRoll ~= '100' then
  1979.                     PLH_SendBroadcast(name .. ' rolled ' .. minRoll .. ' - ' .. maxRoll .. '; roll ignored', false)
  1980.                 elseif currentRolls[name] ~= nil then
  1981.                     PLH_SendBroadcast(name .. ' rolled multiple times; only the first roll of ' .. currentRolls[name] .. ' counts', false)
  1982.                 elseif fullname ~= nil and not IsEquippableItemForCharacter(currentRollItem, fullname) then
  1983.                     PLH_SendBroadcast(name .. ' is not eligible for ' .. currentRollItem .. '; roll ignored', false)
  1984.                 else
  1985.                     currentRolls[name] = roll
  1986.                 end
  1987.             end    
  1988.         end
  1989.     end
  1990. end
  1991.  
  1992. -- note that GetLootMethod() only works if you're in a party or in a raid.  If you're in an instance (i.e. queued via LFR),
  1993. --   then loot method is automatically personal loot
  1994. local function IsPersonalLoot()
  1995.     local isInstance, instanceType = IsInInstance()
  1996.     return (IsInGroup(LE_PARTY_CATEGORY_INSTANCE) or (IsInGroup() and GetLootMethod() == 'personalloot'))
  1997.         and (instanceType == "party" or instanceType == "raid")
  1998. end
  1999.  
  2000. local function ResetVariables()
  2001.     delay = 0
  2002.     nextRollDelay = 0
  2003.  
  2004.     whisperedItems = {}
  2005.  
  2006.     numOfQueuedRollItems = 0;
  2007.     queuedRollOwners = {}
  2008.     queuedRollItems = {}
  2009.  
  2010.     currentRollOwner = nil
  2011.     currentRollItem = nil
  2012.     currentRolls = {}
  2013.  
  2014.     groupInfoCache = {}
  2015. end
  2016.  
  2017. -- The following uses GetInventoryItemLink() to look up unit's equipped items.  That method can only be called for
  2018. --   the player, or within the scope of an INSPECT_READY event for other group members.
  2019. local function UpdateGroupInfoCache(unit)
  2020.     local name = PLH_GetUnitNameWithRealm(unit)
  2021.  
  2022.     if name ~= nil then
  2023. --      PLH_SendDebugMessage('   Updating GroupInfoCache for ' .. name .. ', inspectIndex = ' .. inspectIndex)
  2024.         local characterDetails
  2025.         if groupInfoCache[name] == nil then
  2026.             characterDetails = {}
  2027.             local _, class = UnitClass(unit)
  2028.             characterDetails['ClassName'] = class
  2029.             local spec = GetInspectSpecialization(unit)
  2030.             characterDetails['Spec'] = spec
  2031.             local level = UnitLevel(unit)
  2032.             characterDetails['Level'] = level
  2033.             characterDetails['InspectCount'] = 0
  2034.         else
  2035.             characterDetails = groupInfoCache[name]
  2036. --          spec = GetInspectSpecialization(unit)
  2037.             if characterDetails['InspectCount'] ~= nil then
  2038.                 characterDetails['InspectCount'] = characterDetails['InspectCount'] + 1
  2039.             end
  2040.         end
  2041.        
  2042.         local updatedItemCount = 0
  2043.         local item
  2044.         for i = _G.INVSLOT_FIRST_EQUIPPED, _G.INVSLOT_LAST_EQUIPPED do
  2045.             if i ~= _G.INVSLOT_BODY and i ~= INVSLOT_TABARD then -- ignore shirt and tabard slots
  2046.                 item = GetInventoryItemLink(UnitName(unit), i)
  2047.                 if item ~= nil then
  2048.                     if characterDetails[i] == nil or characterDetails[i] ~= item then
  2049. --                  PLH_SendDebugMessage('      adding/updating item ' .. item)        
  2050.                         updatedItemCount = updatedItemCount + 1
  2051.                         characterDetails[i] = nil  -- remove existing first
  2052.                         characterDetails[i] = item
  2053.                         characterDetails['InspectCount'] = 0  -- if we actually updated something, reset the inspect counter
  2054.                     end
  2055.                    
  2056.                     -- if we're adding the weapon, also add relics
  2057.                     if i == _G.INVSLOT_MAINHAND or i == _G.INVSLOT_OFFHAND then
  2058.                         item = select(2, GetItemInfo(item))  -- hack since relics may not be loaded into cache right away, query the item again
  2059.                         local _, relic1 = GetItemGem(item, 1)
  2060.                         if relic1 ~= nil then
  2061.                             characterDetails[PLH_RELICSLOT + 1] = relic1
  2062.                         end
  2063.                         local _, relic2 = GetItemGem(item, 2)
  2064.                         if relic2 ~= nil then
  2065.                             characterDetails[PLH_RELICSLOT + 2] = relic2
  2066.                         end
  2067.                         local _, relic3 = GetItemGem(item, 3)
  2068.                         if relic3 ~= nil then
  2069.                             characterDetails[PLH_RELICSLOT + 3] = relic3
  2070.                         end
  2071.                     end
  2072.                 end
  2073.             end
  2074.         end
  2075.  
  2076.         -- If we didn't find any items (ex: when player first starts the game), don't add this person to the cache unless it's first time (for class/etc)
  2077.         if updatedItemCount > 0 or groupInfoCache[name] == nil then
  2078.             groupInfoCache[name] = nil  -- remove any existing entry first
  2079.             groupInfoCache[name] = characterDetails
  2080.         end
  2081.         PLH_SendDebugMessage('   Updated ' .. updatedItemCount .. ' items for ' .. name .. '; loop ' .. inspectLoop .. '; char ' .. (inspectIndex - 1))
  2082.     end
  2083. end
  2084.  
  2085. -- returns true if the characterName is in the raid/party
  2086. local function IsCharacterInGroup(characterFullname)
  2087.     local index = 1
  2088.     local name = select(1, GetRaidRosterInfo(index))
  2089.     while name ~= nil do
  2090.         if name == characterFullname then
  2091.             return true
  2092.         end
  2093.         -- RaidRosterInfo will only give us name by itself if the character is on the same realm as the player, so check that scenario too
  2094.         if PLH_GetFullName(name, GetRealmName()) == characterFullname then
  2095.             return true
  2096.         end
  2097.         index = index + 1
  2098.         name = select(1, GetRaidRosterInfo(index))
  2099.     end
  2100.     return false
  2101. end
  2102.  
  2103. -- returns true if group member was able to be inspected; false otherwise
  2104. local function InspectGroupMember(characterName)
  2105.     if characterName ~= nil and characterName ~= UnitName('player') then   -- no need to inspect ourselves
  2106.         if CanInspect(characterName) then -- and UnitIsConnected(characterName) - not necessary to include this check since we're including distance check
  2107. --          if CheckInteractDistance(characterName, 1) then  -- this API call has not been changed by Blizz to match the new larger inspect radius
  2108.             if UnitIsVisible(characterName) then
  2109.                 NotifyInspect(characterName)
  2110.                 notifyInspectName = characterName
  2111.                 PLH_wait(DELAY_BETWEEN_INSPECTIONS, PLH_InspectNextGroupMember)
  2112.                 return true
  2113.             else
  2114.                 PLH_SendDebugMessage('   ' .. characterName .. ' out of range for inspect')
  2115.             end
  2116.         else
  2117.             PLH_SendDebugMessage('   Unable to inspect ' .. characterName)
  2118.         end
  2119.     end
  2120.     return false
  2121. end
  2122.  
  2123. -- An Inspect Loop (managed by inspectLoop) is a complete iteration of inspection for every member in the group.  
  2124. --      Will only attempt to inspect characters whose count of cached items is lower than expected.
  2125. --      The goal of this loop is to work around the limitation whereby the inspect API doesn't necessarily provide us
  2126. --      all items equipped by the character.
  2127. -- not local, because it is called by (and calls) InspectGroupMember(characterName), which is defined above
  2128. function PLH_InspectNextGroupMember()
  2129.     -- attempt the inspect the next person; if they're not inspectable, move onto the next
  2130.  
  2131.     -- Removed this inside-loop retry logic.  It works, but in testing it very rarely accomplished its goal; usually the retry attempts
  2132.     --    to inspect failed just like the original attempt.  We now have inspectLoop logic build in, which will still
  2133.     --    give the cache more opportunities to be updated - so no need for the retries here
  2134.     -- Retry logic
  2135. --  if notifyInspectName ~= nil then   -- InspectReady didn't get called for the person we queued
  2136. --      if inspectRetries < MAX_INSPECT_RETRIES then
  2137. --          PLH_SendDebugMessage('Retrying inspection for ' .. notifyInspectName)
  2138. --          inspectRetries = inspectRetries + 1
  2139. --          if InspectGroupMember(notifyInspectName) then  -- we triggered a notify, so don't do the while loop
  2140. --              return true  -- exit this function so we don't attempt to inspect the next character
  2141. --          end
  2142. --      end
  2143.         -- if we get to here, then it's time to give up on inspecting this character - either we couldn't even trigger a
  2144.         --    NotifyInspect, or we already triggered the max # of NotifyInspects and didn't get an InspectReady response
  2145.         notifyInspectName = nil  -- even though we commented out the retry logic, we still want to nullify notifyInspectName
  2146.             -- since it's unlikely we'll get an INSPECT_READY at this point (3 seconds after calling NotifyInspect)
  2147. --      inspectRetries = 0
  2148. --  end
  2149.  
  2150.     -- Call InspectGroupMember for the next person in the group
  2151.     local characterName
  2152.     local queuedAnInspection = false
  2153.     local expectedItemCount
  2154.     local expectedRelicCount
  2155.     local class
  2156.     local spec
  2157.     local level
  2158.     local inspectCount
  2159.     while inspectIndex <= maxInspectIndex  and not queuedAnInspection do
  2160.         characterName = select(1, GetRaidRosterInfo(inspectIndex))
  2161.         if characterName ~= nil then  -- safeguard; character may have left the roster between the time we started the call and now
  2162.             local numCachedItems = 0
  2163.             local numCachedRelics = 0
  2164.             local fullname = characterName      --characterName may or may not have realm.  we want to preserve it the way it is for the call to InspectGroupMember,
  2165.                                                 --   but need the name-realm version of the name to look up the element in the cache
  2166.             if not string.find(fullname, '-') then
  2167.                 fullname = PLH_GetFullName(characterName, GetRealmName())
  2168.             end
  2169.            
  2170.             if fullname ~= nil then
  2171.                 expectedItemCount = NUM_EXPECTED_ITEMS
  2172.                 expectedRelicCount = 0
  2173.                 inspectCount = 0
  2174.                 if groupInfoCache[fullname] ~= nil then
  2175.                     numCachedItems = PLH_GetItemCountFromCache(fullname)
  2176.                     numCachedRelics = PLH_GetRelicCountFromCache(fullname)
  2177.                     class = groupInfoCache[fullname]['ClassName']
  2178.                     spec = groupInfoCache[fullname]['Spec']
  2179.                     level = groupInfoCache[fullname]['Level']
  2180.                     inspectCount = groupInfoCache[fullname]['InspectCount']
  2181.                     expectedItemCount = GetExpectedItemCount(class, spec, level)
  2182.                     expectedRelicCount = GetExpectedRelicCount(level)
  2183.                 end
  2184.  
  2185. --              if inspectCount >= MAX_INSPECTS_PER_CHARACTER then
  2186. --                  PLH_SendDebugMessage('Discontinuing inspections for ' .. fullname .. ' due to max inspect limit')
  2187. --              end
  2188.                
  2189.                 if inspectCount < MAX_INSPECTS_PER_CHARACTER and (numCachedItems < expectedItemCount or numCachedRelics < expectedRelicCount) then  -- if we've already cached 15 or more items, don't bother refreshing
  2190.                     queuedAnInspection = InspectGroupMember(characterName)
  2191.                 end
  2192.             end
  2193.         end
  2194.         inspectIndex = inspectIndex + 1
  2195.     end
  2196.    
  2197.     -- The following logic is meant to work around a limitation of the inspect API.  When you inspect a character, you're
  2198.     -- not guaranteed to actually receive all of their equipped items back!  To work around this limitation, we will
  2199.     -- perform additional loops of inspecting each character if the number of items we've cached for them is fewer than
  2200.     -- the expected number of items that someone would equip (15 items for a person using a 1-hander, +1 element for the
  2201.     -- ClassName that we're storing in the cache)
  2202.     if inspectIndex > maxInspectIndex then -- that means we just completed our current loop
  2203.         if inspectLoop < MAX_INSPECT_LOOPS then
  2204.             -- let's start the next loop
  2205.             inspectIndex = 1  -- we're triggering the call to the 1st member via PLH_InspectNextGroupMember below; increment
  2206.                 -- the inspectIndex counter so the next element that gets picked up when we come back into PLH_InspectNextGroupMember
  2207.                 -- is the 2nd
  2208.             inspectLoop = inspectLoop + 1
  2209.             if queuedAnInspection then  -- if we just queued someone for inspection, wait before we start the new loop
  2210.                 PLH_wait(DELAY_BETWEEN_INSPECTIONS, PLH_InspectNextGroupMember)
  2211.             else  -- otherwise start the new loop immediately
  2212.                 PLH_InspectNextGroupMember()
  2213.             end
  2214.         elseif not queuedAnInspection then
  2215.             -- we've finished all loops
  2216. --          PLH_PrintCache()
  2217.         end
  2218.     end
  2219.    
  2220. end
  2221.  
  2222. -- note that the INSPECT_READY event may have been triggered by WoW or by another addon
  2223. --   for example, WoW triggers it when a person in inspect range swaps their gear
  2224. local function InspectReadyEvent(self, event, ...)
  2225.     local guid = select(1, ...)
  2226.     local _, _, _, _, _, name, realm = GetPlayerInfoByGUID(guid)
  2227.  
  2228.     if notifyInspectName ~= nil and (notifyInspectName == name or notifyInspectName == PLH_GetFullName(name, realm)) then  -- confirm this InspectReadyEvent is for the person we triggered for inspection.
  2229.         UpdateGroupInfoCache(name)  -- only update the cache if we requested the inspect, otherwise the wrong cache entry could be updated!
  2230.         -- if we triggered the inspection, clear the flags that we had set
  2231.         ClearInspectPlayer()  -- note that we only want to call ClearInspectPlayer() if we're the ones who queued up
  2232.             -- the inspection; otherwise, we'll prevent whoever wanted to inspect the player from seeing data...for
  2233.             -- example, a player using the UI to inpect a player would see all empty slots if we called ClearInspectPlayer()!
  2234.         notifyInspectName = nil  -- clear this flag since we're done inspecting the person
  2235.     end
  2236. end
  2237.  
  2238. local function PopulateGroupInfoCache()
  2239.     local now = time()
  2240.    
  2241.     if now - priorCacheRefreshTime > 3 then  -- only refresh if prior refresh was more than 3 seconds ago
  2242.         priorCacheRefreshTime = now
  2243.    
  2244.         -- remove characters from the cache if they're no long in the raid/party
  2245.         for name, details in pairs (groupInfoCache) do
  2246.             if not IsCharacterInGroup(name) then
  2247.                 PLH_SendDebugMessage('Removing entry for ' .. name .. ' from cache')
  2248.                 groupInfoCache[name] = nil
  2249.             end
  2250.         end
  2251.  
  2252.         if IsInGroup() then
  2253.             -- If we're already doing an inspect loop, don't interupt it; just do nothing with the request to
  2254.             -- PopulateGroupInfoCache() and let the inspect loop continuue on its way!  If the inspectIndex > maxInspectIndex
  2255.             -- and inspectLoop > MAX_INSPECT_LOOPS, then we know we've finished all inspections for all loops, so
  2256.             -- we can start a brand new loop!
  2257.             if inspectIndex > maxInspectIndex and inspectLoop >= MAX_INSPECT_LOOPS then
  2258. --              PLH_SendDebugMessage('Refreshing cache')
  2259.                 inspectIndex = 1
  2260.                 inspectLoop = 1
  2261.                 maxInspectIndex = GetNumGroupMembers()
  2262.                 PLH_InspectNextGroupMember()
  2263.             end
  2264.         end
  2265.     end
  2266. end
  2267.  
  2268. local function Enable()
  2269.     ResetVariables()
  2270.     isEnabled = true
  2271.     lootReceivedEventFrame:RegisterEvent('CHAT_MSG_LOOT')
  2272.     --whisperReceivedEventFrame:RegisterEvent('CHAT_MSG_WHISPER')
  2273.     --bnWhisperReceivedEventFrame:RegisterEvent('CHAT_MSG_BN_WHISPER')
  2274.     rollReceivedEventFrame:RegisterEvent('CHAT_MSG_SYSTEM')
  2275.     inspectReadyEventFrame:RegisterEvent('INSPECT_READY')
  2276.     combatStatusChangedEventFrame:RegisterEvent('PLAYER_REGEN_DISABLED')   -- player entered combat
  2277.     groupMemberInfoChangedEventFrame:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED')
  2278.     groupMemberInfoChangedEventFrame:RegisterEvent('UNIT_INVENTORY_CHANGED')
  2279. end
  2280.  
  2281. local function Disable()
  2282.     isEnabled = false
  2283.     lootReceivedEventFrame:UnregisterAllEvents()
  2284.     -- keep the following listeners enabled so people can still use the roll coordinate mode during master looter (ex: for BoE drops)
  2285.     --whisperReceivedEventFrame:UnregisterAllEvents()
  2286.     --bnWhisperReceivedEventFrame:UnregisterAllEvents()
  2287.     rollReceivedEventFrame:UnregisterAllEvents()
  2288.     inspectReadyEventFrame:UnregisterAllEvents()
  2289.     combatStatusChangedEventFrame:UnregisterAllEvents()
  2290.     groupMemberInfoChangedEventFrame:UnregisterAllEvents()
  2291.     groupInfoCache = {}
  2292. end
  2293.  
  2294. local function EnableOrDisable()
  2295.     local shouldBeEnabled = IsPersonalLoot()
  2296.     if not isEnabled and shouldBeEnabled then  
  2297.         Enable()
  2298.     elseif isEnabled and not shouldBeEnabled then
  2299.         Disable()
  2300.     end
  2301.     if isEnabled then
  2302.         PopulateGroupInfoCache()
  2303.     end
  2304. end
  2305.  
  2306. local function GroupMemberInfoChangedEvent(self, event, ...)
  2307.     local unit = ...
  2308.     if unit == 'player' then
  2309.         -- do nothing
  2310.     else
  2311.         -- update cache
  2312.         local name = PLH_GetUnitNameWithRealm(unit)
  2313.         if name ~= nil then
  2314.             groupInfoCache[name] = nil
  2315.             if event == 'UNIT_INVENTORY_CHANGED' then
  2316.                 -- we need to do another inspect to refresh the cache
  2317.                 PopulateGroupInfoCache()  -- don't want to mess with any existing cache refresh flow by inspecting this specific character,
  2318.                     -- so trigger general refresh, which will refresh this character since we've nullified their entry
  2319.             else  -- PLAYER_SPECIALIZATION_CHANGED
  2320.                 -- spec change already gives us the inspect info
  2321.                 UpdateGroupInfoCache(unit)
  2322.             end
  2323.         end
  2324.     end
  2325. end
  2326.  
  2327. local function RosterUpdatedEvent(self, event, ...)
  2328.     -- the following is a bit of a hack to work around a Blizzard issue.  While the player is logging in, IsInGroup()
  2329.     -- is false.  If the user is already in a group (for example, logging back in after a disconnect or doing a /reload),
  2330.     -- A ROSTER_UPDATE event triggers.  However, IsInGroup() is not immediately set to true when the event fires!
  2331.     -- So if we get a ROSTER_UPDATE event and we're currently disabled, lets wait 1 second to make sure IsInGroup()
  2332.     -- gives us the correct value.  Similar behavoir occurred in LFR testing where people joining/leaving the group
  2333.     -- may not have been automatically available.  Hence the delay.
  2334.     PLH_wait(2, EnableOrDisable)
  2335. end
  2336.  
  2337. -- triggered when the player enters or leaves combat status, which are the perfect times to refresh the cache since
  2338. --    the people who will be eligible for loot should be close enough to be inspected
  2339. local function CombatStatusChangedEvent(self, event, ...)
  2340.     if isEnabled then
  2341.         PopulateGroupInfoCache()
  2342.     end
  2343. end
  2344.  
  2345. function SlashCmdList.PLHelperCommand(msg, editbox)
  2346.     if msg == nil or msg == '' then
  2347.         InterfaceOptionsFrame_OpenToCategory(PLH_LONG_ADDON_NAME)
  2348.         InterfaceOptionsFrame_OpenToCategory(PLH_LONG_ADDON_NAME)  -- hack; called twice to get around Blizz bug of it not opening to correct page right away
  2349.     elseif msg == 'endroll' then
  2350.         if currentRollItem ~= nil then
  2351.             if nextRollDelay > 0 then
  2352.                 PLH_SendUserMessage('Please wait until the next roll begins')
  2353.             else
  2354. --              PLH_SendBroadcast(UnitName('player') .. ' ended rolls', false)
  2355.                 PLH_EndRolls()
  2356.             end
  2357.         else
  2358.             PLH_SendUserMessage('There are currently no items being rolled for')
  2359.         end
  2360. --  elseif msg == 'test' then
  2361. --      PLH_UnitTest()
  2362.     else
  2363.         PLH_SendUserMessage('Unknown parameter. Options are:\n  [/plh]  :  open interface options\n  [/plh endroll]  :  force current roll to end')
  2364.     end
  2365. end
  2366.  
  2367. local function Initialize(self, event, addonName, ...)
  2368.     if addonName == 'PersonalLootHelper' then
  2369.    
  2370.         PLH_SendDebugMessage('PLH Initializing')
  2371.        
  2372.         if PLH_MIN_QUALITY == nil then
  2373.             PLH_NOTIFY_MODE = DEFAULT_NOTIFY_MODE
  2374.             PLH_INCLUDE_BOE = DEFAULT_INCLUDE_BOE
  2375.             PLH_MIN_ILVL = DEFAULT_MIN_ILVL
  2376.             PLH_MIN_QUALITY = DEFAULT_MIN_QUALITY
  2377.             PLH_DEBUG = DEFAULT_DEBUG
  2378.             PLH_CURRENT_SPEC_ONLY = DEFAULT_CURRENT_SPEC_ONLY
  2379.         end
  2380.        
  2381.         -- need global variable option added in version 1.21
  2382.         if PLH_CHECK_CHARACTER_LEVEL == nil then
  2383.             PLH_CHECK_CHARACTER_LEVEL = DEFAULT_CHECK_CHARACTER_LEVEL
  2384.             PLH_COORDINATE_ROLLS = (PLH_NOTIFY_MODE == NOTIFY_MODE_COORDINATE_ROLLS)
  2385.             PLH_NOTIFY_GROUP = (PLH_NOTIFY_MODE == NOTIFY_MODE_GROUP or PLH_NOTIFY_MODE == NOTIFY_MODE_COORDINATE_ROLLS)
  2386.             PLH_HIGHLIGHT_RAID_FRAMES = DEFAULT_HIGHLIGHT_RAID_FRAMES
  2387.             PLH_HIGHLIGHT_SIZE = DEFAULT_HIGHLIGHT_SIZE
  2388.         end
  2389.        
  2390.         if lootReceivedEventFrame == nil then
  2391.  
  2392.             lootReceivedEventFrame = CreateFrame('Frame')
  2393.             lootReceivedEventFrame:SetScript('OnEvent', LootReceivedEvent)
  2394.  
  2395.             whisperReceivedEventFrame = CreateFrame('Frame')
  2396.             whisperReceivedEventFrame:SetScript('OnEvent', WhisperReceivedEvent)
  2397.  
  2398.             bnWhisperReceivedEventFrame = CreateFrame('Frame')
  2399.             bnWhisperReceivedEventFrame:SetScript('OnEvent', BNWhisperReceivedEvent)
  2400.  
  2401.             rollReceivedEventFrame = CreateFrame('Frame')
  2402.             rollReceivedEventFrame:SetScript('OnEvent', RollReceivedEvent)
  2403.  
  2404.             inspectReadyEventFrame = CreateFrame('Frame')
  2405.             inspectReadyEventFrame:SetScript('OnEvent', InspectReadyEvent)
  2406.  
  2407.             rosterUpdatedEventFrame = CreateFrame('Frame')
  2408.             rosterUpdatedEventFrame:SetScript('OnEvent', RosterUpdatedEvent)
  2409.             rosterUpdatedEventFrame:RegisterEvent('GROUP_ROSTER_UPDATE')
  2410.            
  2411.             groupMemberInfoChangedEventFrame = CreateFrame('Frame')
  2412.             groupMemberInfoChangedEventFrame:SetScript('OnEvent', GroupMemberInfoChangedEvent)
  2413.            
  2414.             combatStatusChangedEventFrame = CreateFrame('Frame')
  2415.             combatStatusChangedEventFrame:SetScript('OnEvent', CombatStatusChangedEvent)
  2416.  
  2417.             rollDelayFrame = CreateFrame('Frame')
  2418.             highlightDelayFrame = CreateFrame('Frame')
  2419.  
  2420.             -- enable listeners here so people can use the loot coordination feature even for master loot (ex: for BoE drops)
  2421.             whisperReceivedEventFrame:RegisterEvent('CHAT_MSG_WHISPER')
  2422.             bnWhisperReceivedEventFrame:RegisterEvent('CHAT_MSG_BN_WHISPER')
  2423.         end
  2424.        
  2425.         PLH_CreateOptionsPanel()       
  2426.     end
  2427. end
  2428.  
  2429. --Initialize()
  2430. addonLoadedFrame = CreateFrame('Frame')
  2431. addonLoadedFrame:SetScript('OnEvent', Initialize)
  2432. addonLoadedFrame:RegisterEvent('ADDON_LOADED')
  2433.  
  2434. --[[
  2435. *********************************************************
  2436. Debug/Testing functions
  2437. *********************************************************
  2438. ]]--
  2439.  
  2440. function PLH_PrintCache(showDetails, characterName)
  2441.     if PLH_DEBUG then
  2442.         local num_characters = 0
  2443.         local item_msg = ''
  2444.         for name, characterDetails in pairs(groupInfoCache) do
  2445.             num_characters = num_characters + 1
  2446.             item_msg = item_msg .. PLH_GetItemCountFromCache(name) .. '/' .. PLH_GetRelicCountFromCache(name) .. ' '
  2447. --          item_msg = item_msg .. (#groupInfoCache[name] -1) .. ' '  -- subtracting 1 since everyone will have a ClassName element
  2448.         end
  2449.         PLH_SendDebugMessage('Cache contains ' .. num_characters .. ' member(s). Item count per member: ' .. item_msg)
  2450.  
  2451.         if (showDetails) then
  2452.             for name, details in pairs (groupInfoCache) do
  2453.                 if name == nil or name == characterName then
  2454.                     PLH_SendDebugMessage('Cache information for ' .. name)
  2455.                     if details == nil then 
  2456.                         PLH_SendDebugMessage('   details is nil')
  2457.                     else
  2458.                         for slotID, item in pairs(details) do
  2459.                             PLH_SendDebugMessage('   ' .. slotID .. ' = ' .. item)
  2460.                         end
  2461.                     end
  2462.                 end
  2463.             end
  2464.         end
  2465.     end
  2466. end
  2467.  
  2468. function PLH_EnableDebug()
  2469.     PLH_DEBUG = true
  2470. end
  2471.  
  2472. function PLH_DisableDebug()
  2473.     PLH_DEBUG = false
  2474. end
  2475.  
  2476. function PLH_RefreshCache()
  2477.     groupInfoCache = {}
  2478.     PopulateGroupInfoCache()
  2479. end
  2480.  
  2481. function PLH_TestItems(characterIndex)
  2482.     if characterIndex == nil then
  2483.         PLH_SendDebugMessage('Usage: PLH_TestItems(characterIndex)')
  2484.     else
  2485.         characterName = select(1, GetRaidRosterInfo(characterIndex))
  2486.         if not string.find(characterName, '-') then
  2487.             characterName = PLH_GetFullName(characterName, GetRealmName())
  2488.         end
  2489.        
  2490.         PLH_SendDebugMessage('Evaluating items equipped by ' .. characterName)
  2491.  
  2492.         local item
  2493.         for itemIndex = 1, 19 do
  2494.             if characterName == PLH_GetUnitNameWithRealm('player') then
  2495.                 item = GetInventoryItemLink('player', itemIndex)   
  2496.             else
  2497.                 item = groupInfoCache[characterName][itemIndex]
  2498.             end
  2499.  
  2500.             if item ~= nil then
  2501.                 PLH_SendDebugMessage('   evaluating ' .. item)
  2502.                
  2503.                 local isEquippable
  2504.                 for evalIndex = 1, GetNumGroupMembers() do
  2505.                     evalName = select(1, GetRaidRosterInfo(evalIndex))
  2506.                     if not string.find(evalName, '-') then
  2507.                         evalName = PLH_GetFullName(evalName, GetRealmName())
  2508.                     end
  2509.                
  2510.                     isEquippable = IsEquippableItemForCharacter(item, evalName)
  2511.                     PLH_SendDebugMessage('      For ' .. evalName ..
  2512.                         ' equippable = ' .. tostring(isEquippable) ..
  2513.                         '; upgrade = ' .. tostring(isEquippable and IsAnUpgradeForCharacter(item, evalName))
  2514.                         )
  2515.                 end
  2516.             end
  2517.         end
  2518.        
  2519.         local relic
  2520.         for relicIndex = 1, 3 do
  2521.             if characterName == PLH_GetUnitNameWithRealm('player') then
  2522.                 local weapon = GetInventoryItemLink('player', INVSLOT_MAINHAND)
  2523.                 _, relic = GetItemGem(weapon, relicIndex)
  2524.             else
  2525.                 relic = groupInfoCache[characterName][PLH_RELICSLOT + relicIndex]
  2526.             end
  2527.  
  2528.             if relic ~= nil then
  2529.                 PLH_SendDebugMessage('   evaluating ' .. relic)
  2530.                
  2531.                 local isEquippable
  2532.                 for evalIndex = 1, GetNumGroupMembers() do
  2533.                     evalName = select(1, GetRaidRosterInfo(evalIndex))
  2534.                     if not string.find(evalName, '-') then
  2535.                         evalName = PLH_GetFullName(evalName, GetRealmName())
  2536.                     end
  2537.                
  2538.                     isEquippable = IsEquippableItemForCharacter(relic, evalName)
  2539.                     PLH_SendDebugMessage('      For ' .. evalName ..
  2540.                         ' equippable = ' .. tostring(isEquippable) ..
  2541.                         '; upgrade = ' .. tostring(isEquippable and IsAnUpgradeForCharacter(relic, evalName))
  2542.                         )
  2543.                 end
  2544.             end
  2545.         end
  2546.     end
  2547. end
  2548.  
  2549. function PLH_TestRelic(relic, item)
  2550.     local name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture, vendorPrice = GetItemInfo(relic)
  2551.     print('name ', name)
  2552.     print('class ', class)  -- 'Gem'
  2553. --  print('iLevel ', iLevel)  -- gives wrong value
  2554.     print('subclass ', subclass)  -- 'Artifact Relic'
  2555. --  print('equipSlot ', equipSlot)  -- nil
  2556.     print('real ilvl is ', PLH_GetRealILVL(relic))  -- correct
  2557.     print('IsEquippableItem is ', IsEquippableItem(relic))  -- false, so we'll need to change where we do this check!
  2558.     print('IsArtifactRelicItem is ', IsArtifactRelicItem(relic))  -- true; can add this where we check isEquippable
  2559.    
  2560.     local gemname, gemlink = GetItemGem(item, 1)  -- 2nd arg is index; does return gem link from artifact
  2561.     print('gemname ', gemname)
  2562.     print('gemlink ', gemlink)
  2563.     print('real ilvl is ', PLH_GetRealILVL(gemlink))  -- correct
  2564.  
  2565.     local gemname, gemlink = GetItemGem(item, 2)  -- 2nd arg is index; does return gem link from artifact
  2566.     print('gemname ', gemname)
  2567.     print('gemlink ', gemlink)
  2568.     print('real ilvl is ', PLH_GetRealILVL(gemlink))  -- correct
  2569.  
  2570.     local gemname, gemlink = GetItemGem(item, 3)  -- 2nd arg is index; does return gem link from artifact
  2571.     print('gemname ', gemname)
  2572.     print('gemlink ', gemlink)
  2573.     print('real ilvl is ', PLH_GetRealILVL(gemlink))  -- correct
  2574.  
  2575. --  local slotId, texture, checkRelic = GetInventorySlotInfo("MainHandSlot")
  2576. --  print('checkRelic1 ', checkRelic)  -- false
  2577.    
  2578. --  local slotId, texture, checkRelic = GetInventorySlotInfo("RangedSlot")
  2579. --  print('checkRelic2 ', checkRelic)  -- invalid slot error
  2580.  
  2581.     -- following method not found even though it's in API docs
  2582. --  local gem1, gem2, gem3 = GetInventoryItemGems(INVSLOT_RANGED)   --18
  2583. --  local gem1, gem2, gem3 = GetInventoryItemGems(18)   --18
  2584. --  print('gem1 ', gem1)
  2585. --  print('gem2 ', gem2)
  2586. --  print('gem3 ', gem3)
  2587. end
  2588.  
  2589. function PLH_Test(item)
  2590.     print(IsEquippableItemForCharacter(item, "Madone-Zul'jin"))
  2591. end
  2592.  
  2593. --[[
  2594. *********************************************************
  2595. Code from BlizzBugsSuck.lua to resolve known blizzard bug when opening interface options
  2596. *********************************************************
  2597. ]]--
  2598. -- Fix InterfaceOptionsFrame_OpenToCategory not actually opening the category (and not even scrolling to it)
  2599. -- Confirmed still broken in 6.2.2.20490 (6.2.2a)
  2600. --[[
  2601. local doNotRun = false
  2602.  
  2603. do
  2604.     local function get_panel_name(panel)
  2605.         local tp = type(panel)
  2606.         local cat = INTERFACEOPTIONS_ADDONCATEGORIES
  2607.         if tp == "string" then
  2608.             for i = 1, #cat do
  2609.                 local p = cat[i]
  2610.                 if p.name == panel then
  2611.                     if p.parent then
  2612.                         return get_panel_name(p.parent)
  2613.                     else
  2614.                         return panel
  2615.                     end
  2616.                 end
  2617.             end
  2618.         elseif tp == "table" then
  2619.             for i = 1, #cat do
  2620.                 local p = cat[i]
  2621.                 if p == panel then
  2622.                     if p.parent then
  2623.                         return get_panel_name(p.parent)
  2624.                     else
  2625.                         return panel.name
  2626.                     end
  2627.                 end
  2628.             end
  2629.         end
  2630.     end
  2631.  
  2632.     local function InterfaceOptionsFrame_OpenToCategory_Fix(panel)
  2633.         if doNotRun or InCombatLockdown() then return end
  2634.         local panelName = get_panel_name(panel)
  2635.         if not panelName then return end -- if its not part of our list return early
  2636.         local noncollapsedHeaders = {}
  2637.         local shownpanels = 0
  2638.         local mypanel
  2639.         local t = {}
  2640.         local cat = INTERFACEOPTIONS_ADDONCATEGORIES
  2641.         for i = 1, #cat do
  2642.             local panel = cat[i]
  2643.             if not panel.parent or noncollapsedHeaders[panel.parent] then
  2644.                 if panel.name == panelName then
  2645.                     panel.collapsed = true
  2646.                     t.element = panel
  2647.                     InterfaceOptionsListButton_ToggleSubCategories(t)
  2648.                     noncollapsedHeaders[panel.name] = true
  2649.                     mypanel = shownpanels + 1
  2650.                 end
  2651.                 if not panel.collapsed then
  2652.                     noncollapsedHeaders[panel.name] = true
  2653.                 end
  2654.                 shownpanels = shownpanels + 1
  2655.             end
  2656.         end
  2657.         local Smin, Smax = InterfaceOptionsFrameAddOnsListScrollBar:GetMinMaxValues()
  2658.         if shownpanels > 15 and Smin < Smax then
  2659.             local val = (Smax/(shownpanels-15))*(mypanel-2)
  2660.             InterfaceOptionsFrameAddOnsListScrollBar:SetValue(val)
  2661.         end
  2662.         doNotRun = true
  2663.         InterfaceOptionsFrame_OpenToCategory(panel)
  2664.         doNotRun = false
  2665.     end
  2666.  
  2667.     hooksecurefunc("InterfaceOptionsFrame_OpenToCategory", InterfaceOptionsFrame_OpenToCategory_Fix)
  2668. end
  2669. ]]--
  2670. --[[
  2671. -- following only returns for some items (notably trinkets), and only returns spec IDs relevant to the player
  2672. function PrintItemSpecInfo(item)
  2673.     specs = {}
  2674.     results = GetItemSpecInfo(item, specs)
  2675.     print('results is ', results)
  2676.     print('#results is ', #results)
  2677.     print('#specs is ', #specs)
  2678.     for i = 1, #results do
  2679.         print(results[i])
  2680.     end
  2681.     for i = 1, #specs do
  2682.         print(results[i])
  2683.     end
  2684.     for key, value in pairs(results) do
  2685.         print('key = ', key)
  2686.         print('value = ', value)
  2687.     end
  2688.     for key, value in pairs(specs) do
  2689.         print('key2 = ', key)
  2690.         print('value2 = ', value)
  2691.     end
  2692. end
  2693. ]]--
  2694.  
  2695. function PLH_TestHighlight(item)
  2696.     HighlightRaidFrames("Madone-Zul'jin", item)
  2697. end
Add Comment
Please, Sign In to add comment