WoW:Identifying buffs using textures
In some circumstances, it may be benefecial to use spell textures, rather than names, to identify buff and debuff effects - textures require no additional localization, while buff names will require to be localized for every language your addon should support. This HOWTO describes an easy mechanism to find out if a certain unit is affected by a specific buff or debuff.
Iterating buff / debuffs
While we could write separate functions for buffs and debuffs, it's easier to write a single iterator function and pass the desired spell effect type as an argument - effectively cutting down the amount of code required for the whole routine. We can get the buff's texture from UnitBuff and debuffs from UnitDebuff:
name, rank, iconTexture, count, duration, timeLeft = UnitBuff(unit, buffIndex [, castable]); name, rank, texture, count, debuffType, duration, timeLeft = UnitDebuff(unitID, debuffIndex [, removable]);
The effect's texture is the third return value of both functions, but UnitDebuff has one more return value - which means that we'll need to store all seven return values in order to return them. Castable and removable arguments allow us to save iterations of the loop if we're only interested in buffs/debuffs the player can cast/remove, so we should allow access to those from the iterator function. For both functions, index starts at 1 and goes up until the function returns nil -- while a while loop is ideal, we can use an infinite loop, and call break; when nil is encountered to save iterator calls. So far, our inner iterator loop would look something like:
qFunc, unit, castable, id, match = UnitBuff, "player", 1, nil, "SomeEffectTexture"; -- while true do local name, rank, texture, count, type, duration, timeleft = qFunc(unit, id, castable); -- note UnitDebuff returns; we simply don't care about the names at this point, but we need all seven. if not name then break; -- There are no more buffs / debuffs, exit infinite loop end if string.match(texture, match) then return name, rank, texture, count, type, duration, timeleft; -- We've found a match end i = i + 1; -- Go for the next index end
This would tell return the proper UnitBuff/UnitDebuff returns if the target is affected by a matching buff/debuff, or nil if it's not. Stopping here is good enough; we can, however, make things even better by allowing you to detect multiple textures with the same texture by using another index argument, and then decrementing it on matching effects until we hit zero, returning the found buff entry. Everything combined, the iterator function is:
function iterateQueriableEffect(qFunc, unit, matchTexture, id, castable) id = id or 1; -- default value, making id optional; while true do local name, rank, texture, count, type, duration, timeleft = qFunc(unit, id, castable); if not name then break; end if string.match(texture, matchTexture) then id = id - 1; if id == 0 then return name, rank, texture, count, type, duration, timeleft; end end end end
Using the iterator function to query effects
With the background code written, we can now use it to query actual effects. WoWWiki has a list of buff and debuff textures with associated names; suppose we wanted to know whether the player is buffed with Mark of the Wild: motw is a buff (so we need UnitBuff), player's unitid is "player":
name, rank, texture, count, duration, timeLeft = iterateQueriableEffect(UnitBuff, "player", "Spell_Nature_Regeneration"); if name then message("Player is buffed with " .. name .. " (" .. rank .. ")"); else message("Player is not buffed with Mark of the Wild."); end
Or if we wanted to know whether the target is sapped(UnitDebuff, "target"):
name, rank, texture, count, debuffType, duration, timeLeft = iterateQueriableEffect(UnitDebuff, "target", "Ability_Sap"); message(name and "Target is sapped!" or "Target is not sapped"); -- shorthand if syntax -- if name is non-false and non-nil, the first message appears, otherwise, the second.
Other interesting uses
You can find out whether the unit is affected by a debuff you can remove (by using the castable argument, which doubles up as removable for debuffs):
name, rank, texture, count, debuffType, duration, timeLeft = iterateQueriableEffect(UnitDebuff, unit, ".", 1, true); -- "." matches all textures, so we only have to get a return from UnitDebuff(unit, id, true) for this to return something.
You can also use the id argument to find all buffs/debuffs with the same texture on target; for example, "Spell_Fire_FireArmor" is used for almost all fire-shield effects - Fire Ward, greater protection potions, warlock imps' fire thorns spell... suppose you wanted to iterate through them:
local i, out = 1, ""; while true do local name, rank = iterateQueriableEffect(UnitBuff, unit, "Spell_Fire_FireArmor", i); if not name then break; end out = out .. (out == "" and "" or ", ") .. name; end message("Following debuffs on unit share Spell_Fire_FireArmor texture: " .. out);
Note that the last example is somewhat inefficient - you would be better off acquiring all the buff names in a single local loop rather than iterating through buffs multiple times to find all occurrences of a certain texture.