Quantcast

Workaround for ticket 290 - Dream of Cenarius Second Charge Rip.

Johnny C. Lam [12-05-13 - 08:19]
Workaround for ticket 290 - Dream of Cenarius Second Charge Rip.

When a buff expires simultaneous with a spellcast, a debuff applied by
that spellcast may not snapshot the effects of the expired buff.

Tag an expired buff as "consumed" if the aura ended early and the ending
cast time of the previous spellcast is within 0.3s of the expiration time
of the aura.  Treat an aura as still "active" if it was consumed and we
are checking within 0.3s of when it has expired.  This allows an aura that
provides a damage multiplier to still be added into the snapshot for any
applied auras.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1238 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
OvaleAura.lua
OvaleFuture.lua
diff --git a/OvaleAura.lua b/OvaleAura.lua
index 95ba2a5..2bdd753 100644
--- a/OvaleAura.lua
+++ b/OvaleAura.lua
@@ -23,9 +23,9 @@ local OvaleData = nil
 local OvaleFuture = nil
 local OvaleGUID = nil
 local OvalePaperDoll = nil
+local OvaleSpellBook = nil
 local OvaleState = nil

-local abs = math.abs
 local bit_band = bit.band
 local bit_bor = bit.bor
 local floor = math.floor
@@ -63,6 +63,10 @@ local self_aura = {}
 -- Current age of auras per unit: self_serial[guid] = age.
 local self_serial = {}

+-- Aura lag in milliseconds, with respect to the corresponding spellcast.
+-- TODO: Promote this into a slider option in the config panel.
+local self_auraLag = 300
+
 -- Some auras have a nil caster, so treat those as having a GUID of zero for indexing purposes.
 local UNKNOWN_GUID = 0

@@ -100,9 +104,6 @@ local CLEU_TICK_EVENTS = {
 local CLEU_SCHOOL_MASK_MAGIC = bit_bor(SCHOOL_MASK_ARCANE, SCHOOL_MASK_FIRE, SCHOOL_MASK_FROST, SCHOOL_MASK_HOLY, SCHOOL_MASK_NATURE, SCHOOL_MASK_SHADOW)
 --</private-static-properties>

---<public-static-properties>
---</public-static-properties>
-
 --<private-static-methods>
 local function PutAura(auraDB, guid, auraId, casterGUID, aura)
 	if not auraDB[guid] then
@@ -207,6 +208,10 @@ local function RemoveAurasOnGUID(auraDB, guid)
 		auraDB[guid] = nil
 	end
 end
+
+local function IsWithinAuraLag(time1, time2)
+	return (time1 - time2 < self_auraLag/1000) and (time2 - time1 < self_auraLag/1000)
+end
 --</private-static-methods>

 --<public-static-methods>
@@ -216,6 +221,7 @@ function OvaleAura:OnInitialize()
 	OvaleFuture = Ovale.OvaleFuture
 	OvaleGUID = Ovale.OvaleGUID
 	OvalePaperDoll = Ovale.OvalePaperDoll
+	OvaleSpellBook = Ovale.OvaleSpellBook
 	OvaleState = Ovale.OvaleState
 end

@@ -329,7 +335,15 @@ end

 function OvaleAura:IsActiveAura(aura, now)
 	now = now or API_GetTime()
-	return (aura and aura.serial == self_serial[aura.guid] and aura.stacks > 0 and aura.start <= now and now <= aura.ending)
+	local boolean = false
+	if aura then
+		if aura.serial == self_serial[aura.guid] and aura.stacks > 0 and aura.start <= now and now <= aura.ending then
+			boolean = true
+		elseif aura.consumed and IsWithinAuraLag(aura.ending, now) then
+			boolean = true
+		end
+	end
+	return boolean
 end

 function OvaleAura:GainedAuraOnGUID(guid, atTime, auraId, casterGUID, filter, icon, count, debuffType, duration, expirationTime, isStealable, name, value1, value2, value3)
@@ -378,6 +392,7 @@ function OvaleAura:GainedAuraOnGUID(guid, atTime, auraId, casterGUID, filter, ic
 		end
 		aura.gain = atTime
 		aura.stacks = count
+		aura.consumed = nil
 		aura.filter = filter
 		aura.icon = icon
 		aura.debuffType = debuffType
@@ -387,13 +402,21 @@ function OvaleAura:GainedAuraOnGUID(guid, atTime, auraId, casterGUID, filter, ic
 		-- Snapshot stats for auras applied by the player.
 		if mine then
 			-- Determine whether to snapshot player stats for the aura or to keep the existing stats.
-			local lastSpellcast = OvaleFuture.lastSpellcast
-			local lastSpellId = lastSpellcast and lastSpellcast.spellId
-			if lastSpellId and OvaleData:NeedNewSnapshot(auraId, lastSpellId) then
-				Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Snapshot stats for %s %s (%d) on %s from %f, now=%f, aura.serial=%d",
-					filter, name, auraId, guid, lastSpellcast.snapshot.snapshotTime, atTime, aura.serial)
-				-- TODO: damageMultiplier isn't correct if lastSpellId spreads the DoT.
-				OvaleFuture:UpdateSnapshotFromSpellcast(aura, lastSpellcast)
+			local spellcast = OvaleFuture:LastInFlightSpell()
+			if spellcast and spellcast.stop and not IsWithinAuraLag(spellcast.stop, atTime) then
+				spellcast = OvaleFuture.lastSpellcast
+				if spellcast and spellcast.stop and not IsWithinAuraLag(spellcast.stop, atTime) then
+					spellcast = nil
+				end
+			end
+			if spellcast and spellcast.target == guid then
+				if OvaleData:NeedNewSnapshot(auraId, spellcast.spellId) then
+					local spellName = OvaleSpellBook:GetSpellName(spellcast.spellId) or "Unknown spell"
+					Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Snapshot stats for %s %s (%d) on %s applied by %s (%d) from %f, now=%f, aura.serial=%d",
+						filter, name, auraId, guid, spellName, spellcast.spellId, spellcast.snapshot.snapshotTime, atTime, aura.serial)
+					-- TODO: damageMultiplier isn't correct if spellId spreads the DoT.
+					OvaleFuture:UpdateSnapshotFromSpellcast(aura, spellcast)
+				end
 			end

 			-- Set the tick information for known DoTs.
@@ -421,15 +444,39 @@ end

 function OvaleAura:LostAuraOnGUID(guid, atTime, auraId, casterGUID)
 	local aura = GetAura(self_aura, guid, auraId, casterGUID)
-	Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Expiring %s %s (%s) from %s at %f.",
-		aura.filter, aura.name, auraId, guid, atTime)
+	local filter = aura.filter
+	Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Expiring %s %s (%d) from %s at %f.",
+		filter, aura.name, auraId, guid, atTime)
 	if aura.ending > atTime then
 		aura.ending = atTime
 	end
-	-- Clear old tick information.
-	aura.tick = nil
-	aura.ticksSeen = nil
-	aura.lastTickTime = nil
+
+	local mine = (casterGUID == self_guid)
+	if mine then
+		-- Clear old tick information for player-applied periodic auras.
+		aura.tick = nil
+		aura.ticksSeen = nil
+		aura.lastTickTime = nil
+
+		-- Check if the aura was consumed by the last spellcast.
+		-- The aura must have ended early, i.e., start + duration > ending.
+		if aura.start + aura.duration > aura.ending then
+			local spellcast
+			if guid == self_guid then
+				-- Player aura, so it was possibly consumed by an in-flight spell.
+				spellcast = OvaleFuture:LastInFlightSpell()
+			else
+				-- Non-player aura, so it was possibly consumed by a spell that landed on its target.
+				spellcast = OvaleFuture.lastSpellcast
+			end
+			if spellcast and spellcast.stop and IsWithinAuraLag(spellcast.stop, aura.ending) then
+				aura.consumed = true
+				local spellName = OvaleSpellBook:GetSpellName(spellcast.spellId) or "Unknown spell"
+				Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Consuming %s %s (%d) on %s with %s (%d) at %f.",
+					filter, aura.name, auraId, guid, spellName, spellcast.spellId, spellcast.stop)
+			end
+		end
+	end

 	self:SendMessage("Ovale_AuraRemoved", atTime, guid, auraId, aura.source)
 	local unitId = OvaleGUID:GetUnitId(guid)
@@ -687,7 +734,11 @@ statePrototype.IsActiveAura = function(state, aura, now)
 	local boolean = false
 	if aura then
 		if aura.state then
-			boolean = (aura.serial == state.serial and aura.stacks > 0 and aura.start <= now and now <= aura.ending)
+			if aura.serial == state.serial and aura.stacks > 0 and aura.start <= now and now <= aura.ending then
+				boolean = true
+			elseif aura.consumed and IsWithinAuraLag(aura.ending, now) then
+				boolean = true
+			end
 		else
 			boolean = OvaleAura:IsActiveAura(aura, now)
 		end
@@ -793,6 +844,7 @@ statePrototype.ApplySpellAuras = function(state, spellId, guid, startCast, endCa
 						Ovale:Logf("Aura %d is completely removed.", auraId)
 						-- The aura is completely removed, so set ending to the time that the aura is removed.
 						aura.ending = atTime
+						aura.consumed = true
 					end
 				end
 			else
diff --git a/OvaleFuture.lua b/OvaleFuture.lua
index 6dff9de..bcc0f91 100644
--- a/OvaleFuture.lua
+++ b/OvaleFuture.lua
@@ -163,7 +163,7 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a
 		if si.buffnocd then
 			local now = API_GetTime()
 			local aura = OvaleAura:GetAura("player", si.buffnocd)
-			if aura and aura.stacks > 0 and aura.start <= now and now <= aura.ending then
+			if OvaleAura:IsActiveAura(aura) then
 				spellcast.nocd = true
 			end
 		end
@@ -527,6 +527,13 @@ function OvaleFuture:ApplyInFlightSpells(state)
 	end
 end

+function OvaleFuture:LastInFlightSpell()
+	if #self_activeSpellcast > 0 then
+		return self_activeSpellcast[#self_activeSpellcast]
+	end
+	return self.lastSpellcast
+end
+
 function OvaleFuture:UpdateSnapshotFromSpellcast(dest, spellcast)
 	if dest.snapshot then
 		OvalePaperDoll:ReleaseSnapshot(dest.snapshot)