Quantcast

Fix ticket #196 - DebuffPresent(Magic) wrong with Pally buffs

Johnny C. Lam [03-19-13 - 23:31]
Fix ticket #196 - DebuffPresent(Magic) wrong with Pally buffs

The problem was caused by the Buff*() and Debuff*() conditions being
aliases for each other that actually just checked every aura on a target,
regardless of whether it was a buff or a debuff, e.g., the following two
conditions returned the same result:

    DebuffPresent(Magic)
    BuffPresent(Magic)

This made it impossible to do something like the following if the player
had any dispellable raid buffs:

    if DebuffPresent(Magic) Spell(cleanse)

Fix this by splitting out the buffs and debuffs per unit into separate
lists in OvaleAura and OvaleState, and teaching all of the methods that
manipulate or reference the aura tables to take an additional ``filter''
argument that can be ``HELPFUL'' (buff), ``HARMFUL'' (debuff) or nil
(either).

Introduce a new condition parameter ``filter''.  The ``filter'' parameter
is used to narrow down the auras that Ovale inspects to check a condition.
If specified, its valid values are ``buff'', ``debuff'' or ``any'',
defaulting to ``any''.

The Buff*() and Debuff*() conditions implicitly set the filter to ``buff''
or ``debuff'', respectively.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@802 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
OvaleAura.lua
OvaleCompile.lua
OvaleCondition.lua
OvaleState.lua
diff --git a/OvaleAura.lua b/OvaleAura.lua
index c8c1778..ff59f30 100644
--- a/OvaleAura.lua
+++ b/OvaleAura.lua
@@ -23,17 +23,22 @@ local OvalePool = Ovale.OvalePool
 local pairs = pairs
 local select = select
 local strfind = string.find
+local API_IsHarmfulSpell = IsHarmfulSpell
 local API_UnitAura = UnitAura

 local self_baseDamageMultiplier = 1
 local self_pool = OvalePool:NewPool("OvaleAura_pool")
+-- self_aura[guid][filter][spellId]["mine" or "other"] = { aura properties }
 local self_aura = {}
 local self_serial = 0
 --</private-static-properties>

 --<private-static-methods>
-local function AddAura(unitGUID, spellId, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value)
-	local auraList = self_aura[unitGUID]
+local function AddAura(unitGUID, spellId, filter, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value)
+	if not self_aura[unitGUID][filter] then
+		self_aura[unitGUID][filter] = {}
+	end
+	local auraList = self_aura[unitGUID][filter]
 	if not auraList[spellId] then
 		auraList[spellId] = {}
 	end
@@ -87,12 +92,15 @@ local function RemoveAurasForGUID(guid)
 	-- Return all auras for the given GUID to the aura pool.
 	if not guid or not self_aura[guid] then return end
 	Ovale:DebugPrint("aura", "Removing auras for guid " .. guid)
-	for spellId, whoseTable in pairs(self_aura[guid]) do
-		for whose, aura in pairs(whoseTable) do
-			whoseTable[whose] = nil
-			self_pool:Release(aura)
+	for filter, auraList in pairs(self_aura[guid]) do
+		for spellId, whoseTable in pairs(auraList) do
+			for whose, aura in pairs(whoseTable) do
+				whoseTable[whose] = nil
+				self_pool:Release(aura)
+			end
+			auraList[spellId] = nil
 		end
-		self_aura[guid][spellId] = nil
+		self_aura[guid][filter] = nil
 	end
 	self_aura[guid] = nil

@@ -131,7 +139,7 @@ function OvaleAura:OnDisable()
 end

 function OvaleAura:COMBAT_LOG_EVENT_UNFILTERED(event, ...)
-	local time, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = select(1, ...)
+	local timestamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = select(1, ...)

 	if event == "UNIT_DIED" then
 		RemoveAurasForGUID(destGUID)
@@ -146,8 +154,12 @@ function OvaleAura:COMBAT_LOG_EVENT_UNFILTERED(event, ...)
 		end

 		if sourceGUID == OvaleGUID:GetGUID("player") and (event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" or event == "SPELL_AURA_APPLIED_DOSE") then
-			if self:GetAuraByGUID(destGUID, spellId, true) then
-				local aura = self_aura[destGUID][spellId].mine
+			local filter = "HELPFUL"
+			if API_IsHarmfulSpell(spellName) then
+				filter = "HARMFUL"
+			end
+			if self:GetAuraByGUID(destGUID, spellId, filter, true) then
+				local aura = self_aura[destGUID][filter][spellId].mine
 				aura.spellHasteMultiplier = OvalePaperDoll:GetSpellHasteMultiplier()
 			end
 		end
@@ -196,25 +208,25 @@ function OvaleAura:UpdateAuras(unitId, unitGUID)

 	local i = 1

-	local mode = "HELPFUL"
+	local filter = "HELPFUL"
 	local name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellId
 	local canApplyAura, isBossDebuff, isCastByPlayer, value1, value2, value3
 	while (true) do
 		name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellId,
-			canApplyAura, isBossDebuff, isCastByPlayer, value1, value2, value3 = API_UnitAura(unitId, i, mode)
+			canApplyAura, isBossDebuff, isCastByPlayer, value1, value2, value3 = API_UnitAura(unitId, i, filter)
 		if not name then
-			if mode == "HELPFUL" then
-				mode = "HARMFUL"
+			if filter == "HELPFUL" then
+				filter = "HARMFUL"
 				i = 1
 			else
 				break
 			end
 		else
-			AddAura(unitGUID, spellId, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value1)
+			AddAura(unitGUID, spellId, filter, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value1)
 			if debuffType then
 				-- TODO: not very clean
 				-- should be computed by OvaleState:GetAura
-				AddAura(unitGUID, debuffType, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value1)
+				AddAura(unitGUID, debuffType, filter, unitCaster, icon, count, debuffType, duration, expirationTime, isStealable, name, value1)
 			end

 			if unitId == "player" then
@@ -225,28 +237,29 @@ function OvaleAura:UpdateAuras(unitId, unitGUID)
 			i = i + 1
 		end
 	end
-
+
 	--Removes expired auras
-	local auraList = self_aura[unitGUID]
-	for spellId,whoseTable in pairs(auraList) do
-		for whose,aura in pairs(whoseTable) do
-			if aura.serial ~= self_serial then
-				Ovale:DebugPrint("aura", "Removing "..aura.name.." from "..whose .. ", serial = " ..self_serial.. " aura.serial = " ..aura.serial)
-				whoseTable[whose] = nil
-				self_pool:Release(aura)
+	for filter, auraList in pairs(self_aura[unitGUID]) do
+		for spellId, whoseTable in pairs(auraList) do
+			for whose, aura in pairs(whoseTable) do
+				if aura.serial ~= self_serial then
+					Ovale:DebugPrint("aura", "Removing " ..filter.. " " ..aura.name.. " from " ..whose.. ", serial = " ..self_serial.. " aura.serial = " ..aura.serial)
+					whoseTable[whose] = nil
+					self_pool:Release(aura)
+				end
+			end
+			if not next(whoseTable) then
+				auraList[spellId] = nil
 			end
 		end
-		if not next(whoseTable) then
-			Ovale:DebugPrint("aura", "Removing "..spellId)
-			auraList[spellId] = nil
+		if not next(auraList) then
+			self_aura[unitGUID][filter] = nil
 		end
 	end
-
-	--Clear unit if all aura have been deleted
-	if not next(auraList) then
+	if not next(self_aura[unitGUID]) then
 		self_aura[unitGUID] = nil
 	end
-
+
 	if unitId == "player" then
 		self_baseDamageMultiplier = damageMultiplier
 	end
@@ -255,11 +268,12 @@ function OvaleAura:UpdateAuras(unitId, unitGUID)
 end

 -- Public methods
-function OvaleAura:GetAuraByGUID(guid, spellId, mine, unitId)
+function OvaleAura:GetAuraByGUID(guid, spellId, filter, mine, unitId)
 	if not guid then
 		Ovale:Log(tostring(guid) .. " does not exists in OvaleAura")
 		return nil
 	end
+
 	local auraTable = self_aura[guid]
 	if not auraTable then
 		if not unitId then
@@ -277,71 +291,92 @@ function OvaleAura:GetAuraByGUID(guid, spellId, mine, unitId)
 			return nil
 		end
 	end
-	local whoseTable = auraTable[spellId]
-	if not whoseTable then return nil end
-	local aura
-	if mine or mine == 1 then
-		aura = whoseTable.mine
-	elseif whoseTable.other then
-		aura = whoseTable.other
+
+	local whose, aura
+	if filter then
+		if auraTable[filter] then
+			local whoseTable = auraTable[filter][spellId]
+			if whoseTable then
+				if mine then
+					aura = whoseTable.mine
+				else
+					whose, aura = next(whoseTable)
+				end
+			end
+		end
 	else
-		aura = whoseTable.mine
+		local whoseTable
+		for _, auraList in pairs(auraTable) do
+			whoseTable = auraList[spellId]
+			if whoseTable then
+				if mine then
+					aura = whoseTable.mine
+				else
+					whose, aura = next(whoseTable)
+				end
+				if aura then break end
+			end
+		end
 	end
 	if not aura then return nil end
 	return aura.start, aura.ending, aura.stacks, aura.spellHasteMultiplier, aura.value, aura.gain
 end

-function OvaleAura:GetAura(unitId, spellId, mine)
-	return self:GetAuraByGUID(OvaleGUID:GetGUID(unitId), spellId, mine, unitId)
+function OvaleAura:GetAura(unitId, spellId, filter, mine)
+	return self:GetAuraByGUID(OvaleGUID:GetGUID(unitId), spellId, filter, mine, unitId)
 end

 function OvaleAura:GetStealable(unitId)
 	local auraTable = self_aura[OvaleGUID:GetGUID(unitId)]
-	if not auraTable then
-		return nil
-	end
-	local starting,ending
-
-	for spellId, ownerTable in pairs(auraTable) do
-		local aura = ownerTable.other
+	if not auraTable then return nil end
+
+	-- only buffs are stealable
+	local auraList = auraTable.HELPFUL
+	if not auraList then return nil end
+
+	local start, ending
+	for spellId, whoseTable in pairs(auraList) do
+		local aura = whoseTable.other
 		if aura and aura.stealable then
-			if not starting or aura.start < starting then
-				starting = aura.start
+			if aura.start and (not start or aura.start < start) then
+				start = aura.start
 			end
-			if not ending or aura.ending > ending then
+			if aura.ending and (not ending or aura.ending > ending) then
 				ending = aura.ending
 			end
 		end
 	end
-	return starting, ending
+	return start, ending
 end

 -- Look for the last of my aura on any targt that will expires.
 -- Returns its expiration time
-function OvaleAura:GetExpirationTimeOnAnyTarget(spellId, excludingTarget)
-	local ending = nil
-	local starting = nil
+function OvaleAura:GetExpirationTimeOnAnyTarget(spellId, filter, excludingTarget)
+	local start, ending
 	local count = 0
-
-	for unitId,auraTable in pairs(self_aura) do
-		if unitId ~= excludingTarget then
-			if auraTable[spellId] then
-				local aura = auraTable[spellId].mine
-				if aura then
-					local newEnding = aura.ending
-					local newStarting = aura.start
-					if newStarting and (not starting or newStarting < starting) then
-						starting = newStarting
-					end
-					if newEnding and (not ending or newEnding > ending) then
-						ending = newEnding
+
+	local aura
+	for guid, auraTable in pairs(self_aura) do
+		if guid ~= excludingTarget then
+			for auraFilter, auraList in pairs(auraTable) do
+				if not filter or auraFilter == filter then
+					if auraList[spellId] then
+						aura = auraList[spellId].mine
+						if aura then
+							if aura.start and (not start or aura.start < start) then
+								start = aura.start
+							end
+							if aura.ending and (not ending or aura.ending > ending) then
+								ending = aura.ending
+							end
+							count = count + 1
+						end
 					end
-					count = count + 1
 				end
-			end
+			end
 		end
 	end
-	return starting, ending, count
+	return start, ending, count
 end

 function OvaleAura:GetDamageMultiplier(spellId)
@@ -355,7 +390,7 @@ function OvaleAura:GetDamageMultiplier(spellId)
 			if auraTable then
 				for filter, filterInfo in pairs(si.damageAura) do
 					for auraSpellId, multiplier in pairs(filterInfo) do
-						if auraTable[auraSpellId] then
+						if auraTable[filter] and auraTable[filter][auraSpellId] then
 							damageMultiplier = damageMultiplier * multiplier
 						end
 					end
@@ -368,14 +403,14 @@ end

 function OvaleAura:Debug()
 	self_pool:Debug()
-	for guid,auraTable in pairs(self_aura) do
-		Ovale:Print("***"..guid)
-		for spellId,whoseTable in pairs(auraTable) do
-			for whose,aura in pairs(whoseTable) do
-				Ovale:Print(guid.." "..whose.." "..spellId .. " "..aura.name .. " stacks ="..aura.stacks)
+	for guid, auraTable in pairs(self_aura) do
+		for filter, auraList in pairs(auraTable) do
+			for spellId, whoseTable in pairs(auraList) do
+				for whose, aura in pairs(whoseTable) do
+					Ovale:Print(guid.. " " ..filter.. " " ..whose.. " " ..spellId.. " " ..aura.name.. " stacks=" ..aura.stacks)
+				end
 			end
 		end
 	end
-	Ovale:Print("------")
 end
 --</public-static-methods>
diff --git a/OvaleCompile.lua b/OvaleCompile.lua
index c914355..58c482f 100644
--- a/OvaleCompile.lua
+++ b/OvaleCompile.lua
@@ -198,6 +198,19 @@ local function ParseFunction(prefix, func, params)

 	func = strlower(func)

+	-- "debuff" and "buff" conditions implicitly set their aura filter.
+	if not paramList.filter then
+		if strfind(func, "debuff") == 1 then
+			paramList.filter = "debuff"
+		elseif strfind(func, "buff") == 1 then
+			paramList.filter = "buff"
+		elseif strfind(func, "otherdebuff") == 1 then
+			paramList.filter = "debuff"
+		elseif strfind(func, "otherbuff") == 1 then
+			paramList.filter = "buff"
+		end
+	end
+
 	local node = self_pool:Get()
 	node.type = "function"
 	node.func = func
diff --git a/OvaleCondition.lua b/OvaleCondition.lua
index 0e39e9c..78dabd8 100644
--- a/OvaleCondition.lua
+++ b/OvaleCondition.lua
@@ -178,6 +178,17 @@ local function getTarget(condition)
 	end
 end

+local function getFilter(condition)
+	if condition.filter then
+		if condition.filter == "debuff" then
+			return "HARMFUL"
+		elseif condition.filter == "buff" then
+			return "HELPFUL"
+		end
+	end
+	return nil
+end
+
 local function addTime(time1, duration)
 	if not time1 then
 		return nil
@@ -209,11 +220,11 @@ end
 -- that can be on any unit except the target
 -- Returns the first to expires, the last to expires
 -- Returns nil if the debuff is not present
-local function getOtherAura(spellId, suppTime, excludingTarget)
+local function getOtherAura(spellId, filter, suppTime, excludingTarget)
 	if excludingTarget then
 		excludingTarget = OvaleGUID:GetGUID(excludingTarget)
 	end
-	return OvaleState:GetExpirationTimeOnAnyTarget(spellId, excludingTarget)
+	return OvaleState:GetExpirationTimeOnAnyTarget(spellId, filter, excludingTarget)
 end

 local function GetRuneCount(type, death)
@@ -283,14 +294,14 @@ local function testValue(comparator, limit, value, atTime, rate)
 	end
 end

-local function getAura(target, spellId, mine)
+local function getAura(target, spellId, filter, mine)
 	if type(spellId) == "number" then
-		return OvaleState:GetAura(target, spellId, mine)
+		return OvaleState:GetAura(target, spellId, filter, mine)
 	elseif OvaleData.buffSpellList[spellId] then
 		local newStart, newEnding, newStacks, newSpellHasteMultiplier, newValue, newGain
 		local start, ending, stacks, spellHasteMultiplier, value, gain
 		for _, v in pairs(OvaleData.buffSpellList[spellId]) do
-			start, ending, stacks, spellHasteMultiplier, value, gain = OvaleState:GetAura(target, v, mine)
+			start, ending, stacks, spellHasteMultiplier, value, gain = OvaleState:GetAura(target, v, filter, mine)
 			if start and (not newStart or stacks > newStacks) then
 				newStart = start
 				newEnding = ending
@@ -302,7 +313,7 @@ local function getAura(target, spellId, mine)
 		end
 		return newStart, newEnding, newStacks, newSpellHasteMultiplier, newValue, newGain
 	elseif spellId == "Magic" or spellId == "Disease" or spellId == "Curse" or spellId == "Poison" then
-		return OvaleState:GetAura(target, spellId, mine)
+		return OvaleState:GetAura(target, spellId, filter, mine)
 	end
 end

@@ -334,7 +345,7 @@ local function GetTargetAura(condition, target)
 	local spellId = condition[1]
 	local mine = getMine(condition)

-	local auraStart, auraEnding, auraStacks, auraSpellHasteMultiplier, auraValue, auraGain = getAura(target, spellId, mine)
+	local auraStart, auraEnding, auraStacks, auraSpellHasteMultiplier, auraValue, auraGain = getAura(target, spellId, getFilter(condition), mine)
 	if not auraStart then
 		Ovale:Log("Aura "..spellId.." not found on " .. target .. " mine=" .. tostring(mine))
 		return 0,0,0,0
@@ -515,7 +526,7 @@ end
 -- @see DebuffCount

 OvaleCondition.conditions.buffcount = function(condition)
-	local start, ending, count = OvaleState:GetExpirationTimeOnAnyTarget(condition[1])
+	local start, ending, count = OvaleState:GetExpirationTimeOnAnyTarget(condition[1], getFilter(condition))
 	return start, ending, count, 0, 0
 end
 OvaleCondition.conditions.debuffcount = OvaleCondition.conditions.buffcount
@@ -1598,7 +1609,7 @@ OvaleCondition.conditions.isstunned = function(condition)
 end

 --- Get the damage done by the most recent damage event for the given spell.
--- If the spell is a damage-over-time (DoT) aura, then it gives the damage done by the most recent tick.
+-- If the spell is a periodic aura, then it gives the damage done by the most recent tick.
 -- @name LastSpellDamage
 -- @paramsig number or boolean
 -- @param id The spell ID.
@@ -1969,10 +1980,13 @@ OvaleCondition.conditions.nextswing = function(condition)
 	return 0, nil, 0, OvaleSwing:GetNext(condition[1]), 0, -1
 end

---- Get the number of seconds until the next tick of a damage-over-time (DoT) aura on the target.
+--- Get the number of seconds until the next tick of a periodic aura on the target.
 -- @name NextTick
 -- @paramsig number
 -- @param id The aura spell ID.
+-- @param filter Optional. The type of aura to check.
+--     Default is any.
+--     Valid values: any, buff, debuff
 -- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
 --     Defaults to target=player.
 --     Valid values: player, target, focus, pet.
@@ -1995,35 +2009,39 @@ end
 	-- 1: spell id
 	-- return: bool
 	-- alias: otherauraexpires
-OvaleCondition.conditions.otherdebuffexpires = function(condition)
-	local minTime, maxTime = getOtherAura(condition[1], condition[3], "target")
+OvaleCondition.conditions.otherauraexpires = function(condition)
+	local minTime, maxTime = getOtherAura(condition[1], getFilter(condition), condition[3], "target")
 	if minTime then
 		local timeBefore = condition[2] or 0
 		return minTime - timeBefore, nil
 	end
 	return 0, nil
 end
-OvaleCondition.conditions.otherauraexpires = OvaleCondition.conditions.otherdebuffexpires
+OvaleCondition.conditions.otherbuffexpires = OvaleCondition.conditions.otherauraexpires
+OvaleCondition.conditions.otherdebuffexpires = OvaleCondition.conditions.otherauraexpires

 	-- Check if the aura is present on any other unit than the current target
 	-- return: bool
 	-- alias: otheraurapresent
-OvaleCondition.conditions.otherdebuffpresent = function(condition)
-	local minTime, maxTime = getOtherAura(condition[1], condition[3], "target")
+OvaleCondition.conditions.otheraurapresent = function(condition)
+	local minTime, maxTime = getOtherAura(condition[1], getFilter(condition), condition[3], "target")
 	if maxTime and maxTime>0 then
 		local timeBefore = condition[2] or 0
 		return 0, addTime(maxTime, -timeBefore)
 	end
 	return nil
 end
-OvaleCondition.conditions.otheraurapresent = OvaleCondition.conditions.otherdebuffpresent
+OvaleCondition.conditions.otherbuffpresent = OvaleCondition.conditions.otheraurapresent
+OvaleCondition.conditions.otherdebuffpresent = OvaleCondition.conditions.otheraurapresent

 	-- Get the maximum aura remaining duration on any target
 	-- return: number
 OvaleCondition.conditions.otherauraremains = function(condition)
-	local minTime, maxTime = getOtherAura(condition[1])
+	local minTime, maxTime = getOtherAura(condition[1], getFilter(condition))
 	return 0, nil, 0, maxTime, -1
 end
+OvaleCondition.conditions.otherbuffremains = OvaleCondition.conditions.otherauraremains
+OvaleCondition.conditions.otherdebuffremains = OvaleCondition.conditions.otherauraremains

 --- Test if the target exists and is alive.
 -- @name Present
@@ -2486,11 +2504,14 @@ OvaleCondition.conditions.threat = function(condition)
 	return compare(threatpct, condition[1], condition[2])
 end

---- Get the current tick value of a damage-over-time (DoT) aura on the target.
+--- Get the current tick value of a periodic aura on the target.
 -- @name TickValue
 -- @paramsig number or boolean
 -- @param id The aura spell ID.
 -- @param operator Optional. Comparison operator: equal, less, more.
+-- @param filter Optional. The type of aura to check.
+--     Default is any.
+--     Valid values: any, buff, debuff
 -- @param number Optional. The number to compare against.
 -- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
 --     Defaults to target=player.
@@ -2503,11 +2524,11 @@ end
 --     Spell(purifying_brew)

 OvaleCondition.conditions.tickvalue = function(condition)
-	local value = select(5, getAura(getTarget(condition.target), condition[1], getMine(condition))) or 0
+	local value = select(5, getAura(getTarget(condition.target), condition[1], getFilter(condition), getMine(condition))) or 0
 	return compare(value, condition[2], condition[3])
 end

---- Get the estimated total number of ticks of a damage-over-time (DoT) aura.
+--- Get the estimated total number of ticks of a periodic aura.
 -- @name Ticks
 -- @paramsig number or boolean
 -- @param id The aura spell ID.
@@ -2539,10 +2560,13 @@ OvaleCondition.conditions.ticksadded = function(condition)
 	return 0, nil, 0, 0, 0
 end

---- Get the remaining number of ticks of a damage-over-time (DoT) aura on a target.
+--- Get the remaining number of ticks of a periodic aura on a target.
 -- @name TicksRemain
 -- @paramsig number
 -- @param id The aura spell ID.
+-- @param filter Optional. The type of aura to check.
+--     Default is any.
+--     Valid values: any, buff, debuff
 -- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
 --     Defaults to target=player.
 --     Valid values: player, target, focus, pet.
@@ -2561,12 +2585,15 @@ OvaleCondition.conditions.ticksremain = function(condition)
 	return nil
 end

---- Get the number of seconds between ticks of a damage-over-time (DoT) aura on a target.
+--- Get the number of seconds between ticks of a periodic aura on a target.
 -- @name TickTime
 -- @paramsig number or boolean
 -- @param id The aura spell ID.
 -- @param operator Optional. Comparison operator: equal, less, more.
 -- @param number Optional. The number to compare against.
+-- @param filter Optional. The type of aura to check.
+--     Default is any.
+--     Valid values: any, buff, debuff
 -- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
 --     Defaults to target=player.
 --     Valid values: player, target, focus, pet.
diff --git a/OvaleState.lua b/OvaleState.lua
index f6afa68..4aff7eb 100644
--- a/OvaleState.lua
+++ b/OvaleState.lua
@@ -110,7 +110,7 @@ function OvaleState:UpdatePowerRates()
 		end

 		-- Blade Flurry (combat rogue)
-		if OvaleState:GetAura("player", 13877, true) then
+		if OvaleState:GetAura("player", 13877, "HELPFUL", true) then
 			energyRegen = energyRegen * 0.8
 		end

@@ -120,7 +120,7 @@ function OvaleState:UpdatePowerRates()
 		end

 		-- Adrenaline Rush (rogue)
-		if OvaleState:GetAura("player", 13750, true) then
+		if OvaleState:GetAura("player", 13750, "HELPFUL", true) then
 			energyRegen = energyRegen * 2
 		end
 	end
@@ -246,7 +246,7 @@ function OvaleState:AddSpellToStack(spellId, startCast, endCast, nextCast, nocd,
 					-- Add extra resource generated by presence of a buff.
 					local buffParam = "buff_" .. tostring(k)
 					local buffAmoumtParam = buffParam .. "_amount"
-					if newSpellInfo[k] < 0 and newSpellInfo[buffParam] and self:GetAura("player", newSpellInfo[buffParam], true) then
+					if newSpellInfo[k] < 0 and newSpellInfo[buffParam] and self:GetAura("player", newSpellInfo[buffParam], nil, true) then
 						local buffAmount = newSpellInfo[buffAmountParam] or 1
 						self.state[k] = self.state[k] + buffAmount
 					end
@@ -271,7 +271,7 @@ function OvaleState:AddSpellToStack(spellId, startCast, endCast, nextCast, nocd,
 				elseif newSpellInfo.combo > 0 then
 					self.state.combo = self.state.combo + newSpellInfo.combo
 					-- Add extra combo points generated by presence of a buff.
-					if newSpellInfo.buff_combo and self:GetAura("player", newSpellInfo.buff_combo, true) then
+					if newSpellInfo.buff_combo and self:GetAura("player", newSpellInfo.buff_combo, nil, true) then
 						local buffAmount = newSpellInfo.buff_combo_amount or 1
 						self.state.combo = self.state.combo + buffAmount
 					end
@@ -315,7 +315,7 @@ function OvaleState:AddSpellToStack(spellId, startCast, endCast, nextCast, nocd,
 			end
 			--On vrifie si le buff "buffnocd" est prsent, auquel cas le CD du sort n'est pas dclench
 			if newSpellInfo.buffnocd and not nocd then
-				local buffStart, buffEnding, buffStacks = self:GetAura("player", newSpellInfo.buffnocd, true)
+				local buffStart, buffEnding, buffStacks = self:GetAura("player", newSpellInfo.buffnocd, nil, true)
 				if self.traceAura then
 					if buffStart then
 						Ovale:Print("buffnocd stacks = "..tostring(buffStacks).." start="..tostring(buffStart).." ending = "..tostring(buffEnding))
@@ -394,8 +394,8 @@ function OvaleState:AddSpellToStack(spellId, startCast, endCast, nextCast, nocd,
 								stacks = 1
 							end

-							local oldStart, oldEnding, oldStacks, oldSpellHasteMultiplier = self:GetAuraByGUID(auraGUID, auraSpellId, true, target)
-							local newAura = self:NewAura(auraGUID, auraSpellId)
+							local oldStart, oldEnding, oldStacks, oldSpellHasteMultiplier = self:GetAuraByGUID(auraGUID, auraSpellId, true, filter, target)
+							local newAura = self:NewAura(auraGUID, auraSpellId, filter)

 							newAura.mine = true

@@ -510,69 +510,96 @@ function OvaleState:GetComputedSpellCD(spellId)
 end

 function OvaleState:AddEclipse(endCast, spellId)
-	local newAura = self:NewAura(OvaleGUID:GetGUID("player"), spellId)
+	local newAura = self:NewAura(OvaleGUID:GetGUID("player"), spellId, "HELPFUL")
 	newAura.start = endCast + 0.5
 	newAura.stacks = 1
 	newAura.ending = nil
 end

-function OvaleState:GetAuraByGUID(guid, spellId, mine, target)
-	if self.aura[guid] and self.aura[guid][spellId] and self.aura[guid][spellId].serial == self.serial then
-		Ovale:Log("Found aura " .. spellId .. " on " .. tostring(guid))
-		local aura = self.aura[guid][spellId]
+function OvaleState:GetAuraByGUID(guid, spellId, filter, mine, target)
+	local aura
+	if mine then
+		local auraTable = self.aura[guid]
+		if auraTable then
+			if filter then
+				local auraList = auraTable[filter]
+				if auraList then
+					if auraList[spellId] and auraList[spellId].serial == self.serial then
+						aura = auraList[spellId]
+					end
+				end
+			else
+				for auraFilter, auraList in pairs(auraTable) do
+					if auraList[spellId] and auraList[spellId].serial == self.serial then
+						aura = auraList[spellId]
+						filter = auraFilter
+						break
+					end
+				end
+			end
+		end
+	end
+	if aura then
+		Ovale:Log("Found " .. filter .. " aura " .. spellId .. " on " .. tostring(guid))
 		return aura.start, aura.ending, aura.stacks, aura.spellHasteMultiplier, aura.value, aura.gain
 	else
 		Ovale:Log("Aura " .. spellId .. " not found in state for " .. tostring(guid))
-		return OvaleAura:GetAuraByGUID(guid, spellId, mine, target)
+		return OvaleAura:GetAuraByGUID(guid, spellId, filter, mine, target)
 	end
 end

-function OvaleState:GetAura(target, spellId, mine)
-	return self:GetAuraByGUID(OvaleGUID:GetGUID(target), spellId, mine, target)
+function OvaleState:GetAura(target, spellId, filter, mine)
+	return self:GetAuraByGUID(OvaleGUID:GetGUID(target), spellId, filter, mine, target)
 end

-function OvaleState:GetExpirationTimeOnAnyTarget(spellId, excludingTarget)
-	local starting, ending, count = OvaleAura:GetExpirationTimeOnAnyTarget(spellId, excludingTarget)
-	for unitId,auraTable in pairs(self.aura) do
-		if unitId ~= excludingTarget then
-			local aura = auraTable[spellId]
-			if aura and aura.serial == self.serial then
-				local newEnding = aura.ending
-				local newStarting = aura.start
-				if newStarting and (not starting or newStarting < starting) then
-					starting = newStarting
-				end
-				if newEnding and (not ending or newEnding > ending) then
-					ending = newEnding
+function OvaleState:GetExpirationTimeOnAnyTarget(spellId, filter, excludingTarget)
+	local start, ending, count = OvaleAura:GetExpirationTimeOnAnyTarget(spellId, filter, excludingTarget)
+	local aura
+	for guid, auraTable in pairs(self.aura) do
+		if guid ~= excludingTarget then
+			for auraFilter, auraList in pairs(auraTable) do
+				if not filter or auraFilter == filter then
+					aura = auraList[spellId]
+					if aura and aura.serial == self.serial then
+						if aura.start and (not start or aura.start < start) then
+							start = aura.start
+						end
+						if aura.ending and (not ending or aura.ending > ending) then
+							ending = aura.ending
+						end
+						count = count + 1
+					end
 				end
-				count = count + 1
 			end
 		end
 	end
-	return starting, ending, count
+	return start, ending, count
 end

-function OvaleState:NewAura(guid, spellId)
+function OvaleState:NewAura(guid, spellId, filter)
 	if not self.aura[guid] then
 		self.aura[guid] = {}
 	end
-	if not self.aura[guid][spellId] then
-		self.aura[guid][spellId] = {}
+	if not self.aura[guid][filter] then
+		self.aura[guid][filter] = {}
+	end
+	if not self.aura[guid][filter][spellId] then
+		self.aura[guid][filter][spellId] = {}
 	end
-	local myAura = self.aura[guid][spellId]
-	myAura.serial = self.serial
-	myAura.mine = true
-	myAura.gain = self.currentTime
-	return myAura
+	local aura = self.aura[guid][filter][spellId]
+	aura.serial = self.serial
+	aura.mine = true
+	aura.gain = self.currentTime
+	return aura
 end


 function OvaleState:GetEclipseDir()
-	local stacks = select(3, self:GetAura("player", 48517)) -- Solar
+	local stacks = select(3, self:GetAura("player", 48517, "HELPFUL")) -- Solar
 	if stacks and stacks > 0 then
 		return -1
 	else
-		stacks = select(3, self:GetAura("player", 48518)) --Lunar
+		stacks = select(3, self:GetAura("player", 48518, "HELPFUL")) --Lunar
 		if stacks and stacks > 0 then
 			return 1
 		elseif self.state.eclipse < 0 then