Quantcast

Teach OvaleCooldown about spell charges.

Johnny C. Lam [05-11-14 - 08:10]
Teach OvaleCooldown about spell charges.

Lazily update the cooldown information of spells based on the age of the
cooldown state, which is flagged to be updated when a spell's charges or
cooldowns is changed or if a spell is successfully cast.

Add a GetSpellCharges() method to the state machine to act like the
Blizzard API function of the same name.

Fixes ticket 357.

Age cooldown state properly.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1419 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
OvaleCooldown.lua
conditions/SpellChargeCooldown.lua
conditions/SpellCharges.lua
conditions/SpellCooldown.lua
scripts/ovale_hunter_spells.lua
scripts/ovale_monk_spells.lua
scripts/ovale_warlock_spells.lua
diff --git a/OvaleCooldown.lua b/OvaleCooldown.lua
index 6ba4aae..6a98618 100644
--- a/OvaleCooldown.lua
+++ b/OvaleCooldown.lua
@@ -9,7 +9,7 @@
 --]]--------------------------------------------------------------------

 local _, Ovale = ...
-local OvaleCooldown = Ovale:NewModule("OvaleCooldown")
+local OvaleCooldown = Ovale:NewModule("OvaleCooldown", "AceEvent-3.0")
 Ovale.OvaleCooldown = OvaleCooldown

 --<private-static-properties>
@@ -20,6 +20,7 @@ local OvalePaperDoll = nil
 local OvaleStance = nil
 local OvaleState = nil

+local API_GetSpellCharges = GetSpellCharges
 local API_GetSpellCooldown = GetSpellCooldown
 local API_UnitHealth = UnitHealth
 local API_UnitHealthMax = UnitHealthMax
@@ -27,6 +28,8 @@ local API_UnitClass = UnitClass

 -- Player's class.
 local _, self_class = API_UnitClass("player")
+-- Current age of cooldown state.
+local self_serial = 0
 --</private-static-properties>

 --<public-static-methods>
@@ -40,11 +43,22 @@ function OvaleCooldown:OnInitialize()
 end

 function OvaleCooldown:OnEnable()
+	self:RegisterEvent("SPELL_UPDATE_CHARGES", "Update")
+	self:RegisterEvent("SPELL_UPDATE_USABLE", "Update")
+	self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", "Update")
 	OvaleState:RegisterState(self, self.statePrototype)
 end

 function OvaleCooldown:OnDisable()
 	OvaleState:UnregisterState(self)
+	self:UnregisterEvent("SPELL_UPDATE_CHARGES")
+	self:UnregisterEvent("SPELL_UPDATE_USABLE")
+	self:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED")
+end
+
+function OvaleCooldown:Update()
+	-- Advance age of current cooldown state.
+	self_serial = self_serial + 1
 end

 -- Return the GCD after the given spellId is cast.
@@ -118,9 +132,12 @@ end
 -- Reset the state to the current conditions.
 function OvaleCooldown:ResetState(state)
 	for _, cd in pairs(state.cd) do
-		cd.start = nil
-		cd.duration = nil
-		cd.enable = 0
+		-- Remove outdated cooldown state.
+		if cd.serial and cd.serial < self_serial then
+			for k in pairs(cd) do
+				cd[k] = nil
+			end
+		end
 	end
 end

@@ -139,95 +156,153 @@ function OvaleCooldown:ApplySpellAfterCast(state, spellId, targetGUID, startCast
 	local si = OvaleData.spellInfo[spellId]
 	if si then
 		local cd = state:GetCD(spellId)
-		if cd then
-			cd.start = isChanneled and startCast or endCast
-			cd.duration = si.cd or 0
-			cd.enable = 1
-
-			-- Test for no cooldown.
-			if nocd then
-				cd.duration = 0
-			else
-				-- There is no cooldown if the buff named by "buffnocd" parameter is present.
-				if si.buffnocd then
-					local aura = state:GetAura("player", si.buffnocd)
-					if state:IsActiveAura(aura) then
-						Ovale:Logf("buffnocd stacks = %s, start = %s, ending = %s, startCast = %f", aura.stacks, aura.start, aura.ending, startCast)
-						if aura.start <= startCast and startCast < aura.ending then
-							cd.duration = 0
-						end
-					end
-				end
+		cd.start = isChanneled and startCast or endCast
+		cd.duration = si.cd or 0
+		cd.enable = 1

-				-- There is no cooldown if the target's health percent is below what's specified
-				-- with the "targetlifenocd" parameter.
-				local target = OvaleGUID:GetUnitId(targetGUID)
-				if target and si.targetlifenocd then
-					local healthPercent = API_UnitHealth(target) / API_UnitHealthMax(target) * 100
-					if healthPercent < si.targetlifenocd then
-						cd.duration = 0
-					end
+		-- If the spell has charges, then remove a charge.
+		if cd.charges and cd.charges > 0 then
+			cd.chargeStart = cd.start
+			cd.charges = cd.charges - 1
+			if cd.charges == 0 then
+				cd.duration = cd.chargeDuration
+			end
+		end
+
+		-- Test for no cooldown.
+		if nocd then
+			cd.duration = 0
+		else
+			-- There is no cooldown if the buff named by "buff_no_cd" parameter is present.
+			local buffNoCooldown = si.buff_no_cd or si.buffnocd
+			if buffNoCooldown then
+				local aura = state:GetAura("player", buffNoCooldown)
+				if state:IsActiveAura(aura, cd.start) then
+					Ovale:Logf("buff_no_cd stacks = %s, start = %s, ending = %s, cd.start = %f", aura.stacks, aura.start, aura.ending, cd.start)
+					cd.duration = 0
 				end
 			end

-			-- Adjust cooldown duration if it is affected by haste: "cd_haste=melee" or "cd_haste=spell".
-			if cd.duration > 0 and si.cd_haste then
-				if si.cd_haste == "melee" then
-					cd.duration = cd.duration / state:GetMeleeHasteMultiplier(spellcast.snapshot)
-				elseif si.cd_haste == "spell" then
-					cd.duration = cd.duration / state:GetSpellHasteMultiplier(spellcast.snapshot)
+			-- There is no cooldown if the target's health percent is below what's specified
+			-- with the "target_health_pct_no_cd" parameter.
+			local target = OvaleGUID:GetUnitId(targetGUID)
+			local targetHealthPctNoCooldown = si.target_health_pct_no_cd or si.targetlifenocd
+			if target and targetHealthPctNoCooldown then
+				local healthPercent = API_UnitHealth(target) / API_UnitHealthMax(target) * 100
+				if healthPercent < targetHealthPctNoCooldown then
+					cd.duration = 0
 				end
 			end
+		end

-			Ovale:Logf("Spell %d cooldown info: start=%f, duration=%f", spellId, cd.start, cd.duration)
+		-- Adjust cooldown duration if it is affected by haste: "cd_haste=melee" or "cd_haste=spell".
+		if cd.duration > 0 and si.cd_haste then
+			if si.cd_haste == "melee" then
+				cd.duration = cd.duration / state:GetMeleeHasteMultiplier(spellcast.snapshot)
+			elseif si.haste == "ranged" then
+				cd.duration = cd.duration / OvalePaperDoll:GetSpellHasteMultiplier()
+			elseif si.cd_haste == "spell" then
+				cd.duration = cd.duration / state:GetSpellHasteMultiplier(spellcast.snapshot)
+			end
 		end
+
+		Ovale:Logf("Spell %d cooldown info: start=%f, duration=%f", spellId, cd.start, cd.duration)
 	end
 end
 --</public-static-methods>

 --<state-methods>
+statePrototype.DebugCooldown = function(state)
+	for spellId, cd in pairs(state.cd) do
+		if cd.start then
+			if cd.charges then
+				Ovale:FormatPrint("Spell %s cooldown: start=%f, duration=%f, charges=%d, maxCharges=%d, chargeStart=%f, chargeDuration=%f",
+					spellId, cd.start, cd.duration, cd.charges, cd.start, cd.duration)
+			else
+				Ovale:FormatPrint("Spell %s cooldown: start=%f, duration=%f", spellId, cd.start, cd.duration)
+			end
+		end
+	end
+end
+
 -- Return the table holding the simulator's cooldown information for the given spell.
 statePrototype.GetCD = function(state, spellId)
-	if spellId then
-		local si = OvaleData.spellInfo[spellId]
-		if si and si.cd then
-			local cdname = si.sharedcd and si.sharedcd or spellId
-			if not state.cd[cdname] then
-				state.cd[cdname] = {}
+	local cdName = spellId
+	local si = OvaleData.spellInfo[spellId]
+	if si and si.sharedcd then
+		cdName = si.sharedcd
+	end
+	if not state.cd[cdName] then
+		state.cd[cdName] = {}
+	end
+
+	-- Populate the cooldown information from the current game state if it is outdated.
+	local cd = state.cd[cdName]
+	if not cd.start or not cd.serial or cd.serial < self_serial then
+		local start, duration, enable = API_GetSpellCooldown(spellId)
+		if start and start > 0 then
+			charges = 0
+		end
+		if si and si.forcecd then
+			if si.forcecd then
+				start, duration = API_GetSpellCooldown(si.forcecd)
 			end
-			return state.cd[cdname]
+		end
+		cd.serial = self_serial
+		cd.start = start
+		cd.duration = duration
+		cd.enable = enable
+
+		local charges, maxCharges, chargeStart, chargeDuration = API_GetSpellCharges(spellId)
+		if charges then
+			cd.charges = charges
+			cd.maxCharges = maxCharges
+			cd.chargeStart = chargeStart
+			cd.chargeDuration = chargeDuration
 		end
 	end
-	return nil
+
+	-- Advance the cooldown state to the current time.
+	local now = state.currentTime
+	if cd.start then
+		if cd.start + cd.duration <= now then
+			cd.start = 0
+			cd.duration = 0
+		end
+	end
+	if cd.charges then
+		local charges, maxCharges, chargeStart, chargeDuration = cd.charges, cd.maxCharges, cd.chargeStart, cd.chargeDuration
+		while chargeStart + chargeDuration <= now and charges < maxCharges do
+			chargeStart = chargeStart + chargeDuration
+			charges = charges + 1
+		end
+		cd.charges = charges
+		cd.chargeStart = chargeStart
+	end
+
+	return cd
 end

 -- Return the cooldown for the spell in the simulator.
 statePrototype.GetSpellCooldown = function(state, spellId)
-	local start, duration, enable
 	local cd = state:GetCD(spellId)
-	if cd and cd.start then
-		start, duration, enable = cd.start, cd.duration, cd.enable
-	else
-		start, duration, enable = API_GetSpellCooldown(spellId)
-		local si = OvaleData.spellInfo[spellId]
-		if si and si.forcecd then
-			start, duration = API_GetSpellCooldown(si.forcecd)
-		end
-	end
-	return start, duration, enable
+	return cd.start, cd.duration, cd.enable
+end
+
+-- Return the information on the number of charges for the spell in the simulator.
+statePrototype.GetSpellCharges = function(state, spellId)
+	local cd = state:GetCD(spellId)
+	return cd.charges, cd.maxCharges, cd.chargeStart, cd.chargeDuration
 end

 -- Force the cooldown of a spell to reset at the specified time.
 statePrototype.ResetSpellCooldown = function(state, spellId, atTime)
-	if atTime >= state.currentTime then
-		local start, duration, enable = state:GetSpellCooldown(spellId)
-		if start + duration > state.currentTime then
-			local cd = state:GetCD(spellId)
-			if cd then
-				cd.start = state.currentTime
-				cd.duration = atTime - state.currentTime
-				cd.enable = 1
-			end
+	local now = state.currentTime
+	if atTime >= now then
+		local cd = state:GetCD(spellId)
+		if cd.start + cd.duration > now then
+			cd.start = now
+			cd.duration = atTime - now
 		end
 	end
 end
diff --git a/conditions/SpellChargeCooldown.lua b/conditions/SpellChargeCooldown.lua
index 723836f..655a75f 100644
--- a/conditions/SpellChargeCooldown.lua
+++ b/conditions/SpellChargeCooldown.lua
@@ -1,6 +1,6 @@
 --[[--------------------------------------------------------------------
     Ovale Spell Priority
-    Copyright (C) 2013 Johnny C. Lam
+    Copyright (C) 2013, 2014 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
@@ -11,10 +11,11 @@ local _, Ovale = ...

 do
 	local OvaleCondition = Ovale.OvaleCondition
+	local OvaleState = Ovale.OvaleState

-	local API_GetSpellCharges = GetSpellCharges
 	local Compare = OvaleCondition.Compare
 	local TestValue = OvaleCondition.TestValue
+	local state = OvaleState.state

 	--- Get the cooldown in seconds on a spell before it gains another charge.
 	-- @name SpellChargeCooldown
@@ -31,8 +32,8 @@ do

 	local function SpellChargeCooldown(condition)
 		local spellId, comparator, limit = condition[1], condition[2], condition[3]
-		local charges, maxCharges, start, duration = API_GetSpellCharges(spellId)
-		if charges < maxCharges then
+		local charges, maxCharges, start, duration = state:GetSpellCharges(spellId)
+		if charges and charges < maxCharges then
 			return TestValue(start, start + duration, duration, start, -1, comparator, limit)
 		end
 		return Compare(0, comparator, limit)
diff --git a/conditions/SpellCharges.lua b/conditions/SpellCharges.lua
index c7827cd..977c590 100644
--- a/conditions/SpellCharges.lua
+++ b/conditions/SpellCharges.lua
@@ -1,6 +1,6 @@
 --[[--------------------------------------------------------------------
     Ovale Spell Priority
-    Copyright (C) 2013 Johnny C. Lam
+    Copyright (C) 2013, 2014 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
@@ -11,9 +11,10 @@ local _, Ovale = ...

 do
 	local OvaleCondition = Ovale.OvaleCondition
+	local OvaleState = Ovale.OvaleState

-	local API_GetSpellCharges = GetSpellCharges
 	local Compare = OvaleCondition.Compare
+	local state = OvaleState.state

 	--- Get the number of charges of the spell.
 	-- @name SpellCharges
@@ -30,8 +31,9 @@ do

 	local function SpellCharges(condition)
 		local spellId, comparator, limit = condition[1], condition[2], condition[3]
-		local value = API_GetSpellCharges(spellId)
-		return Compare(value, comparator, limit)
+		local charges, maxCharges, start, duration = state:GetSpellCharges(spellId)
+		charges = charges or 0
+		return Compare(charges, comparator, limit)
 	end

 	OvaleCondition:RegisterCondition("charges", true, SpellCharges)
diff --git a/conditions/SpellCooldown.lua b/conditions/SpellCooldown.lua
index e8977a4..da57cbe 100644
--- a/conditions/SpellCooldown.lua
+++ b/conditions/SpellCooldown.lua
@@ -1,22 +1,19 @@
 --[[--------------------------------------------------------------------
     Ovale Spell Priority
     Copyright (C) 2012, 2013 Sidoine
-    Copyright (C) 2012, 2013 Johnny C. Lam
+    Copyright (C) 2012, 2013, 2014 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 OvaleSpellBook = Ovale.OvaleSpellBook
 	local OvaleState = Ovale.OvaleState

-	local type = type
 	local Compare = OvaleCondition.Compare
 	local TestValue = OvaleCondition.TestValue
 	local state = OvaleState.state
@@ -35,18 +32,7 @@ do

 	local function SpellCooldown(condition)
 		local spellId, comparator, limit = condition[1], condition[2], condition[3]
-		local start, duration
-		if type(spellId) == "string" then
-			local sharedCd = state.cd[spellId]
-			if not sharedCd then
-				return nil
-			end
-			start, duration = sharedCd.start, sharedCd.duration
-		elseif not OvaleSpellBook:IsKnownSpell(spellId) then
-			return nil
-		else
-			start, duration = state:GetSpellCooldown(spellId)
-		end
+		local start, duration = state:GetSpellCooldown(spellId)
 		if start > 0 and duration > 0 then
 			return TestValue(start, start + duration, duration, start, -1, comparator, limit)
 		end
diff --git a/scripts/ovale_hunter_spells.lua b/scripts/ovale_hunter_spells.lua
index bff664b..abd8123 100644
--- a/scripts/ovale_hunter_spells.lua
+++ b/scripts/ovale_hunter_spells.lua
@@ -56,9 +56,8 @@ Define(cobra_shot 77767)
 Define(counter_shot 147362)
 	SpellInfo(counter_shot cd=24)
 Define(crouching_tiger_hidden_chimera_talent 3)
-Define(deterrence 19263)
-	SpellInfo(deterrence cd=180)
-	SpellInfo(deterrence addcd=-60 talent=crouching_tiger_hidden_chimera_talent)
+Define(deterrence 148467)
+	SpellInfo(deterrence cd=5)
 Define(dire_beast 120679)
 	SpellInfo(dire_beast cd=30)
 Define(dire_beast_talent 11)
diff --git a/scripts/ovale_monk_spells.lua b/scripts/ovale_monk_spells.lua
index 316b815..0d9487d 100644
--- a/scripts/ovale_monk_spells.lua
+++ b/scripts/ovale_monk_spells.lua
@@ -19,7 +19,7 @@ Define(breath_of_fire 115181)
 	SpellInfo(breath_of_fire chi=2)
 Define(brewmaster_training 117967)
 Define(chi_brew 115399)
-	SpellInfo(chi_brew cd=45 chi=-2)
+	SpellInfo(chi_brew chi=-2)
 Define(chi_brew_talent 9)
 Define(chi_burst 123986)
 	SpellInfo(chi_burst cd=30)
diff --git a/scripts/ovale_warlock_spells.lua b/scripts/ovale_warlock_spells.lua
index 5226fc3..dbb70fe 100644
--- a/scripts/ovale_warlock_spells.lua
+++ b/scripts/ovale_warlock_spells.lua
@@ -43,18 +43,18 @@ Define(dark_regeneration 108359)
 Define(dark_regeneration_talent 1)
 Define(dark_intent 109773)
 Define(dark_soul_knowledge 113861)
-	SpellInfo(dark_soul_knowledge cd=120)
+	SpellInfo(dark_soul_knowledge cd=120 talent=!archimondes_darkness_talent)
 	SpellAddBuff(dark_soul_knowledge dark_soul_knowledge_buff=1)
 Define(dark_soul_knowledge_buff 113858)
 	SpellInfo(dark_soul_knowledge_buff duration=20)
 Define(dark_soul_instability 113858)
-	SpellInfo(dark_soul_instability cd=120)
+	SpellInfo(dark_soul_instability cd=120 talent=!archimondes_darkness_talent)
 	SpellAddBuff(dark_soul_instability dark_soul_instability_buff=1)
 Define(dark_soul_instability_buff 113858)
 	SpellInfo(dark_soul_instability_buff duration=20)
 Define(dark_soul_misery 113860)
-	SpellInfo(dark_soul_misery cd=120)
-	SpellAddBuff(dark_soul_misery dark_soul_misery_buff=1)
+	SpellInfo(dark_soul_misery cd=120 talent=!archimondes_darkness_talent)
+	SpellAddBuffoul_misery dark_soul_misery_buff=1)
 Define(dark_soul_misery_buff 113858)
 	SpellInfo(dark_soul_misery_buff duration=20)
 Define(demonic_circle_teleport 48020)