Quantcast

Refactor OvaleData module.

Johnny C. Lam [09-28-13 - 12:49]
Refactor OvaleData module.

- Move the spellbook/talent/glyph-handling to a new module OvaleSpellBook.

- Move methods closer to the place where they are mostly used:
  - :GetTickLength() moved to OvaleAura.
  - :GetDuration() moved to OvaleState, with simplifications.

- Simplify OvaleData into a module without dependencies that can be used
  as a global data store for other modules where needed.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1034 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
Ovale.toc
OvaleAura.lua
OvaleBestAction.lua
OvaleCompile.lua
OvaleCondition.lua
OvaleData.lua
OvaleFrame.lua
OvaleFuture.lua
OvaleIcone.lua
OvaleOptions.lua
OvaleSpellBook.lua
OvaleState.lua
compiler.pl
diff --git a/Ovale.toc b/Ovale.toc
index b91a774..762d809 100644
--- a/Ovale.toc
+++ b/Ovale.toc
@@ -31,6 +31,7 @@ Ovale.lua

 # Modules with no dependencies
 OvaleActionBar.lua
+OvaleData.lua
 OvaleEnemies.lua
 OvaleEquipement.lua
 OvaleGUID.lua
@@ -40,12 +41,12 @@ OvalePoolGC.lua
 OvaleQueue.lua
 OvaleRecount.lua
 OvaleSkada.lua
+OvaleSpellBook.lua
 OvaleStance.lua
 #
 OvaleDamageTaken.lua
 OvalePaperDoll.lua
 #
-OvaleData.lua
 OvaleScripts.lua
 defaut\Chaman.lua
 defaut\Chasseur.lua
diff --git a/OvaleAura.lua b/OvaleAura.lua
index 3139ba6..4bcca3c 100644
--- a/OvaleAura.lua
+++ b/OvaleAura.lua
@@ -144,7 +144,7 @@ local function UnitGainedAura(guid, spellId, filter, casterGUID, icon, count, de
 				-- Only set the initial tick information for new auras.
 				if not existingAura then
 					aura.ticksSeen = 0
-					aura.tick = OvaleData:GetTickLength(spellId)
+					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
@@ -279,6 +279,7 @@ end

 -- Update the tick length of an aura using event timestamps from the combat log.
 local function UpdateAuraTick(guid, spellId, timestamp)
+	local self = OvaleAura
 	local aura, filter
 	if self_aura[guid] then
 		local serial = self_serial[guid]
@@ -304,7 +305,7 @@ local function UpdateAuraTick(guid, spellId, timestamp)
 		if not aura.lastTickTime then
 			-- For some reason, there was no lastTickTime information recorded,
 			-- so approximate the tick time using the player's current stats.
-			tick = OvaleData:GetTickLength(spellId)
+			tick = self:GetTickLength(spellId)
 			ticksSeen = 0
 		else
 			-- Tick times tend to vary about the "true" value by a up to a few
@@ -561,6 +562,32 @@ function OvaleAura:GetAuraOnAnyTarget(spellId, filter, mine, excludingGUID)
 	return start, ending, count
 end

+function OvaleAura:GetTickLength(spellId)
+	local si
+	if type(spellId) == "number" then
+		si = OvaleData.spellInfo[spellId]
+	elseif OvaleData.buffSpellList[spellId] then
+		for auraId in pairs(OvaleData.buffSpellList[spellId]) do
+			si = OvaleData.spellInfo[auraId]
+			if si then break end
+		end
+	end
+	if si then
+		local tick = si.tick or 3
+		local hasteMultiplier = 1
+		if si.haste then
+			if si.haste == "spell" then
+				hasteMultiplier = OvalePaperDoll:GetSpellHasteMultiplier()
+			elseif si.haste == "melee" then
+				hasteMultiplier = OvalePaperDoll:GetMeleeHasteMultiplier()
+			end
+			return tick / hasteMultiplier
+		else
+			return tick
+		end
+	end
+end
+
 function OvaleAura:Debug()
 	self_pool:Debug()
 	self_aura_pool:Debug()
diff --git a/OvaleBestAction.lua b/OvaleBestAction.lua
index 85bb08e..80f9740 100644
--- a/OvaleBestAction.lua
+++ b/OvaleBestAction.lua
@@ -18,6 +18,7 @@ local OvaleCondition = Ovale.OvaleCondition
 local OvaleData = Ovale.OvaleData
 local OvaleEquipement = Ovale.OvaleEquipement
 local OvalePaperDoll = Ovale.OvalePaperDoll
+local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleStance = Ovale.OvaleStance
 local OvaleState = Ovale.OvaleState

@@ -755,7 +756,7 @@ function OvaleBestAction:GetActionInfo(element)

 	if (element.func == "spell" ) then
 		action = OvaleActionBar:GetForSpell(spellId)
-		if not OvaleData.spellList[spellId] and not action then
+		if not OvaleSpellBook:IsKnownSpell(spellId) and not action then
 			Ovale:Logf("Spell %s not learnt", spellId)
 			return nil
 		end
@@ -788,9 +789,8 @@ function OvaleBestAction:GetActionInfo(element)
 			end
 		end

-		local spellName = OvaleData:GetSpellName(spellId)
 		actionTexture = actionTexture or API_GetSpellTexture(spellId)
-		actionInRange = API_IsSpellInRange(spellName, target)
+		actionInRange = API_IsSpellInRange(OvaleSpellBook:GetSpellName(spellId), target)
 		actionUsable = API_IsUsableSpell(spellId)
 		actionShortcut = nil
 	elseif (element.func=="macro") then
diff --git a/OvaleCompile.lua b/OvaleCompile.lua
index cef8867..b9d51af 100644
--- a/OvaleCompile.lua
+++ b/OvaleCompile.lua
@@ -21,6 +21,7 @@ local OvaleOptions = Ovale.OvaleOptions
 local OvalePaperDoll = Ovale.OvalePaperDoll
 local OvalePool = Ovale.OvalePool
 local OvaleScripts = Ovale.OvaleScripts
+local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleStance = Ovale.OvaleStance

 local ipairs = ipairs
@@ -103,23 +104,16 @@ local function ParseParameters(params)
 end

 local function HasTalent(talentId)
-	if not OvaleData.listeTalentsRemplie then
-		OvaleData:RemplirListeTalents()
-	end
-	if OvaleData.listeTalentsRemplie then
-		if OvaleData.pointsTalent[talentId]~=nil then
-			return OvaleData.pointsTalent[talentId]>0
-		else
-			Ovale:FormatPrint("Unknown talent %s", talentId)
-			return false
-		end
+	if OvaleSpellBook:IsKnownTalent(talentId) then
+		return OvaleSpellBook:GetTalentPoints(talentId) > 0
 	else
+		Ovale:FormatPrint("Unknown talent %s", talentId)
 		return false
 	end
 end

 local function TestConditions(paramList)
-	if paramList.glyph and not OvaleData.glyphs[paramList.glyph] then
+	if paramList.glyph and not OvaleSpellBook:IsActiveGlyph(paramList.glyph) then
 		return false
 	end
 	if paramList.mastery and OvalePaperDoll.specialization ~= paramList.mastery then
@@ -131,7 +125,7 @@ local function TestConditions(paramList)
 			return false
 		end
 	end
-	if paramList.if_spell and not OvaleData.spellList[paramList.if_spell] then
+	if paramList.if_spell and not OvaleSpellBook:IsKnownSpell(paramList.if_spell) then
 		return false
 	end
 	if paramList.talent and not HasTalent(paramList.talent) then
@@ -247,7 +241,7 @@ local function ParseFunction(prefix, func, params)
 		-- is a variant of a spell with the same name as one already in the
 		-- spellbook.  If it is, then add that variant spell ID to our spellList.
 		if OvaleCondition.spellbookConditions[func] then
-			if not OvaleData.spellList[spellId] and not self_missingSpellList[spellId] then
+			if not OvaleSpellBook:IsKnownSpell(spellId) and not self_missingSpellList[spellId] then
 				local spellName
 				if type(spellId) == "number" then
 					spellName = API_GetSpellInfo(spellId)
@@ -682,7 +676,7 @@ end

 local function ParseSpellName(text)
 	local spellId = tonumber(text)
-	local spell = OvaleData:GetSpellName(spellId)
+	local spell = OvaleSpellBook:GetSpellName(spellId)
 	if spell then
 		return '"' .. spell .. '"'
 	else
@@ -802,7 +796,7 @@ local function CompileScript(text)

 	-- Add any missing spells found while compiling the script into the spellbook.
 	for k, v in pairs(self_missingSpellList) do
-		OvaleData.spellList[k] = v
+		OvaleSpellBook:AddSpell(k, v)
 	end
 end
 --</private-static-methods>
diff --git a/OvaleCondition.lua b/OvaleCondition.lua
index 40babff..68a6449 100644
--- a/OvaleCondition.lua
+++ b/OvaleCondition.lua
@@ -26,6 +26,7 @@ local OvaleFuture = Ovale.OvaleFuture
 local OvaleGUID = Ovale.OvaleGUID
 local OvaleLatency = Ovale.OvaleLatency
 local OvalePaperDoll = Ovale.OvalePaperDoll
+local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleSpellDamage = Ovale.OvaleSpellDamage
 local OvaleStance = Ovale.OvaleStance
 local OvaleState = Ovale.OvaleState
@@ -116,7 +117,7 @@ local function IsSameSpell(spellIdA, spellIdB, spellNameB)
 	if spellIdB then
 		return spellIdA == spellIdB
 	elseif spellIdA and spellNameB then
-		return OvaleData:GetSpellName(spellIdA) == spellNameB
+		return OvaleSpellBook:GetSpellName(spellIdA) == spellNameB
 	else
 		return false
 	end
@@ -1093,7 +1094,7 @@ OvaleCondition.conditions.casting = function(condition)
 		return nil
 	elseif spellId == "harmful" then
 		if not castSpellName then
-			castSpellName = OvaleData:GetSpellName(castSpellId)
+			castSpellName = OvaleSpellBook:GetSpellName(castSpellId)
 		end
 		if API_IsHarmfulSpell(castSpellName) then
 			return start, ending
@@ -1102,7 +1103,7 @@ OvaleCondition.conditions.casting = function(condition)
 		end
 	elseif spellId == "helpful" then
 		if not castSpellName then
-			castSpellName = OvaleData:GetSpellName(castSpellId)
+			castSpellName = OvaleSpellBook:GetSpellName(castSpellId)
 		end
 		if API_IsHelpfulSpell(castSpellName) then
 			return start, ending
@@ -1639,7 +1640,7 @@ end
 --     Spell(savage_roar)

 OvaleCondition.conditions.glyph = function(condition)
-	return TestBoolean(OvaleData.glyphs[condition[1]], condition[2])
+	return TestBoolean(OvaleSpellBook:IsActiveGlyph(condition[1]), condition[2])
 end

 --- Test if the player has full control, i.e., isn't feared, charmed, etc.
@@ -1852,7 +1853,7 @@ end
 --     Spell(kick)

 OvaleCondition.conditions.inrange = function(condition)
-	local spellName = OvaleData:GetSpellName(condition[1])
+	local spellName = OvaleSpellBook:GetSpellName(condition[1])
 	return TestBoolean(API_IsSpellInRange(spellName, GetTarget(condition)) == 1,condition[2])
 end

@@ -2948,7 +2949,7 @@ OvaleCondition.conditions.critchance = OvaleCondition.conditions.spellcritchance
 --     Spell(guardian_of_ancient_kings_retribution)

 OvaleCondition.conditions.spellknown = function(condition)
-	return TestBoolean(OvaleData.spellList[condition[1]], condition[2])
+	return TestBoolean(OvaleSpellBook:IsKnownSpell(condition[1]), condition[2])
 end
 OvaleCondition.spellbookConditions.spellknown = true

@@ -3021,17 +3022,18 @@ OvaleCondition.spellbookConditions.spellchargecooldown = true
 --     Spell(devouring_plague)

 OvaleCondition.conditions.spellcooldown = function(condition)
-	if type(condition[1]) == "string" then
-		local sharedCd = OvaleState.state.cd[condition[1]]
+	local spellId = condition[1]
+	if type(spellId) == "string" then
+		local sharedCd = OvaleState.state.cd[spellId]
 		if sharedCd then
 			return 0, nil, sharedCd.duration, sharedCd.start, -1
 		else
 			return nil
 		end
-	elseif not OvaleData.spellList[condition[1]] then
+	elseif not OvaleSpellBook:IsKnownSpell(spellId) then
 		return 0, nil, 0, OvaleState.currentTime + 3600, -1
 	else
-		local actionCooldownStart, actionCooldownDuration, actionEnable = OvaleState:GetComputedSpellCD(condition[1])
+		local actionCooldownStart, actionCooldownDuration, actionEnable = OvaleState:GetComputedSpellCD(spellId)
 		return 0, nil, actionCooldownDuration, actionCooldownStart, -1
 	end
 end
@@ -3152,7 +3154,7 @@ end
 -- if TalentPoints(blood_tap_talent) Spell(blood_tap)

 OvaleCondition.conditions.talentpoints = function(condition)
-	return Compare(OvaleData:GetTalentPoints(condition[1]), condition[2], condition[3])
+	return Compare(OvaleSpellBook:GetTalentPoints(condition[1]), condition[2], condition[3])
 end

 --- Test if the player is the in-game target of the target.
@@ -3240,7 +3242,7 @@ OvaleCondition.conditions.ticks = function(condition)
 			numTicks = floor(duration / tick + 0.5)
 		end
 	else
-		duration, tick, numTicks = OvaleData:GetDuration(condition[1], OvaleState.state.combo, OvaleState.state.holy)
+		duration, tick, numTicks = OvaleState:GetDuration(condition[1])
 	end
 	if numTicks then
 		return Compare(numTicks, condition[2], condition[3])
@@ -3307,7 +3309,7 @@ OvaleCondition.conditions.ticktime = function(condition)
 	local start, ending = GetAura(condition, self_auraFound)
 	local tick = self_auraFound.tick
 	if not tick then
-		tick = OvaleData:GetTickLength(condition[1])
+		tick = OvaleAura:GetTickLength(condition[1])
 	end
 	if tick then
 		return Compare(tick, condition[2], condition[3])
@@ -3457,7 +3459,7 @@ OvaleCondition.conditions.totemexpires = function(condition)
 	if not startTime then
 		return 0
 	end
-	if condition.totem and OvaleData:GetSpellName(condition.totem) ~= totemName then
+	if condition.totem and OvaleSpellBook:GetSpellName(condition.totem) ~= totemName then
 		return 0
 	end
 	return AddToTime(startTime + duration, -(condition[2] or 0))
@@ -3485,7 +3487,7 @@ OvaleCondition.conditions.totempresent = function(condition)
 	if not startTime then
 		return nil
 	end
-	if condition.totem and OvaleData:GetSpellName(condition.totem) ~= totemName then
+	if condition.totem and OvaleSpellBook:GetSpellName(condition.totem) ~= totemName then
 		return nil
 	end
 	return startTime, startTime + duration
@@ -3495,7 +3497,7 @@ end
 	-- 1: the spell id
 	-- return bool
 OvaleCondition.conditions.tracking = function(condition)
-	local what = OvaleData:GetSpellName(condition[1])
+	local what = OvaleSpellBook:GetSpellName(condition[1])
 	local numTrackingTypes = API_GetNumTrackingTypes()
 	local present = false
 	for i = 1, numTrackingTypes do
diff --git a/OvaleData.lua b/OvaleData.lua
index 5e79a41..4280f5e 100644
--- a/OvaleData.lua
+++ b/OvaleData.lua
@@ -9,31 +9,13 @@
 --]]--------------------------------------------------------------------

 local _, Ovale = ...
-local OvaleData = Ovale:NewModule("OvaleData", "AceEvent-3.0")
+local OvaleData = {}
 Ovale.OvaleData = OvaleData

 --<private-static-properties>
-local OvalePaperDoll = Ovale.OvalePaperDoll
-local OvaleStance = Ovale.OvaleStance
-
 local floor = math.floor
-local ipairs = ipairs
 local pairs = pairs
-local tinsert = table.insert
-local tonumber = tonumber
-local tostring = tostring
-local tsort = table.sort
-local wipe = table.wipe
-local API_GetNumGlyphSockets = GetNumGlyphSockets
-local API_GetGlyphSocketInfo = GetGlyphSocketInfo
 local API_GetSpellCooldown = GetSpellCooldown
-local API_GetSpellBookItemInfo = GetSpellBookItemInfo
-local API_GetSpellBookItemName = GetSpellBookItemName
-local API_GetSpellInfo = GetSpellInfo
-local API_GetSpellTabInfo = GetSpellTabInfo
-local API_GetTalentInfo = GetTalentInfo
-local API_HasPetSpells = HasPetSpells
-local BOOKTYPE_SPELL, BOOKTYPE_PET = BOOKTYPE_SPELL, BOOKTYPE_PET
 local SPELL_POWER_ALTERNATE_POWER = SPELL_POWER_ALTERNATE_POWER
 local SPELL_POWER_BURNING_EMBERS = SPELL_POWER_BURNING_EMBERS
 local SPELL_POWER_CHI = SPELL_POWER_CHI
@@ -75,18 +57,7 @@ self_buffNoSnapshotSpellList =
 --</private-static-properties>

 --<public-static-properties>
-OvaleData.spellList = {}
 OvaleData.itemList = {}
---allows to fill the player talent tables on first use
-OvaleData.listeTalentsRemplie = false
---key: talentId / value: points in this talent
-OvaleData.pointsTalent = {}
---key: talentId / value: talent name (not used)
-OvaleData.talentIdToName = {}
---key: talent name / value: talent id
-OvaleData.talentNameToId = {}
---active glyphs: self.glyphs[glyphId] is true if the given glyphId is active
-OvaleData.glyphs = {}
 --spell info from the current script (by spellId)
 OvaleData.spellInfo = {}
 --spells that count for scoring
@@ -110,6 +81,11 @@ OvaleData.power =
 }
 OvaleData.secondaryPower = {"rage", "focus", "shards", "holy", "chi", "shadoworbs", "burningembers", "demonicfury"}
 OvaleData.powerType = {}
+do
+	for k,v in pairs(OvaleData.power) do
+		OvaleData.powerType[v.id] = k
+	end
+end

 OvaleData.buffSpellList =
 {
@@ -308,123 +284,6 @@ OvaleData.buffSpellList.heroism = OvaleData.buffSpellList.burst_haste
 --</public-static-properties>

 --<public-static-methods>
-function OvaleData:OnInitialize()
-	for k,v in pairs(self.power) do
-		self.powerType[v.id] = k
-	end
-end
-
-function OvaleData:OnEnable()
-	self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "Update")
-	self:RegisterEvent("CHARACTER_POINTS_CHANGED", "RemplirListeTalents")
-	self:RegisterEvent("GLYPH_ADDED", "UpdateGlyphs")
-	self:RegisterEvent("GLYPH_DISABLED", "UpdateGlyphs")
-	self:RegisterEvent("GLYPH_ENABLED", "UpdateGlyphs")
-	self:RegisterEvent("GLYPH_REMOVED", "UpdateGlyphs")
-	self:RegisterEvent("GLYPH_UPDATED", "UpdateGlyphs")
-	self:RegisterEvent("PLAYER_ALIVE", "Update")
-	self:RegisterEvent("PLAYER_ENTERING_WORLD", "Update")
-	self:RegisterEvent("PLAYER_TALENT_UPDATE", "RemplirListeTalents")
-	self:RegisterEvent("SPELLS_CHANGED", "FillSpellList")
-	self:RegisterEvent("UNIT_PET", "FillPetSpellList")
-end
-
-function OvaleData:OnDisable()
-	self:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
-	self:UnregisterEvent("CHARACTER_POINTS_CHANGED")
-	self:UnregisterEvent("GLYPH_ADDED")
-	self:UnregisterEvent("GLYPH_DISABLED")
-	self:UnregisterEvent("GLYPH_ENABLED")
-	self:UnregisterEvent("GLYPH_REMOVED")
-	self:UnregisterEvent("GLYPH_UPDATED")
-	self:UnregisterEvent("PLAYER_ALIVE")
-	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
-	self:UnregisterEvent("PLAYER_TALENT_UPDATE")
-	self:UnregisterEvent("SPELLS_CHANGED")
-	self:UnregisterEvent("UNIT_PET")
-end
-
-function OvaleData:Update()
-	self:RemplirListeTalents()
-	self:UpdateGlyphs()
-	self:FillSpellList()
-end
-
-function OvaleData:GetSpellName(spellId)
-	if not spellId then return nil end
-	return self.spellList[spellId] or API_GetSpellInfo(spellId)
-end
-
-function OvaleData:FillPetSpellList()
-	--TODO pas moyen d'avoir le nombre de skills pour le pet
-	local book=BOOKTYPE_PET
-	local numSpells, _ = API_HasPetSpells()
-	if not numSpells then return end
-	local i=1
-	while i <= numSpells do
-		local skillType, spellId = API_GetSpellBookItemInfo(i, book)
-		if skillType~="FUTURESPELL" and spellId then
-			local spellName = API_GetSpellBookItemName(i, book)
-			self.spellList[spellId] = spellName
-		end
-		i = i + 1
-	end
-end
-
-function OvaleData:FillSpellList()
-	self.spellList = {}
-
-	--TODO pas moyen d'avoir le nombre de skills pour le pet
-	local book=BOOKTYPE_SPELL
-	local name, texture, offset, numSpells, isGuild = API_GetSpellTabInfo(2)
-
-	numSpells = numSpells + offset
-
-	local i=1
-	while i <= numSpells do
-		local skillType, spellId = API_GetSpellBookItemInfo(i, book)
-		if skillType~="FUTURESPELL" and spellId then
-			local spellName = API_GetSpellBookItemName(i, book)
-			self.spellList[spellId] = spellName
-		end
-		i = i + 1
-	end
-	self:FillPetSpellList()
-	self:SendMessage("Ovale_SpellsChanged")
-end
-
-function OvaleData:RemplirListeTalents()
-	local talentId = 1
-	local talentsChanged = false
-	while true do
-		local name, texture, tier, column, selected, available = API_GetTalentInfo(talentId)
-		if not name then
-			break
-		end
-		talentId = tonumber(talentId)
-		self.talentIdToName[talentId] = name
-		self.talentNameToId[name] = talentId
-		if selected then
-			self.pointsTalent[talentId] = 1
-		else
-			self.pointsTalent[talentId] = 0
-		end
-		self.listeTalentsRemplie = true
-		talentsChanged = true
-		talentId = talentId + 1
-	end
-	if talentsChanged then
-		self:SendMessage("Ovale_TalentsChanged")
-	end
-end
-
-function OvaleData:GetTalentPoints(talentId)
-	if not self.listeTalentsRemplie then
-		self:RemplirListeTalents()
-	end
-	return self.pointsTalent[talentId]
-end
-
 function OvaleData:GetSpellInfo(spellId)
 	if (not self.spellInfo[spellId]) then
 		self.spellInfo[spellId] =
@@ -436,17 +295,6 @@ function OvaleData:GetSpellInfo(spellId)
 	return self.spellInfo[spellId]
 end

-function OvaleData:UpdateGlyphs()
-	wipe(self.glyphs)
-	for i = 1, API_GetNumGlyphSockets() do
-		local enabled, _, _, glyphSpell, _ = API_GetGlyphSocketInfo(i)
-		if enabled and glyphSpell then
-			self.glyphs[glyphSpell] = true
-		end
-	end
-	self:SendMessage("Ovale_GlyphsChanged")
-end
-
 function OvaleData:ResetSpellInfo()
 	self.spellInfo = {}
 end
@@ -461,45 +309,6 @@ function OvaleData:NeedNewSnapshot(auraSpellId, spellId)
 	return true
 end

-function OvaleData:GetGCD(spellId)
-	if spellId and self.spellInfo[spellId] then
-		local si = self.spellInfo[spellId]
-		if si.haste then
-			local cd = si.gcd or 1.5
-			if si.haste == "melee" then
-				cd = cd / OvalePaperDoll:GetMeleeHasteMultiplier()
-			elseif si.haste == "spell" then
-				cd = cd / OvalePaperDoll:GetSpellHasteMultiplier()
-			end
-			if cd < 1 then
-				cd = 1
-			end
-			return cd
-		elseif si.gcd then
-			return si.gcd
-		end
-	end
-
-	-- Default value
-	local class = OvalePaperDoll.class
-	if class == "DEATHKNIGHT" or class == "ROGUE"
-		or (class == "MONK"
-			and (OvaleStance:IsStance("monk_stance_of_the_sturdy_ox")
-				or OvaleStance:IsStance("monk_stance_of_the_fierce_tiger")))
-		or (class == "DRUID" and OvaleStance:IsStance("druid_cat_form")) then
-		return 1.0
-	elseif class == "MAGE" or class == "WARLOCK" or class == "PRIEST" or
-			(class == "DRUID" and not OvaleStance:IsStance("druid_bear_form")) then
-		local cd = 1.5 / OvalePaperDoll:GetSpellHasteMultiplier()
-		if cd < 1 then
-			cd = 1
-		end
-		return cd
-	else
-		return 1.5
-	end
-end
-
 --Compute the spell Cooldown
 function OvaleData:GetSpellCD(spellId)
 	local actionCooldownStart, actionCooldownDuration, actionEnable = API_GetSpellCooldown(spellId)
@@ -541,105 +350,4 @@ function OvaleData:GetDamage(spellId, attackpower, spellpower, mainHandWeaponDam
 	end
 	return damage
 end
-
--- Returns the duration, tick length, and number of ticks of an aura.
-function OvaleData:GetDuration(spellId, combo, holy)
-	local si
-	if type(spellId) == "number" then
-		si = self.spellInfo[spellId]
-	elseif OvaleData.buffSpellList[spellId] then
-		for auraId in pairs(OvaleData.buffSpellList[spellId]) do
-			si = self.spellInfo[auraId]
-			if si then
-				spellId = auraId
-				break
-			end
-		end
-	end
-	if si and si.duration then
-		local duration = si.duration
-		combo = combo or 0
-		holy = holy or 1
-		if si.adddurationcp then
-			duration = duration + si.adddurationcp * combo
-		end
-		if si.adddurationholy then
-			duration = duration + si.adddurationholy * (holy - 1)
-		end
-		if si.tick then	-- DoT
-			--DoT duration is tick * numberOfTicks.
-			local tick = self:GetTickLength(spellId)
-			local numTicks = floor(duration / tick + 0.5)
-			duration = tick * numTicks
-			return duration, tick, numTicks
-		end
-		return duration
-	else
-		return nil
-	end
-end
-
-function OvaleData:GetTickLength(spellId)
-	local si
-	if type(spellId) == "number" then
-		si = self.spellInfo[spellId]
-	elseif OvaleData.buffSpellList[spellId] then
-		for auraId in pairs(OvaleData.buffSpellList[spellId]) do
-			si = self.spellInfo[auraId]
-			if si then break end
-		end
-	end
-	if si then
-		local tick = si.tick or 3
-		local hasteMultiplier = 1
-		if si.haste then
-			if si.haste == "spell" then
-				hasteMultiplier = OvalePaperDoll:GetSpellHasteMultiplier()
-			elseif si.haste == "melee" then
-				hasteMultiplier = OvalePaperDoll:GetMeleeHasteMultiplier()
-			end
-			return tick / hasteMultiplier
-		else
-			return tick
-		end
-	else
-		return nil
-	end
-end
-
--- Print out the list of active glyphs in alphabetical order.
-function OvaleData:DebugGlyphs()
-	local array = {}
-	for glyphId in pairs(self.glyphs) do
-		tinsert(array, self:GetSpellName(glyphId) .. ": " .. glyphId)
-	end
-	tsort(array)
-	for _, v in ipairs(array) do
-		Ovale:Print(v)
-	end
-end
-
--- Print out the list of known spells in alphabetical order.
-function OvaleData:DebugSpellList()
-	local array = {}
-	for k, v in pairs(self.spellList) do
-		tinsert(array, v .. ": " .. k)
-	end
-	tsort(array)
-	for _, v in ipairs(array) do
-		Ovale:Print(v)
-	end
-end
-
--- Print out the list of talents in alphabetical order.
-function OvaleData:DebugTalents()
-	local array = {}
-	for name, id in pairs(self.talentNameToId) do
-		tinsert(array, name .. " = " .. id)
-	end
-	tsort(array)
-	for _, v in ipairs(array) do
-		Ovale:Print(v)
-	end
-end
 --</public-static-methods>
diff --git a/OvaleFrame.lua b/OvaleFrame.lua
index 4926ffd..ffaa159 100644
--- a/OvaleFrame.lua
+++ b/OvaleFrame.lua
@@ -18,7 +18,6 @@ do
 	local OvaleBestAction = Ovale.OvaleBestAction
 	local OvaleCompile = Ovale.OvaleCompile
 	local OvaleCondition = Ovale.OvaleCondition
-	local OvaleData = Ovale.OvaleData
 	local OvaleGUID = Ovale.OvaleGUID
 	local OvaleOptions = Ovale.OvaleOptions
 	local OvaleState = Ovale.OvaleState
@@ -257,7 +256,7 @@ do
 									castTime = _castTime/1000
 								end
 							end
-							local gcd = OvaleData:GetGCD(spellId)
+							local gcd = OvaleState:GetGCD(spellId)
 							local nextCast
 							if (castTime>gcd) then
 								nextCast = start + castTime
diff --git a/OvaleFuture.lua b/OvaleFuture.lua
index cc08ee1..d450862 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 OvaleSpellBook = Ovale.OvaleSpellBook

 local ipairs = ipairs
 local pairs = pairs
@@ -76,7 +77,7 @@ local function TracePrintf(spellId, ...)
 	if self.traceSpellList then
 		local name = spellId
 		if type(spellId) == "number" then
-			name = OvaleData:GetSpellName(spellId)
+			name = OvaleSpellBook:GetSpellName(spellId)
 		end
 		if self.traceSpellList[spellId] or self.traceSpellList[name] then
 			local now = API_GetTime()
@@ -132,13 +133,14 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a
 	spellcast.allowRemove = allowRemove

 	-- Set the target from the data taken from UNIT_SPELLCAST_SENT if it's the same spellcast.
-	if lineId == self_lastLineID and OvaleData:GetSpellName(spellId) == self_lastSpell then
+	local spellName = OvaleSpellBook:GetSpellName(spellId)
+	if lineId == self_lastLineID and spellName == self_lastSpell then
 		spellcast.target = self_lastTarget
 	else
 		spellcast.target = API_UnitGUID("target")
 	end
 	TracePrintf(spellId, "    AddSpellToQueue: %s (%d), lineId=%d, startTime=%f, endTime=%f, target=%s",
-		OvaleData:GetSpellName(spellId), spellId, lineId, startTime, endTime, spellcast.target)
+		spellName, spellId, lineId, startTime, endTime, spellcast.target)

 	-- Snapshot the current stats for the spellcast.
 	OvalePaperDoll:SnapshotStats(spellcast)
@@ -196,7 +198,7 @@ local function RemoveSpellFromQueue(spellId, lineId)
 	local self = OvaleFuture
 	for index, spellcast in ipairs(self_activeSpellcast) do
 		if spellcast.lineId == lineId then
-			TracePrintf(spellId, "    RemoveSpellFromQueue: %s (%d)", OvaleData:GetSpellName(spellId), spellId)
+			TracePrintf(spellId, "    RemoveSpellFromQueue: %s (%d)", OvaleSpellBook:GetSpellName(spellId), spellId)
 			tremove(self_activeSpellcast, index)
 			self_pool:Release(spellcast)
 			break
@@ -234,7 +236,7 @@ local function UpdateLastSpellInfo(spellcast)
 				OvalePaperDoll:SnapshotStats(spellcast)
 				spellcast.damageMultiplier = GetDamageMultiplier(spellId)
 				TracePrintf(spellId, "    Updated spell info for %s (%d) to snapshot from %f.",
-					OvaleData:GetSpellName(spellId), spellId, spellcast.snapshotTime)
+					OvaleSpellBook:GetSpellName(spellId), spellId, spellcast.snapshotTime)
 			end
 		end

@@ -501,7 +503,7 @@ function OvaleFuture:Debug()
 		Ovale:Print("No spells in flight!")
 	end
 	for _, spellcast in ipairs(self_activeSpellcast) do
-		Ovale:FormatPrint("    %s (%d), lineId=%s", OvaleData:GetSpellName(spellcast.spellId), spellcast.spellId, spellcast.lineId)
+		Ovale:FormatPrint("    %s (%d), lineId=%s", OvaleSpellBook:GetSpellName(spellcast.spellId), spellcast.spellId, spellcast.lineId)
 	end
 end
 --</public-static-methods>
diff --git a/OvaleIcone.lua b/OvaleIcone.lua
index e4234aa..a1960c7 100644
--- a/OvaleIcone.lua
+++ b/OvaleIcone.lua
@@ -14,8 +14,8 @@ local _, Ovale = ...

 --<private-static-properties>
 local L = Ovale.L
-local OvaleData = Ovale.OvaleData
 local OvaleOptions = Ovale.OvaleOptions
+local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleState = Ovale.OvaleState

 local strfind = string.find
@@ -203,7 +203,7 @@ local function SetParams(self, params, secure)
 				Ovale:FormatPrint("%stype%s", prefix, suffix)
 				self:SetAttribute(prefix .. "type" .. suffix, "spell")
 				self:SetAttribute("unit", self.params.target or "target")
-				self:SetAttribute(k, OvaleData.spellList[v])
+				self:SetAttribute(k, OvaleSpellBook:GetSpellName(v))
 				self.actionButton = true
 			end
 		end
@@ -235,7 +235,7 @@ function OvaleIcone_OnEnter(self)
 			GameTooltip:SetText(L[self.help])
 		end
 		if self.spellId then
-			GameTooltip:AddLine(OvaleData:GetSpellName(self.spellId), 0.5, 1, 0.75)
+			GameTooltip:AddLine(OvaleSpellBook:GetSpellName(self.spellId), 0.5, 1, 0.75)
 		end
 		if next(Ovale.casesACocher) or next(Ovale.listes) then
 			GameTooltip:AddLine(L["Cliquer pour afficher/cacher les options"],1,1,1)
diff --git a/OvaleOptions.lua b/OvaleOptions.lua
index a5c9825..f33cf17 100644
--- a/OvaleOptions.lua
+++ b/OvaleOptions.lua
@@ -18,6 +18,7 @@ Ovale.OvaleOptions = OvaleOptions
 local L = Ovale.L
 local OvalePaperDoll = Ovale.OvalePaperDoll
 local OvaleScripts = Ovale.OvaleScripts
+local OvaleSpellBook = Ovale.OvaleSpellBook

 local strgmatch = string.gmatch
 local strgsub = string.gsub
@@ -607,9 +608,7 @@ local self_options =
 					order = -4,
 					name = "List talent id",
 					type = "execute",
-					func = function()
-						if Ovale.OvaleData then Ovale.OvaleData:DebugTalents() end
-					end
+					func = function() OvaleSpellBook:DebugTalents() end
 				},
 				targetbuff =
 				{
@@ -642,18 +641,14 @@ local self_options =
 					order = -7,
 					name = "List player glyphs",
 					type = "execute",
-					func = function()
-						if Ovale.OvaleData then Ovale.OvaleData:DebugGlyphs() end
-					end
+					func = function() OvaleSpellBook:DebugGlyphs() end
 				},
 				spell =
 				{
 					order = -8,
 					name = "List player spells",
 					type = "execute",
-					func = function()
-						if Ovale.OvaleData then Ovale.OvaleData:DebugSpellList() end
-					end
+					func = function() OvaleSpellBook:DebugSpells() end
 				},
 				stance =
 				{
diff --git a/OvaleSpellBook.lua b/OvaleSpellBook.lua
new file mode 100644
index 0000000..76edf26
--- /dev/null
+++ b/OvaleSpellBook.lua
@@ -0,0 +1,242 @@
+--[[--------------------------------------------------------------------
+    Ovale Spell Priority
+    Copyright (C) 2012 Sidoine
+    Copyright (C) 2012, 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.
+--]]--------------------------------------------------------------------
+
+-- This addon tracks the player's active spells, talents, and glyphs.
+
+local _, Ovale = ...
+local OvaleSpellBook = Ovale:NewModule("OvaleSpellBook", "AceEvent-3.0")
+Ovale.OvaleSpellBook = OvaleSpellBook
+
+--<private-static-properties>
+local ipairs = ipairs
+local pairs = pairs
+local tinsert = table.insert
+local tostring = tostring
+local tsort = table.sort
+local wipe = table.wipe
+local API_GetNumGlyphSockets = GetNumGlyphSockets
+local API_GetGlyphSocketInfo = GetGlyphSocketInfo
+local API_GetSpellBookItemInfo = GetSpellBookItemInfo
+local API_GetSpellBookItemName = GetSpellBookItemName
+local API_GetSpellInfo = GetSpellInfo
+local API_GetSpellTabInfo = GetSpellTabInfo
+local API_GetTalentInfo = GetTalentInfo
+local API_HasPetSpells = HasPetSpells
+local BOOKTYPE_PET = BOOKTYPE_PET
+local BOOKTYPE_SPELL = BOOKTYPE_SPELL
+
+-- spell[spellId] = spellName
+self_spell = {}
+-- talent[talentId] = talentName
+self_talent = {}
+-- talentPoints[talentId] = 0 or 1
+self_talentPoints = {}
+-- glyph[glyphSpellId] = glyphName
+self_glyph = {}
+--</private-static-properties>
+
+--<private-static-methods>
+local function PrintTableValues(tbl)
+	local array = {}
+	for k, v in pairs(tbl) do
+		tinsert(array, tostring(v) .. ": " .. tostring(k))
+	end
+	tsort(array)
+	for _, v in ipairs(array) do
+		Ovale:Print(v)
+	end
+end
+--</private-static-methods>
+
+--<public-static-methods>
+function OvaleSpellBook:OnEnable()
+	self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "Update")
+	self:RegisterEvent("CHARACTER_POINTS_CHANGED", "UpdateTalents")
+	self:RegisterEvent("GLYPH_ADDED", "UpdateGlyphs")
+	self:RegisterEvent("GLYPH_DISABLED", "UpdateGlyphs")
+	self:RegisterEvent("GLYPH_ENABLED", "UpdateGlyphs")
+	self:RegisterEvent("GLYPH_REMOVED", "UpdateGlyphs")
+	self:RegisterEvent("GLYPH_UPDATED", "UpdateGlyphs")
+	self:RegisterEvent("PLAYER_ALIVE", "Update")
+	self:RegisterEvent("PLAYER_ENTERING_WORLD", "Update")
+	self:RegisterEvent("PLAYER_TALENT_UPDATE", "UpdateTalents")
+	self:RegisterEvent("SPELLS_CHANGED", "UpdateSpells")
+	self:RegisterEvent("UNIT_PET")
+end
+
+function OvaleSpellBook:OnDisable()
+	self:UnregisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
+	self:UnregisterEvent("CHARACTER_POINTS_CHANGED")
+	self:UnregisterEvent("GLYPH_ADDED")
+	self:UnregisterEvent("GLYPH_DISABLED")
+	self:UnregisterEvent("GLYPH_ENABLED")
+	self:UnregisterEvent("GLYPH_REMOVED")
+	self:UnregisterEvent("GLYPH_UPDATED")
+	self:UnregisterEvent("PLAYER_ALIVE")
+	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
+	self:UnregisterEvent("PLAYER_TALENT_UPDATE")
+	self:UnregisterEvent("SPELLS_CHANGED")
+	self:UnregisterEvent("UNIT_PET")
+end
+
+-- Update spells if the player's pet is summoned or dismissed.
+function OvaleSpellBook:UNIT_PET(unitId)
+	if unitId == "player" then
+		UpdateSpells()
+	end
+end
+
+function OvaleSpellBook:Update()
+	self:UpdateTalents()
+	self:UpdateGlyphs()
+	self:UpdateSpells()
+end
+
+-- Update the player's talents by scanning the talent tab for the active specialization.
+-- Store the number of points assigned to each talent.
+function OvaleSpellBook:UpdateTalents()
+	wipe(self_talent)
+	wipe(self_talentPoints)
+
+	local name, selected
+	local i = 1
+	while true do
+		name, _, _, _, selected, _ = API_GetTalentInfo(i)
+		if not name then break end
+		self_talent[i] = name
+		if selected then
+			self_talentPoints[i] = 1
+		else
+			self_talentPoints[i] = 0
+		end
+		i = i + 1
+	end
+	self:SendMessage("Ovale_TalentsChanged")
+end
+
+-- Update the player's glyphs by scanning the glyph socket tab for the active specialization.
+function OvaleSpellBook:UpdateGlyphs()
+	wipe(self_glyph)
+
+	local enabled, glyphSpell
+	for i = 1, API_GetNumGlyphSockets() do
+		enabled, _, _, glyphSpell, _ = API_GetGlyphSocketInfo(i)
+		if enabled and glyphSpell then
+			self_glyph[glyphSpell] = API_GetSpellInfo(glyphSpell)
+		end
+	end
+	self:SendMessage("Ovale_GlyphsChanged")
+end
+
+-- Update the player's spells by scanning the first two tabs of the spellbook.
+function OvaleSpellBook:UpdateSpells()
+	wipe(self_spell)
+
+	local name, _, offset, numSpells = API_GetSpellTabInfo(2)
+	if name then
+		local skillType, spellId
+		local spellName
+		for i = 1, offset + numSpells do
+			skillType, spellId = API_GetSpellBookItemInfo(i, BOOKTYPE_SPELL)
+			if spellId and skillType ~= "FUTURESPELL" then
+				spellName = API_GetSpellBookItemName(i, BOOKTYPE_SPELL)
+				self_spell[spellId] = spellName
+			end
+		end
+	end
+	self:UpdatePetSpells()
+	self:SendMessage("Ovale_SpellsChanged")
+end
+
+-- Update the player's pet spells by scanning the pet spellbook.
+function OvaleSpellBook:UpdatePetSpells()
+	local hasPetSpells = API_HasPetSpells()
+	if hasPetSpells then
+		local skillType, spellId
+		local spellName
+		local i = 1
+		while true do
+			skillType, spellId = API_GetSpellBookItemInfo(i, BOOKTYPE_PET)
+			if not spellId then break end
+			if skillType ~= "FUTURESPELL" then
+				spellName = API_GetSpellBookItemName(i, BOOKTYPE_PET)
+				self_spell[spellId] = spellName
+			end
+			i = i + 1
+		end
+	end
+end
+
+function OvaleSpellBook:GetSpellName(spellId)
+	if spellId then
+		local name = self_spell[spellId]
+		if not name then
+			name = API_GetSpellInfo(spellId)
+		end
+		return name
+	end
+end
+
+function OvaleSpellBook:GetTalentPoints(talentId)
+	local points = 0
+	if talentId and self_talentPoints[talentId] then
+		points = self_talentPoints[talentId]
+	end
+	return points
+end
+
+function OvaleSpellBook:AddSpell(spellId, name)
+	if spellId and name then
+		self_spell[spellId] = name
+	end
+end
+
+-- Returns true if the given glyph spell Id is an active glyph in the player's glyph tab.
+function OvaleSpellBook:IsActiveGlyph(glyphId)
+	if glyphId and self_glyph[glyphId] then
+		return true
+	else
+		return false
+	end
+end
+
+-- Returns true if the given spellId is found in the player's list of known spells.
+function OvaleSpellBook:IsKnownSpell(spellId)
+	if spellId and self_spell[spellId] then
+		return true
+	else
+		return false
+	end
+end
+
+-- Returns true if the given talentId is found in the player's talent tree.
+function OvaleSpellBook:IsKnownTalent(talentId)
+	if talentId and self_talentPoints[talentId] then
+		return true
+	else
+		return false
+	end
+end
+
+-- Print out the list of active glyphs in alphabetical order.
+function OvaleSpellBook:DebugGlyphs()
+	PrintTableValues(self_glyph)
+end
+
+-- Print out the list of known spells in alphabetical order.
+function OvaleSpellBook:DebugSpells()
+	PrintTableValues(self_spell)
+end
+
+-- Print out the list of talents in alphabetical order.
+function OvaleSpellBook:DebugTalents()
+	PrintTableValues(self_talent)
+end
+--</public-static-methods>
\ No newline at end of file
diff --git a/OvaleState.lua b/OvaleState.lua
index 3674339..483815a 100644
--- a/OvaleState.lua
+++ b/OvaleState.lua
@@ -22,6 +22,7 @@ local OvaleEquipement = Ovale.OvaleEquipement
 local OvaleFuture = Ovale.OvaleFuture
 local OvaleGUID = Ovale.OvaleGUID
 local OvalePaperDoll = Ovale.OvalePaperDoll
+local OvaleSpellBook = Ovale.OvaleSpellBook
 local OvaleStance = Ovale.OvaleStance

 local floor = math.floor
@@ -91,7 +92,7 @@ end
 --<public-static-methods>
 function OvaleState:StartNewFrame()
 	self.maintenant = API_GetTime()
-	self.gcd = OvaleData:GetGCD()
+	self.gcd = self:GetGCD()
 end

 function OvaleState:UpdatePowerRates()
@@ -114,7 +115,7 @@ function OvaleState:UpdatePowerRates()
 				energyRegen = energyRegen / 1.4
 			end
 			-- Ascension (monk): increases energy regen by 15%.
-			if OvaleData:GetTalentPoints(8) > 0 then
+			if OvaleSpellBook:GetTalentPoints(8) > 0 then
 				energyRegen = energyRegen * 1.15
 			end
 			-- Stance of the Sturdy Ox (brewmaster monk): increases Energy regeneration by 10%.
@@ -272,8 +273,8 @@ function OvaleState:ApplySpell(spellId, startCast, endCast, nextCast, nocd, targ
 				if si.eclipsedir then
 					energy = energy * direction
 				end
-				-- Eclipse energy generated is doubled if not in an Eclipse state with Euphoria.
-				if OvaleData.spellList[81062]
+				-- Euphoria: While not in an Eclipse state, your spells generate double the normal amount of Solar or Lunar energy.
+				if OvaleSpellBook:IsKnownSpell(81062)
 						and not self:GetAura("player", LUNAR_ECLIPSE, "HELPFUL", true)
 						and not self:GetAura("player", SOLAR_ECLIPSE, "HELPFUL", true) then
 					energy = energy * 2
@@ -396,7 +397,7 @@ function OvaleState:ApplySpell(spellId, startCast, endCast, nextCast, nocd, targ

 							-- Set the duration to the proper length if it's a DoT.
 							if auraSpellInfo and auraSpellInfo.duration then
-								duration = OvaleData:GetDuration(auraSpellId, self.state.combo, self.state.holy)
+								duration = self:GetDuration(auraSpellId)
 							end

 							-- If aura is specified with a duration, then assume stacks == 1.
@@ -427,7 +428,7 @@ function OvaleState:ApplySpell(spellId, startCast, endCast, nextCast, nocd, targ
 										-- Add new duration after the next tick is complete.
 										local remainingTicks = floor((oldEnding - endCast) / oldTick)
 										newAura.ending = (oldEnding - oldTick * remainingTicks) + duration
-										newAura.tick = OvaleData:GetTickLength(auraSpellId)
+										newAura.tick = OvaleAura:GetTickLength(auraSpellId)
 										-- Re-snapshot stats for the DoT.
 										OvalePaperDoll:SnapshotStats(newAura, stats)
 										newAura.damageMultiplier = self:GetDamageMultiplier(auraSpellId)
@@ -453,7 +454,7 @@ function OvaleState:ApplySpell(spellId, startCast, endCast, nextCast, nocd, targ
 								newAura.start = endCast
 								newAura.ending = endCast + duration
 								if isDoT then
-									newAura.tick = OvaleData:GetTickLength(auraSpellId)
+									newAura.tick = OvaleAura:GetTickLength(auraSpellId)
 									OvalePaperDoll:SnapshotStats(newAura, stats)
 									newAura.damageMultiplier = self:GetDamageMultiplier(auraSpellId)
 								end
@@ -486,6 +487,61 @@ function OvaleState:GetCounterValue(id)
 	end
 end

+-- Return the GCD after the given spellId is cast.
+-- If no spellId is given, then returns the GCD after a "yellow-hit" ability has been cast.
+function OvaleState:GetGCD(spellId)
+	-- Use SpellInfo() information if available.
+	if spellId and OvaleData.spellInfo[spellId] then
+		local si = OvaleData.spellInfo[spellId]
+		if si.haste then
+			local cd = si.gcd or 1.5
+			if si.haste == "melee" then
+				cd = cd / OvalePaperDoll:GetMeleeHasteMultiplier()
+			elseif si.haste == "spell" then
+				cd = cd / OvalePaperDoll:GetSpellHasteMultiplier()
+			end
+			if cd < 1 then
+				cd = 1
+			end
+			return cd
+		elseif si.gcd then
+			return si.gcd
+		end
+	end
+
+	-- Default value.
+	local class = OvalePaperDoll.class
+	local isCaster = false
+	if class == "DRUID" and not (OvaleStance:IsStance("druid_bear_form") or OvaleStance:IsStance("druid_cat_form")) then
+		isCaster = true
+	elseif class == "MAGE" then
+		isCaster = true
+	elseif class == "PRIEST" then
+		isCaster = true
+	elseif class == "SHAMAN" then
+		isCaster = true
+	elseif class == "WARLOCK" then
+		isCaster = true
+	end
+	if isCaster then
+		local cd = 1.5 / OvalePaperDoll:GetSpellHasteMultiplier()
+		if cd < 1 then
+			cd = 1
+		end
+		return cd
+	elseif class == "DEATHKNIGHT" then
+		return 1.0
+	elseif class == "DRUID" and OvaleStance:IsStance("druid_cat_form") then
+		return 1.0
+	elseif class == "MONK" then
+		return 1.0
+	elseif class == "ROGUE" then
+		return 1.0
+	else
+		return 1.5
+	end
+end
+
 function OvaleState:GetCD(spellId)
 	if not spellId then
 		return nil
@@ -742,6 +798,41 @@ function OvaleState:GetRunesCooldown(blood, frost, unholy, death, nodeath)
 	return maxCD
 end

+-- Returns the duration, tick length, and number of ticks of an aura.
+function OvaleState:GetDuration(auraSpellId)
+	local si
+	if type(auraSpellId) == "number" then
+		si = OvaleData.spellInfo[auraSpellId]
+	elseif OvaleData.buffSpellList[auraSpellId] then
+		for spellId in pairs(OvaleData.buffSpellList[auraSpellId]) do
+			si = OvaleData.spellInfo[spellId]
+			if si then
+				auraSpellId = spellId
+				break
+			end
+		end
+	end
+	if si and si.duration then
+		local duration = si.duration
+		local combo = self.state.combo or 0
+		local holy = self.state.holy or 1
+		if si.adddurationcp then
+			duration = duration + si.adddurationcp * combo
+		end
+		if si.adddurationholy then
+			duration = duration + si.adddurationholy * (holy - 1)
+		end
+		if si.tick then	-- DoT
+			--DoT duration is tick * numTicks.
+			local tick = OvaleAura:GetTickLength(auraSpellId)
+			local numTicks = floor(duration / tick + 0.5)
+			duration = tick * numTicks
+			return duration, tick, numTicks
+		end
+		return duration
+	end
+end
+
 -- Print out the levels of each power type in the current state.
 function OvaleState:DebugPower()
 	for powerType in pairs(OvaleData.power) do
diff --git a/compiler.pl b/compiler.pl
index a521919..54f3a53 100644
--- a/compiler.pl
+++ b/compiler.pl
@@ -92,6 +92,7 @@ $p{Skada}{total} = true;

 $sp{Ovale}{OvaleBestAction} = true;
 $sp{Ovale}{OvaleCondition} = true;
+$sp{Ovale}{OvaleData} = true;
 $sp{Ovale}{OvaleQueue} = true;
 $sp{Ovale}{OvalePool} = true;
 $sp{Ovale}{OvalePoolGC} = true;