ugh merge
[QuestHelper.git] / LibRangeCheck-2.0 / LibRangeCheck-2.0.lua
blob7a538a75683c0a46cdd995b8fc8e9a8213489ace
1 --[[
2 Name: LibRangeCheck-2.0
3 Revision: $Revision: 98 $
4 Author(s): mitch0
5 Website: http://www.wowace.com/projects/librangecheck-2-0/
6 Description: A range checking library based on interact distances and spell ranges
7 Dependencies: LibStub
8 License: Public Domain
9 ]]
11 --- LibRangeCheck-2.0 provides an easy way to check for ranges and get suitable range checking functions for specific ranges.\\
12 -- The checkers use spell and item range checks, or interact based checks for special units where those two cannot be used.\\
13 -- The lib handles the refreshing of checker lists in case talents / spells / glyphs change and in some special cases when equipment changes (for example some of the mage pvp gloves change the range of the Fire Blast spell), and also handles the caching of items used for item-based range checks.\\
14 -- A callback is provided for those interested in checker changes.
15 -- @usage
16 -- local rc = LibStub("LibRangeCheck-2.0")
17 --
18 -- rc.RegisterCallback(self, rc.CHECKERS_CHANGED, function() print("need to refresh my stored checkers") end)
19 --
20 -- local minRange, maxRange = rc:GetRange('target')
21 -- if not minRange then
22 -- print("cannot get range estimate for target")
23 -- elseif not maxRange then
24 -- print("target is over " .. minRange .. " yards")
25 -- else
26 -- print("target is between " .. minRange .. " and " .. maxRange .. " yards")
27 -- end
28 --
29 -- local meleeChecker = rc:GetFriendMaxChecker(rc.MeleeRange) -- 5 yds
30 -- for i = 1, 4 do
31 -- -- TODO: check if unit is valid, etc
32 -- if meleeChecker("party" .. i) then
33 -- print("Party member " .. i .. " is in Melee range")
34 -- end
35 -- end
37 -- local safeDistanceChecker = rc:GetHarmMinChecker(30)
38 -- -- negate the result of the checker!
39 -- local isSafelyAway = not safeDistanceChecker('target')
41 -- @class file
42 -- @name LibRangeCheck-2.0
43 local MAJOR_VERSION = "LibRangeCheck-2.0"
44 local MINOR_VERSION = tonumber(("$Revision: 98 $"):match("%d+")) + 100000
46 local lib, oldminor = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
47 if not lib then
48 return
49 end
51 -- << STATIC CONFIG
53 local UpdateDelay = .5
54 local ItemRequestTimeout = 10.0
56 -- interact distance based checks. ranges are based on my own measurements (thanks for all the folks who helped me with this)
57 local DefaultInteractList = {
58 [3] = 8,
59 [2] = 9,
60 [4] = 28,
63 -- interact list overrides for races
64 local InteractLists = {
65 ["Tauren"] = {
66 [3] = 6,
67 [2] = 7,
68 [4] = 25,
70 ["Scourge"] = {
71 [3] = 7,
72 [2] = 8,
73 [4] = 27,
77 local MeleeRange = 5
79 -- list of friendly spells that have different ranges
80 local FriendSpells = {}
81 -- list of harmful spells that have different ranges
82 local HarmSpells = {}
84 FriendSpells["DRUID"] = {
85 5185, -- ["Healing Touch"], -- 40
86 467, -- ["Thorns"], -- 30 (Nature's Reach: 33, 36)
87 1126, -- ["Mark of the Wild"], -- 30
89 HarmSpells["DRUID"] = {
90 16979, -- ["Feral Charge"], -- 8-25
91 5176, -- ["Wrath"], -- 30 (Nature's Reach: 33, 36)
92 33786, -- ["Cyclone"], -- 20 (Nature's Reach: 22, 24; Gale Winds: +10/20%)
93 6795, -- ["Growl"], -- 20
94 5211, -- ["Bash"], -- 5
97 FriendSpells["HUNTER"] = {}
98 HarmSpells["HUNTER"] = {
99 1130, -- ["Hunter's Mark"] -- 100
100 53351, -- ["Kill Shot"] -- 5-45 (Hawk Eye: 47, 49, 51)
101 75, -- ["Auto Shot"], -- 5-35 (Hawk Eye: 37, 39, 41)
102 2764, -- ["Throw"], -- 30
103 19503, -- ["Scatter Shot"], -- 15 (Hawk Eye: 17, 19, 21; Glyph of Scatter Shot: +3)
104 2974, -- ["Wing Clip"], -- 5
107 FriendSpells["MAGE"] = {
108 475, -- ["Remove Curse"], -- 40 (Magic Attunement: 43, 46)
109 1459, -- ["Arcane Intellect"], -- 30 (Magic Attunement: 33, 36)
111 HarmSpells["MAGE"] = {
112 44614, -- ["Frostfire Bolt"], -- 40
113 133, -- ["Fireball"], -- 35 (Flame Throwing: 38, 41)
114 116, -- ["Frostbolt"], -- 30 (Arctic Reach: 33, 36)
115 30455, -- ["Ice Lance"], -- 30 (Arctic Reach: 33, 36, Glyph of Ice Lance: +5)
116 5143, -- ["Arcane Missiles"], -- 30 (Magic Attunement: 33, 36; Glyph of Arcane Missiles: +5)
117 30451, -- ["Arcane Blast"], -- 30 (Magic Attunement: 33, 36)
118 2948, -- ["Scorch"], -- 30 (Flame Throwing: 33, 36)
119 5019, -- ["Shoot"], -- 30
120 2136, -- ["Fire Blast"], -- 20 (Flame Throwing: 23, 26; Gladiator Gloves: +5)
123 FriendSpells["PALADIN"] = {
124 635, -- ["Holy Light"], -- 40
125 19740, -- ["Blessing of Might"], -- 30
126 20473, -- ["Holy Shock"], -- 20
128 HarmSpells["PALADIN"] = {
129 24275, -- ["Hammer of Wrath"], -- 30 (Glyph of Hammer of Wrath: +5)
130 20473, -- ["Holy Shock"], -- 20
131 20271, -- ["Judgement"], -- 10
132 35395, -- ["Crusader Strike"], -- 5
135 FriendSpells["PRIEST"] = {
136 2050, -- ["Lesser Heal"], -- 40
137 1243, -- ["Power Word: Fortitude"], -- 30
139 HarmSpells["PRIEST"] = {
140 585, -- ["Smite"], -- 30 (Holy Reach: 33, 36)
141 589, -- ["Shadow Word: Pain"], -- 30 (Shadow Reach: 33, 36)
142 5019, -- ["Shoot"], -- 30
143 15407, -- ["Mind Flay"], -- 20 (Shadow Reach: 22, 24, Glyph of Mind Flay: +10)
146 FriendSpells["ROGUE"] = {}
147 HarmSpells["ROGUE"] = {
148 2764, -- ["Throw"], -- 30
149 26679, -- ["Deadly Throw"], -- 30 (Glyph of Deadly Throw: +5)
150 2094, -- ["Blind"], -- 10 (Dirty Tricks: 12, 15)
151 2098, -- ["Eviscerate"], -- 5
154 FriendSpells["SHAMAN"] = {
155 331, -- ["Healing Wave"], -- 40
156 526, -- ["Cure Poison"], -- 30
158 HarmSpells["SHAMAN"] = {
159 403, -- ["Lightning Bolt"], -- 30 (Storm Reach: 33, 36)
160 370, -- ["Purge"], -- 30
161 8050, -- ["Flame Shock"], -- 20 (Elemental Reach: 27, 35; Gladiator Gloves: +5)
162 -- 8042, -- ["Earth Shock"], -- 20 (Storm, Earth and Fire: 21-25; Gladiator Gloves: +5)
163 8056, -- ["Frost Shock"], -- 20 (Gladiator Gloves: +5)
166 FriendSpells["WARRIOR"] = {}
167 HarmSpells["WARRIOR"] = {
168 100, -- ["Charge"], -- 8-25 (Glyph of Charge: +5)
169 3018, -- ["Shoot"], -- 30
170 2764, -- ["Throw"], -- 30
171 355, -- ["Taunt"], -- 30
172 5246, -- ["Intimidating Shout"], -- 8
173 772, -- ["Rend"], -- 5
176 FriendSpells["WARLOCK"] = {
177 5697, -- ["Unending Breath"], -- 30 (demo)
179 HarmSpells["WARLOCK"] = {
180 5019, -- ["Shoot"], -- 30
181 348, -- ["Immolate"], -- 30 (Destructive Reach: 33, 36)
182 172, -- ["Corruption"], -- 30 (Grim Reach: 33, 36)
183 18223, -- ["Curse of Exhaustion"], -- 30 (Grim Reach: 33, 36, Glyph of Curse of Exhaustion: +5)
184 5782, -- ["Fear"], -- 20 (Grim Reach: 22, 24)
185 17877, -- ["Shadowburn"], -- 20 (Destructive Reach: 22, 24)
188 FriendSpells["DEATHKNIGHT"] = {
190 HarmSpells["DEATHKNIGHT"] = {
191 47541, -- ["Death Coil"], -- 30
192 47476, -- ["Strangulate"], -- 30 (Glyph of Strangulate: +20)
193 45477, -- ["Icy Touch"], -- 20 (Icy Reach: 25, 30)
194 56222, -- ["Dark Command"], -- 20
195 50842, -- ["Pestilence"], -- 5
196 45902, -- ["Blood Strike"], -- 5, but requires weapon, use Pestilence if possible, so keep it after Pestilence in this list
199 -- Items [Special thanks to Maldivia for the nice list]
201 local FriendItems = {
202 [5] = {
203 37727, -- Ruby Acorn
205 [8] = {
206 34368, -- Attuned Crystal Cores
207 33278, -- Burning Torch
209 [10] = {
210 32321, -- Sparrowhawk Net
212 [15] = {
213 1251, -- Linen Bandage
214 2581, -- Heavy Linen Bandage
215 3530, -- Wool Bandage
216 3531, -- Heavy Wool Bandage
217 6450, -- Silk Bandage
218 6451, -- Heavy Silk Bandage
219 8544, -- Mageweave Bandage
220 8545, -- Heavy Mageweave Bandage
221 14529, -- Runecloth Bandage
222 14530, -- Heavy Runecloth Bandage
223 21990, -- Netherweave Bandage
224 21991, -- Heavy Netherweave Bandage
225 34721, -- Frostweave Bandage
226 34722, -- Heavy Frostweave Bandage
227 -- 38643, -- Thick Frostweave Bandage
228 -- 38640, -- Dense Frostweave Bandage
230 [20] = {
231 21519, -- Mistletoe
233 [25] = {
234 31463, -- Zezzak's Shard
236 [30] = {
237 1180, -- Scroll of Stamina
238 1478, -- Scroll of Protection II
239 3012, -- Scroll of Agility
240 1712, -- Scroll of Spirit II
241 2290, -- Scroll of Intellect II
242 1711, -- Scroll of Stamina II
243 34191, -- Handful of Snowflakes
245 [35] = {
246 18904, -- Zorbin's Ultra-Shrinker
248 [40] = {
249 34471, -- Vial of the Sunwell
251 [45] = {
252 32698, -- Wrangling Rope
254 [60] = {
255 32825, -- Soul Cannon
256 37887, -- Seeds of Nature's Wrath
258 [80] = {
259 35278, -- Reinforced Net
263 local HarmItems = {
264 [5] = {
265 37727, -- Ruby Acorn
267 [8] = {
268 34368, -- Attuned Crystal Cores
269 33278, -- Burning Torch
271 [10] = {
272 32321, -- Sparrowhawk Net
274 [15] = {
275 33069, -- Sturdy Rope
277 [20] = {
278 10645, -- Gnomish Death Ray
280 [25] = {
281 24268, -- Netherweave Net
282 41509, -- Frostweave Net
283 31463, -- Zezzak's Shard
285 [30] = {
286 835, -- Large Rope Net
287 7734, -- Six Demon Bag
288 34191, -- Handful of Snowflakes
290 [35] = {
291 24269, -- Heavy Netherweave Net
292 18904, -- Zorbin's Ultra-Shrinker
294 [40] = {
295 28767, -- The Decapitator
297 [45] = {
298 32698, -- Wrangling Rope
300 [60] = {
301 32825, -- Soul Cannon
302 37887, -- Seeds of Nature's Wrath
304 [80] = {
305 35278, -- Reinforced Net
309 -- This could've been done by checking player race as well and creating tables for those, but it's easier like this
310 for k, v in pairs(FriendSpells) do
311 tinsert(v, 28880) -- ["Gift of the Naaru"]
313 for k, v in pairs(HarmSpells) do
314 tinsert(v, 28734) -- ["Mana Tap"]
317 -- >> END OF STATIC CONFIG
319 -- cache
321 local setmetatable = setmetatable
322 local tonumber = tonumber
323 local pairs = pairs
324 local tostring = tostring
325 local print = print
326 local next = next
327 local type = type
328 local wipe = wipe
329 local tinsert = tinsert
330 local tremove = tremove
331 local BOOKTYPE_SPELL = BOOKTYPE_SPELL
332 local GetSpellInfo = GetSpellInfo
333 local GetSpellName = GetSpellName
334 local GetItemInfo = GetItemInfo
335 local UnitCanAttack = UnitCanAttack
336 local UnitCanAssist = UnitCanAssist
337 local UnitExists = UnitExists
338 local UnitIsDeadOrGhost = UnitIsDeadOrGhost
339 local CheckInteractDistance = CheckInteractDistance
340 local IsSpellInRange = IsSpellInRange
341 local IsItemInRange = IsItemInRange
342 local UnitClass = UnitClass
343 local UnitRace = UnitRace
344 local GetInventoryItemLink = GetInventoryItemLink
345 local GetTime = GetTime
346 local HandSlotId = GetInventorySlotInfo("HandsSlot")
347 local TT = ItemRefTooltip
349 -- temporary stuff
351 local itemRequestTimeoutAt
352 local foundNewItems
353 local cacheAllItems
354 local friendItemRequests
355 local harmItemRequests
356 local lastUpdate = 0
358 -- minRangeCheck is a function to check if spells with minimum range are really out of range, or fail due to range < minRange. See :init() for its setup
359 local minRangeCheck = function(unit) return CheckInteractDistance(unit, 2) end
361 local checkers_Spell = setmetatable({}, {
362 __index = function(t, spellIdx)
363 local func = function(unit)
364 if IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1 then
365 return true
368 t[spellIdx] = func
369 return func
372 local checkers_SpellWithMin = setmetatable({}, {
373 __index = function(t, spellIdx)
374 local func = function(unit)
375 if IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1 then
376 return true
377 elseif minRangeCheck(unit) then
378 return true, true
381 t[spellIdx] = func
382 return func
385 local checkers_Item = setmetatable({}, {
386 __index = function(t, item)
387 local func = function(unit)
388 if IsItemInRange(item, unit) == 1 then
389 return true
392 t[item] = func
393 return func
396 local checkers_Interact = setmetatable({}, {
397 __index = function(t, index)
398 local func = function(unit)
399 if CheckInteractDistance(unit, index) then
400 return true
403 t[index] = func
404 return func
408 -- helper functions
410 local function copyTable(src, dst)
411 if type(dst) ~= "table" then dst = {} end
412 if type(src) == "table" then
413 for k, v in pairs(src) do
414 if type(v) == "table" then
415 v = copyTable(v, dst[k])
417 dst[k] = v
420 return dst
424 local function initItemRequests(cacheAll)
425 friendItemRequests = copyTable(FriendItems)
426 harmItemRequests = copyTable(HarmItems)
427 cacheAllItems = cacheAll
428 foundNewItems = nil
431 local function requestItemInfo(itemId)
432 if not itemId then return end
433 TT:SetHyperlink(string.format("item:%d", itemId))
436 -- return the spellIndex of the given spell by scanning the spellbook
437 local function findSpellIdx(spellName)
438 local i = 1
439 while true do
440 local spell, rank = GetSpellName(i, BOOKTYPE_SPELL)
441 if not spell then return nil end
442 if spell == spellName then return i end
443 i = i + 1
445 return nil
448 -- minRange should be nil if there's no minRange, not 0
449 local function addChecker(t, range, minRange, checker)
450 local rc = { ["range"] = range, ["minRange"] = minRange, ["checker"] = checker }
451 for i = 1, #t do
452 local v = t[i]
453 if rc.range == v.range then return end
454 if rc.range > v.range then
455 tinsert(t, i, rc)
456 return
459 tinsert(t, rc)
462 local function createCheckerList(spellList, itemList, interactList)
463 local res = {}
464 if spellList then
465 for i = 1, #spellList do
466 local sid = spellList[i]
467 local name, _, _, _, _, _, _, minRange, range = GetSpellInfo(sid)
468 local spellIdx = findSpellIdx(name)
469 if spellIdx and range then
470 minRange = math.floor(minRange + 0.5)
471 range = math.floor(range + 0.5)
472 -- print("### spell: " .. tostring(name) .. ", " .. tostring(minRange) .. " - " .. tostring(range))
473 if minRange == 0 then -- getRange() expects minRange to be nil in this case
474 minRange = nil
476 if range == 0 then
477 range = MeleeRange
479 if minRange then
480 addChecker(res, range, minRange, checkers_SpellWithMin[spellIdx])
481 else
482 addChecker(res, range, minRange, checkers_Spell[spellIdx])
488 if itemList then
489 for range, items in pairs(itemList) do
490 for i = 1, #items do
491 local item = items[i]
492 if GetItemInfo(item) then
493 addChecker(res, range, nil, checkers_Item[item])
494 break
500 if interactList and not next(res) then
501 for index, range in pairs(interactList) do
502 addChecker(res, range, nil, checkers_Interact[index])
506 return res
509 -- returns minRange, maxRange or nil
510 local function getRange(unit, checkerList)
511 local min, max = 0, nil
512 for i = 1, #checkerList do
513 local rc = checkerList[i]
514 if not max or max > rc.range then
515 if rc.minRange then
516 local inRange, inMinRange = rc.checker(unit)
517 if inMinRange then
518 max = rc.minRange
519 elseif inRange then
520 min, max = rc.minRange, rc.range
521 elseif min > rc.range then
522 return min, max
523 else
524 return rc.range, max
526 elseif rc.checker(unit) then
527 max = rc.range
528 elseif min > rc.range then
529 return min, max
530 else
531 return rc.range, max
535 return min, max
538 local function updateCheckers(origList, newList)
539 if #origList ~= #newList then
540 wipe(origList)
541 copyTable(newList, origList)
542 return true
544 for i = 1, #origList do
545 if origList[i].range ~= newList[i].range or origList[i].checker ~= newList[i].checker then
546 wipe(origList)
547 copyTable(newList, origList)
548 return true
553 local function rcIterator(checkerList)
554 local curr = #checkerList
555 return function()
556 local rc = checkerList[curr]
557 if not rc then
558 return nil
560 curr = curr - 1
561 return rc.range, rc.checker
565 local function getMinChecker(checkerList, range)
566 local checker, checkerRange
567 for i = 1, #checkerList do
568 local rc = checkerList[i]
569 if rc.range < range then
570 return checker, checkerRange
572 checker, checkerRange = rc.checker, rc.range
574 return checker, checkerRange
577 local function getMaxChecker(checkerList, range)
578 for i = 1, #checkerList do
579 local rc = checkerList[i]
580 if rc.range <= range then
581 return rc.checker, rc.range
586 local function getChecker(checkerList, range)
587 for i = 1, #checkerList do
588 local rc = checkerList[i]
589 if rc.range == range then
590 return rc.checker
595 local function null()
598 local function createSmartChecker(friendChecker, harmChecker, miscChecker)
599 miscChecker = miscChecker or null
600 friendChecker = friendChecker or miscChecker
601 harmChecker = harmChecker or miscChecker
602 return function(unit)
603 if not UnitExists(unit) then
604 return nil
606 if UnitIsDeadOrGhost(unit) then
607 return miscChecker(unit)
609 if UnitCanAttack("player", unit) then
610 return harmChecker(unit)
611 elseif UnitCanAssist("player", unit) then
612 return friendChecker(unit)
613 else
614 return miscChecker(unit)
620 -- OK, here comes the actual lib
622 -- pre-initialize the checkerLists here so that we can return some meaningful result even if
623 -- someone manages to call us before we're properly initialized. miscRC should be independent of
624 -- race/class/talents, so it's safe to initialize it here
625 -- friendRC and harmRC will be properly initialized later when we have all the necessary data for them
626 lib.checkerCache_Spell = lib.checkerCache_Spell or {}
627 lib.checkerCache_Item = lib.checkerCache_Item or {}
628 lib.miscRC = createCheckerList(nil, nil, DefaultInteractList)
629 lib.friendRC = createCheckerList(nil, nil, DefaultInteractList)
630 lib.harmRC = createCheckerList(nil, nil, DefaultInteractList)
632 lib.failedItemRequests = {}
634 -- << Public API
638 --- The callback name that is fired when checkers are changed.
639 -- @field
640 lib.CHECKERS_CHANGED = "CHECKERS_CHANGED"
641 -- "export" it, maybe someone will need it for formatting
642 --- Constant for Melee range (5yd).
643 -- @field
644 lib.MeleeRange = MeleeRange
646 function lib:findSpellIndex(spell)
647 if type(spell) == 'number' then
648 spell = GetSpellInfo(spell)
650 if not spell then return nil end
651 return findSpellIdx(spell)
654 -- returns the range estimate as a string
655 -- deprecated, use :getRange(unit) instead and build your own strings
656 -- (checkVisible is not used any more, kept for compatibility only)
657 function lib:getRangeAsString(unit, checkVisible, showOutOfRange)
658 local minRange, maxRange = self:getRange(unit)
659 if not minRange then return nil end
660 if not maxRange then
661 return showOutOfRange and minRange .. " +" or nil
663 return minRange .. " - " .. maxRange
666 -- initialize RangeCheck if not yet initialized or if "forced"
667 function lib:init(forced)
668 if self.initialized and (not forced) then return end
669 self.initialized = true
670 local _, playerClass = UnitClass("player")
671 local _, playerRace = UnitRace("player")
673 minRangeCheck = nil
674 -- first try to find a nice item we can use for minRangeCheck
675 if HarmItems[15] then
676 local items = HarmItems[15]
677 for i = 1, #items do
678 local item = items[i]
679 if GetItemInfo(item) then
680 minRangeCheck = function(unit)
681 return (IsItemInRange(item, unit) == 1)
683 break
687 if not minRangeCheck then
688 -- ok, then try to find some class specific spell
689 if playerClass == "WARRIOR" then
690 -- for warriors, use Intimidating Shout if available
691 local name = GetSpellInfo(5246) -- ["Intimidating Shout"]
692 local spellIdx = findSpellIdx(name)
693 if spellIdx then
694 minRangeCheck = function(unit)
695 return (IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1)
698 elseif playerClass == "ROGUE" then
699 -- for rogues, use Blind if available
700 local name = GetSpellInfo(2094) -- ["Blind"]
701 local spellIdx = findSpellIdx(name)
702 if spellIdx then
703 minRangeCheck = function(unit)
704 return (IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1)
709 if not minRangeCheck then
710 -- fall back to interact distance checks
711 if playerClass == "HUNTER" or playerRace == "Tauren" then
712 -- for hunters, use interact4 as it's safer
713 -- for Taurens interact4 is actually closer than 25yd and interact2 is closer than 8yd, so we can't use that
714 minRangeCheck = checkers_Interact[4]
715 else
716 minRangeCheck = checkers_Interact[2]
720 local interactList = InteractLists[playerRace] or DefaultInteractList
721 self.handSlotItem = GetInventoryItemLink("player", HandSlotId)
722 local changed = false
723 if updateCheckers(self.friendRC, createCheckerList(FriendSpells[playerClass], FriendItems, interactList)) then
724 changed = true
726 if updateCheckers(self.harmRC, createCheckerList(HarmSpells[playerClass], HarmItems, interactList)) then
727 changed = true
729 if updateCheckers(self.miscRC, createCheckerList(nil, nil, interactList)) then
730 changed = true
732 if changed and self.callbacks then
733 self.callbacks:Fire(self.CHECKERS_CHANGED)
737 --- Return an iterator for checkers usable on friendly units as (**range**, **checker**) pairs.
738 function lib:GetFriendCheckers()
739 return rcIterator(self.friendRC)
742 --- Return an iterator for checkers usable on enemy units as (**range**, **checker**) pairs.
743 function lib:GetHarmCheckers()
744 return rcIterator(self.harmRC)
747 --- Return an iterator for checkers usable on miscellaneous units as (**range**, **checker**) pairs. These units are neither enemy nor friendly, such as people in sanctuaries or corpses.
748 function lib:GetMiscCheckers()
749 return rcIterator(self.miscRC)
752 --- Return a checker suitable for out-of-range checking on friendly units, that is, a checker whose range is equal or larger than the requested range.
753 -- @param range the range to check for.
754 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
755 function lib:GetFriendMinChecker(range)
756 return getMinChecker(self.friendRC, range)
759 --- Return a checker suitable for out-of-range checking on enemy units, that is, a checker whose range is equal or larger than the requested range.
760 -- @param range the range to check for.
761 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
762 function lib:GetHarmMinChecker(range)
763 return getMinChecker(self.harmRC, range)
766 --- Return a checker suitable for out-of-range checking on miscellaneous units, that is, a checker whose range is equal or larger than the requested range.
767 -- @param range the range to check for.
768 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
769 function lib:GetMiscMinChecker(range)
770 return getMinChecker(self.miscRC, range)
773 --- Return a checker suitable for in-range checking on friendly units, that is, a checker whose range is equal or smaller than the requested range.
774 -- @param range the range to check for.
775 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
776 function lib:GetFriendMaxChecker(range)
777 return getMaxChecker(self.friendRC, range)
780 --- Return a checker suitable for in-range checking on enemy units, that is, a checker whose range is equal or smaller than the requested range.
781 -- @param range the range to check for.
782 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
783 function lib:GetHarmMaxChecker(range)
784 return getMaxChecker(self.harmRC, range)
787 --- Return a checker suitable for in-range checking on miscellaneous units, that is, a checker whose range is equal or smaller than the requested range.
788 -- @param range the range to check for.
789 -- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
790 function lib:GetMiscMaxChecker(range)
791 return getMaxChecker(self.miscRC, range)
794 --- Return a checker for the given range for friendly units.
795 -- @param range the range to check for.
796 -- @return **checker** function or **nil** if no suitable checker is available.
797 function lib:GetFriendChecker(range)
798 return getChecker(self.friendRC, range)
801 --- Return a checker for the given range for enemy units.
802 -- @param range the range to check for.
803 -- @return **checker** function or **nil** if no suitable checker is available.
804 function lib:GetHarmChecker(range)
805 return getChecker(self.harmRC, range)
808 --- Return a checker for the given range for miscellaneous units.
809 -- @param range the range to check for.
810 -- @return **checker** function or **nil** if no suitable checker is available.
811 function lib:GetMiscChecker(range)
812 return getChecker(self.miscRC, range)
815 --- Return a checker suitable for out-of-range checking that checks the unit type and calls the appropriate checker (friend/harm/misc).
816 -- @param range the range to check for.
817 -- @return **checker** function.
818 function lib:GetSmartMinChecker(range)
819 return createSmartChecker(
820 getMinChecker(self.friendRC, range),
821 getMinChecker(self.harmRC, range),
822 getMinChecker(self.miscRC, range))
825 --- Return a checker suitable for in-of-range checking that checks the unit type and calls the appropriate checker (friend/harm/misc).
826 -- @param range the range to check for.
827 -- @return **checker** function.
828 function lib:GetSmartMaxChecker(range)
829 return createSmartChecker(
830 getMaxChecker(self.friendRC, range),
831 getMaxChecker(self.harmRC, range),
832 getMaxChecker(self.miscRC, range))
835 --- Return a checker for the given range that checks the unit type and calls the appropriate checker (friend/harm/misc).
836 -- @param range the range to check for.
837 -- @param fallback optional fallback function that gets called as fallback(unit) if a checker is not available for the given type (friend/harm/misc) at the requested range. The default fallback function return nil.
838 -- @return **checker** function.
839 function lib:GetSmartChecker(range, fallback)
840 return createSmartChecker(
841 getChecker(self.friendRC, range) or fallback,
842 getChecker(self.harmRC, range) or fallback,
843 getChecker(self.miscRC, range) or fallback)
846 --- Get a range estimate as **minRange**, **maxRange**.
847 -- @param unit the target unit to check range to.
848 -- @return **minRange**, **maxRange** pair if a range estimate could be determined, **nil** otherwise. **maxRange** is **nil** if **unit** is further away than the highest possible range we can check.
849 -- Includes checks for unit validity and friendly/enemy status.
850 -- @usage
851 -- local rc = LibStub("LibRangeCheck-2.0")
852 -- local minRange, maxRange = rc:GetRange('target')
853 function lib:GetRange(unit)
854 if not UnitExists(unit) then
855 return nil
857 if UnitIsDeadOrGhost(unit) then
858 return getRange(unit, self.miscRC)
860 if UnitCanAttack("player", unit) then
861 return getRange(unit, self.harmRC)
862 elseif UnitCanAssist("player", unit) then
863 return getRange(unit, self.friendRC)
864 else
865 return getRange(unit, self.miscRC)
869 -- keep this for compatibility
870 lib.getRange = lib.GetRange
872 -- >> Public API
874 function lib:OnEvent(event, ...)
875 if type(self[event]) == 'function' then
876 self[event](self, event, ...)
880 function lib:LEARNED_SPELL_IN_TAB()
881 self:scheduleInit()
884 function lib:CHARACTER_POINTS_CHANGED()
885 self:scheduleInit()
888 function lib:PLAYER_TALENT_UPDATE()
889 self:scheduleInit()
892 function lib:GLYPH_ADDED()
893 self:scheduleInit()
896 function lib:GLYPH_REMOVED()
897 self:scheduleInit()
900 function lib:GLYPH_UPDATED()
901 self:scheduleInit()
904 function lib:UNIT_INVENTORY_CHANGED(event, unit)
905 if self.initialized and unit == "player" and self.handSlotItem ~= GetInventoryItemLink("player", HandSlotId) then
906 self:scheduleInit()
910 function lib:processItemRequests(itemRequests)
911 while true do
912 local range, items = next(itemRequests)
913 if not range then return end
914 while true do
915 local i, item = next(items)
916 if not i then
917 itemRequests[range] = nil
918 break
919 elseif self.failedItemRequests[item] then
920 tremove(items, i)
921 elseif GetItemInfo(item) then
922 if itemRequestTimeoutAt then
923 foundNewItems = true
924 itemRequestTimeoutAt = nil
926 if not cacheAllItems then
927 itemRequests[range] = nil
928 break
930 tremove(items, i)
931 elseif not itemRequestTimeoutAt then
932 requestItemInfo(item)
933 itemRequestTimeoutAt = GetTime() + ItemRequestTimeout
934 return true
935 elseif GetTime() > itemRequestTimeoutAt then
936 if cacheAllItems then
937 print(MAJOR_VERSION .. ": timeout for item: " .. tostring(item))
939 self.failedItemRequests[item] = true
940 itemRequestTimeoutAt = nil
941 tremove(items, i)
942 else
943 return true -- still waiting for server response
949 function lib:initialOnUpdate()
950 self:init()
951 if friendItemRequests then
952 if self:processItemRequests(friendItemRequests) then return end
953 friendItemRequests = nil
955 if harmItemRequests then
956 if self:processItemRequests(harmItemRequests) then return end
957 harmItemRequests = nil
959 if foundNewItems then
960 self:init(true)
961 foundNewItems = nil
963 if cacheAllItems then
964 print(MAJOR_VERSION .. ": finished cache")
965 cacheAllItems = nil
967 self.frame:Hide()
970 function lib:scheduleInit()
971 self.initialized = nil
972 lastUpdate = 0
973 self.frame:Show()
978 -- << load-time initialization
980 function lib:activate()
981 if not self.frame then
982 local frame = CreateFrame("Frame")
983 self.frame = frame
984 frame:RegisterEvent("LEARNED_SPELL_IN_TAB")
985 frame:RegisterEvent("CHARACTER_POINTS_CHANGED")
986 frame:RegisterEvent("PLAYER_TALENT_UPDATE")
987 frame:RegisterEvent("GLYPH_ADDED")
988 frame:RegisterEvent("GLYPH_REMOVED")
989 frame:RegisterEvent("GLYPH_UPDATED")
990 local _, playerClass = UnitClass("player")
991 if playerClass == "MAGE" or playerClass == "SHAMAN" then
992 -- Mage and Shaman gladiator gloves modify spell ranges
993 frame:RegisterEvent("UNIT_INVENTORY_CHANGED")
996 initItemRequests()
997 self.frame:SetScript("OnEvent", function(frame, ...) self:OnEvent(...) end)
998 self.frame:SetScript("OnUpdate", function(frame, elapsed)
999 lastUpdate = lastUpdate + elapsed
1000 if lastUpdate < UpdateDelay then
1001 return
1003 lastUpdate = 0
1004 self:initialOnUpdate()
1005 end)
1006 self:scheduleInit()
1009 --- BEGIN CallbackHandler stuff
1012 local lib = lib -- to keep a ref even though later we nil lib
1013 --- Register a callback to get called when checkers are updated
1014 -- @class function
1015 -- @name lib.RegisterCallback
1016 -- @usage
1017 -- rc.RegisterCallback(self, rc.CHECKERS_CHANGED, "myCallback")
1018 -- -- or
1019 -- rc.RegisterCallback(self, "CHECKERS_CHANGED", someCallbackFunction)
1020 -- @see CallbackHandler-1.0 documentation for more details
1021 lib.RegisterCallback = lib.RegisterCallback or function(...)
1022 local CBH = LibStub("CallbackHandler-1.0")
1023 lib.RegisterCallback = nil -- extra safety, we shouldn't get this far if CBH is not found, but better an error later than an infinite recursion now
1024 lib.callbacks = CBH:New(lib)
1025 -- ok, CBH hopefully injected or new shiny RegisterCallback
1026 return lib.RegisterCallback(...)
1030 --- END CallbackHandler stuff
1032 lib:activate()
1033 lib = nil