Quantcast

Don't consider spells waiting on the GCD to be on cooldown.

Johnny C. Lam [12-12-14 - 20:41]
Don't consider spells waiting on the GCD to be on cooldown.

This differs from the return value of Blizzard's GetSpellCooldown() but
makes more sense from the point of view of a player.

Adjust OvaleCooldown:GetSpellCooldown() to check for whether the GCD is
active and to consider a spell that is waiting on the GCD to just be ready
when the GCD is over.  A spell is on cooldown only if the duration
returned by OvaleCooldown:GetSpellCooldown() is greater than zero.

Introduce a new state method GetTimeToSpell() that returns the time until
the spell is ready to cast, accounting for cooldowns and resource
requirements, and use it instead of very similar code in
OvaleBestAction:GetActionInfo() and the TimeToSpell() script condition.

Note that OvaleRunes is a core Ovale module since it provides state
methods that are needed for these computations to work.

This is the rest of the fix for ticket 494 by @Reason2012.
Filename
BestAction.lua
Cooldown.lua
Ovale.toc
SpellBook.lua
conditions.lua
diff --git a/BestAction.lua b/BestAction.lua
index 23dbf31..396740a 100644
--- a/BestAction.lua
+++ b/BestAction.lua
@@ -247,38 +247,15 @@ local function GetActionSpellInfo(element, state, target)
 					local texture = state:GetSpellInfoProperty(spellId, "texture", target)
 					actionTexture = "Interface\\Icons\\" .. texture
 				end
-				-- Fix spell cooldown information using primary resource requirements specified in SpellInfo().
+				-- Extend the cooldown duration if the spell needs additional time to pool resources.
 				if actionCooldownStart and actionCooldownDuration then
-					-- Get the maximum time before all "primary" resources are ready.
-					local atTime = state.currentTime
-					for powerType in pairs(OvalePower.PRIMARY_POWER) do
-						if si[powerType] then
-							local t = state.currentTime + state:TimeToPower(spellId, target, powerType)
-							if atTime < t then
-								atTime = t
-							end
-						end
-					end
-					if actionCooldownStart > 0 then
+					local seconds = state:GetTimeToSpell(spellId, target)
+					if seconds > 0 then
+						local atTime = state.currentTime + seconds
 						if atTime > actionCooldownStart + actionCooldownDuration then
 							state:Log("Delaying spell ID '%s' for primary resource.", spellId)
 							actionCooldownDuration = atTime - actionCooldownStart
 						end
-					else
-						actionCooldownStart = state.currentTime
-						actionCooldownDuration = atTime - actionCooldownStart
-					end
-
-					local blood = state:GetSpellInfoProperty(spellId, "blood", target)
-					local frost = state:GetSpellInfoProperty(spellId, "frost", target)
-					local unholy = state:GetSpellInfoProperty(spellId, "unholy", target)
-					local death = state:GetSpellInfoProperty(spellId, "death", target)
-					if blood or frost or unholy or death then
-						-- Spell requires runes.
-						local ending = state.currentTime + state:GetRunesCooldown(blood, unholy, frost, death)
-						if ending > actionCooldownStart + actionCooldownDuration then
-							actionCooldownDuration = ending - actionCooldownStart
-						end
 					end
 				end
 			end
@@ -462,7 +439,7 @@ function OvaleBestAction:ComputeAction(element, state)
 	local timeSpan = GetTimeSpan(element)
 	local priority, result

-	state:Log("[%d]    evaluating action: %s(%s)", element.nodeId, element.name, element.paramsAsString)
+	state:Log("[%d]    evaluating action: %s(%s)", nodeId, element.name, element.paramsAsString)
 	local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
 		actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, actionTarget = self:GetActionInfo(element, state)

@@ -489,12 +466,19 @@ function OvaleBestAction:ComputeAction(element, state)

 		-- If the action is not on cooldown, then treat it like it's immediately ready.
 		local start
-		if actionCooldownDuration and actionCooldownStart and actionCooldownStart > 0 then
-			start = actionCooldownDuration + actionCooldownStart
+		if actionCooldownStart and actionCooldownStart > 0 then
+			-- Spell is on cooldown.
+			if actionCooldownDuration and actionCooldownDuration > 0 then
+				state:Log("[%s]    Action %s is on cooldown (start=%f, duration=%f).", nodeId, action, actionCooldownStart, actionCooldownDuration)
+				start = actionCooldownStart + actionCooldownDuration
+			else
+				state:Log("[%s]    Action %s is waiting on the GCD (start=%f).", nodeId, action, actionCooldownStart)
+				start = actionCooldownStart
+			end
 		else
+			state:Log("[%s]    Action %s is off cooldown.", nodeId, action)
 			start = state.currentTime
 		end
-
 		state:Log("[%d]    start=%f nextCast=%s", nodeId, start, state.nextCast)

 		-- If the action is available before the end of the current spellcast, then wait until we can first cast the action.
diff --git a/Cooldown.lua b/Cooldown.lua
index 01bea0e..c51744b 100644
--- a/Cooldown.lua
+++ b/Cooldown.lua
@@ -25,6 +25,9 @@ local API_GetSpellCharges = GetSpellCharges
 local API_GetSpellCooldown = GetSpellCooldown
 local API_UnitClass = UnitClass

+-- Spell ID for the dummy Global Cooldown spell.
+local GLOBAL_COOLDOWN = 61304
+
 -- Register for profiling.
 OvaleProfiler:RegisterProfiling(OvaleCooldown)

@@ -116,21 +119,37 @@ end
 -- then cycle through all spells associated with that spell ID to find the cooldown
 -- information.
 function OvaleCooldown:GetSpellCooldown(spellId)
-	local start, duration, enable
+	local cdStart, cdDuration, cdEnable = 0, 0, 1
+	local gcdStart, gcdDuration = API_GetSpellCooldown(GLOBAL_COOLDOWN)
 	if self_sharedCooldownSpells[spellId] then
 		for id in pairs(self_sharedCooldownSpells[spellId]) do
-			start, duration, enable = self:GetSpellCooldown(id)
+			local start, duration, enable = self:GetSpellCooldown(id)
 			if start then break end
 		end
 	else
+		local start, duration, enable
 		local index, bookType = OvaleSpellBook:GetSpellBookIndex(spellId)
 		if index and bookType then
 			start, duration, enable = API_GetSpellCooldown(index, bookType)
 		else
 			start, duration, enable = API_GetSpellCooldown(spellId)
 		end
+		if start and start > 0 then
+			if duration > gcdDuration then
+				-- Spell is on cooldown.
+				cdStart, cdDuration, cdEnable = start, duration, enable
+			else
+				-- GCD is active, so set the start to when the spell can next be cast.
+				cdStart = start + duration
+				cdDuration = 0
+				cdEnable = enable
+			end
+		else
+			-- Spell is ready now.
+			cdStart, cdDuration, cdEnable = start, duration, enable
+		end
 	end
-	return start, duration, enable
+	return cdStart, cdDuration, cdEnable
 end

 -- Return the base GCD and caster status.
@@ -359,7 +378,7 @@ end
 -- already on cooldown or the duration if cast at the specified time.
 statePrototype.GetSpellCooldownDuration = function(state, spellId, atTime, target)
 	local start, duration = state:GetSpellCooldown(spellId)
-	if start + duration > atTime then
+	if duration > 0 and start + duration > atTime then
 		state:Log("Spell %d is on cooldown for %fs starting at %s.", spellId, duration, start)
 	else
 		local si = OvaleData.spellInfo[spellId]
diff --git a/Ovale.toc b/Ovale.toc
index fc8b6b4..106f8a1 100644
--- a/Ovale.toc
+++ b/Ovale.toc
@@ -45,6 +45,7 @@ Latency.lua
 Lexer.lua
 PaperDoll.lua
 Power.lua
+Runes.lua
 Score.lua
 Scripts.lua
 SimulationCraft.lua
@@ -67,7 +68,6 @@ Enemies.lua
 HonorAmongThieves.lua
 PassiveAura.lua
 Recount.lua
-Runes.lua
 ShadowWordDeath.lua
 Skada.lua
 SpellDamage.lua
diff --git a/SpellBook.lua b/SpellBook.lua
index ab76b53..25220eb 100644
--- a/SpellBook.lua
+++ b/SpellBook.lua
@@ -16,8 +16,10 @@ local OvaleDebug = Ovale.OvaleDebug
 local OvaleProfiler = Ovale.OvaleProfiler

 -- Forward declarations for module dependencies.
+local OvaleCooldown = nil
 local OvaleData = nil
 local OvalePower = nil
+local OvaleRunes = nil
 local OvaleState = nil

 local ipairs = ipairs
@@ -147,8 +149,10 @@ end
 --<public-static-methods>
 function OvaleSpellBook:OnInitialize()
 	-- Resolve module dependencies.
+	OvaleCooldown = Ovale.OvaleCooldown
 	OvaleData = Ovale.OvaleData
 	OvalePower = Ovale.OvalePower
+	OvaleRunes = Ovale.OvaleRunes
 	OvaleState = Ovale.OvaleState
 end

@@ -521,4 +525,38 @@ statePrototype.IsUsableSpell = function(state, spellId, target)
 	OvaleSpellBook:StopProfiling("OvaleSpellBook_state_IsUsableSpell")
 	return isUsable, noMana
 end
+
+-- Get the number of seconds before the spell is ready to be cast, either due to cooldown or resources.
+statePrototype.GetTimeToSpell = function(state, spellId, target)
+	local timeToSpell = 0
+	-- Cooldown.
+	do
+		local start, duration = state:GetSpellCooldown(spellId)
+		local seconds = (duration > 0) and (start + duration - state.currentTime) or 0
+		if timeToSpell < seconds then
+			timeToSpell = seconds
+		end
+	end
+	-- Pooled resource.
+	do
+		local seconds = state:TimeToPower(spellId, target)
+		if timeToSpell < seconds then
+			timeToSpell = seconds
+		end
+	end
+	-- Death knight runes.
+	do
+		local blood = state:GetSpellInfoProperty(spellId, "blood", target)
+		local unholy = state:GetSpellInfoProperty(spellId, "unholy", target)
+		local frost = state:GetSpellInfoProperty(spellId, "frost", target)
+		local death = state:GetSpellInfoProperty(spellId, "death", target)
+		if blood or unholy or frost or death then
+			local seconds = state:GetRunesCooldown(blood, unholy, frost, death)
+			if timeToSpell < seconds then
+				timeToSpell = seconds
+			end
+		end
+	end
+	return timeToSpell
+end
 --</state-methods>
diff --git a/conditions.lua b/conditions.lua
index 0b73571..37a3eff 100644
--- a/conditions.lua
+++ b/conditions.lua
@@ -5402,33 +5402,7 @@ do
 	local function TimeToSpell(condition, state)
 		local spellId, comparator, limit = condition[1], condition[2], condition[3]
 		local target = ParseCondition(condition, state, "target")
-		local seconds = 0
-		-- Cooldown
-		do
-			local start, duration = state:GetSpellCooldown(spellId)
-			local timeToCooldown = start + duration - state.currentTime
-			if seconds < timeToCooldown then
-				seconds = timeToCooldown
-			end
-		end
-		-- Pooled resource.
-		do
-			local timeToPower = state:TimeToPower(spellId, target)
-			if seconds < timeToPower then
-				seconds = timeToPower
-			end
-		end
-		-- Runes.
-		local blood = state:GetSpellInfoProperty(spellId, "blood", target)
-		local unholy = state:GetSpellInfoProperty(spellId, "unholy", target)
-		local frost = state:GetSpellInfoProperty(spellId, "frost", target)
-		local death = state:GetSpellInfoProperty(spellId, "death", target)
-		if blood or unholy or frost or death then
-			local timeToRunes = state:GetRunesCooldown(blood, unholy, frost, death)
-			if seconds < timeToRunes then
-				seconds = timeToRunes
-			end
-		end
+		local seconds = state:GetTimeToSpell(spellId, target)
 		if seconds == 0 then
 			return Compare(0, comparator, limit)
 		elseif seconds < INFINITY then