1034 lignes
34 Kio
Lua
1034 lignes
34 Kio
Lua
|
--[[
|
||
|
Name: LibRangeCheck-2.0
|
||
|
Revision: $Revision: 98 $
|
||
|
Author(s): mitch0
|
||
|
Website: http://www.wowace.com/projects/librangecheck-2-0/
|
||
|
Description: A range checking library based on interact distances and spell ranges
|
||
|
Dependencies: LibStub
|
||
|
License: Public Domain
|
||
|
]]
|
||
|
|
||
|
--- LibRangeCheck-2.0 provides an easy way to check for ranges and get suitable range checking functions for specific ranges.\\
|
||
|
-- The checkers use spell and item range checks, or interact based checks for special units where those two cannot be used.\\
|
||
|
-- 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.\\
|
||
|
-- A callback is provided for those interested in checker changes.
|
||
|
-- @usage
|
||
|
-- local rc = LibStub("LibRangeCheck-2.0")
|
||
|
--
|
||
|
-- rc.RegisterCallback(self, rc.CHECKERS_CHANGED, function() print("need to refresh my stored checkers") end)
|
||
|
--
|
||
|
-- local minRange, maxRange = rc:GetRange('target')
|
||
|
-- if not minRange then
|
||
|
-- print("cannot get range estimate for target")
|
||
|
-- elseif not maxRange then
|
||
|
-- print("target is over " .. minRange .. " yards")
|
||
|
-- else
|
||
|
-- print("target is between " .. minRange .. " and " .. maxRange .. " yards")
|
||
|
-- end
|
||
|
--
|
||
|
-- local meleeChecker = rc:GetFriendMaxChecker(rc.MeleeRange) -- 5 yds
|
||
|
-- for i = 1, 4 do
|
||
|
-- -- TODO: check if unit is valid, etc
|
||
|
-- if meleeChecker("party" .. i) then
|
||
|
-- print("Party member " .. i .. " is in Melee range")
|
||
|
-- end
|
||
|
-- end
|
||
|
--
|
||
|
-- local safeDistanceChecker = rc:GetHarmMinChecker(30)
|
||
|
-- -- negate the result of the checker!
|
||
|
-- local isSafelyAway = not safeDistanceChecker('target')
|
||
|
--
|
||
|
-- @class file
|
||
|
-- @name LibRangeCheck-2.0
|
||
|
local MAJOR_VERSION = "LibRangeCheck-2.0"
|
||
|
local MINOR_VERSION = tonumber(("$Revision: 98 $"):match("%d+")) + 100000
|
||
|
|
||
|
local lib, oldminor = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
|
||
|
if not lib then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- << STATIC CONFIG
|
||
|
|
||
|
local UpdateDelay = .5
|
||
|
local ItemRequestTimeout = 10.0
|
||
|
|
||
|
-- interact distance based checks. ranges are based on my own measurements (thanks for all the folks who helped me with this)
|
||
|
local DefaultInteractList = {
|
||
|
[3] = 8,
|
||
|
[2] = 9,
|
||
|
[4] = 28,
|
||
|
}
|
||
|
|
||
|
-- interact list overrides for races
|
||
|
local InteractLists = {
|
||
|
["Tauren"] = {
|
||
|
[3] = 6,
|
||
|
[2] = 7,
|
||
|
[4] = 25,
|
||
|
},
|
||
|
["Scourge"] = {
|
||
|
[3] = 7,
|
||
|
[2] = 8,
|
||
|
[4] = 27,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
local MeleeRange = 5
|
||
|
|
||
|
-- list of friendly spells that have different ranges
|
||
|
local FriendSpells = {}
|
||
|
-- list of harmful spells that have different ranges
|
||
|
local HarmSpells = {}
|
||
|
|
||
|
FriendSpells["DRUID"] = {
|
||
|
5185, -- ["Healing Touch"], -- 40
|
||
|
467, -- ["Thorns"], -- 30 (Nature's Reach: 33, 36)
|
||
|
1126, -- ["Mark of the Wild"], -- 30
|
||
|
}
|
||
|
HarmSpells["DRUID"] = {
|
||
|
16979, -- ["Feral Charge"], -- 8-25
|
||
|
5176, -- ["Wrath"], -- 30 (Nature's Reach: 33, 36)
|
||
|
33786, -- ["Cyclone"], -- 20 (Nature's Reach: 22, 24; Gale Winds: +10/20%)
|
||
|
6795, -- ["Growl"], -- 20
|
||
|
5211, -- ["Bash"], -- 5
|
||
|
}
|
||
|
|
||
|
FriendSpells["HUNTER"] = {}
|
||
|
HarmSpells["HUNTER"] = {
|
||
|
1130, -- ["Hunter's Mark"] -- 100
|
||
|
53351, -- ["Kill Shot"] -- 5-45 (Hawk Eye: 47, 49, 51)
|
||
|
75, -- ["Auto Shot"], -- 5-35 (Hawk Eye: 37, 39, 41)
|
||
|
2764, -- ["Throw"], -- 30
|
||
|
19503, -- ["Scatter Shot"], -- 15 (Hawk Eye: 17, 19, 21; Glyph of Scatter Shot: +3)
|
||
|
2974, -- ["Wing Clip"], -- 5
|
||
|
}
|
||
|
|
||
|
FriendSpells["MAGE"] = {
|
||
|
475, -- ["Remove Curse"], -- 40 (Magic Attunement: 43, 46)
|
||
|
1459, -- ["Arcane Intellect"], -- 30 (Magic Attunement: 33, 36)
|
||
|
}
|
||
|
HarmSpells["MAGE"] = {
|
||
|
44614, -- ["Frostfire Bolt"], -- 40
|
||
|
133, -- ["Fireball"], -- 35 (Flame Throwing: 38, 41)
|
||
|
116, -- ["Frostbolt"], -- 30 (Arctic Reach: 33, 36)
|
||
|
30455, -- ["Ice Lance"], -- 30 (Arctic Reach: 33, 36, Glyph of Ice Lance: +5)
|
||
|
5143, -- ["Arcane Missiles"], -- 30 (Magic Attunement: 33, 36; Glyph of Arcane Missiles: +5)
|
||
|
30451, -- ["Arcane Blast"], -- 30 (Magic Attunement: 33, 36)
|
||
|
2948, -- ["Scorch"], -- 30 (Flame Throwing: 33, 36)
|
||
|
5019, -- ["Shoot"], -- 30
|
||
|
2136, -- ["Fire Blast"], -- 20 (Flame Throwing: 23, 26; Gladiator Gloves: +5)
|
||
|
}
|
||
|
|
||
|
FriendSpells["PALADIN"] = {
|
||
|
635, -- ["Holy Light"], -- 40
|
||
|
19740, -- ["Blessing of Might"], -- 30
|
||
|
20473, -- ["Holy Shock"], -- 20
|
||
|
}
|
||
|
HarmSpells["PALADIN"] = {
|
||
|
24275, -- ["Hammer of Wrath"], -- 30 (Glyph of Hammer of Wrath: +5)
|
||
|
20473, -- ["Holy Shock"], -- 20
|
||
|
20271, -- ["Judgement"], -- 10
|
||
|
35395, -- ["Crusader Strike"], -- 5
|
||
|
}
|
||
|
|
||
|
FriendSpells["PRIEST"] = {
|
||
|
2050, -- ["Lesser Heal"], -- 40
|
||
|
1243, -- ["Power Word: Fortitude"], -- 30
|
||
|
}
|
||
|
HarmSpells["PRIEST"] = {
|
||
|
585, -- ["Smite"], -- 30 (Holy Reach: 33, 36)
|
||
|
589, -- ["Shadow Word: Pain"], -- 30 (Shadow Reach: 33, 36)
|
||
|
5019, -- ["Shoot"], -- 30
|
||
|
15407, -- ["Mind Flay"], -- 20 (Shadow Reach: 22, 24, Glyph of Mind Flay: +10)
|
||
|
}
|
||
|
|
||
|
FriendSpells["ROGUE"] = {}
|
||
|
HarmSpells["ROGUE"] = {
|
||
|
2764, -- ["Throw"], -- 30
|
||
|
26679, -- ["Deadly Throw"], -- 30 (Glyph of Deadly Throw: +5)
|
||
|
2094, -- ["Blind"], -- 10 (Dirty Tricks: 12, 15)
|
||
|
2098, -- ["Eviscerate"], -- 5
|
||
|
}
|
||
|
|
||
|
FriendSpells["SHAMAN"] = {
|
||
|
331, -- ["Healing Wave"], -- 40
|
||
|
526, -- ["Cure Poison"], -- 30
|
||
|
}
|
||
|
HarmSpells["SHAMAN"] = {
|
||
|
403, -- ["Lightning Bolt"], -- 30 (Storm Reach: 33, 36)
|
||
|
370, -- ["Purge"], -- 30
|
||
|
8050, -- ["Flame Shock"], -- 20 (Elemental Reach: 27, 35; Gladiator Gloves: +5)
|
||
|
-- 8042, -- ["Earth Shock"], -- 20 (Storm, Earth and Fire: 21-25; Gladiator Gloves: +5)
|
||
|
8056, -- ["Frost Shock"], -- 20 (Gladiator Gloves: +5)
|
||
|
}
|
||
|
|
||
|
FriendSpells["WARRIOR"] = {}
|
||
|
HarmSpells["WARRIOR"] = {
|
||
|
100, -- ["Charge"], -- 8-25 (Glyph of Charge: +5)
|
||
|
3018, -- ["Shoot"], -- 30
|
||
|
2764, -- ["Throw"], -- 30
|
||
|
355, -- ["Taunt"], -- 30
|
||
|
5246, -- ["Intimidating Shout"], -- 8
|
||
|
772, -- ["Rend"], -- 5
|
||
|
}
|
||
|
|
||
|
FriendSpells["WARLOCK"] = {
|
||
|
5697, -- ["Unending Breath"], -- 30 (demo)
|
||
|
}
|
||
|
HarmSpells["WARLOCK"] = {
|
||
|
5019, -- ["Shoot"], -- 30
|
||
|
348, -- ["Immolate"], -- 30 (Destructive Reach: 33, 36)
|
||
|
172, -- ["Corruption"], -- 30 (Grim Reach: 33, 36)
|
||
|
18223, -- ["Curse of Exhaustion"], -- 30 (Grim Reach: 33, 36, Glyph of Curse of Exhaustion: +5)
|
||
|
5782, -- ["Fear"], -- 20 (Grim Reach: 22, 24)
|
||
|
17877, -- ["Shadowburn"], -- 20 (Destructive Reach: 22, 24)
|
||
|
}
|
||
|
|
||
|
FriendSpells["DEATHKNIGHT"] = {
|
||
|
}
|
||
|
HarmSpells["DEATHKNIGHT"] = {
|
||
|
47541, -- ["Death Coil"], -- 30
|
||
|
47476, -- ["Strangulate"], -- 30 (Glyph of Strangulate: +20)
|
||
|
45477, -- ["Icy Touch"], -- 20 (Icy Reach: 25, 30)
|
||
|
56222, -- ["Dark Command"], -- 20
|
||
|
50842, -- ["Pestilence"], -- 5
|
||
|
45902, -- ["Blood Strike"], -- 5, but requires weapon, use Pestilence if possible, so keep it after Pestilence in this list
|
||
|
}
|
||
|
|
||
|
-- Items [Special thanks to Maldivia for the nice list]
|
||
|
|
||
|
local FriendItems = {
|
||
|
[5] = {
|
||
|
37727, -- Ruby Acorn
|
||
|
},
|
||
|
[8] = {
|
||
|
34368, -- Attuned Crystal Cores
|
||
|
33278, -- Burning Torch
|
||
|
},
|
||
|
[10] = {
|
||
|
32321, -- Sparrowhawk Net
|
||
|
},
|
||
|
[15] = {
|
||
|
1251, -- Linen Bandage
|
||
|
2581, -- Heavy Linen Bandage
|
||
|
3530, -- Wool Bandage
|
||
|
3531, -- Heavy Wool Bandage
|
||
|
6450, -- Silk Bandage
|
||
|
6451, -- Heavy Silk Bandage
|
||
|
8544, -- Mageweave Bandage
|
||
|
8545, -- Heavy Mageweave Bandage
|
||
|
14529, -- Runecloth Bandage
|
||
|
14530, -- Heavy Runecloth Bandage
|
||
|
21990, -- Netherweave Bandage
|
||
|
21991, -- Heavy Netherweave Bandage
|
||
|
34721, -- Frostweave Bandage
|
||
|
34722, -- Heavy Frostweave Bandage
|
||
|
-- 38643, -- Thick Frostweave Bandage
|
||
|
-- 38640, -- Dense Frostweave Bandage
|
||
|
},
|
||
|
[20] = {
|
||
|
21519, -- Mistletoe
|
||
|
},
|
||
|
[25] = {
|
||
|
31463, -- Zezzak's Shard
|
||
|
},
|
||
|
[30] = {
|
||
|
1180, -- Scroll of Stamina
|
||
|
1478, -- Scroll of Protection II
|
||
|
3012, -- Scroll of Agility
|
||
|
1712, -- Scroll of Spirit II
|
||
|
2290, -- Scroll of Intellect II
|
||
|
1711, -- Scroll of Stamina II
|
||
|
34191, -- Handful of Snowflakes
|
||
|
},
|
||
|
[35] = {
|
||
|
18904, -- Zorbin's Ultra-Shrinker
|
||
|
},
|
||
|
[40] = {
|
||
|
34471, -- Vial of the Sunwell
|
||
|
},
|
||
|
[45] = {
|
||
|
32698, -- Wrangling Rope
|
||
|
},
|
||
|
[60] = {
|
||
|
32825, -- Soul Cannon
|
||
|
37887, -- Seeds of Nature's Wrath
|
||
|
},
|
||
|
[80] = {
|
||
|
35278, -- Reinforced Net
|
||
|
},
|
||
|
}
|
||
|
|
||
|
local HarmItems = {
|
||
|
[5] = {
|
||
|
37727, -- Ruby Acorn
|
||
|
},
|
||
|
[8] = {
|
||
|
34368, -- Attuned Crystal Cores
|
||
|
33278, -- Burning Torch
|
||
|
},
|
||
|
[10] = {
|
||
|
32321, -- Sparrowhawk Net
|
||
|
},
|
||
|
[15] = {
|
||
|
33069, -- Sturdy Rope
|
||
|
},
|
||
|
[20] = {
|
||
|
10645, -- Gnomish Death Ray
|
||
|
},
|
||
|
[25] = {
|
||
|
24268, -- Netherweave Net
|
||
|
41509, -- Frostweave Net
|
||
|
31463, -- Zezzak's Shard
|
||
|
},
|
||
|
[30] = {
|
||
|
835, -- Large Rope Net
|
||
|
7734, -- Six Demon Bag
|
||
|
34191, -- Handful of Snowflakes
|
||
|
},
|
||
|
[35] = {
|
||
|
24269, -- Heavy Netherweave Net
|
||
|
18904, -- Zorbin's Ultra-Shrinker
|
||
|
},
|
||
|
[40] = {
|
||
|
28767, -- The Decapitator
|
||
|
},
|
||
|
[45] = {
|
||
|
32698, -- Wrangling Rope
|
||
|
},
|
||
|
[60] = {
|
||
|
32825, -- Soul Cannon
|
||
|
37887, -- Seeds of Nature's Wrath
|
||
|
},
|
||
|
[80] = {
|
||
|
35278, -- Reinforced Net
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- This could've been done by checking player race as well and creating tables for those, but it's easier like this
|
||
|
for k, v in pairs(FriendSpells) do
|
||
|
tinsert(v, 28880) -- ["Gift of the Naaru"]
|
||
|
end
|
||
|
for k, v in pairs(HarmSpells) do
|
||
|
tinsert(v, 28734) -- ["Mana Tap"]
|
||
|
end
|
||
|
|
||
|
-- >> END OF STATIC CONFIG
|
||
|
|
||
|
-- cache
|
||
|
|
||
|
local setmetatable = setmetatable
|
||
|
local tonumber = tonumber
|
||
|
local pairs = pairs
|
||
|
local tostring = tostring
|
||
|
local print = print
|
||
|
local next = next
|
||
|
local type = type
|
||
|
local wipe = wipe
|
||
|
local tinsert = tinsert
|
||
|
local tremove = tremove
|
||
|
local BOOKTYPE_SPELL = BOOKTYPE_SPELL
|
||
|
local GetSpellInfo = GetSpellInfo
|
||
|
local GetSpellBookItemName = GetSpellBookItemName
|
||
|
local GetItemInfo = GetItemInfo
|
||
|
local UnitCanAttack = UnitCanAttack
|
||
|
local UnitCanAssist = UnitCanAssist
|
||
|
local UnitExists = UnitExists
|
||
|
local UnitIsDeadOrGhost = UnitIsDeadOrGhost
|
||
|
local CheckInteractDistance = CheckInteractDistance
|
||
|
local IsSpellInRange = IsSpellInRange
|
||
|
local IsItemInRange = IsItemInRange
|
||
|
local UnitClass = UnitClass
|
||
|
local UnitRace = UnitRace
|
||
|
local GetInventoryItemLink = GetInventoryItemLink
|
||
|
local GetTime = GetTime
|
||
|
local HandSlotId = GetInventorySlotInfo("HandsSlot")
|
||
|
local TT = ItemRefTooltip
|
||
|
|
||
|
-- temporary stuff
|
||
|
|
||
|
local itemRequestTimeoutAt
|
||
|
local foundNewItems
|
||
|
local cacheAllItems
|
||
|
local friendItemRequests
|
||
|
local harmItemRequests
|
||
|
local lastUpdate = 0
|
||
|
|
||
|
-- 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
|
||
|
local minRangeCheck = function(unit) return CheckInteractDistance(unit, 2) end
|
||
|
|
||
|
local checkers_Spell = setmetatable({}, {
|
||
|
__index = function(t, spellIdx)
|
||
|
local func = function(unit)
|
||
|
if IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1 then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
t[spellIdx] = func
|
||
|
return func
|
||
|
end
|
||
|
})
|
||
|
local checkers_SpellWithMin = setmetatable({}, {
|
||
|
__index = function(t, spellIdx)
|
||
|
local func = function(unit)
|
||
|
if IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1 then
|
||
|
return true
|
||
|
elseif minRangeCheck(unit) then
|
||
|
return true, true
|
||
|
end
|
||
|
end
|
||
|
t[spellIdx] = func
|
||
|
return func
|
||
|
end
|
||
|
})
|
||
|
local checkers_Item = setmetatable({}, {
|
||
|
__index = function(t, item)
|
||
|
local func = function(unit)
|
||
|
if IsItemInRange(item, unit) == 1 then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
t[item] = func
|
||
|
return func
|
||
|
end
|
||
|
})
|
||
|
local checkers_Interact = setmetatable({}, {
|
||
|
__index = function(t, index)
|
||
|
local func = function(unit)
|
||
|
if CheckInteractDistance(unit, index) then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
t[index] = func
|
||
|
return func
|
||
|
end
|
||
|
})
|
||
|
|
||
|
-- helper functions
|
||
|
|
||
|
local function copyTable(src, dst)
|
||
|
if type(dst) ~= "table" then dst = {} end
|
||
|
if type(src) == "table" then
|
||
|
for k, v in pairs(src) do
|
||
|
if type(v) == "table" then
|
||
|
v = copyTable(v, dst[k])
|
||
|
end
|
||
|
dst[k] = v
|
||
|
end
|
||
|
end
|
||
|
return dst
|
||
|
end
|
||
|
|
||
|
|
||
|
local function initItemRequests(cacheAll)
|
||
|
friendItemRequests = copyTable(FriendItems)
|
||
|
harmItemRequests = copyTable(HarmItems)
|
||
|
cacheAllItems = cacheAll
|
||
|
foundNewItems = nil
|
||
|
end
|
||
|
|
||
|
local function requestItemInfo(itemId)
|
||
|
if not itemId then return end
|
||
|
TT:SetHyperlink(string.format("item:%d", itemId))
|
||
|
end
|
||
|
|
||
|
-- return the spellIndex of the given spell by scanning the spellbook
|
||
|
local function findSpellIdx(spellName)
|
||
|
local i = 1
|
||
|
while true do
|
||
|
local spell, rank = GetSpellBookItemName(i, BOOKTYPE_SPELL)
|
||
|
if not spell then return nil end
|
||
|
if spell == spellName then return i end
|
||
|
i = i + 1
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
-- minRange should be nil if there's no minRange, not 0
|
||
|
local function addChecker(t, range, minRange, checker)
|
||
|
local rc = { ["range"] = range, ["minRange"] = minRange, ["checker"] = checker }
|
||
|
for i = 1, #t do
|
||
|
local v = t[i]
|
||
|
if rc.range == v.range then return end
|
||
|
if rc.range > v.range then
|
||
|
tinsert(t, i, rc)
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
tinsert(t, rc)
|
||
|
end
|
||
|
|
||
|
local function createCheckerList(spellList, itemList, interactList)
|
||
|
local res = {}
|
||
|
if spellList then
|
||
|
for i = 1, #spellList do
|
||
|
local sid = spellList[i]
|
||
|
local name, _, _, _, _, _, _, minRange, range = GetSpellInfo(sid)
|
||
|
local spellIdx = findSpellIdx(name)
|
||
|
if spellIdx and range then
|
||
|
minRange = math.floor(minRange + 0.5)
|
||
|
range = math.floor(range + 0.5)
|
||
|
-- print("### spell: " .. tostring(name) .. ", " .. tostring(minRange) .. " - " .. tostring(range))
|
||
|
if minRange == 0 then -- getRange() expects minRange to be nil in this case
|
||
|
minRange = nil
|
||
|
end
|
||
|
if range == 0 then
|
||
|
range = MeleeRange
|
||
|
end
|
||
|
if minRange then
|
||
|
addChecker(res, range, minRange, checkers_SpellWithMin[spellIdx])
|
||
|
else
|
||
|
addChecker(res, range, minRange, checkers_Spell[spellIdx])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if itemList then
|
||
|
for range, items in pairs(itemList) do
|
||
|
for i = 1, #items do
|
||
|
local item = items[i]
|
||
|
if GetItemInfo(item) then
|
||
|
addChecker(res, range, nil, checkers_Item[item])
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if interactList and not next(res) then
|
||
|
for index, range in pairs(interactList) do
|
||
|
addChecker(res, range, nil, checkers_Interact[index])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
-- returns minRange, maxRange or nil
|
||
|
local function getRange(unit, checkerList)
|
||
|
local min, max = 0, nil
|
||
|
for i = 1, #checkerList do
|
||
|
local rc = checkerList[i]
|
||
|
if not max or max > rc.range then
|
||
|
if rc.minRange then
|
||
|
local inRange, inMinRange = rc.checker(unit)
|
||
|
if inMinRange then
|
||
|
max = rc.minRange
|
||
|
elseif inRange then
|
||
|
min, max = rc.minRange, rc.range
|
||
|
elseif min > rc.range then
|
||
|
return min, max
|
||
|
else
|
||
|
return rc.range, max
|
||
|
end
|
||
|
elseif rc.checker(unit) then
|
||
|
max = rc.range
|
||
|
elseif min > rc.range then
|
||
|
return min, max
|
||
|
else
|
||
|
return rc.range, max
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return min, max
|
||
|
end
|
||
|
|
||
|
local function updateCheckers(origList, newList)
|
||
|
if #origList ~= #newList then
|
||
|
wipe(origList)
|
||
|
copyTable(newList, origList)
|
||
|
return true
|
||
|
end
|
||
|
for i = 1, #origList do
|
||
|
if origList[i].range ~= newList[i].range or origList[i].checker ~= newList[i].checker then
|
||
|
wipe(origList)
|
||
|
copyTable(newList, origList)
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function rcIterator(checkerList)
|
||
|
local curr = #checkerList
|
||
|
return function()
|
||
|
local rc = checkerList[curr]
|
||
|
if not rc then
|
||
|
return nil
|
||
|
end
|
||
|
curr = curr - 1
|
||
|
return rc.range, rc.checker
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function getMinChecker(checkerList, range)
|
||
|
local checker, checkerRange
|
||
|
for i = 1, #checkerList do
|
||
|
local rc = checkerList[i]
|
||
|
if rc.range < range then
|
||
|
return checker, checkerRange
|
||
|
end
|
||
|
checker, checkerRange = rc.checker, rc.range
|
||
|
end
|
||
|
return checker, checkerRange
|
||
|
end
|
||
|
|
||
|
local function getMaxChecker(checkerList, range)
|
||
|
for i = 1, #checkerList do
|
||
|
local rc = checkerList[i]
|
||
|
if rc.range <= range then
|
||
|
return rc.checker, rc.range
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function getChecker(checkerList, range)
|
||
|
for i = 1, #checkerList do
|
||
|
local rc = checkerList[i]
|
||
|
if rc.range == range then
|
||
|
return rc.checker
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function null()
|
||
|
end
|
||
|
|
||
|
local function createSmartChecker(friendChecker, harmChecker, miscChecker)
|
||
|
miscChecker = miscChecker or null
|
||
|
friendChecker = friendChecker or miscChecker
|
||
|
harmChecker = harmChecker or miscChecker
|
||
|
return function(unit)
|
||
|
if not UnitExists(unit) then
|
||
|
return nil
|
||
|
end
|
||
|
if UnitIsDeadOrGhost(unit) then
|
||
|
return miscChecker(unit)
|
||
|
end
|
||
|
if UnitCanAttack("player", unit) then
|
||
|
return harmChecker(unit)
|
||
|
elseif UnitCanAssist("player", unit) then
|
||
|
return friendChecker(unit)
|
||
|
else
|
||
|
return miscChecker(unit)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
-- OK, here comes the actual lib
|
||
|
|
||
|
-- pre-initialize the checkerLists here so that we can return some meaningful result even if
|
||
|
-- someone manages to call us before we're properly initialized. miscRC should be independent of
|
||
|
-- race/class/talents, so it's safe to initialize it here
|
||
|
-- friendRC and harmRC will be properly initialized later when we have all the necessary data for them
|
||
|
lib.checkerCache_Spell = lib.checkerCache_Spell or {}
|
||
|
lib.checkerCache_Item = lib.checkerCache_Item or {}
|
||
|
lib.miscRC = createCheckerList(nil, nil, DefaultInteractList)
|
||
|
lib.friendRC = createCheckerList(nil, nil, DefaultInteractList)
|
||
|
lib.harmRC = createCheckerList(nil, nil, DefaultInteractList)
|
||
|
|
||
|
lib.failedItemRequests = {}
|
||
|
|
||
|
-- << Public API
|
||
|
|
||
|
|
||
|
|
||
|
--- The callback name that is fired when checkers are changed.
|
||
|
-- @field
|
||
|
lib.CHECKERS_CHANGED = "CHECKERS_CHANGED"
|
||
|
-- "export" it, maybe someone will need it for formatting
|
||
|
--- Constant for Melee range (5yd).
|
||
|
-- @field
|
||
|
lib.MeleeRange = MeleeRange
|
||
|
|
||
|
function lib:findSpellIndex(spell)
|
||
|
if type(spell) == 'number' then
|
||
|
spell = GetSpellInfo(spell)
|
||
|
end
|
||
|
if not spell then return nil end
|
||
|
return findSpellIdx(spell)
|
||
|
end
|
||
|
|
||
|
-- returns the range estimate as a string
|
||
|
-- deprecated, use :getRange(unit) instead and build your own strings
|
||
|
-- (checkVisible is not used any more, kept for compatibility only)
|
||
|
function lib:getRangeAsString(unit, checkVisible, showOutOfRange)
|
||
|
local minRange, maxRange = self:getRange(unit)
|
||
|
if not minRange then return nil end
|
||
|
if not maxRange then
|
||
|
return showOutOfRange and minRange .. " +" or nil
|
||
|
end
|
||
|
return minRange .. " - " .. maxRange
|
||
|
end
|
||
|
|
||
|
-- initialize RangeCheck if not yet initialized or if "forced"
|
||
|
function lib:init(forced)
|
||
|
if self.initialized and (not forced) then return end
|
||
|
self.initialized = true
|
||
|
local _, playerClass = UnitClass("player")
|
||
|
local _, playerRace = UnitRace("player")
|
||
|
|
||
|
minRangeCheck = nil
|
||
|
-- first try to find a nice item we can use for minRangeCheck
|
||
|
if HarmItems[15] then
|
||
|
local items = HarmItems[15]
|
||
|
for i = 1, #items do
|
||
|
local item = items[i]
|
||
|
if GetItemInfo(item) then
|
||
|
minRangeCheck = function(unit)
|
||
|
return (IsItemInRange(item, unit) == 1)
|
||
|
end
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if not minRangeCheck then
|
||
|
-- ok, then try to find some class specific spell
|
||
|
if playerClass == "WARRIOR" then
|
||
|
-- for warriors, use Intimidating Shout if available
|
||
|
local name = GetSpellInfo(5246) -- ["Intimidating Shout"]
|
||
|
local spellIdx = findSpellIdx(name)
|
||
|
if spellIdx then
|
||
|
minRangeCheck = function(unit)
|
||
|
return (IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1)
|
||
|
end
|
||
|
end
|
||
|
elseif playerClass == "ROGUE" then
|
||
|
-- for rogues, use Blind if available
|
||
|
local name = GetSpellInfo(2094) -- ["Blind"]
|
||
|
local spellIdx = findSpellIdx(name)
|
||
|
if spellIdx then
|
||
|
minRangeCheck = function(unit)
|
||
|
return (IsSpellInRange(spellIdx, BOOKTYPE_SPELL, unit) == 1)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if not minRangeCheck then
|
||
|
-- fall back to interact distance checks
|
||
|
if playerClass == "HUNTER" or playerRace == "Tauren" then
|
||
|
-- for hunters, use interact4 as it's safer
|
||
|
-- for Taurens interact4 is actually closer than 25yd and interact2 is closer than 8yd, so we can't use that
|
||
|
minRangeCheck = checkers_Interact[4]
|
||
|
else
|
||
|
minRangeCheck = checkers_Interact[2]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local interactList = InteractLists[playerRace] or DefaultInteractList
|
||
|
self.handSlotItem = GetInventoryItemLink("player", HandSlotId)
|
||
|
local changed = false
|
||
|
if updateCheckers(self.friendRC, createCheckerList(FriendSpells[playerClass], FriendItems, interactList)) then
|
||
|
changed = true
|
||
|
end
|
||
|
if updateCheckers(self.harmRC, createCheckerList(HarmSpells[playerClass], HarmItems, interactList)) then
|
||
|
changed = true
|
||
|
end
|
||
|
if updateCheckers(self.miscRC, createCheckerList(nil, nil, interactList)) then
|
||
|
changed = true
|
||
|
end
|
||
|
if changed and self.callbacks then
|
||
|
self.callbacks:Fire(self.CHECKERS_CHANGED)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Return an iterator for checkers usable on friendly units as (**range**, **checker**) pairs.
|
||
|
function lib:GetFriendCheckers()
|
||
|
return rcIterator(self.friendRC)
|
||
|
end
|
||
|
|
||
|
--- Return an iterator for checkers usable on enemy units as (**range**, **checker**) pairs.
|
||
|
function lib:GetHarmCheckers()
|
||
|
return rcIterator(self.harmRC)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
function lib:GetMiscCheckers()
|
||
|
return rcIterator(self.miscRC)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetFriendMinChecker(range)
|
||
|
return getMinChecker(self.friendRC, range)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetHarmMinChecker(range)
|
||
|
return getMinChecker(self.harmRC, range)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetMiscMinChecker(range)
|
||
|
return getMinChecker(self.miscRC, range)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetFriendMaxChecker(range)
|
||
|
return getMaxChecker(self.friendRC, range)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetHarmMaxChecker(range)
|
||
|
return getMaxChecker(self.harmRC, range)
|
||
|
end
|
||
|
|
||
|
--- 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.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker**, **range** pair or **nil** if no suitable checker is available. **range** is the actual range the returned **checker** checks for.
|
||
|
function lib:GetMiscMaxChecker(range)
|
||
|
return getMaxChecker(self.miscRC, range)
|
||
|
end
|
||
|
|
||
|
--- Return a checker for the given range for friendly units.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker** function or **nil** if no suitable checker is available.
|
||
|
function lib:GetFriendChecker(range)
|
||
|
return getChecker(self.friendRC, range)
|
||
|
end
|
||
|
|
||
|
--- Return a checker for the given range for enemy units.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker** function or **nil** if no suitable checker is available.
|
||
|
function lib:GetHarmChecker(range)
|
||
|
return getChecker(self.harmRC, range)
|
||
|
end
|
||
|
|
||
|
--- Return a checker for the given range for miscellaneous units.
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker** function or **nil** if no suitable checker is available.
|
||
|
function lib:GetMiscChecker(range)
|
||
|
return getChecker(self.miscRC, range)
|
||
|
end
|
||
|
|
||
|
--- Return a checker suitable for out-of-range checking that checks the unit type and calls the appropriate checker (friend/harm/misc).
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker** function.
|
||
|
function lib:GetSmartMinChecker(range)
|
||
|
return createSmartChecker(
|
||
|
getMinChecker(self.friendRC, range),
|
||
|
getMinChecker(self.harmRC, range),
|
||
|
getMinChecker(self.miscRC, range))
|
||
|
end
|
||
|
|
||
|
--- Return a checker suitable for in-of-range checking that checks the unit type and calls the appropriate checker (friend/harm/misc).
|
||
|
-- @param range the range to check for.
|
||
|
-- @return **checker** function.
|
||
|
function lib:GetSmartMaxChecker(range)
|
||
|
return createSmartChecker(
|
||
|
getMaxChecker(self.friendRC, range),
|
||
|
getMaxChecker(self.harmRC, range),
|
||
|
getMaxChecker(self.miscRC, range))
|
||
|
end
|
||
|
|
||
|
--- Return a checker for the given range that checks the unit type and calls the appropriate checker (friend/harm/misc).
|
||
|
-- @param range the range to check for.
|
||
|
-- @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.
|
||
|
-- @return **checker** function.
|
||
|
function lib:GetSmartChecker(range, fallback)
|
||
|
return createSmartChecker(
|
||
|
getChecker(self.friendRC, range) or fallback,
|
||
|
getChecker(self.harmRC, range) or fallback,
|
||
|
getChecker(self.miscRC, range) or fallback)
|
||
|
end
|
||
|
|
||
|
--- Get a range estimate as **minRange**, **maxRange**.
|
||
|
-- @param unit the target unit to check range to.
|
||
|
-- @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.
|
||
|
-- Includes checks for unit validity and friendly/enemy status.
|
||
|
-- @usage
|
||
|
-- local rc = LibStub("LibRangeCheck-2.0")
|
||
|
-- local minRange, maxRange = rc:GetRange('target')
|
||
|
function lib:GetRange(unit)
|
||
|
if not UnitExists(unit) then
|
||
|
return nil
|
||
|
end
|
||
|
if UnitIsDeadOrGhost(unit) then
|
||
|
return getRange(unit, self.miscRC)
|
||
|
end
|
||
|
if UnitCanAttack("player", unit) then
|
||
|
return getRange(unit, self.harmRC)
|
||
|
elseif UnitCanAssist("player", unit) then
|
||
|
return getRange(unit, self.friendRC)
|
||
|
else
|
||
|
return getRange(unit, self.miscRC)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- keep this for compatibility
|
||
|
lib.getRange = lib.GetRange
|
||
|
|
||
|
-- >> Public API
|
||
|
|
||
|
function lib:OnEvent(event, ...)
|
||
|
if type(self[event]) == 'function' then
|
||
|
self[event](self, event, ...)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function lib:LEARNED_SPELL_IN_TAB()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:CHARACTER_POINTS_CHANGED()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:PLAYER_TALENT_UPDATE()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:GLYPH_ADDED()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:GLYPH_REMOVED()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:GLYPH_UPDATED()
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
function lib:UNIT_INVENTORY_CHANGED(event, unit)
|
||
|
if self.initialized and unit == "player" and self.handSlotItem ~= GetInventoryItemLink("player", HandSlotId) then
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function lib:processItemRequests(itemRequests)
|
||
|
while true do
|
||
|
local range, items = next(itemRequests)
|
||
|
if not range then return end
|
||
|
while true do
|
||
|
local i, item = next(items)
|
||
|
if not i then
|
||
|
itemRequests[range] = nil
|
||
|
break
|
||
|
elseif self.failedItemRequests[item] then
|
||
|
tremove(items, i)
|
||
|
elseif GetItemInfo(item) then
|
||
|
if itemRequestTimeoutAt then
|
||
|
foundNewItems = true
|
||
|
itemRequestTimeoutAt = nil
|
||
|
end
|
||
|
if not cacheAllItems then
|
||
|
itemRequests[range] = nil
|
||
|
break
|
||
|
end
|
||
|
tremove(items, i)
|
||
|
elseif not itemRequestTimeoutAt then
|
||
|
requestItemInfo(item)
|
||
|
itemRequestTimeoutAt = GetTime() + ItemRequestTimeout
|
||
|
return true
|
||
|
elseif GetTime() > itemRequestTimeoutAt then
|
||
|
if cacheAllItems then
|
||
|
print(MAJOR_VERSION .. ": timeout for item: " .. tostring(item))
|
||
|
end
|
||
|
self.failedItemRequests[item] = true
|
||
|
itemRequestTimeoutAt = nil
|
||
|
tremove(items, i)
|
||
|
else
|
||
|
return true -- still waiting for server response
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function lib:initialOnUpdate()
|
||
|
self:init()
|
||
|
if friendItemRequests then
|
||
|
if self:processItemRequests(friendItemRequests) then return end
|
||
|
friendItemRequests = nil
|
||
|
end
|
||
|
if harmItemRequests then
|
||
|
if self:processItemRequests(harmItemRequests) then return end
|
||
|
harmItemRequests = nil
|
||
|
end
|
||
|
if foundNewItems then
|
||
|
self:init(true)
|
||
|
foundNewItems = nil
|
||
|
end
|
||
|
if cacheAllItems then
|
||
|
print(MAJOR_VERSION .. ": finished cache")
|
||
|
cacheAllItems = nil
|
||
|
end
|
||
|
self.frame:Hide()
|
||
|
end
|
||
|
|
||
|
function lib:scheduleInit()
|
||
|
self.initialized = nil
|
||
|
lastUpdate = 0
|
||
|
self.frame:Show()
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- << load-time initialization
|
||
|
|
||
|
function lib:activate()
|
||
|
if not self.frame then
|
||
|
local frame = CreateFrame("Frame")
|
||
|
self.frame = frame
|
||
|
frame:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||
|
frame:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
||
|
frame:RegisterEvent("PLAYER_TALENT_UPDATE")
|
||
|
frame:RegisterEvent("GLYPH_ADDED")
|
||
|
frame:RegisterEvent("GLYPH_REMOVED")
|
||
|
frame:RegisterEvent("GLYPH_UPDATED")
|
||
|
local _, playerClass = UnitClass("player")
|
||
|
if playerClass == "MAGE" or playerClass == "SHAMAN" then
|
||
|
-- Mage and Shaman gladiator gloves modify spell ranges
|
||
|
frame:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||
|
end
|
||
|
end
|
||
|
initItemRequests()
|
||
|
self.frame:SetScript("OnEvent", function(frame, ...) self:OnEvent(...) end)
|
||
|
self.frame:SetScript("OnUpdate", function(frame, elapsed)
|
||
|
lastUpdate = lastUpdate + elapsed
|
||
|
if lastUpdate < UpdateDelay then
|
||
|
return
|
||
|
end
|
||
|
lastUpdate = 0
|
||
|
self:initialOnUpdate()
|
||
|
end)
|
||
|
self:scheduleInit()
|
||
|
end
|
||
|
|
||
|
--- BEGIN CallbackHandler stuff
|
||
|
|
||
|
do
|
||
|
local lib = lib -- to keep a ref even though later we nil lib
|
||
|
--- Register a callback to get called when checkers are updated
|
||
|
-- @class function
|
||
|
-- @name lib.RegisterCallback
|
||
|
-- @usage
|
||
|
-- rc.RegisterCallback(self, rc.CHECKERS_CHANGED, "myCallback")
|
||
|
-- -- or
|
||
|
-- rc.RegisterCallback(self, "CHECKERS_CHANGED", someCallbackFunction)
|
||
|
-- @see CallbackHandler-1.0 documentation for more details
|
||
|
lib.RegisterCallback = lib.RegisterCallback or function(...)
|
||
|
local CBH = LibStub("CallbackHandler-1.0")
|
||
|
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
|
||
|
lib.callbacks = CBH:New(lib)
|
||
|
-- ok, CBH hopefully injected or new shiny RegisterCallback
|
||
|
return lib.RegisterCallback(...)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- END CallbackHandler stuff
|
||
|
|
||
|
lib:activate()
|
||
|
lib = nil
|