Johnny C. Lam [11-15-13 - 19:54]
diff --git a/Ovale.toc b/Ovale.toc
index b34e42c..b5ffc7f 100644
--- a/Ovale.toc
+++ b/Ovale.toc
@@ -47,6 +47,7 @@ OvaleCooldown.lua
OvaleOptions.lua
OvalePower.lua
OvaleRecount.lua
+OvaleRunes.lua
OvaleSkada.lua
OvaleSpellDamage.lua
OvaleSwing.lua
diff --git a/OvaleBestAction.lua b/OvaleBestAction.lua
index 755901c..fa10f92 100644
--- a/OvaleBestAction.lua
+++ b/OvaleBestAction.lua
@@ -752,7 +752,8 @@ function OvaleBestAction:GetActionInfo(element)
end
if actionCooldownStart and actionCooldownDuration then
if si.blood or si.frost or si.unholy or si.death then
- local runecd = OvaleState:GetRunesCooldown(si.blood, si.frost, si.unholy, si.death, false)
+ -- Spell requires runes.
+ local runecd = state:GetRunesCooldown(si.blood, si.unholy, si.frost, si.death, false)
if runecd > actionCooldownStart + actionCooldownDuration then
actionCooldownDuration = runecd - actionCooldownStart
end
diff --git a/OvaleRunes.lua b/OvaleRunes.lua
new file mode 100644
index 0000000..64c2f10
--- /dev/null
+++ b/OvaleRunes.lua
@@ -0,0 +1,429 @@
+--[[--------------------------------------------------------------------
+ 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 rune information on death knights.
+--]]
+
+local _, Ovale = ...
+local OvaleRunes = Ovale:NewModule("OvaleRunes", "AceEvent-3.0")
+Ovale.OvaleRunes = OvaleRunes
+
+--<private-static-properties>
+local OvaleData = Ovale.OvaleData
+local OvalePaperDoll = Ovale.OvalePaperDoll
+local OvaleSpellBook = Ovale.OvaleSpellBook
+local OvaleStance = Ovale.OvaleStance
+local OvaleState = Ovale.OvaleState
+
+local ipairs = ipairs
+local pairs = pairs
+local select = select
+local API_GetRuneCooldown = GetRuneCooldown
+local API_GetRuneType = GetRuneType
+local API_GetTime = GetTime
+local API_UnitClass = UnitClass
+
+-- Player's class.
+local self_class = select(2, API_UnitClass("player"))
+
+local BLOOD_RUNE = 1
+local UNHOLY_RUNE = 2
+local FROST_RUNE = 3
+local DEATH_RUNE = 4
+
+local RUNE_TYPE = {
+ blood = BLOOD_RUNE,
+ unholy = UNHOLY_RUNE,
+ frost = FROST_RUNE,
+ death = DEATH_RUNE,
+}
+local RUNE_NAME = {}
+do
+ for k, v in pairs(RUNE_TYPE) do
+ RUNE_NAME[v] = k
+ end
+end
+
+--[[
+ Rune slots are numbered as follows in the default UI:
+
+ blood frost unholy
+ [1][2] [5][6] [3][4]
+--]]
+local RUNE_SLOTS = {
+ [BLOOD_RUNE] = { 1, 2 },
+ [UNHOLY_RUNE] = { 3, 4 },
+ [FROST_RUNE] = { 5, 6 },
+}
+
+--[[
+ In-game testing shows that death runes are preferred in the order:
+ Frost death runes > Blood death runes > Unholy death runes
+--]]
+local DEATH_RUNE_PRIORITY = { 5, 6, 1, 2, 4, 5 }
+
+-- Improved Blood Presence increases rune regenerate rate by 20%.
+local IMPROVED_BLOOD_PRESENCE = 50371
+--</private-static-properties>
+
+--<public-static-properties>
+-- Current rune information, indexed by slot.
+OvaleRunes.rune = {}
+OvaleRunes.RUNE_TYPE = RUNE_TYPE
+--</public-static-properties>
+
+--<public-static-methods>
+function OvaleRunes:OnEnable()
+ if self_class == "DEATHKNIGHT" then
+ -- Initialize rune database.
+ for runeType, slots in ipairs(RUNE_SLOTS) do
+ for _, slot in pairs(slots) do
+ self.rune[slot] = { slotType = runeType }
+ end
+ end
+ self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateAllRunes")
+ self:RegisterEvent("PLAYER_LOGIN", "UpdateAllRunes")
+ self:RegisterEvent("RUNE_POWER_UPDATE")
+ self:RegisterEvent("RUNE_TYPE_UPDATE")
+ self:RegisterEvent("UNIT_RANGEDDAMAGE")
+ self:RegisterEvent("UNIT_SPELL_HASTE", "UNIT_RANGEDDAMAGE")
+ OvaleState:RegisterState(self, self.statePrototype)
+ end
+end
+
+function OvaleRunes:OnDisable()
+ if self_class == "DEATHKNIGHT" then
+ self:UnregisterEvent("PLAYER_ENTERING_WORLD")
+ self:UnregisterEvent("PLAYER_LOGIN")
+ self:UnregisterEvent("RUNE_POWER_UPDATE")
+ self:UnregisterEvent("RUNE_TYPE_UPDATE")
+ self:UnregisterEvent("UNIT_RANGEDDAMAGE")
+ self:UnregisterEvent("UNIT_SPELL_HASTE")
+ OvaleState:UnregisterState(self)
+ self.rune = {}
+ end
+end
+
+function OvaleRunes:RUNE_POWER_UPDATE(event, slot, usable)
+ self:UpdateRune(slot)
+end
+
+function OvaleRunes:RUNE_TYPE_UPDATE(event, slot)
+ self:UpdateRune(slot)
+end
+
+function OvaleRunes:UNIT_RANGEDDAMAGE(event, unitId)
+ if unitId == "player" then
+ self:UpdateAllRunes()
+ end
+end
+
+function OvaleRunes:UpdateRune(slot)
+ local rune = self.rune[slot]
+ local runeType = API_GetRuneType(slot)
+ local start, duration, runeReady = API_GetRuneCooldown(slot)
+ rune.type = runeType
+ if start > 0 then
+ -- Rune is on cooldown.
+ rune.startCooldown = start
+ rune.endCooldown = start + duration
+ else
+ -- Rune is active.
+ rune.startCooldown = 0
+ rune.endCooldown = 0
+ end
+ rune.active = runeReady
+end
+
+function OvaleRunes:UpdateAllRunes()
+ for slot = 1, 6 do
+ self:UpdateRune(slot)
+ end
+end
+
+function OvaleRunes:Debug()
+ local now = API_GetTime()
+ for slot = 1, 6 do
+ local rune = self.rune[slot]
+ if rune.active then
+ Ovale:FormatPrint("rune[%d] (%s) is active.", slot, RUNE_NAME[rune.type])
+ else
+ Ovale:FormatPrint("rune[%d] (%s) comes off cooldown in %f seconds.", slot, RUNE_NAME[rune.type], rune.endCooldown - now)
+ end
+ end
+end
+--</public-static-methods>
+
+--[[----------------------------------------------------------------------------
+ State machine for simulator.
+--]]----------------------------------------------------------------------------
+
+--<public-static-properties>
+OvaleRunes.statePrototype = {
+ rune = nil, -- indexed by slot (1 through 6)
+}
+--</public-static-properties>
+
+--<public-static-methods>
+-- Initialize the state.
+function OvaleRunes:InitializeState(state)
+ state.rune = {}
+ for slot = 1, 6 do
+ state.rune[slot] = {}
+ end
+end
+
+-- Reset the state to the current conditions.
+function OvaleRunes:ResetState(state)
+ for slot = 1, 6 do
+ local rune = state.rune[slot]
+ for k, v in pairs(self.rune[slot]) do
+ rune[k] = v
+ end
+ end
+end
+
+-- Apply the effects of the spell on the player's state, assuming the spellcast completes.
+function OvaleRunes:ApplySpellOnPlayer(state, spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
+ -- If the spellcast has already ended, then the effects on the player have already occurred.
+ if endCast <= OvaleState.now then
+ return
+ end
+
+ local si = OvaleData.spellInfo[spellId]
+ if si then
+ for i, name in ipairs(RUNE_NAME) do
+ local count = si[name] or 0
+ while count > 0 do
+ state:ConsumeRune(endCast, name)
+ count = count - 1
+ end
+ end
+ end
+end
+--</public-static-methods>
+
+-- Mix-in methods for simulator state.
+do
+ local statePrototype = OvaleRunes.statePrototype
+
+ function statePrototype:DebugRunes()
+ local now = OvaleState.currentTime
+ for slot = 1, 6 do
+ local rune = self.rune[slot]
+ if rune.active then
+ Ovale:FormatPrint("rune[%d] (%s) is active.", slot, RUNE_NAME[rune.type])
+ else
+ Ovale:FormatPrint("rune[%d] (%s) comes off cooldown in %f seconds.", slot, RUNE_NAME[rune.type], rune.endCooldown - now)
+ end
+ end
+ end
+
+ -- Consume a rune of the given type. Assume that the required runes are available.
+ function statePrototype:ConsumeRune(atTime, name)
+ local state = self
+ --[[
+ Find a usable rune, preferring a regular rune of that rune type over death
+ runes of that rune type over death runes of any rune type.
+ --]]
+ local consumedRune
+ local runeType = RUNE_TYPE[name]
+ if runeType ~= DEATH_RUNE then
+ -- Search for an active regular rune of the given rune type.
+ for _, slot in ipairs(RUNE_SLOTS[runeType]) do
+ local rune = state.rune[slot]
+ if rune.type == runeType and rune.active then
+ consumedRune = rune
+ break
+ end
+ end
+ if not consumedRune then
+ -- Search for an active death rune of the given rune type.
+ for _, slot in ipairs(RUNE_SLOTS[runeType]) do
+ if rune.type == DEATH_RUNE and rune.active then
+ consumedRune = rune
+ break
+ end
+ end
+ end
+ end
+ -- No runes of the right type are active, so look for any active death rune.
+ if not consumedRune then
+ for _, slot in ipairs(DEATH_RUNE_PRIORITY) do
+ local rune = state.rune[slot]
+ if rune.type == DEATH_RUNE and rune.active then
+ consumedRune = rune
+ break
+ end
+ end
+ end
+ if consumedRune then
+ -- Put that rune on cooldown, starting when the other rune of that slot type comes off cooldown.
+ local k = consumedRune.slotType
+ local start = atTime
+ for _, slot in ipairs(RUNE_SLOTS[consumedRune.slotType]) do
+ local rune = state.rune[slot]
+ if rune.endCooldown > start then
+ start = rune.endCooldown
+ end
+ end
+ local duration = 10 / OvalePaperDoll:GetSpellHasteMultiplier()
+ if OvaleStance:IsStance("death_knight_blood_presence") and OvaleSpellBook:IsKnownSpell(IMPROVED_BLOOD_PRESENCE) then
+ -- Improved Blood Presence increases rune regeneration rate by 20%.
+ duration = duration / 1.2
+ end
+ consumedRune.startCooldown = start
+ consumedRune.endCooldown = start + duration
+ consumedRune.active = false
+ else
+ Ovale:Errorf("No %s rune available to consume!", RUNE_NAME[runeType])
+ end
+ end
+
+ function statePrototype:RuneCount(name, death)
+ local state = self
+ local count = 0
+ local startCooldown, endCooldown = math.huge, math.huge
+ local runeType = RUNE_TYPE[name]
+ if runeType ~= DEATH_RUNE then
+ if deathCondition == "any" 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.active 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" and rune.type ~= DEATH_RUNE) then
+ if rune.active then
+ count = count + 1
+ elseif rune.endCooldown < endCooldown then
+ startCooldown, endCooldown = rune.startCooldown, rune.endCooldown
+ end
+ end
+ end
+ end
+ else
+ -- Match any requested death runes.
+ for slot, rune in ipairs(state.rune) do
+ if rune.type == DEATH_RUNE then
+ if rune.active then
+ count = count + 1
+ elseif rune.endCooldown < endCooldown then
+ startCooldown, endCooldown = rune.startCooldown, rune.endCooldown
+ end
+ end
+ end
+ end
+ return count, startCooldown, endCooldown
+ 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 MatchRune(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
+ 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 = {}
+
+ function statePrototype:GetRunesCooldown(blood, unholy, frost, death, deathCondition)
+ local state = self
+
+ -- 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 = MatchRune(rune, runeCount[runeType], runeEndCooldown[runeType])
+ runeCount[runeType] = count
+ runeEndCooldown[runeType] = endCooldown
+ end
+ end
+ -- Use death runes of the matching rune type to meet the count requirements.
+ if deathCondition ~= "none" then
+ for slot, rune in ipairs(state.rune) do
+ if rune.type == DEATH_RUNE then
+ local runeType = rune.slotType
+ local count, endCooldown = MatchRune(rune, runeCount[runeType], runeEndCooldown[runeType])
+ runeCount[runeType] = count
+ runeEndCooldown[runeType] = endCooldown
+ end
+ end
+ end
+
+ -- Remaining rune requirements that have not yet been met.
+ local remainingCount = 0
+ for runeType = 1, 4 do
+ remainingCount = remainingCount + runeCount[runeType]
+ end
+
+ -- Use death runes of any type to meet any remaining count requirements.
+ if deathCondition == "any" then
+ for _, slot in ipairs(DEATH_RUNE_PRIORITY) do
+ local rune = state.rune[slot]
+ local runeType = DEATH_RUNE
+ local count, endCooldown = MatchRune(rune, remainingCount, runeEndCooldown[runeType])
+ remainingCount = count
+ runeEndCooldown[runeType] = endCooldown
+ 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
+ end
+
+ local maxEndCooldown = 0
+ for runeType = 1, 4 do
+ if runeEndCooldown[runeType] > maxEndCooldown then
+ maxEndCooldown = runeEndCooldown[runeType]
+ end
+ end
+ if maxEndCooldown > 0 then
+ return maxEndCooldown - OvaleState.currentTime
+ end
+ return 0
+ end
+ end
+end
diff --git a/OvaleState.lua b/OvaleState.lua
index 9eda4dd..a098fbc 100644
--- a/OvaleState.lua
+++ b/OvaleState.lua
@@ -8,8 +8,9 @@
file accompanying this program.
--]]--------------------------------------------------------------------
--- Keep the current state in the simulation
--- XXX Split out Runes module.
+--[[
+ This addon is the core of the state machine for the simulator.
+--]]
local _, Ovale = ...
local OvaleState = Ovale:NewModule("OvaleState")
@@ -20,28 +21,19 @@ local OvaleData = Ovale.OvaleData
local OvaleQueue = Ovale.OvaleQueue
local pairs = pairs
-local select = select
-local API_GetRuneCooldown = GetRuneCooldown
-local API_GetRuneType = GetRuneType
local API_GetTime = GetTime
-local API_UnitClass = UnitClass
local self_statePrototype = {}
local self_stateModules = OvaleQueue:NewQueue("OvaleState_stateModules")
-local self_runes = {}
-local self_runesCD = {}
-
--- Player's class.
-local self_class = select(2, API_UnitClass("player"))
-- Whether the state of the simulator has been initialized.
local self_stateIsInitialized = false
--</private-static-properties>
--<public-static-properties>
--- The state in the current frame
+-- The state for the simulator.
OvaleState.state = {}
--- The spell being cast
+-- The spell being cast.
OvaleState.currentSpellId = nil
OvaleState.now = nil
OvaleState.currentTime = nil
@@ -52,16 +44,6 @@ OvaleState.lastSpellId = nil
--</public-static-properties>
--<private-static-methods>
--- XXX The way this function updates the rune state looks completely wrong.
-local function AddRune(atTime, runeType, value)
- local self = OvaleState
- for i = 1, 6 do
- local rune = self.state.rune[i]
- if (rune.type == runeType or rune.type == 4) and rune.cd <= atTime then
- rune.cd = atTIme + 10
- end
- end
-end
--</private-static-methods>
--<public-static-methods>
@@ -110,39 +92,18 @@ end
function OvaleState:InitializeState()
self:InvokeMethod("InitializeState")
-
- self.state.rune = {}
- for i = 1, 6 do
- self.state.rune[i] = {}
- end
-
self_stateIsInitialized = true
end
function OvaleState:Reset()
- self.lastSpellId = Ovale.lastSpellcast and Ovale.lastSpellcast.spellId
self.currentTime = self.now
Ovale:Logf("Reset state with current time = %f", self.currentTime)
+
+ self.lastSpellId = Ovale.lastSpellcast and Ovale.lastSpellcast.spellId
self.currentSpellId = nil
self.nextCast = self.now
self:InvokeMethod("ResetState")
-
- if self_class == "DEATHKNIGHT" then
- for i=1,6 do
- self.state.rune[i].type = API_GetRuneType(i)
- local start, duration, runeReady = API_GetRuneCooldown(i)
- self.state.rune[i].duration = duration
- if runeReady then
- self.state.rune[i].cd = start
- else
- self.state.rune[i].cd = duration + start
- if self.state.rune[i].cd<0 then
- self.state.rune[i].cd = 0
- end
- end
- end
- end
end
--[[
@@ -186,105 +147,8 @@ function OvaleState:ApplySpell(spellId, startCast, endCast, nextCast, nocd, targ
2. Spell effects on player assuming the cast completes.
3. Spell effects on target when it lands.
--]]
- self:ApplySpellStart(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
- self:ApplySpellOnPlayer(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
- self:ApplySpellOnTarget(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
-end
-
--- Apply the effects of the spell at the start of the spellcast.
-function OvaleState:ApplySpellStart(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
self:InvokeMethod("ApplySpellStart", spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
-end
-
--- Apply the effects of the spell on the player's state, assuming the spellcast completes.
-function OvaleState:ApplySpellOnPlayer(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
self:InvokeMethod("ApplySpellOnPlayer", spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
- --[[
- If the spellcast has already ended, then the effects have already occurred,
- so only consider spells that have not yet finished casting in the simulator.
- --]]
- if endCast > self.now then
- local si = OvaleData.spellInfo[spellId]
- if si then
- -- Runes
- if si.blood and si.blood < 0 then
- AddRune(startCast, 1, si.blood)
- end
- if si.unholy and si.unholy < 0 then
- AddRune(startCast, 2, si.unholy)
- end
- if si.frost and si.frost < 0 then
- AddRune(startCast, 3, si.frost)
- end
- if si.death and si.death < 0 then
- AddRune(startCast, 4, si.death)
- end
- end
- end
-end
-
--- Apply the effects of the spell on the target's state when it lands on the target.
-function OvaleState:ApplySpellOnTarget(spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
self:InvokeMethod("ApplySpellOnTarget", spellId, startCast, endCast, nextCast, nocd, targetGUID, spellcast)
end
-
--- Returns the cooldown time before all of the required runes are available.
-function OvaleState:GetRunesCooldown(blood, frost, unholy, death, nodeath)
- local nombre = 0
- local nombreCD = 0
- local maxCD = nil
-
- for i=1,4 do
- self_runesCD[i] = 0
- end
-
- self_runes[1] = blood or 0
- self_runes[2] = frost or 0
- self_runes[3] = unholy or 0
- self_runes[4] = death or 0
-
- for i=1,6 do
- local rune = self.state.rune[i]
- if rune then
- if self_runes[rune.type] > 0 then
- self_runes[rune.type] = self_runes[rune.type] - 1
- if rune.cd > self_runesCD[rune.type] then
- self_runesCD[rune.type] = rune.cd
- end
- elseif rune.cd < self_runesCD[rune.type] then
- self_runesCD[rune.type] = rune.cd
- end
- end
- end
-
- if not nodeath then
- for i=1,6 do
- local rune = self.state.rune[i]
- if rune and rune.type == 4 then
- for j=1,3 do
- if self_runes[j]>0 then
- self_runes[j] = self_runes[j] - 1
- if rune.cd > self_runesCD[j] then
- self_runesCD[j] = rune.cd
- end
- break
- elseif rune.cd < self_runesCD[j] then
- self_runesCD[j] = rune.cd
- break
- end
- end
- end
- end
- end
-
- for i=1,4 do
- if self_runes[i]> 0 then
- return nil
- end
- if not maxCD or self_runesCD[i]>maxCD then
- maxCD = self_runesCD[i]
- end
- end
- return maxCD
-end
--</public-static-methods>
diff --git a/conditions/RuneCount.lua b/conditions/RuneCount.lua
index 6f53e4e..a1709ec 100644
--- a/conditions/RuneCount.lua
+++ b/conditions/RuneCount.lua
@@ -12,27 +12,25 @@ local _, Ovale = ...
do
local OvaleCondition = Ovale.OvaleCondition
+ local OvaleRunes = Ovale.OvaleRunes
local OvaleState = Ovale.OvaleState
+ local Compare = OvaleCondition.Compare
local TestValue = OvaleCondition.TestValue
- local RUNE_TYPE = {
- blood = 1,
- unholy = 2,
- frost = 3,
- death = 4,
- }
-
- --- Get the current number of runes of the given type for death knights.
+ --- Get the current number of active runes of the given type for death knights.
-- @name RuneCount
-- @paramsig number or boolean
-- @param type The type of rune.
-- 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 whether death runes can fulfill the rune count requirements. If set to 1, then death runes are allowed.
- -- Defaults to death=0 (zero).
- -- Valid values: 0, 1.
+ -- @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.
-- @usage
@@ -40,33 +38,18 @@ do
-- Spell(obliterate)
local function RuneCount(condition)
- local runeType, comparator, limit = condition[1], condition[2], condition[3]
- local death = condition.death
- local state = OvaleState.state
- runeType = RUNE_TYPE[runeType]
+ local name, comparator, limit = condition[1], condition[2], condition[3]
+ local deathCondition = condition.death
- -- Loop through the rune state and count the number of runes that match the given rune type.
- local value, origin, rate = 0, nil, nil
- for i = 1, 6 do
- local rune = state.rune[i]
- if rune and (rune.type == runeType or (rune.type == 4 and death == 1)) then
- if rune.cd > OvaleState.currentTime then
- -- Rune matches but is on cooldown.
- if not origin or rune.cd < origin then
- origin = rune.cd
- rate = 1 / rune.duration
- end
- else
- -- Rune matches and is available, so increment the counter.
- value = value + 1
- end
- end
- end
- if not origin then
- origin, rate = 0, 0
+ local state = OvaleState.state
+ local count, startCooldown, endCooldown = state:RuneCount(name, deathCondition)
+ if startCooldown < math.huge then
+ local origin = startCooldown
+ local rate = 1 / (endCooldown - startCooldown)
+ local start, ending = startCooldown, math.huge
+ return TestValue(start, ending, count, origin, rate, comparator, limit)
end
- local start, ending = OvaleState.currentTime, math.huge
- return TestValue(start, ending, value, origin, rate, comparator, limit)
+ return Compare(count, comparator, limit)
end
OvaleCondition:RegisterCondition("runecount", false, RuneCount)
diff --git a/conditions/Runes.lua b/conditions/Runes.lua
index 7f57218..f7e9063 100644
--- a/conditions/Runes.lua
+++ b/conditions/Runes.lua
@@ -12,56 +12,66 @@ local _, Ovale = ...
do
local OvaleCondition = Ovale.OvaleCondition
+ local OvaleRunes = Ovale.OvaleRunes
local OvaleState = Ovale.OvaleState
- local wipe = table.wipe
+ local RUNE_TYPE = OvaleRunes.RUNE_TYPE
- local RUNE_TYPE = {
- blood = 1,
- unholy = 2,
- frost = 3,
- death = 4
- }
+ local ParseRuneCondition = nil
+ do
+ local runes = {}
- local runes = {}
-
- local function ParseRuneCondition(condition)
- wipe(runes)
-
- local k = 1
- while true do
- local runeType, count = condition[2*k - 1], condition[2*k]
- if not RUNE_TYPE[runeType] then break end
- runes[runeType] = runes[runeType] + count
- k = k + 1
+ function ParseRuneCondition(condition)
+ for name in pairs(RUNE_TYPE) do
+ runes[name] = 0
+ end
+ local k = 1
+ while true do
+ local name, count = condition[2*k - 1], condition[2*k]
+ if not RUNE_TYPE[name] then break end
+ 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
end
- return runes.blood, runes.frost, runes.unholy, runes.death, condition.nodeath
end
- --- Test if the current rune count meets the minimum rune requirements set out in the parameters.
+ --- Test if the current active rune counst meets the minimum rune requirements set out in the parameters.
-- This condition takes pairs of "type number" to mean that there must be a minimum of number runes of the named type.
- -- E.g., Runes(blood 1 frost 1 unholy 1) means at least one blood, one frost, and one unholy rune is available, death runes included.
+ -- E.g., Runes(blood 1 frost 1 unholy 1) means at least one blood, one frost, and one unholy rune is available.
-- @name Runes
-- @paramsig boolean
-- @param type The type of rune.
-- Valid values: blood, frost, unholy, death
-- @param number The number of runes
-- @param ... Optional. Additional "type number" pairs for minimum rune requirements.
- -- @param nodeath Sets whether death runes can fulfill the rune count requirements. If set to 0, then death runes are allowed.
- -- Defaults to nodeath=0 (zero).
- -- Valid values: 0, 1.
+ -- @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, frost, unholy, death, nodeath = ParseRuneCondition(condition)
- local seconds = OvaleState:GetRunesCooldown(blood, frost, unholy, death, nodeath)
- local boolean = (seconds == 0)
- if boolean then
- return 0, math.huge
- end
- return nil
+ local blood, unholy, frost, death, deathCondition = ParseRuneCondition(condition)
+ local state = OvaleState.state
+ local seconds = state:GetRunesCooldown(blood, unholy, frost, death, deathCondition)
+ return OvaleState.currentTime + seconds, math.huge
end
--- Get the number of seconds before the rune conditions are met.
@@ -74,21 +84,19 @@ do
-- Valid values: blood, frost, unholy, death
-- @param number The number of runes
-- @param ... Optional. Additional "type number" pairs for minimum rune requirements.
- -- @param nodeath Sets whether death runes can fulfill the rune count requirements. If set to 0, then death runes are allowed.
- -- Defaults to nodeath=0 (zero).
- -- Valid values: 0, 1.
+ -- @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 seconds.
local function RunesCooldown(condition)
- local blood, frost, unholy, death, nodeath = ParseRuneCondition(condition)
- local seconds = OvaleState:GetRunesCooldown(blood, frost, unholy, death, nodeath)
- if seconds then
- if seconds < OvaleState.now then
- seconds = OvaleState.now
- end
- return 0, OvaleState.currentTime + seconds, seconds, OvaleState.currentTime, -1
- end
- return nil
+ local blood, unholy, frost, death, deathCondition = ParseRuneCondition(condition)
+ local state = OvaleState.state
+ local seconds = state:GetRunesCooldown(blood, unholy, frost, death, deathCondition)
+ return 0, OvaleState.currentTime + seconds, seconds, OvaleState.currentTime, -1
end
OvaleCondition:RegisterCondition("runes", false, Runes)