Quantcast

Use reference-counted table pool to manage snapshots instead of a queue.

Johnny C. Lam [11-17-13 - 16:29]
Use reference-counted table pool to manage snapshots instead of a queue.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1187 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
Ovale.lua
OvaleAura.lua
OvaleFuture.lua
OvalePaperDoll.lua
OvaleState.lua
conditions/BuffAmount.lua
conditions/BuffComboPoints.lua
conditions/BuffDamageMultiplier.lua
conditions/BuffSnapshot.lua
conditions/Damage.lua
conditions/DamageMultiplier.lua
conditions/LastComboPoints.lua
conditions/LastEstimatedDamage.lua
conditions/LastSnapshot.lua
conditions/PaperDoll.lua
conditions/WeaponDamage.lua
conditions/conditions.xml
diff --git a/Ovale.lua b/Ovale.lua
index 5220106..20147f3 100644
--- a/Ovale.lua
+++ b/Ovale.lua
@@ -33,8 +33,6 @@ local OVALE_TRUE_STRING = tostring(true)

 --<public-static-properties>
 Ovale.L = L
--- The most recent spell cast.
-Ovale.lastSpellcast = {}
 --The table of check boxes definition
 Ovale.casesACocher = {}
 --the frame with the icons
@@ -123,13 +121,6 @@ function Ovale:ToggleOptions()
 	self.frame:ToggleOptions()
 end

-function Ovale:UpdateLastSpellcast(spellcast)
-	wipe(self.lastSpellcast)
-	for k, v in pairs(spellcast) do
-		self.lastSpellcast[k] = v
-	end
-end
-
 function Ovale:UpdateVisibility()
 	local visible = true
 	local profile = OvaleOptions:GetProfile()
diff --git a/OvaleAura.lua b/OvaleAura.lua
index dc9fe00..5cb6bc1 100644
--- a/OvaleAura.lua
+++ b/OvaleAura.lua
@@ -17,6 +17,7 @@ Ovale.OvaleAura = OvaleAura

 --<private-static-properties>
 local OvaleData = Ovale.OvaleData
+local OvaleFuture = nil		-- forward declaration
 local OvaleGUID = Ovale.OvaleGUID
 local OvalePaperDoll = Ovale.OvalePaperDoll
 local OvalePool = Ovale.OvalePool
@@ -34,6 +35,14 @@ local API_UnitAura = UnitAura

 -- aura pool
 local self_pool = OvalePool("OvaleAura_pool")
+do
+	self_pool.Clean = function(self, aura)
+		-- Release reference-counted snapshot before wiping.
+		if aura.snapshot then
+			aura.snapshot:ReleaseReference()
+		end
+	end
+end
 -- self_aura[guid] pool
 local self_aura_pool = OvalePool("OvaleAura_aura_pool")
 -- player's GUID
@@ -145,14 +154,13 @@ local function UnitGainedAura(guid, spellId, filter, casterGUID, icon, count, de
 					aura.tick = self:GetTickLength(spellId)
 				end
 				-- Determine whether to snapshot player stats for the aura or to keep the existing stats.
-				local lastSpellcast = Ovale.lastSpellcast
+				local lastSpellcast = OvaleFuture.lastSpellcast
 				local lastSpellId = lastSpellcast and lastSpellcast.spellId
 				if lastSpellId and OvaleData:NeedNewSnapshot(spellId, lastSpellId) then
 					Ovale:DebugPrintf(OVALE_AURA_DEBUG, "    Snapshot stats for %s %s (%s) on %s from %f, now=%f, aura.serial=%d",
 						filter, name, spellId, guid, lastSpellcast.snapshotTime, now, aura.serial)
-					OvalePaperDoll:SnapshotStats(aura, Ovale.lastSpellcast)
-					-- TODO: This isn't correct if lastSpellId doesn't directly apply the DoT.
-					aura.damageMultiplier = Ovale.lastSpellcast.damageMultiplier
+					-- TODO: damageMultiplier isn't correct if lastSpellId spreads the DoT.
+					OvaleFuture:UpdateFromSpellcast(aura, lastSpellcast)
 				end
 			end
 		end
@@ -322,6 +330,7 @@ end

 --<public-static-methods>
 function OvaleAura:OnEnable()
+	OvaleFuture = Ovale.OvaleFuture		-- resolve forward declaration
 	self_guid = OvaleGUID:GetGUID("player")
 	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
 	self:RegisterEvent("PLAYER_ENTERING_WORLD")
@@ -719,9 +728,7 @@ do
 							newAura.ending = (ending - tick * remainingTicks) + duration
 							newAura.tick = OvaleAura:GetTickLength(auraId)
 							-- Re-snapshot stats for the DoT.
-							-- XXX This is not quite right because it uses the current player stats instead of the simulator's state.
-							OvalePaperDoll:SnapshotStats(newAura, spellcast)
-							newAura.damageMultiplier = state:GetDamageMultiplier(auraId)
+							OvaleFuture:UpdateFromSpellcast(newAura, spellcast)
 						else
 							newAura.ending = atTime + duration
 						end
@@ -745,9 +752,7 @@ do
 					if isDoT then
 						newAura.tick = OvaleAura:GetTickLength(auraId)
 						-- Snapshot stats for the DoT.
-						-- XXX This is not quite right because it uses the current player stats instead of the simulator's state.
-						OvalePaperDoll:SnapshotStats(newAura, spellcast)
-						newAura.damageMultiplier = state:GetDamageMultiplier(auraId)
+						OvaleFuture:UpdateFromSpellcast(newAura, spellcast)
 					end
 				end
 			end
diff --git a/OvaleFuture.lua b/OvaleFuture.lua
index 3e22902..3d7249d 100644
--- a/OvaleFuture.lua
+++ b/OvaleFuture.lua
@@ -21,6 +21,7 @@ local OvaleData = Ovale.OvaleData
 local OvaleGUID = Ovale.OvaleGUID
 local OvalePaperDoll = Ovale.OvalePaperDoll
 local OvalePool = Ovale.OvalePool
+local OvalePower = Ovale.OvalePower
 local OvaleScore = Ovale.OvaleScore
 local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleState = Ovale.OvaleState
@@ -37,13 +38,22 @@ local API_UnitChannelInfo = UnitChannelInfo
 local API_UnitGUID = UnitGUID
 local API_UnitName = UnitName

-local self_playerName = nil
+-- Player's GUID.
+local self_guid = nil

 -- The spells that the player is casting or has cast but are still in-flight toward their targets.
 local self_activeSpellcast = {}
 -- self_lastSpellcast[targetGUID][spellId] is the most recent spell that has landed successfully on the target.
 local self_lastSpellcast = {}
 local self_pool = OvalePool("OvaleFuture_pool")
+do
+	self_pool.Clean = function(self, spellcast)
+		-- Release reference-counted snapshot before wiping.
+		if spellcast.snapshot then
+			spellcast.snapshot:ReleaseReference()
+		end
+	end
+end

 -- Used to track the most recent spellcast started with UNIT_SPELLCAST_SENT.
 local self_lastLineID = nil
@@ -69,6 +79,8 @@ local OVALE_CLEU_SPELLCAST_RESULTS = {
 --<public-static-properties>
 --spell counter (see Counter function)
 OvaleFuture.counter = {}
+-- Most recent spellcast.
+OvaleFuture.lastSpellcast = nil
 -- Debugging: spells to trace
 OvaleFuture.traceSpellList = nil
 --</public-static-properties>
@@ -134,7 +146,7 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a
 		spellName, spellId, lineId, startTime, endTime, spellcast.target)

 	-- Snapshot the current stats for the spellcast.
-	OvalePaperDoll:SnapshotStats(spellcast)
+	spellcast.snapshot = OvalePaperDoll:GetSnapshot()
 	spellcast.damageMultiplier = GetDamageMultiplier(spellId)

 	local si = OvaleData.spellInfo[spellId]
@@ -143,9 +155,15 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a

 		-- Save the number of combo points used if this spell is a finisher.
 		if si.combo == 0 then
-			local comboPoints = OvaleComboPoints.combo
-			if comboPoints > 0 then
-				spellcast.comboPoints = comboPoints
+			if OvaleComboPoints.combo > 0 then
+				spellcast.combo = OvaleComboPoints.combo
+			end
+		end
+
+		-- Save the number of holy power used if this spell is a finisher.
+		if si.holy == 0 then
+			if OvalePower.power.holy > 0 then
+				spellcast.holy = OvalePower.power.holy
 			end
 		end

@@ -198,11 +216,12 @@ local function RemoveSpellFromQueue(spellId, lineId)
 	Ovale.refreshNeeded["player"] = true
 end

--- UpdateLastSpellInfo() is called at the end of the event handler for OVALE_CLEU_SPELLCAST_RESULTS[].
+-- UpdateLastSpellcast() is called at the end of the event handler for OVALE_CLEU_SPELLCAST_RESULTS[].
 -- It saves the given spellcast as the most recent one on its target and ensures that the spellcast
 -- snapshot values are correctly adjusted for buffs that are added or cleared simultaneously with the
 -- spellcast.
-local function UpdateLastSpellInfo(spellcast)
+local function UpdateLastSpellcast(spellcast)
+	local self = OvaleFuture
 	local targetGUID = spellcast.target
 	local spellId = spellcast.spellId
 	if targetGUID and spellId then
@@ -214,6 +233,7 @@ local function UpdateLastSpellInfo(spellcast)
 			self_pool:Release(oldSpellcast)
 		end
 		self_lastSpellcast[targetGUID][spellId] = spellcast
+		self.lastSpellcast = spellcast

 		--[[
 			If any auras have been added between the start of the spellcast and this event,
@@ -221,25 +241,22 @@ local function UpdateLastSpellInfo(spellcast)

 			This is needed to see any auras that were applied at the same time as the
 			spellcast, e.g., potions or other on-use abilities or items.
-		]]--
+		--]]
 		if self_timeAuraAdded then
 			if self_timeAuraAdded >= spellcast.start and self_timeAuraAdded - spellcast.stop < 1 then
-				OvalePaperDoll:SnapshotStats(spellcast)
+				OvalePaperDoll:UpdateSnapshot(spellcast.snapshot)
 				spellcast.damageMultiplier = GetDamageMultiplier(spellId)
 				TracePrintf(spellId, "    Updated spell info for %s (%d) to snapshot from %f.",
-					OvaleSpellBook:GetSpellName(spellId), spellId, spellcast.snapshotTime)
+					OvaleSpellBook:GetSpellName(spellId), spellId, spellcast.snapshot.snapshotTime)
 			end
 		end
-
-		-- Save this most recent spellcast.
-		Ovale:UpdateLastSpellcast(spellcast)
 	end
 end
 --</private-static-methods>

 --<public-static-methods>
 function OvaleFuture:OnEnable()
-	self_playerName = API_UnitName("player")
+	self_guid = OvaleGUID:GetGUID("player")
 	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
 	self:RegisterEvent("PLAYER_ENTERING_WORLD")
 	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
@@ -275,7 +292,7 @@ function OvaleFuture:PLAYER_ENTERING_WORLD(event)
 end

 function OvaleFuture:Ovale_AuraAdded(event, timestamp, guid, spellId, caster)
-	if guid == OvaleGUID:GetGUID("player") then
+	if guid == self_guid then
 		self_timeAuraAdded = timestamp
 	end
 end
@@ -367,7 +384,10 @@ function OvaleFuture:UNIT_SPELLCAST_SUCCEEDED(event, unit, name, rank, lineId, s
 			if spellcast.lineId == lineId then
 				spellcast.allowRemove = true
 				-- Take a more recent snapshot of the player stats for this cast-time spell.
-				OvalePaperDoll:SnapshotStats(spellcast)
+				if spellcast.snapshot then
+					OvalePaperDoll:ReleaseSnapshot(spellcast.snapshot)
+				end
+				spellcast.snapshot = OvalePaperDoll:GetSnapshot()
 				spellcast.damageMultiplier = GetDamageMultiplier(spellId)
 				return
 			end
@@ -439,7 +459,7 @@ function OvaleFuture:COMBAT_LOG_EVENT_UNFILTERED(event, ...)
 					if not spellcast.channeled and (spellcast.removeOnSuccess or event ~= "SPELL_CAST_SUCCESS") then
 						TracePrintf(spellId, "    Spell finished: %s (%d)", spellName, spellId)
 						tremove(self_activeSpellcast, index)
-						UpdateLastSpellInfo(spellcast)
+						UpdateLastSpellcast(spellcast)
 						Ovale.refreshNeeded["player"] = true
 					end
 					break
@@ -471,9 +491,31 @@ function OvaleFuture:ApplyInFlightSpells()
 	end
 end

+function OvaleFuture:UpdateFromSpellcast(dest, spellcast)
+	if dest.snapshot then
+		dest.snapshot:ReleaseReference()
+	end
+	if spellcast.snapshot then
+		dest.snapshot = spellcast.snapshot:GetReference()
+	end
+	if spellcast.damageMultiplier then
+		dest.damageMultiplier = spellcast.damageMultiplier
+	end
+	if spellcast.combo then
+		dest.combo = spellcast.combo
+	end
+	if spellcast.holy then
+		dest.holy = spellcast.holy
+	end
+end
+
 function OvaleFuture:GetLastSpellInfo(guid, spellId, statName)
 	if self_lastSpellcast[guid] and self_lastSpellcast[guid][spellId] then
-		return self_lastSpellcast[guid][spellId][statName]
+		if OvalePaperDoll.SNAPSHOT_STATS[statName] then
+			return self_lastSpellcast[guid][spellId].snapshot[statName]
+		else
+			return self_lastSpellcast[guid][spellId][statName]
+		end
 	end
 end

diff --git a/OvalePaperDoll.lua b/OvalePaperDoll.lua
index 9a86b85..db8c5d7 100644
--- a/OvalePaperDoll.lua
+++ b/OvalePaperDoll.lua
@@ -15,8 +15,7 @@ Ovale.OvalePaperDoll = OvalePaperDoll

 --<private-static-properties>
 local OvaleEquipement = Ovale.OvaleEquipement
-local OvalePool = Ovale.OvalePool
-local OvaleQueue = Ovale.OvaleQueue
+local OvalePoolRefCount = Ovale.OvalePoolRefCount
 local OvaleStance = Ovale.OvaleStance

 local select = select
@@ -43,13 +42,9 @@ local API_UnitStat = UnitStat
 -- Player's class.
 local self_class = select(2, API_UnitClass("player"))
 -- Snapshot table pool.
-local self_pool = OvalePool("OvalePaperDoll_pool")
--- Snapshot queue: new snapshots are inserted at the front of the queue.
-local self_snapshot = OvaleQueue:NewDeque("OvalePaperDoll_snapshot")
+local self_pool = OvalePoolRefCount("OvalePaperDoll_pool")
 -- Total number of snapshots taken.
 local self_snapshotCount = 0
--- Time window (past number of seconds) for which snapshots are stored.
-local SNAPSHOT_WINDOW = 5

 local OVALE_PAPERDOLL_DEBUG = "paper_doll"
 local OVALE_SNAPSHOT_DEBUG = "snapshot"
@@ -82,12 +77,10 @@ OvalePaperDoll.level = API_UnitLevel("player")
 -- Player's current specialization.
 OvalePaperDoll.specialization = nil
 -- Most recent snapshot.
-OvalePaperDoll.stat = nil
+OvalePaperDoll.snapshot = nil

 -- Maps field names to default value & descriptions for player's stats.
 OvalePaperDoll.SNAPSHOT_STATS = {
-	snapshotTime =			{ default = 0, description = "snapshot time" }
-
 	-- primary stats
 	agility = 				{ default = 0, description = "agility" },
 	intellect =				{ default = 0, description = "intellect" },
@@ -119,38 +112,35 @@ OvalePaperDoll.SNAPSHOT_STATS = {
 --</public-static-properties>

 --<private-static-methods>
--- Return stat table for most recent snapshot no older than the given time.
-local function GetSnapshot(t)
+-- Return table for most recent snapshot to be updated through events.
+local function UpdateCurrentSnapshot()
 	local self = OvalePaperDoll
-	self:RemoveOldSnapshots()
-	local stat = self_snapshot:Front()
 	local now = API_GetTime()
-	if not stat then
-		local newStat = self_pool:Get()
-		do
-			-- Initialize stat table.
-			for k, info in pairs(self.SNAPSHOT_STATS) do
-				newStat[k] = info.default
-			end
-		end
-		newStat.snapshotTime = now
-		self_snapshot:InsertFront(newStat)
-		stat = self_snapshot:Front()
-	elseif stat.snapshotTime < t then
-		local newStat = self_pool:Get()
-		self:SnapshotStats(newStat, stat)
-		newStat.snapshotTime = now
-		self_snapshot:InsertFront(newStat)
-		stat = self_snapshot:Front()
+	if self.snapshot.snapshotTime < now then
 		Ovale:DebugPrintf(OVALE_SNAPSHOT_DEBUG, true, "New snapshot.")
 		self_snapshotCount = self_snapshotCount + 1
+		local snapshot = self_pool:Get()
+		-- Pre-populate snapshot with the most recent previously-captured stats.
+		for k in pairs(self.SNAPSHOT_STATS) do
+			snapshot[k] = self.snapshot[k]
+		end
+		snapshot.snapshotTime = now
+		self_pool:Release(self.snapshot)
+		self.snapshot = snapshot
 	end
-	return stat
+	return self.snapshot
 end
 --</private-static-methods>

 --<public-static-methods>
 function OvalePaperDoll:OnEnable()
+	-- Initialize latest snapshot table.
+	self.snapshot = self_pool:Get()
+	for k, info in pairs(self.SNAPSHOT_STATS) do
+		self.snapshot[k] = info.default
+	end
+	self.snapshot.snapshotTime = 0
+
 	self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "UpdateStats")
 	self:RegisterEvent("COMBAT_RATING_UPDATE")
 	self:RegisterEvent("MASTERY_UPDATE")
@@ -170,9 +160,6 @@ function OvalePaperDoll:OnEnable()
 	self:RegisterEvent("UNIT_STATS")
 	self:RegisterMessage("Ovale_EquipmentChanged", "UpdateDamage")
 	self:RegisterMessage("Ovale_StanceChanged", "UpdateDamage")
-
-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
 end

 function OvalePaperDoll:OnDisable()
@@ -198,26 +185,24 @@ function OvalePaperDoll:OnDisable()
 end

 function OvalePaperDoll:COMBAT_RATING_UPDATE(event)
-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
-	self.stat.meleeCrit = API_GetCritChance()
-	self.stat.rangedCrit = API_GetRangedCritChance()
-	self.stat.spellCrit = API_GetSpellCritChance(OVALE_SPELLDAMAGE_SCHOOL[self_class])
+	local snapshot = UpdateCurrentSnapshot()
+	snapshot.meleeCrit = API_GetCritChance()
+	snapshot.rangedCrit = API_GetRangedCritChance()
+	snapshot.spellCrit = API_GetSpellCritChance(OVALE_SPELLDAMAGE_SCHOOL[self_class])
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s", event)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeCrit"].description, self.stat.meleeCrit)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["rangedCrit"].description, self.stat.rangedCrit)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellCrit"].description, self.stat.spellCrit)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeCrit"].description, snapshot.meleeCrit)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["rangedCrit"].description, snapshot.rangedCrit)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellCrit"].description, snapshot.spellCrit)
 end

 function OvalePaperDoll:MASTERY_UPDATE(event)
-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
+	local snapshot = UpdateCurrentSnapshot()
 	if self.level < 80 then
-		self.stat.masteryEffect = 0
+		snapshot.masteryEffect = 0
 	else
-		self.stat.masteryEffect = API_GetMasteryEffect()
+		snapshot.masteryEffect = API_GetMasteryEffect()
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %f%%",
-			event, self.SNAPSHOT_STATS["masteryEffect"].description, self.stat.masteryEffect)
+			event, self.SNAPSHOT_STATS["masteryEffect"].description, snapshot.masteryEffect)
 	end
 end

@@ -227,11 +212,10 @@ function OvalePaperDoll:PLAYER_LEVEL_UP(event, level, ...)
 end

 function OvalePaperDoll:PLAYER_DAMAGE_DONE_MODS(event, unitId)
-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
-	self.stat.spellBonusHealing = API_GetSpellBonusHealing()
+	local snapshot = UpdateCurrentSnapshot()
+	snapshot.spellBonusHealing = API_GetSpellBonusHealing()
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
-		event, self.SNAPSHOT_STATS["spellBonusHealing"].description, self.stat.spellBonusHealing)
+		event, self.SNAPSHOT_STATS["spellBonusHealing"].description, snapshot.spellBonusHealing)
 end

 function OvalePaperDoll:PLAYER_REGEN_DISABLED(event)
@@ -248,21 +232,19 @@ function OvalePaperDoll:PLAYER_REGEN_ENABLED(event)
 end

 function OvalePaperDoll:SPELL_POWER_CHANGED(event)
-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
-	self.stat.spellBonusDamage = API_GetSpellBonusDamage(OVALE_SPELLDAMAGE_SCHOOL[self_class])
+	local snapshot = UpdateCurrentSnapshot()
+	snapshot.spellBonusDamage = API_GetSpellBonusDamage(OVALE_SPELLDAMAGE_SCHOOL[self_class])
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
-		event, self.SNAPSHOT_STATS["spellBonusDamage"].description, self.stat.spellBonusDamage)
+		event, self.SNAPSHOT_STATS["spellBonusDamage"].description, snapshot.spellBonusDamage)
 end

 function OvalePaperDoll:UNIT_ATTACK_POWER(event, unitId)
 	if unitId == "player" then
-		local now = API_GetTime()
-		self.stat = GetSnapshot(now)
+		local snapshot = UpdateCurrentSnapshot()
 		local base, posBuff, negBuff = API_UnitAttackPower(unitId)
-		self.stat.attackPower = base + posBuff + negBuff
+		snapshot.attackPower = base + posBuff + negBuff
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
-			event, self.SNAPSHOT_STATS["attackPower"].description, self.stat.attackPower)
+			event, self.SNAPSHOT_STATS["attackPower"].description, snapshot.attackPower)
 		self:UpdateDamage(event)
 	end
 end
@@ -276,53 +258,49 @@ end

 function OvalePaperDoll:UNIT_RANGEDDAMAGE(event, unitId)
 	if unitId == "player" then
-		local now = API_GetTime()
-		self.stat = GetSnapshot(now)
-		self.stat.rangedHaste = API_GetRangedHaste()
+		local snapshot = UpdateCurrentSnapshot()
+		snapshot.rangedHaste = API_GetRangedHaste()
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %f%%",
-			event, self.SNAPSHOT_STATS["rangedHaste"].description, self.stat.rangedHaste)
+			event, self.SNAPSHOT_STATS["rangedHaste"].description, snapshot.rangedHaste)
 	end
 end

 function OvalePaperDoll:UNIT_RANGED_ATTACK_POWER(event, unitId)
 	if unitId == "player" then
 		local base, posBuff, negBuff = API_UnitRangedAttackPower(unitId)
-		local now = API_GetTime()
-		self.stat = GetSnapshot(now)
-		self.stat.rangedAttackPower = base + posBuff + negBuff
+		local snapshot = UpdateCurrentSnapshot()
+		snapshot.rangedAttackPower = base + posBuff + negBuff
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
-			event, self.SNAPSHOT_STATS["rangedAttackPower"].description, self.stat.rangedAttackPower)
+			event, self.SNAPSHOT_STATS["rangedAttackPower"].description, snapshot.rangedAttackPower)
 	end
 end

 function OvalePaperDoll:UNIT_SPELL_HASTE(event, unitId)
 	if unitId == "player" then
-		local now = API_GetTime()
-		self.stat = GetSnapshot(now)
-		self.stat.meleeHaste = API_GetMeleeHaste()
-		self.stat.spellHaste = API_UnitSpellHaste(unitId)
+		local snapshot = UpdateCurrentSnapshot()
+		snapshot.meleeHaste = API_GetMeleeHaste()
+		snapshot.spellHaste = API_UnitSpellHaste(unitId)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s", event)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeHaste"].description, self.stat.meleeHaste)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellHaste"].description, self.stat.spellHaste)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeHaste"].description, snapshot.meleeHaste)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellHaste"].description, snapshot.spellHaste)
 		self:UpdateDamage(event)
 	end
 end

 function OvalePaperDoll:UNIT_STATS(event, unitId)
 	if unitId == "player" then
-		local now = API_GetTime()
-		self.stat = GetSnapshot(now)
-		self.stat.strength = API_UnitStat(unitId, 1)
-		self.stat.agility = API_UnitStat(unitId, 2)
-		self.stat.stamina = API_UnitStat(unitId, 3)
-		self.stat.intellect = API_UnitStat(unitId, 4)
-		self.stat.spirit = API_UnitStat(unitId, 5)
+		local snapshot = UpdateCurrentSnapshot()
+		snapshot.strength = API_UnitStat(unitId, 1)
+		snapshot.agility = API_UnitStat(unitId, 2)
+		snapshot.stamina = API_UnitStat(unitId, 3)
+		snapshot.intellect = API_UnitStat(unitId, 4)
+		snapshot.spirit = API_UnitStat(unitId, 5)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s", event)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["agility"].description, self.stat.agility)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["intellect"].description, self.stat.intellect)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["spirit"].description, self.stat.spirit)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["stamina"].description, self.stat.stamina)
-		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["strength"].description, self.stat.strength)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["agility"].description, snapshot.agility)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["intellect"].description, snapshot.intellect)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["spirit"].description, snapshot.spirit)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["stamina"].description, snapshot.stamina)
+		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["strength"].description, snapshot.strength)
 		self:COMBAT_RATING_UPDATE(event)
 	end
 end
@@ -331,9 +309,8 @@ function OvalePaperDoll:UpdateDamage(event)
 	local minDamage, maxDamage, minOffHandDamage, maxOffHandDamage, _, _, damageMultiplier = API_UnitDamage("player")
 	local mainHandAttackSpeed, offHandAttackSpeed = API_UnitAttackSpeed("player")

-	local now = API_GetTime()
-	self.stat = GetSnapshot(now)
-	self.stat.baseDamageMultiplier = damageMultiplier
+	local snapshot = UpdateCurrentSnapshot()
+	snapshot.baseDamageMultiplier = damageMultiplier
 	if self_class == "DRUID" and OvaleStance:IsStance("druid_cat_form") then
 		-- Cat Form: 100% increased auto-attack damage.
 		damageMultiplier = damageMultiplier * 2
@@ -354,7 +331,7 @@ function OvalePaperDoll:UpdateDamage(event)
 			normalizedMainHandWeaponSpeed = 2.5
 		end
 	end
-	self.stat.mainHandWeaponDamage = avgDamage / mainHandWeaponSpeed * normalizedMainHandWeaponSpeed
+	snapshot.mainHandWeaponDamage = avgDamage / mainHandWeaponSpeed * normalizedMainHandWeaponSpeed
 	--Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    MH weapon damage = ((%f + %f) / 2 / %f) / %f * %f",
 	--	minDamage, maxDamage, damageMultiplier, mainHandWeaponSpeed, normalizedMainHandWeaponSpeed)

@@ -371,17 +348,17 @@ function OvalePaperDoll:UpdateDamage(event)
 				normalizedOffHandWeaponSpeed = 2.5
 			end
 		end
-		self.stat.offHandWeaponDamage = avgOffHandDamage / offHandWeaponSpeed * normalizedOffHandWeaponSpeed
+		snapshot.offHandWeaponDamage = avgOffHandDamage / offHandWeaponSpeed * normalizedOffHandWeaponSpeed
 		--Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    OH weapon damage = ((%f + %f) / 2 / %f) / %f * %f",
 		--	minOffHandDamage, maxOffHandDamage, damageMultiplier, offHandWeaponSpeed, normalizedOffHandWeaponSpeed)
 	else
-		self.stat.offHandWeaponDamage = 0
+		snapshot.offHandWeaponDamage = 0
 	end

 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s", event)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["baseDamageMultiplier"].description, self.stat.baseDamageMultiplier)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["mainHandWeaponDamage"].description, self.stat.mainHandWeaponDamage)
-	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["offHandWeaponDamage"].description, self.stat.offHandWeaponDamage)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["baseDamageMultiplier"].description, snapshot.baseDamageMultiplier)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["mainHandWeaponDamage"].description, snapshot.mainHandWeaponDamage)
+	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["offHandWeaponDamage"].description, snapshot.offHandWeaponDamage)
 end

 function OvalePaperDoll:UpdateSpecialization(event)
@@ -407,77 +384,79 @@ function OvalePaperDoll:UpdateStats(event)
 end

 function OvalePaperDoll:GetMasteryMultiplier()
-	return 1 + self.stat.masteryEffect / 100
+	local snapshot = self:CurrentSnapshot()
+	return 1 + snapshot.masteryEffect / 100
 end

 function OvalePaperDoll:GetMeleeHasteMultiplier()
-	return 1 + self.stat.meleeHaste / 100
+	local snapshot = self:CurrentSnapshot()
+	return 1 + snapshot.meleeHaste / 100
 end

 function OvalePaperDoll:GetRangedHasteMultiplier()
-	return 1 + self.stat.rangedHaste / 100
+	local snapshot = self:CurrentSnapshot()
+	return 1 + snapshot.rangedHaste / 100
 end

 function OvalePaperDoll:GetSpellHasteMultiplier()
-	return 1 + self.stat.spellHaste / 100
+	local snapshot = self:CurrentSnapshot()
+	return 1 + snapshot.spellHaste / 100
 end

--- Snapshot the stats into the given table using the same keynames as SNAPSHOT_STATS.
--- If source is nil, then use the most recent player stats; otherwise, use the given stat table.
-function OvalePaperDoll:SnapshotStats(t, source)
-	if not source then
-		self:UpdateStats("SnapshotStats")
-		source = self_snapshot:Front()
-	end
+-- Copy the current snapshot into the given snapshot table.
+function OvalePaperDoll:UpdateSnapshot(snapshot)
+	local snapshot = self:CurrentSnapshot()
 	for k in pairs(self.SNAPSHOT_STATS) do
-		if source[k] then
-			t[k] = source[k]
-		end
-	end
-	-- Copy other properties that might be present that are relevant for auras.
-	-- TODO: Holy power?
-	if source.comboPoints then
-		t.comboPoints = source.comboPoints
+		snapshot[k] = self.snapshot[k]
 	end
+	return snapshot
 end

--- Remove snapshots older than SNAPSHOT_WINDOW seconds from now, but always leave most recent snapshot.
-function OvalePaperDoll:RemoveOldSnapshots()
+-- Return a raw reference to the current snapshot.
+function OvalePaperDoll:CurrentSnapshot()
 	local now = API_GetTime()
-	while self_snapshot:Size() > 1 do
-		local stat = self_snapshot:Back()
-		if stat then
-			if now - stat.snapshotTime < SNAPSHOT_WINDOW then break end
-			self_snapshot:RemoveBack()
-			self_pool:Release(stat)
-		end
+	if self.snapshot.snapshotTime < now then
+		self:UpdateStats("CurrentSnapshot")
 	end
+	return self.snapshot
 end

-function OvalePaperDoll:Debug(stat)
-	stat = stat or self.stat
+-- Get a new reference to the current snapshot.
+function OvalePaperDoll:GetSnapshot()
+	local snapshot = self:CurrentSnapshot()
+	return snapshot:GetReference()
+end
+
+-- Release a reference to the given snapshot.
+function OvalePaperDoll:ReleaseSnapshot(snapshot)
+	return snapshot:ReleaseReference()
+end
+
+function OvalePaperDoll:Debug(snapshot)
+	snapshot = snapshot or self.snapshot
+	self_pool:Debug()
+	Ovale:FormatPrint("Total snapshots: %d", self_snapshotCount)
 	Ovale:FormatPrint("Level: %d", self.level)
 	Ovale:FormatPrint("Specialization: %s", self.specialization)
-	Ovale:FormatPrint("Total snapshots: %d", self_snapshotCount)
-	Ovale:FormatPrint("Snapshot time: %f", stat.snapshotTime)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["agility"].description, stat.agility)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["intellect"].description, stat.intellect)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spirit"].description, stat.spirit)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["stamina"].description, stat.stamina)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["strength"].description, stat.strength)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["attackPower"].description, stat.attackPower)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["rangedAttackPower"].description, stat.rangedAttackPower)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spellBonusDamage"].description, stat.spellBonusDamage)
-	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spellBonusHealing"].description, stat.spellBonusHealing)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["spellCrit"].description, stat.spellCrit)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["spellHaste"].description, stat.spellHaste)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["meleeCrit"].description, stat.meleeCrit)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["meleeHaste"].description, stat.meleeHaste)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["rangedCrit"].description, stat.rangedCrit)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["rangedHaste"].description, stat.rangedHaste)
-	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["masteryEffect"].description, stat.masteryEffect)
-	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["baseDamageMultiplier"].description, stat.baseDamageMultiplier)
-	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["mainHandWeaponDamage"].description, stat.mainHandWeaponDamage)
-	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["offHandWeaponDamage"].description, stat.offHandWeaponDamage)
+	Ovale:FormatPrint("Snapshot time: %f", snapshot.snapshotTime)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["agility"].description, snapshot.agility)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["intellect"].description, snapshot.intellect)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spirit"].description, snapshot.spirit)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["stamina"].description, snapshot.stamina)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["strength"].description, snapshot.strength)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["attackPower"].description, snapshot.attackPower)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["rangedAttackPower"].description, snapshot.rangedAttackPower)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spellBonusDamage"].description, snapshot.spellBonusDamage)
+	Ovale:FormatPrint("%s: %d", self.SNAPSHOT_STATS["spellBonusHealing"].description, snapshot.spellBonusHealing)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["spellCrit"].description, snapshot.spellCrit)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["spellHaste"].description, snapshot.spellHaste)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["meleeCrit"].description, snapshot.meleeCrit)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["meleeHaste"].description, snapshot.meleeHaste)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["rangedCrit"].description, snapshot.rangedCrit)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["rangedHaste"].description, snapshot.rangedHaste)
+	Ovale:FormatPrint("%s: %f%%", self.SNAPSHOT_STATS["masteryEffect"].description, snapshot.masteryEffect)
+	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["baseDamageMultiplier"].description, snapshot.baseDamageMultiplier)
+	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["mainHandWeaponDamage"].description, snapshot.mainHandWeaponDamage)
+	Ovale:FormatPrint("%s: %f", self.SNAPSHOT_STATS["offHandWeaponDamage"].description, snapshot.offHandWeaponDamage)
 end
 --</public-static-methods>
diff --git a/OvaleState.lua b/OvaleState.lua
index e199c27..4f87ed2 100644
--- a/OvaleState.lua
+++ b/OvaleState.lua
@@ -18,6 +18,7 @@ Ovale.OvaleState = OvaleState

 --<private-static-properties>
 local OvaleData = Ovale.OvaleData
+local OvaleFuture = nil		-- forward declaration
 local OvaleQueue = Ovale.OvaleQueue

 local pairs = pairs
@@ -50,6 +51,11 @@ OvaleState.state = {
 --</private-static-methods>

 --<public-static-methods>
+function OvaleState:OnInitialize()
+	-- Resolve module dependencies.
+	OvaleFuture = Ovale.OvaleFuture
+end
+
 function OvaleState:RegisterState(addon, statePrototype)
 	self_stateModules:Insert(addon)
 	self_statePrototype[addon] = statePrototype
@@ -103,7 +109,7 @@ function OvaleState:Reset()
 	state.currentTime = self.now
 	Ovale:Logf("Reset state with current time = %f", state.currentTime)

-	self.lastSpellId = Ovale.lastSpellcast and Ovale.lastSpellcast.spellId
+	self.lastSpellId = OvaleFuture.lastSpellcast and OvaleFuture.lastSpellcast.spellId
 	self.currentSpellId = nil
 	self.isChanneling = false
 	self.nextCast = self.now
diff --git a/conditions/BuffAmount.lua b/conditions/BuffAmount.lua
new file mode 100644
index 0000000..71a1b16
--- /dev/null
+++ b/conditions/BuffAmount.lua
@@ -0,0 +1,65 @@
+--[[--------------------------------------------------------------------
+    Ovale Spell Priority
+    Copyright (C) 2013 Johnny C. Lam
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License in the LICENSE
+    file accompanying this program.
+--]]--------------------------------------------------------------------
+
+local _, Ovale = ...
+
+do
+	local OvaleCondition = Ovale.OvaleCondition
+	local OvaleState = Ovale.OvaleState
+
+	local ParseCondition = OvaleCondition.ParseCondition
+	local TestValue = OvaleCondition.TestValue
+
+	local auraFound = {}
+
+	--- Get the value of a buff as a number.  Not all buffs return an amount.
+	-- @name BuffAmount
+	-- @paramsig number
+	-- @param id The spell ID of the aura or the name of a spell list.
+	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
+	-- @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.
+	--     Valid values: player, target, focus, pet.
+	-- @param any Optional. Sets by whom the aura was applied. If the aura can be applied by anyone, then set any=1.
+	--     Defaults to any=0.
+	--     Valid values: 0, 1.
+	-- @param value Optional. Sets which aura value to return from UnitAura().
+	--     Defaults to value=1.
+	--     Valid values: 1, 2, 3.
+	-- @return The value of the buff as a number.
+	-- @see DebuffAmount
+	-- @see TickValue
+	-- @usage
+	-- if DebuffAmount(stagger) >10000 Spell(purifying_brew)
+	-- if DebuffAmount(stagger more 10000) Spell(purifying_brew)
+
+	local function BuffAmount(condition)
+		local auraId, comparator, limit = condition[1], condition[2], condition[3]
+		local target, filter, mine = ParseCondition(condition)
+		local value = condition.value or 1
+		local state = OvaleState.state
+		local statName = "value1"
+		if value == 1 then
+			statName = "value1"
+		elseif value == 2 then
+			statName = "value2"
+		elseif value == 3 then
+			statName = "value3"
+		end
+		auraFound[statName] = nil
+		local start, ending = state:GetAura(target, auraId, filter, mine, auraFound)
+		local value = auraFound[statName] or 0
+		return TestValue(start, ending, value, start, 0, comparator, limit)
+	end
+
+	OvaleCondition:RegisterCondition("buffamount", false, BuffAmount)
+	OvaleCondition:RegisterCondition("debuffamount", false, BuffAmount)
+	OvaleCondition:RegisterCondition("tickvalue", false, BuffAmount)
+end
diff --git a/conditions/BuffComboPoints.lua b/conditions/BuffComboPoints.lua
new file mode 100644
index 0000000..534013b
--- /dev/null
+++ b/conditions/BuffComboPoints.lua
@@ -0,0 +1,48 @@
+--[[--------------------------------------------------------------------
+    Ovale Spell Priority
+    Copyright (C) 2013 Johnny C. Lam
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License in the LICENSE
+    file accompanying this program.
+--]]--------------------------------------------------------------------
+
+local _, Ovale = ...
+
+do
+	local OvaleCondition = Ovale.OvaleCondition
+	local OvaleState = Ovale.OvaleState
+
+	local ParseCondition = OvaleCondition.ParseCondition
+	local TestValue = OvaleCondition.TestValue
+
+	local auraFound = {}
+
+	--- Get the player's combo points for the given aura at the time the aura was applied on the target.
+	-- @name BuffComboPoints
+	-- @paramsig number or boolean
+	-- @param id The aura spell ID.
+	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
+	-- @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.
+	--     Valid values: player, target, focus, pet.
+	-- @return The number of combo points.
+	-- @return A boolean value for the result of the comparison.
+	-- @see DebuffComboPoints
+	-- @usage
+	-- if target.DebuffComboPoints(rip) <5 Spell(rip)
+
+	local function BuffComboPoints(condition)
+		local auraId, comparator, limit = condition[1], condition[2], condition[3]
+		local target, filter, mine = ParseCondition(condition)
+		local state = OvaleState.state
+		auraFound.combo = nil
+		local start, ending = state:GetAura(target, auraId, filter, mine, auraFound)
+		local value = auraFound.combo or 1
+		return TestValue(start, ending, value, start, 0, comparator, limit)
+	end
+
+	OvaleCondition:RegisterCondition("buffcombopoints", false, BuffComboPoints)
+	OvaleCondition:RegisterCondition("debuffcombopoints", false, BuffComboPoints)
+end
diff --git a/conditions/BuffDamageMultiplier.lua b/conditions/BuffDamageMultiplier.lua
index ad2b1b8..5f7985b 100644
--- a/conditions/BuffDamageMultiplier.lua
+++ b/conditions/BuffDamageMultiplier.lua
@@ -37,9 +37,13 @@ do
 		local auraId, comparator, limit = condition[1], condition[2], condition[3]
 		local target, filter, mine = ParseCondition(condition)
 		local state = OvaleState.state
-		auraFound.comboPoints = nil
+		auraFound.snapshot = nil
+		auraFound.damageMultiplier = nil
 		local start, ending = state:GetAura(target, auraId, filter, mine, auraFound)
-		local baseDamageMultiplier = auraFound.baseDamageMultiplier or 1
+		local baseDamageMultiplier = 1
+		if auraFound.snapshot and auraFound.snapshot.baseDamageMultiplier then
+			baseDamageMultiplier = auraFound.snapshot.baseDamageMultiplier
+		end
 		local damageMultiplier = auraFound.damageMultiplier or 1
 		local value = baseDamageMultiplier * damageMultiplier
 		return TestValue(start, ending, value, start, 0, comparator, limit)
diff --git a/conditions/BuffSnapshot.lua b/conditions/BuffSnapshot.lua
index ec86406..3347347 100644
--- a/conditions/BuffSnapshot.lua
+++ b/conditions/BuffSnapshot.lua
@@ -23,10 +23,12 @@ do
 		local auraId, comparator, limit = condition[1], condition[2], condition[3]
 		local target, filter, mine = ParseCondition(condition)
 		local state = OvaleState.state
-		auraFound[statName] = nil
+		auraFound.snapshot = nil
 		local start, ending = state:GetAura(target, auraId, filter, mine, auraFound)
-		local value = auraFound[statName]
-		value = value or defaultValue
+		local value = defaultValue
+		if auraFound.snapshot and auraFound.snapshot[statName] then
+			value = auraFound.snapshot[statName]
+		end
 		return TestValue(start, ending, value, start, 0, comparator, limit)
 	end

@@ -37,55 +39,16 @@ do
 		local state = OvaleState.state
 		auraFound[statName] = nil
 		local start, ending = state:GetAura(target, auraId, filter, mine, auraFound)
-		local value = auraFound[statName]
-		value = value or defaultValue
+		local value = defaultValue
+		if auraFound.snapshot and auraFound.snapshot[statName] then
+			value = auraFound.snapshot[statName]
+		end
 		if condition.unlimited ~= 1 and value > 100 then
 			value = 100
 		end
 		return TestValue(start, ending, value, start, 0, comparator, limit)
 	end

-	--- Get the value of a buff as a number.  Not all buffs return an amount.
-	-- @name BuffAmount
-	-- @paramsig number
-	-- @param id The spell ID of the aura or the name of a spell list.
-	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
-	-- @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.
-	--     Valid values: player, target, focus, pet.
-	-- @param any Optional. Sets by whom the aura was applied. If the aura can be applied by anyone, then set any=1.
-	--     Defaults to any=0.
-	--     Valid values: 0, 1.
-	-- @param value Optional. Sets which aura value to return from UnitAura().
-	--     Defaults to value=1.
-	--     Valid values: 1, 2, 3.
-	-- @return The value of the buff as a number.
-	-- @see DebuffAmount
-	-- @see TickValue
-	-- @usage
-	-- if DebuffAmount(stagger) >10000 Spell(purifying_brew)
-	-- if DebuffAmount(stagger more 10000) Spell(purifying_brew)
-
-	local function BuffAmount(condition)
-		local value = condition.value or 1
-		local statName
-		if value == 1 then
-			statName = "value1"
-		elseif value == 2 then
-			statName = "value2"
-		elseif value == 3 then
-			statName = "value3"
-		else
-			statName = "value1"
-		end
-		return BuffSnapshot(statName, 0, condition)
-	end
-
-	OvaleCondition:RegisterCondition("buffamount", false, BuffAmount)
-	OvaleCondition:RegisterCondition("debuffamount", false, BuffAmount)
-	OvaleCondition:RegisterCondition("tickvalue", false, BuffAmount)
-
 	--- Get the player's attack power at the time the given aura was applied on the target.
 	-- @name BuffAttackPower
 	-- @paramsig number or boolean
@@ -108,29 +71,6 @@ do
 	OvaleCondition:RegisterCondition("buffattackpower", false, BuffAttackPower)
 	OvaleCondition:RegisterCondition("debuffattackpower", false, BuffAttackPower)

-	--- Get the player's combo points for the given aura at the time the aura was applied on the target.
-	-- @name BuffComboPoints
-	-- @paramsig number or boolean
-	-- @param id The aura spell ID.
-	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
-	-- @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.
-	--     Valid values: player, target, focus, pet.
-	-- @return The number of combo points.
-	-- @return A boolean value for the result of the comparison.
-	-- @see DebuffComboPoints
-	-- @usage
-	-- if target.DebuffComboPoints(rip) <5 Spell(rip)
-
-	local function BuffComboPoints(condition)
-		-- If the buff is presetnt, then it had at least one combo point.
-		return BuffSnapshot("comboPoints", 1, condition)
-	end
-
-	OvaleCondition:RegisterCondition("buffcombopoints", false, BuffComboPoints)
-	OvaleCondition:RegisterCondition("debuffcombopoints", false, BuffComboPoints)
-
 	--- Get the player's mastery effect at the time the given aura was applied on the target.
 	-- @name BuffMasteryEffect
 	-- @paramsig number or boolean
diff --git a/conditions/Damage.lua b/conditions/Damage.lua
index 9613ad6..8049f83 100644
--- a/conditions/Damage.lua
+++ b/conditions/Damage.lua
@@ -21,11 +21,11 @@ do
 	local function GetDamage(spellId)
 		local state = OvaleState.state
 		-- TODO: Use target's debuffs in this calculation.
-		local ap = OvalePaperDoll.stat.attackPower or 0
-		local sp = OvalePaperDoll.stat.spellBonusDamage or 0
-		local mh = OvalePaperDoll.stat.mainHandWeaponDamage or 0
-		local oh = OvalePaperDoll.stat.offHandWeaponDamage or 0
-		local bdm = OvalePaperDoll.stat.baseDamageMultiplier or 1
+		local ap = OvalePaperDoll.snapshot.attackPower or 0
+		local sp = OvalePaperDoll.snapshot.spellBonusDamage or 0
+		local mh = OvalePaperDoll.snapshot.mainHandWeaponDamage or 0
+		local oh = OvalePaperDoll.snapshot.offHandWeaponDamage or 0
+		local bdm = OvalePaperDoll.snapshot.baseDamageMultiplier or 1
 		local dm = state:GetDamageMultiplier(spellId) or 1
 		local combo = state.combo or 0
 		return OvaleData:GetDamage(spellId, ap, sp, mh, oh, combo) * bdm * dm
diff --git a/conditions/DamageMultiplier.lua b/conditions/DamageMultiplier.lua
index 7a1ad37..e49fceb 100644
--- a/conditions/DamageMultiplier.lua
+++ b/conditions/DamageMultiplier.lua
@@ -33,7 +33,7 @@ do
 	local function DamageMultiplier(condition)
 		local spellId, comparator, limit = condition[1], condition[2], condition[3]
 		local state = OvaleState.state
-		local bdm = OvalePaperDoll.stat.baseDamageMultiplier
+		local bdm = OvalePaperDoll.snapshot.baseDamageMultiplier
 		local dm = state:GetDamageMultiplier(spellId)
 		local value = bdm * dm
 		return Compare(value, comparator, limit)
diff --git a/conditions/LastComboPoints.lua b/conditions/LastComboPoints.lua
new file mode 100644
index 0000000..d09d85d
--- /dev/null
+++ b/conditions/LastComboPoints.lua
@@ -0,0 +1,46 @@
+--[[--------------------------------------------------------------------
+    Ovale Spell Priority
+    Copyright (C) 2013 Johnny C. Lam
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License in the LICENSE
+    file accompanying this program.
+--]]--------------------------------------------------------------------
+
+local _, Ovale = ...
+
+do
+	local OvaleCondition = Ovale.OvaleCondition
+	local OvaleGUID = Ovale.OvaleGUID
+	local OvaleFuture = Ovale.OvaleFuture
+
+	local Compare = OvaleCondition.Compare
+	local ParseCondition = OvaleCondition.ParseCondition
+
+	--- Get the number of combo points consumed by the most recent cast of a spell on the target for a feral druid or a rogue.
+	-- @name LastComboPoints
+	-- @paramsig number or boolean
+	-- @param id The spell ID.
+	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
+	-- @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=target.
+	--     Valid values: player, target, focus, pet.
+	-- @return The number of combo points.
+	-- @return A boolean value for the result of the comparison.
+	-- @see ComboPoints
+	-- @usage
+	-- if ComboPoints() >3 and target.LastComboPoints(rip) <3
+	--     Spell(rip)
+
+	local function LastComboPoints(condition)
+		local spellId, comparator, limit = condition[1], condition[2], condition[3]
+		local target = ParseCondition(condition, "target")
+		local guid = OvaleGUID:GetGUID(target)
+		local value = OvaleFuture:GetLastSpellInfo(guid, spellId, "combo") or 0
+		return Compare(value, comparator, limit)
+	end
+
+	OvaleCondition:RegisterCondition("lastcombopoints", false, LastComboPoints)
+	OvaleCondition:RegisterCondition("lastspellcombopoints", false, LastComboPoints)
+end
diff --git a/conditions/LastEstimatedDamage.lua b/conditions/LastEstimatedDamage.lua
index b83281d..8a10b03 100644
--- a/conditions/LastEstimatedDamage.lua
+++ b/conditions/LastEstimatedDamage.lua
@@ -47,7 +47,7 @@ do
 		local sp = OvaleFuture:GetLastSpellInfo(guid, spellId, "spellBonusDamage") or 0
 		local mh = OvaleFuture:GetLastSpellInfo(guid, spellId, "mainHandWeaponDamage") or 0
 		local oh = OvaleFuture:GetLastSpellInfo(guid, spellId, "offHandWeaponDamage") or 0
-		local combo = OvaleFuture:GetLastSpellInfo(guid, spellId, "comboPoints") or 0
+		local combo = OvaleFuture:GetLastSpellInfo(guid, spellId, "combo") or 0
 		local bdm = OvaleFuture:GetLastSpellInfo(guid, spellId, "baseDamageMultiplier") or 1
 		local dm = OvaleFuture:GetLastSpellInfo(guid, spellId, "damageMultiplier") or 1
 		local value = OvaleData:GetDamage(spellId, ap, sp, mh, oh, combo) * bdm * dm
diff --git a/conditions/LastSnapshot.lua b/conditions/LastSnapshot.lua
index de68eae..21d371b 100644
--- a/conditions/LastSnapshot.lua
+++ b/conditions/LastSnapshot.lua
@@ -63,29 +63,6 @@ do
 	OvaleCondition:RegisterCondition("lastattackpower", false, LastAttackPower)
 	OvaleCondition:RegisterCondition("lastspellattackpower", false, LastAttackPower)

-	--- Get the number of combo points consumed by the most recent cast of a spell on the target for a feral druid or a rogue.
-	-- @name LastComboPoints
-	-- @paramsig number or boolean
-	-- @param id The spell ID.
-	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
-	-- @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=target.
-	--     Valid values: player, target, focus, pet.
-	-- @return The number of combo points.
-	-- @return A boolean value for the result of the comparison.
-	-- @see ComboPoints
-	-- @usage
-	-- if ComboPoints() >3 and target.LastComboPoints(rip) <3
-	--     Spell(rip)
-
-	local function LastComboPoints(condition)
-		return LastSnapshot("comboPoints", 0, condition)
-	end
-
-	OvaleCondition:RegisterCondition("lastcombopoints", false, LastComboPoints)
-	OvaleCondition:RegisterCondition("lastspellcombopoints", false, LastComboPoints)
-
 	--- Get the mastery effect of the player during the most recent cast of a spell on the target.
 	-- Mastery effect is the effect of the player's mastery, typically a percent-increase to damage
 	-- or a percent-increase to chance to trigger some effect.
diff --git a/conditions/PaperDoll.lua b/conditions/PaperDoll.lua
index 9db4f3d..43e655f 100644
--- a/conditions/PaperDoll.lua
+++ b/conditions/PaperDoll.lua
@@ -18,7 +18,7 @@ do
 	-- Returns the value of the given paper-doll stat.
 	local function PaperDoll(statName, defaultValue, condition)
 		local comparator, limit = condition[1], condition[2]
-		local value = OvalePaperDoll.stat[statName]
+		local value = OvalePaperDoll.snapshot[statName]
 		value = value or defaultValue
 		return Compare(value, comparator, limit)
 	end
@@ -26,7 +26,7 @@ do
 	-- Returns the critical strike chance of the given paper-doll stat.
 	local function PaperDollCritChance(statName, defaultValue, condition)
 		local comparator, limit = condition[1], condition[2]
-		local value = OvalePaperDoll.stat[statName]
+		local value = OvalePaperDoll.snapshot[statName]
 		value = value or defaultValue
 		if condition.unlimited ~= 1 and value > 100 then
 			value = 100
diff --git a/conditions/WeaponDamage.lua b/conditions/WeaponDamage.lua
index 65c728f..94b0dcd 100644
--- a/conditions/WeaponDamage.lua
+++ b/conditions/WeaponDamage.lua
@@ -36,13 +36,13 @@ do
 		local value = 0
 		if hand == "offhand" or hand == "off" then
 			comparator, limit = condition[2], condition[3]
-			value = OvalePaperDoll.stat.offHandWeaponDamage
+			value = OvalePaperDoll.snapshot.offHandWeaponDamage
 		elseif hand == "mainhand" or hand == "main" then
 			comparator, limit = condition[2], condition[3]
-			value = OvalePaperDoll.stat.mainHandWeaponDamage
+			value = OvalePaperDoll.snapshot.mainHandWeaponDamage
 		else
 			comparator, limit = condition[1], condition[2]
-			value = OvalePaperDoll.stat.mainHandWeaponDamage
+			value = OvalePaperDoll.snapshot.mainHandWeaponDamage
 		end
 		return Compare(value, comparator, limit)
 	end
diff --git a/conditions/conditions.xml b/conditions/conditions.xml
index 59af71f..c6a80dc 100644
--- a/conditions/conditions.xml
+++ b/conditions/conditions.xml
@@ -1,6 +1,8 @@
 <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
 	<Script file="AfterWhiteHit.lua" />
 	<Script file="ArmorSetParts.lua" />
+	<Script file="BuffAmount.lua" />
+	<Script file="BuffComboPoints.lua" />
 	<Script file="BuffDamageMultiplier.lua" />
 	<Script file="BuffDuration.lua" />
 	<Script file="BuffExpires.lua" />
@@ -52,6 +54,7 @@
 	<Script file="ItemCharges.lua" />
 	<Script file="ItemCooldown.lua" />
 	<Script file="ItemCount.lua" />
+	<Script file="LastComboPoints.lua" />
 	<Script file="LastDamage.lua" />
 	<Script file="LastDamageMultiplier.lua" />
 	<Script file="LastEstimatedDamage.lua" />