Quantcast

Fix state.nextCast to actually be the earliest start for next GCD spell.

Johnny C. Lam [01-01-15 - 09:36]
Fix state.nextCast to actually be the earliest start for next GCD spell.

This change also makes off-GCD spells suggested only when the GCD is not
on cooldown, which matches the behavior of SimulationCraft.

To allow a spell to be suggested during the global cooldown, it should be
tagged in the SpellInfo() with "offgcd=1".  This is useful for some spells
that should be cast right away, e.g., defensive cooldowns.

Fixes ticket 507 by @ShmooDude.
Filename
BestAction.lua
Future.lua
diff --git a/BestAction.lua b/BestAction.lua
index d5f520d..1b74077 100644
--- a/BestAction.lua
+++ b/BestAction.lua
@@ -458,11 +458,12 @@ function OvaleBestAction:ComputeAction(element, state)
 		state:Log("[%d]    Action %s not usable.", nodeId, action)
 	else
 		-- Set the cast time of the action.
+		local spellInfo
 		if actionType == "spell" then
 			local spellId = actionId
-			local si = spellId and OvaleData.spellInfo[spellId]
-			if si and si.casttime then
-				element.castTime = si.casttime
+			local spellInfo = spellId and OvaleData.spellInfo[spellId]
+			if spellInfo and spellInfo.casttime then
+				element.castTime = spellInfo.casttime
 			else
 				element.castTime = OvaleSpellBook:GetCastTime(spellId)
 			end
@@ -488,12 +489,13 @@ function OvaleBestAction:ComputeAction(element, state)
 		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.
-		if start < state.nextCast then
+		local offgcd = spellInfo and (spellInfo.offgcd == 1)
+		if not offgcd and start < state.nextCast then
 			-- Default to starting at next available cast time.
 			local newStart = state.nextCast
 			-- If we are currently channeling a spellcast, then see if it is interruptible.
 			-- If we are allowed to interrupt it, then start after the next tick of the channel.
-			if state.isChanneling then
+			if state:IsChanneling() then
 				local spellId = state.currentSpellId
 				local si = spellId and OvaleData.spellInfo[spellId]
 				if si then
diff --git a/Future.lua b/Future.lua
index d5c590c..285380e 100644
--- a/Future.lua
+++ b/Future.lua
@@ -783,8 +783,8 @@ statePrototype.endCast = nil
 statePrototype.nextCast = nil
 -- The most recent time the spell was cast in the simulator.
 statePrototype.lastCast = nil
--- Whether the player is channeling a spell in the simulator at the current time.
-statePrototype.isChanneling = nil
+-- Whether the spell being cast in the simulator is a channeled spell.
+statePrototype.isChanneled = nil
 -- The previous spell cast in the simulator.
 statePrototype.lastSpellId = nil
 -- The previous GCD spell cast in the simulator.
@@ -809,11 +809,18 @@ function OvaleFuture:ResetState(state)

 	state.inCombat = self.inCombat
 	state.combatStartTime = self.combatStartTime or 0
+
 	state.lastSpellId = self.lastSpellcast and self.lastSpellcast.spellId
+	state.isChanneled = self.lastSpellcast and self.lastSpellcast.channeled
+	state.currentSpellId = state.lastSpellId
+
+	local start, duration = OvaleCooldown:GetGlobalCooldown(now)
+	if start and start > 0 then
+		state.nextCast = start + duration
+	else
+		state.nextCast = now
+	end
 	state.lastGCDSpellId = self.lastGCDSpellcast and self.lastGCDSpellcast.spellId
-	state.currentSpellId = nil
-	state.isChanneling = false
-	state.nextCast = now

 	for k in pairs(state.lastCast) do
 		state.lastCast[k] = nil
@@ -826,16 +833,6 @@ end

 -- Release state resources prior to removing from the simulator.
 function OvaleFuture:CleanState(state)
-	state.inCombat = nil
-	state.currentTime = nil
-	state.currentSpellId = nil
-	state.startCast = nil
-	state.endCast = nil
-	state.nextCast = nil
-	state.isChanneling = nil
-	state.lastSpellId = nil
-	state.lastGCDSpellId = nil
-
 	for k in pairs(state.lastCast) do
 		state.lastCast[k] = nil
 	end
@@ -882,6 +879,11 @@ statePrototype.TimeOfLastCast = function(state, spellId)
 	return state.lastCast[spellId] or OvaleFuture.lastCastTime[spellId] or 0
 end

+-- Return whether the player is currently channeling a spell in the simulator.
+statePrototype.IsChanneling = function(state)
+	return state.isChanneled and (state.currentTime < state.endCast)
+end
+
 --[[
 	Cast a spell in the simulator and advance the state of the simulator.

@@ -896,26 +898,32 @@ end
 statePrototype.ApplySpell = function(state, spellId, targetGUID, startCast, endCast, isChanneled, spellcast)
 	OvaleFuture:StartProfiling("OvaleFuture_state_ApplySpell")
 	if spellId and targetGUID then
-		local target = OvaleGUID:GetUnitId(targetGUID)
-		local gcd = state:GetGCD(spellId, target)
-		local nextCast

 		-- Handle missing start/end/next cast times.
-		if not startCast or not endCast then
-			local castTime = OvaleSpellBook:GetCastTime(spellId) or 0
+		local castTime
+		if startCast and endCast then
+			castTime = endCast - startCast
+		else
+			castTime = OvaleSpellBook:GetCastTime(spellId) or 0
 			startCast = startCast or state.nextCast
 			endCast = endCast or (startCast + castTime)
-			nextCast = (castTime > gcd) and endCast or (startCast + gcd)
 		end

 		-- Update the latest spell cast in the simulator.
 		state.currentSpellId = spellId
 		state.startCast = startCast
 		state.endCast = endCast
-		state.nextCast = nextCast
-		state.isChanneling = isChanneled
 		state.lastCast[spellId] = endCast
 		state.lastSpellId = spellId
+		state.isChanneled = isChanneled
+
+		-- Update the GCD-related spell information in the simulator.
+		local target = OvaleGUID:GetUnitId(targetGUID)
+		local gcd = state:GetGCD(spellId, target)
+		local nextCast = (castTime > gcd) and endCast or (startCast + gcd)
+		if state.nextCast < nextCast then
+			state.nextCast = nextCast
+		end
 		if gcd > 0 then
 			state.lastGCDSpellId = spellId
 		end