Quantcast

Fix GetRunesCooldown() calculation.

Johnny C. Lam [05-15-14 - 23:18]
Fix GetRunesCooldown() calculation.

Also remove the death condition from all rune conditions.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1452 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
OvaleBestAction.lua
OvaleRunes.lua
conditions/RuneCount.lua
conditions/Runes.lua
diff --git a/OvaleBestAction.lua b/OvaleBestAction.lua
index 7a7ed8c..1e46c8d 100644
--- a/OvaleBestAction.lua
+++ b/OvaleBestAction.lua
@@ -800,7 +800,7 @@ function OvaleBestAction:GetActionInfo(element, state)
 						end
 					end
 					if needRunes then
-						local ending = state.currentTime + state:GetRunesCooldown(si.blood, si.unholy, si.frost, si.death, false)
+						local ending = state.currentTime + state:GetRunesCooldown(si.blood, si.unholy, si.frost, si.death)
 						if ending > actionCooldownStart + actionCooldownDuration then
 							actionCooldownDuration = ending - actionCooldownStart
 						end
diff --git a/OvaleRunes.lua b/OvaleRunes.lua
index dc23e0f..699f63e 100644
--- a/OvaleRunes.lua
+++ b/OvaleRunes.lua
@@ -24,6 +24,7 @@ local OvaleSpellBook = nil
 local OvaleStance = nil
 local OvaleState = nil

+--local debugprint = print
 local ipairs = ipairs
 local pairs = pairs
 local API_GetRuneCooldown = GetRuneCooldown
@@ -128,7 +129,7 @@ function OvaleRunes:OnEnable()
 		-- Initialize rune database.
 		for runeType, slots in ipairs(RUNE_SLOTS) do
 			for _, slot in pairs(slots) do
-				self.rune[slot] = { slotType = runeType, IsActiveRune = IsActiveRune }
+				self.rune[slot] = { slot = slot, slotType = runeType, IsActiveRune = IsActiveRune }
 			end
 		end
 		self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateAllRunes")
@@ -369,7 +370,7 @@ end
 --     count			The number of currently active runes of the given type.
 --     startCooldown	The time at which the next rune of the given type went on cooldown.
 --     endCooldown		The time at which the next rune of the given type will be active.
-statePrototype.RuneCount = function(state, name, deathCondition, atTime)
+statePrototype.RuneCount = function(state, name, atTime)
 	-- Default to checking the rune count at the end of the current spellcast in the
 	-- simulator, or at the current time if no spell is being cast.
 	if not atTime then
@@ -383,27 +384,14 @@ statePrototype.RuneCount = function(state, name, deathCondition, atTime)
 	local startCooldown, endCooldown = math.huge, math.huge
 	local runeType = RUNE_TYPE[name]
 	if runeType ~= DEATH_RUNE then
-		if deathCondition == "any" or deathCondition == 1 then
-			-- Match runes of the given type or any death runes.
-			for slot, rune in ipairs(state.rune) do
-				if rune.type == runeType or rune.type == DEATH_RUNE then
-					if rune:IsActiveRune(atTime) then
-						count = count + 1
-					elseif rune.endCooldown < endCooldown then
-						startCooldown, endCooldown = rune.startCooldown, rune.endCooldown
-					end
-				end
-			end
-		else
-			-- Match only the runes of the given type.
-			for _, slot in ipairs(RUNE_SLOTS[runeType]) do
-				local rune = state.rune[slot]
-				if not deathCondition or ((deathCondition == "none" or deathCondition == 0) and rune.type ~= DEATH_RUNE) then
-					if rune:IsActiveRune(atTime) then
-						count = count + 1
-					elseif rune.endCooldown < endCooldown then
-						startCooldown, endCooldown = rune.startCooldown, rune.endCooldown
-					end
+		-- Match only the runes of the given type.
+		for _, slot in ipairs(RUNE_SLOTS[runeType]) do
+			local rune = state.rune[slot]
+			if not deathCondition or ((deathCondition == "none" or deathCondition == 0) and rune.type ~= DEATH_RUNE) then
+				if rune:IsActiveRune(atTime) then
+					count = count + 1
+				elseif rune.endCooldown < endCooldown then
+					startCooldown, endCooldown = rune.startCooldown, rune.endCooldown
 				end
 			end
 		end
@@ -425,90 +413,156 @@ end
 -- Returns the number of seconds before all of the required runes are available.
 statePrototype.GetRunesCooldown = nil
 do
-	-- If the rune is active, then return the remaining active runes count requirement.
-	-- Also return the time of the next rune becoming active.
-	local function MatchingRune(rune, count, endCooldown)
-		if count > 0 then
-			count = count - 1
-			if rune.endCooldown > endCooldown then
-				endCooldown = rune.endCooldown
-			end
-		else
-			if rune.endCooldown < endCooldown then
-				endCooldown = rune.endCooldown
+	-- The remaining count requirements, indexed by rune type.
+	count = {}
+	usedRune = {}
+
+	statePrototype.GetRunesCooldown = function(state, blood, unholy, frost, death, atTime)
+		-- Default to checking runes at the end of the current spellcast in the
+		-- simulator, or at the current time if no spell is being cast.
+		if not atTime then
+			if state.endCast and state.endCast > state.currentTime then
+				atTime = state.endCast
+			else
+				atTime = state.currentTime
 			end
 		end
-		return count, endCooldown
-	end

-	-- The remaining count requirements, indexed by rune type.
-	local runeCount = {}
-	-- The latest time till a rune of that type is off cooldown, indexed by rune type.
-	local runeEndCooldown = {}
-
-	statePrototype.GetRunesCooldown = function(state, blood, unholy, frost, death, deathCondition)
 		-- Initialize static variables.
-		runeCount[BLOOD_RUNE] = blood or 0
-		runeCount[UNHOLY_RUNE] = unholy or 0
-		runeCount[FROST_RUNE] = frost or 0
-		runeCount[DEATH_RUNE] = death or 0
-		runeEndCooldown[BLOOD_RUNE] = 0
-		runeEndCooldown[UNHOLY_RUNE] = 0
-		runeEndCooldown[FROST_RUNE] = 0
-		runeEndCooldown[DEATH_RUNE] = 0
-
-		-- Use regular runes to meet the count requirements.
-		for slot, rune in ipairs(state.rune) do
-			if rune.type ~= DEATH_RUNE then
-				local runeType = rune.type
-				local count, endCooldown = MatchingRune(rune, runeCount[runeType], runeEndCooldown[runeType])
-				runeCount[runeType] = count
-				runeEndCooldown[runeType] = endCooldown
+		count[BLOOD_RUNE] = blood or 0
+		count[UNHOLY_RUNE] = unholy or 0
+		count[FROST_RUNE] = frost or 0
+		count[DEATH_RUNE] = death or 0
+		wipe(usedRune)
+
+		for runeType in pairs(RUNE_SLOTS) do
+			-- Match active, regular runes.
+			for _, slot in pairs(RUNE_SLOTS[runeType]) do
+				if count[runeType] == 0 then break end
+				local rune = state.rune[slot]
+				if not usedRune[rune] and rune.type ~= DEATH_RUNE and IsActiveRune(rune, atTime) then
+					--debugprint(string.format("    [1] Match active regular rune in slot %d to %s", slot, RUNE_NAME[runeType]))
+					usedRune[rune] = true
+					count[runeType] = count[runeType] - 1
+				end
+			end
+			-- Match active death runes of the same socket type.
+			for _, slot in pairs(RUNE_SLOTS[runeType]) do
+				if count[runeType] == 0 then break end
+				local rune = state.rune[slot]
+				if not usedRune[rune] and rune.type == DEATH_RUNE and IsActiveRune(rune, atTime) then
+					--debugprint(string.format("    [2] Match active death rune in slot %d to %s, type = %s", slot, RUNE_NAME[slotType]))
+					usedRune[rune] = true
+					count[runeType] = count[runeType] - 1
+				end
 			end
 		end
-		-- Use death runes of the matching rune type to meet the count requirements.
-		if deathCondition ~= "none" and deathCondition ~= 0 then
-			for slot, rune in ipairs(state.rune) do
-				if rune.type == DEATH_RUNE then
-					local runeType = rune.slotType
-					local count, endCooldown = MatchingRune(rune, runeCount[runeType], runeEndCooldown[runeType])
-					runeCount[runeType] = count
-					runeEndCooldown[runeType] = endCooldown
+		-- Match active death runes in DEATH_RUNE_PRIORITY order to meet death count requirements.
+		for _, slot in ipairs(DEATH_RUNE_PRIORITY) do
+			if count[DEATH_RUNE] == 0 then break end
+			local rune = state.rune[slot]
+			if not usedRune[rune] and rune.type == DEATH_RUNE and IsActiveRune(rune, atTime) then
+				--debugprint(string.format("    [3] Match active death rune in slot %d", slot))
+				usedRune[rune] = true
+				count[DEATH_RUNE] = count[DEATH_RUNE] - 1
+			end
+		end
+		-- At this point, if count[runeType] > 0 then there are no active runes of the appropriate type that match that requirement.
+		-- Match active death runes in ANY_RUNE_PRIORITY order to meet remaining count requirements.
+		for _, runeType in pairs(RUNE_TYPE) do
+			for _, slot in ipairs(ANY_RUNE_PRIORITY) do
+				if count[runeType] == 0 then break end
+				local rune = state.rune[slot]
+				if not usedRune[rune] and rune.type == DEATH_RUNE and IsActiveRune(rune, atTime) then
+					--debugprint(string.format("    [4] Match active death rune in slot %d to %s", slot, RUNE_NAME[runeType]))
+					usedRune[rune] = true
+					count[runeType] = count[runeType] - 1
 				end
 			end
 		end

-		-- Remaining rune requirements that have not yet been met.
-		local remainingCount = 0
-		for runeType = 1, 4 do
-			remainingCount = remainingCount + runeCount[runeType]
+		-- At this point, there are no more active runes, death or otherwise, that can satisfy count requirements.
+		for runeType, slotList in pairs(RUNE_SLOTS) do
+			-- Match regenerating runes of the appropriate socket type.
+			if count[runeType] > 0 then
+				local slot1, slot2 = slotList[1], slotList[2]
+				local rune1, rune2 = state.rune[slot1], state.rune[slot2]
+				if count[runeType] == 1 then
+					local rune, slot
+					if not usedRune[rune1] and not usedRune[rune2] then
+						rune = (rune1.endCooldown < rune2.endCooldown) and rune1 or rune2
+						slot = (rune1.endCooldown < rune2.endCooldown) and slot1 or slot2
+					elseif not usedRune[rune1] then
+						rune = rune1
+						slot = slot1
+					elseif not usedRune[rune2] then
+						rune = rune2
+						slot = slot2
+					end
+					if rune then
+						--debugprint(string.format("    [5] Match regenerating rune in slot %d to %s", slot, RUNE_NAME[runeType]))
+						usedRune[rune] = true
+						count[runeType] = 0
+					end
+				else -- if count[runeType] == 2 then
+					if not usedRune[rune1] and not usedRune[rune2] then
+						--debugprint(string.format("    [5] Match regenerating rune in slot %d to %s", slot1, RUNE_NAME[runeType]))
+						--debugprint(string.format("    [5] Match regenerating rune in slot %d to %s", slot2, RUNE_NAME[runeType]))
+						usedRune[rune1] = true
+						usedRune[rune2] = true
+						count[runeType] = 0
+					elseif not usedRune[rune1] then
+						--debugprint(string.format("    [5] Match regenerating rune in slot %d to %s", slot1, RUNE_NAME[runeType]))
+						usedRune[rune1] = true
+						count[runeType] = 1
+					elseif not usedRune[rune2] then
+						--debugprint(string.format("    [5] Match regenerating rune in slot %d to %s", slot2, RUNE_NAME[runeType]))
+						usedRune[rune2] = true
+						count[runeType] = 1
+					end
+				end
+			end
+			-- Match any unused, regenerating death runes.
+			for slot, rune in pairs(state.rune) do
+				if count[runeType] == 0 then break end
+				if not usedRune[rune] and rune.type == DEATH_RUNE then
+					--debugprint(string.format("    [6] Match regenerating rune in slot %d to %s", slot, RUNE_NAME[runeType]))
+					usedRune[rune] = true
+					count[runeType] = count[runeType] - 1
+				end
+			end
 		end

-		-- Use death runes of any type to meet any remaining count requirements.
-		if deathCondition == "any" or deathCondition == 1 then
-			for _, slot in ipairs(DEATH_RUNE_PRIORITY) do
-				local rune = state.rune[slot]
-				local runeType = DEATH_RUNE
-				local count, endCooldown = MatchingRune(rune, remainingCount, runeEndCooldown[runeType])
-				remainingCount = count
-				runeEndCooldown[runeType] = endCooldown
+		-- Replace any used runes with a regenerating death rune with a shorter cooldown.
+		for slot, rune in pairs(state.rune) do
+			if not usedRune[rune] and rune.type == DEATH_RUNE then
+				for used in pairs(usedRune) do
+					if rune.endCooldown < used.endCooldown then
+						--debugprint(string.format("    [7] Replacing matched rune in slot %d with regenerating rune in slot %d", used.slot, slot))
+						usedRune[used] = nil
+						usedRune[rune] = true
+						break
+					end
+				end
 			end
 		end

 		-- This shouldn't happen because it means the rune requirements will never be met.
-		if remainingCount > 0 then
-			Ovale:Logf("Impossible rune count requirements: blood=%d, unholy=%d, frost=%d, death=%d", blood, unholy, frost, death)
-			return math.huge
+		for _, runeType in pairs(RUNE_TYPE) do
+			if count[runeType] > 0 then
+				Ovale:Logf("Impossible rune count requirements: blood=%d, unholy=%d, frost=%d, death=%d", blood, unholy, frost, death)
+				return math.huge
+			end
 		end

 		local maxEndCooldown = 0
-		for runeType = 1, 4 do
-			if runeEndCooldown[runeType] > maxEndCooldown then
-				maxEndCooldown = runeEndCooldown[runeType]
+		for rune in pairs(usedRune) do
+			if maxEndCooldown < rune.endCooldown then
+				maxEndCooldown = rune.endCooldown
 			end
 		end
-		if maxEndCooldown > 0 then
-			return maxEndCooldown - state.currentTime
+		if maxEndCooldown > atTime then
+			return maxEndCooldown - atTime
 		end
 		return 0
 	end
diff --git a/conditions/RuneCount.lua b/conditions/RuneCount.lua
index ceafdde..35dc81f 100644
--- a/conditions/RuneCount.lua
+++ b/conditions/RuneCount.lua
@@ -25,12 +25,6 @@ do
 	--     Valid values: blood, frost, unholy, death
 	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
 	-- @param number Optional. The number to compare against.
-	-- @param death Sets how death runes are used to fulfill the rune count requirements.
-	--     If not set, then only death runes of the proper rune type are used.
-	--     If set with "death=0", then no death runes are used.
-	--     If set with "death=1", then death runes of any rune type are used.
-	--     Default is unset.
-	--     Valid values: unset, 0, 1
 	-- @return The number of runes.
 	-- @return A boolean value for the result of the comparison.
 	-- @see Rune
@@ -40,9 +34,7 @@ do

 	local function RuneCount(condition)
 		local name, comparator, limit = condition[1], condition[2], condition[3]
-		local deathCondition = condition.death
-
-		local count, startCooldown, endCooldown = state:RuneCount(name, deathCondition)
+		local count, startCooldown, endCooldown = state:RuneCount(name)
 		if startCooldown < math.huge then
 			local start, ending = startCooldown, endCooldown
 			return TestValue(start, ending, count, start, 0, comparator, limit)
@@ -57,12 +49,6 @@ do
 	--     Valid values: blood, frost, unholy, death
 	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
 	-- @param number Optional. The number to compare against.
-	-- @param death Sets how death runes are used to fulfill the rune count requirements.
-	--     If not set, then only death runes of the proper rune type are used.
-	--     If set with "death=0", then no death runes are used.
-	--     If set with "death=1", then death runes of any rune type are used.
-	--     Default is unset.
-	--     Valid values: unset, 0, 1
 	-- @return The number of runes.
 	-- @return A boolean value for the result of the comparison.
 	-- @see RuneCount
@@ -71,9 +57,7 @@ do

 	local function Rune(condition)
 		local name, comparator, limit = condition[1], condition[2], condition[3]
-		local deathCondition = condition.death
-
-		local count, startCooldown, endCooldown = state:RuneCount(name, deathCondition)
+		local count, startCooldown, endCooldown = state:RuneCount(name)
 		if startCooldown < math.huge then
 			local origin = startCooldown
 			local rate = 1 / (endCooldown - startCooldown)
diff --git a/conditions/Runes.lua b/conditions/Runes.lua
index c15f53c..0ef8feb 100644
--- a/conditions/Runes.lua
+++ b/conditions/Runes.lua
@@ -33,19 +33,7 @@ do
 				runes[name] = runes[name] + count
 				k = k + 1
 			end
-			local deathCondition
-			if condition.death == 0 then
-				deathCondition = "none"
-			elseif condition.death == 1 then
-				deathCondition = "any"
-			end
-			-- Legacy parameter "nodeath"; no longer documented.
-			if not condition.death and condition.nodeath == 1 then
-				deathCondition = "none"
-			elseif condition.nodeath == 0 then
-				deathCondition = "any"
-			end
-			return runes.blood, runes.unholy, runes.frost, runes.death, deathCondition
+			return runes.blood, runes.unholy, runes.frost, runes.death
 		end
 	end

@@ -58,19 +46,13 @@ do
 	--     Valid values: blood, frost, unholy, death
 	-- @param number The number of runes
 	-- @param ... Optional. Additional "type number" pairs for minimum rune requirements.
-	-- @param death Sets how death runes are used to fulfill the rune count requirements.
-	--     If not set, then only death runes of the proper rune type are used.
-	--     If set with "death=0", then no death runes are used.
-	--     If set with "death=1", then death runes of any rune type are used.
-	--     Default is unset.
-	--     Valid values: unset, 0, 1
 	-- @return A boolean value.
 	-- @usage
 	-- if Runes(frost 1) Spell(howling_blast)

 	local function Runes(condition)
-		local blood, unholy, frost, death, deathCondition = ParseRuneCondition(condition)
-		local seconds = state:GetRunesCooldown(blood, unholy, frost, death, deathCondition)
+		local blood, unholy, frost, death = ParseRuneCondition(condition)
+		local seconds = state:GetRunesCooldown(blood, unholy, frost, death)
 		return state.currentTime + seconds, math.huge
 	end

@@ -93,8 +75,8 @@ do
 	-- @return The number of seconds.

 	local function RunesCooldown(condition)
-		local blood, unholy, frost, death, deathCondition = ParseRuneCondition(condition)
-		local seconds = state:GetRunesCooldown(blood, unholy, frost, death, deathCondition)
+		local blood, unholy, frost, death = ParseRuneCondition(condition)
+		local seconds = state:GetRunesCooldown(blood, unholy, frost, death)
 		return 0, state.currentTime + seconds, seconds, state.currentTime, -1
 	end