Johnny C. Lam [07-13-14 - 11:31]
diff --git a/OvaleBestAction.lua b/OvaleBestAction.lua
index 3fcfa05..29d1824 100644
--- a/OvaleBestAction.lua
+++ b/OvaleBestAction.lua
@@ -9,7 +9,7 @@
--]]--------------------------------------------------------------------
local _, Ovale = ...
-local OvaleBestAction = Ovale:NewModule("OvaleBestAction")
+local OvaleBestAction = Ovale:NewModule("OvaleBestAction", "AceEvent-3.0")
Ovale.OvaleBestAction = OvaleBestAction
--<private-static-properties>
@@ -17,7 +17,9 @@ local OvalePool = Ovale.OvalePool
local OvaleTimeSpan = Ovale.OvaleTimeSpan
-- Forward declarations for module dependencies.
+local OvaleAST = nil
local OvaleActionBar = nil
+local OvaleCompile = nil
local OvaleCondition = nil
local OvaleCooldown = nil
local OvaleData = nil
@@ -103,423 +105,718 @@ end
local OVALE_DEFAULT_PRIORITY = 3
+-- Table of node types to visitor methods.
+local COMPUTE_VISITOR = {
+ ["action"] = "ComputeAction",
+ ["arithmetic"] = "ComputeArithmetic",
+ ["compare"] = "ComputeCompare",
+ ["custom_function"] = "ComputeCustomFunction",
+ ["function"] = "ComputeFunction",
+ ["group"] = "ComputeGroup",
+ ["if"] = "ComputeIf",
+ ["logical"] = "ComputeLogical",
+ ["lua"] = "ComputeLua",
+ ["unless"] = "ComputeIf",
+ ["value"] = "ComputeValue",
+ ["wait"] = "ComputeWait",
+}
+
-- Age of the current computation.
local self_serial = 0
+
-- Pool of time-span tables.
-local self_pool = OvalePool("OvaleBestAction_pool")
+local self_timeSpanPool = OvalePool("OvaleBestAction_timeSpanPool")
+-- timeSpan[node] = computed time span for that node.
+local self_timeSpan = {}
+
+-- Pool of value nodes for results.
+local self_valuePool = OvalePool("OvaleBestAction_valuePool")
+-- value[node] = result node of that node.
+local self_value = {}
--</private-static-properties>
--<private-static-methods>
-local function PutValue(element, value, origin, rate)
- if not element.result then
- element.result = { type = "value" }
+local function SetValue(node, value, origin, rate)
+ -- Re-use existing result.
+ local result = self_value[node]
+ if not result then
+ result = self_valuePool:Get()
+ self_value[node] = result
end
- local result = element.result
- result.value = value
- result.origin = origin
- result.rate = rate
+ -- Overwrite any pre-existing values.
+ result.type = "value"
+ result.value = value or 0
+ result.origin = origin or 0
+ result.rate = rate or 0
return result
end
-local function ComputeAction(element, state)
- profiler.Start("OvaleBestAction_ComputeAction")
- local self = OvaleBestAction
- local action = element.params[1]
+local function GetTimeSpan(node)
+ local timeSpan = self_timeSpan[node]
+ if timeSpan then
+ timeSpan:Reset()
+ else
+ timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
+ self_timeSpan[node] = timeSpan
+ end
+ return timeSpan
+end
+
+local function GetActionItemInfo(element, state, target)
+ profiler.Start("OvaleBestAction_GetActionItemInfo")
+
local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
- actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId = self:GetActionInfo(element, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId
- if not actionTexture then
- Ovale:Logf("Action %s not found", action)
- profiler.Stop("OvaleBestAction_ComputeAction")
- return timeSpan
- elseif not (actionEnable and actionEnable > 0) then
- Ovale:Logf("Action %s not enabled", action)
- profiler.Stop("OvaleBestAction_ComputeAction")
- return timeSpan
- elseif element.params.usable == 1 and not actionUsable then
- Ovale:Logf("Action %s not usable", action)
- profiler.Stop("OvaleBestAction_ComputeAction")
- return timeSpan
+ local itemId = element.params[1]
+ if type(itemId) ~= "number" then
+ itemId = OvaleEquipement:GetEquippedItem(itemId)
end
+ if not itemId then
+ Ovale:Logf("Unknown item '%s'.", element.params[1])
+ else
+ Ovale:Logf("Item ID '%s'", itemId)
+ local action = OvaleActionBar:GetForItem(itemId)
+ local spellName = API_GetItemSpell(itemId)
- -- Set the cast time of the action.
- if actionType == "spell" then
- local spellId = actionId
- local si = spellId and OvaleData.spellInfo[spellId]
- if si and si.casttime then
- element.castTime = si.casttime
- else
- local _, _, _, _, _, _, castTime = API_GetSpellInfo(spellId)
- if castTime then
- element.castTime = castTime / 1000
- else
- element.castTime = nil
- end
+ -- Use texture specified in the action if given.
+ if element.params.texture then
+ actionTexture = "Interface\\Icons\\" .. element.params.texture
end
- else
- element.castTime = 0
+ actionTexture = actionTexture or API_GetItemIcon(itemId)
+ actionInRange = API_IsItemInRange(itemId, target)
+ actionCooldownStart, actionCooldownDuration, actionEnable = API_GetItemCooldown(itemId)
+ actionUsable = spellName and API_IsUsableItem(itemId)
+ if action then
+ actionShortcut = OvaleActionBar:GetBinding(action)
+ actionIsCurrent = API_IsCurrentAction(action)
+ end
+ actionType = "item"
+ actionId = itemId
end
- -- If the action is not on cooldown, then treat it like it's immediately ready.
- local start
- if actionCooldownDuration and actionCooldownStart and actionCooldownStart > 0 then
- start = actionCooldownDuration + actionCooldownStart
+ profiler.Stop("OvaleBestAction_GetActionItemInfo")
+ return actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target
+end
+
+local function GetActionMacroInfo(element, state, target)
+ profiler.Start("OvaleBestAction_GetActionMacroInfo")
+
+ local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId
+
+ local macro = element.params[1]
+ local action = OvaleActionBar:GetForMacro(macro)
+ if not action then
+ Ovale:Logf("Unknown macro '%s'.", macro)
else
- start = state.currentTime
+ -- Use texture specified in the action if given.
+ if element.params.texture then
+ actionTexture = "Interface\\Icons\\" .. element.params.texture
+ end
+ actionTexture = actionTexture or API_GetActionTexture(action)
+ actionInRange = API_IsActionInRange(action, target)
+ actionCooldownStart, actionCooldownDuration, actionEnable = API_GetActionCooldown(action)
+ actionUsable = API_IsUsableAction(action)
+ actionShortcut = OvaleActionBar:GetBinding(action)
+ actionIsCurrent = API_IsCurrentAction(action)
+ actionType = "macro"
+ actionId = macro
end
- Ovale:Logf("start=%f nextCast=%s [%d]", start, state.nextCast, element.nodeId)
+ profiler.Stop("OvaleBestAction_GetActionMacroInfo")
+ return actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target
+end
- -- 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
- -- 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
- local spellId = state.currentSpellId
- local si = spellId and OvaleData.spellInfo[spellId]
- if si then
- -- "channel=N" means that the channel has N total ticks and can be interrupted.
- local channel = si.channel or si.canStopChannelling
- if channel then
- local hasteMultiplier = 1
- if si.haste == "spell" then
- hasteMultiplier = state:GetSpellHasteMultiplier()
- elseif si.haste == "melee" then
- hasteMultiplier = state:GetMeleeHasteMultiplier()
- end
- local numTicks = floor(channel * hasteMultiplier + 0.5)
- local tick = (state.nextCast - state.startCast) / numTicks
- local tickTime = state.startCast
- for i = 1, numTicks do
- tickTime = tickTime + tick
- if start <= tickTime then
+local function GetActionSpellInfo(element, state, target)
+ profiler.Start("OvaleBestAction_GetActionSpellInfo")
+
+ local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId
+
+ local spellId = element.params[1]
+ local action = OvaleActionBar:GetForSpell(spellId)
+ if not OvaleSpellBook:IsKnownSpell(spellId) and not action then
+ Ovale:Logf("Unknown spell ID '%s'.", spellId)
+ else
+ -- Use texture specified in the action if given.
+ if element.params.texture then
+ actionTexture = "Interface\\Icons\\" .. element.params.texture
+ end
+ actionTexture = actionTexture or API_GetSpellTexture(spellId)
+ actionInRange = API_IsSpellInRange(OvaleSpellBook:GetSpellName(spellId), target)
+ actionCooldownStart, actionCooldownDuration, actionEnable = state:GetSpellCooldown(spellId)
+ actionUsable = OvaleSpellBook:IsUsableSpell(spellId)
+ if action then
+ actionShortcut = OvaleActionBar:GetBinding(action)
+ actionIsCurrent = API_IsCurrentAction(action)
+ end
+ actionType = "spell"
+ actionId = spellId
+
+ local si = OvaleData.spellInfo[spellId]
+ if si then
+ -- Verify that the spell may be cast given restrictions specified in SpellInfo().
+ local meetsRequirements = true
+ if si.stance and not OvaleStance:IsStance(si.stance) then
+ Ovale:Logf("Spell ID '%s' requires the player to be in stance '%s'", spellId, si.stance)
+ meetsRequirements = false
+ elseif si.combo then
+ -- Spell requires combo points.
+ local cost = state:ComboPointCost(spellId)
+ if state.combo < cost then
+ Ovale:Logf("Spell ID '%s' requires at least %d combo points.", spellId, cost)
+ meetsRequirements = false
+ end
+ else
+ for powerType in pairs(OvalePower.SECONDARY_POWER) do
+ if si[powerType] then
+ -- Spell requires "secondary" resources, e.g., chi, focus, rage, etc.,
+ local cost = state:PowerCost(spellId, powerType)
+ if state[powerType] < cost then
+ Ovale:Logf("Spell ID '%s' requires at least %d %s.", spellId, cost, powerType)
+ meetsRequirements = false
break
end
end
- newStart = tickTime
- Ovale:Logf("%s start=%f, numTicks=%d, tick=%f, tickTime=%f", spellId, newStart, numTicks, tick, tickTime)
end
end
+
+ -- Fix spell cooldown information using primary resource requirements specified in SpellInfo().
+ if actionCooldownStart and actionCooldownDuration then
+ -- Get the maximum time before all "primary" resources are ready.
+ local atTime = state.currentTime
+ for powerType in pairs(OvalePower.PRIMARY_POWER) do
+ if si[powerType] then
+ local t = state.currentTime + state:TimeToPower(spellId, powerType)
+ if atTime < t then
+ atTime = t
+ end
+ end
+ end
+ if actionCooldownStart > 0 then
+ if atTime > actionCooldownStart + actionCooldownDuration then
+ Ovale:Logf("Delaying spell ID '%s' for primary resource.", spellId)
+ actionCooldownDuration = atTime - actionCooldownStart
+ end
+ else
+ actionCooldownStart = state.currentTime
+ actionCooldownDuration = atTime - actionCooldownStart
+ end
+
+ if si.blood or si.frost or si.unholy or si.death then
+ -- Spell requires runes.
+ local needRunes = true
+ -- "buff_runes_none" is the spell ID of the buff that makes casting the spell cost no runes.
+ local buffNoRunes = si.buff_runes_none
+ if buffNoRunes then
+ local aura = state:GetAura("player", buffNoRunes)
+ if state:IsActiveAura(aura) then
+ needRunes = false
+ end
+ end
+ if needRunes then
+ local ending = state.currentTime + state:GetRunesCooldown(si.blood, si.unholy, si.frost, si.death)
+ if ending > actionCooldownStart + actionCooldownDuration then
+ actionCooldownDuration = ending - actionCooldownStart
+ end
+ end
+ end
+ end
+
+ -- Use texture specified in the SpellInfo() if given.
+ if si.texture then
+ actionTexture = "Interface\\Icons\\" .. si.texture
+ end
+
+ if not meetsRequirements then
+ -- Assign all return values to nil.
+ actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target = nil
+ end
end
- start = newStart
end
- Ovale:Logf("Action %s can start at %f", action, start)
- timeSpan[1], timeSpan[2] = start, math.huge
- --[[
- Allow for the return value of an to be "typecast" to a constant value by specifying
- asValue=1 as a parameter.
+ profiler.Stop("OvaleBestAction_GetActionSpellInfo")
+ return actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target
+end
- Return 1 if the action is off of cooldown, or 0 if it is on cooldown.
- --]]
- local value
- if element.params.asValue and element.params.asValue == 1 then
- local atTime = state.currentTime
- if HasTime(timeSpan, atTime) then
- value = 1
+local function GetActionTextureInfo(element, state, target)
+ profiler.Start("OvaleBestAction_GetActionTextureInfo")
+
+ local texture = element.params[1]
+ local actionTexture = "Interface\\Icons\\" .. texture
+ local actionInRange = nil
+ local actionCooldownStart = 0
+ local actionCooldownDuration = 0
+ local actionEnable = 1
+ local actionUsable = true
+ local actionShortcut = nil
+ local actionIsCurrent = nil
+ local actionType = "texture"
+ local actionId = texture
+
+ profiler.Stop("OvaleBestAction_GetActionTextureInfo")
+ return actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target
+end
+--</private-static-methods>
+
+--<public-static-methods>
+function OvaleBestAction:OnInitialize()
+ -- Resolve module dependencies.
+ OvaleAST = Ovale.OvaleAST
+ OvaleActionBar = Ovale.OvaleActionBar
+ OvaleCompile = Ovale.OvaleCompile
+ OvaleCondition = Ovale.OvaleCondition
+ OvaleCooldown = Ovale.OvaleCooldown
+ OvaleData = Ovale.OvaleData
+ OvaleEquipement = Ovale.OvaleEquipement
+ OvaleFuture = Ovale.OvaleFuture
+ OvalePower = Ovale.OvalePower
+ OvaleSpellBook = Ovale.OvaleSpellBook
+ OvaleStance = Ovale.OvaleStance
+end
+
+function OvaleBestAction:OnEnable()
+ self:RegisterMessage("Ovale_ScriptChanged")
+end
+
+function OvaleBestAction:OnDisable()
+ self:UnregisterMessage("Ovale_ScriptChanged")
+end
+
+function OvaleBestAction:Ovale_ScriptChanged()
+ -- Clean-up tables that are referenced using obsolete nodes as keys.
+ for node, timeSpan in pairs(self_timeSpan) do
+ self_timeSpanPool:Release(timeSpan)
+ self_timeSpan[node] = nil
+ end
+ for node, value in pairs(self_value) do
+ self_valuePool:Release(value)
+ self_value[node] = nil
+ end
+end
+
+function OvaleBestAction:StartNewAction(state)
+ state:Reset()
+ OvaleFuture:ApplyInFlightSpells(state)
+ self_serial = self_serial + 1
+end
+
+function OvaleBestAction:GetActionInfo(element, state)
+ if element and element.type == "action" then
+ local target = element.params.target or OvaleCondition.defaultTarget
+ if element.lowername == "item" then
+ return GetActionItemInfo(element, state, target)
+ elseif element.lowername == "macro" then
+ return GetActionMacroInfo(element, state, target)
+ elseif element.lowername == "spell" then
+ return GetActionSpellInfo(element, state, target)
+ elseif element.lowername == "texture" then
+ return GetActionTextureInfo(element, state, target)
+ end
+ end
+ return nil
+end
+
+function OvaleBestAction:Compute(element, state)
+ local timeSpan, priority, result
+ if element then
+ if element.asString then
+ Ovale:Logf("[%d] >>> Computing '%s': %s", element.nodeId, element.type, element.asString)
else
- value = 0
+ Ovale:Logf("[%d] >>> Computing '%s'", element.nodeId, element.type)
+ end
+ -- Check for recently cached computation results.
+ if element.serial and element.serial >= self_serial then
+ timeSpan = element.timeSpan
+ priority = element.priority
+ result = element.result
+ Ovale:Logf("[%d] using cached result (age = %d)", element.nodeId, element.serial)
+ else
+ local visitor = COMPUTE_VISITOR[element.type]
+ if visitor and self[visitor] then
+ timeSpan, priority, result = self[visitor](self, element, state)
+ element.serial = self_serial
+ element.timeSpan = timeSpan
+ element.priority = priority
+ element.result = result
+ else
+ Ovale:Logf("[%d] Runtime error: unable to compute node of type '%s'.", element.nodeId, element.type)
+ end
+ end
+ if result and result.type == "value" then
+ local value, origin, rate = result.value, result.origin, result.rate
+ Ovale:Logf("[%d] <<< '%s' returns %s with value = %f, %f, %f", element.nodeId, element.type, tostring(timeSpan), value, origin, rate)
+ elseif result and result.nodeId then
+ Ovale:Logf("[%d] <<< '%s' returns [%d] %s", element.nodeId, element.type, result.nodeId, tostring(timeSpan))
+ else
+ Ovale:Logf("[%d] <<< '%s' returns %s", element.nodeId, element.type, tostring(timeSpan))
end
- timeSpan[1], timeSpan[2] = 0, math.huge
end
+ return timeSpan, priority, result
+end
- local priority = element.params.priority or OVALE_DEFAULT_PRIORITY
- if value then
- local result = PutValue(element, value, 0, 0)
- profiler.Stop("OvaleBestAction_ComputeAction")
- return timeSpan, priority, result
+function OvaleBestAction:ComputeBool(element, state)
+ local timeSpan, _, newElement = self:Compute(element, state)
+ -- Match SimulationCraft: 0 is false, non-zero is true.
+ -- (https://code.google.com/p/simulationcraft/wiki/ActionLists#Logical_operators)
+ if newElement and newElement.type == "value" and newElement.value == 0 and newElement.rate == 0 then
+ return nil
else
- profiler.Stop("OvaleBestAction_ComputeAction")
- return timeSpan, priority, element
+ return timeSpan
end
end
-local function ComputeAnd(element, state)
- profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
- local self = OvaleBestAction
- local timeSpanA = self:ComputeBool(element.a, state)
- local timeSpan = element.timeSpan
-
- -- Short-circuit evaluation of left argument to AND.
- if Measure(timeSpanA) == 0 then
- timeSpan:Reset(timeSpanA)
+function OvaleBestAction:ComputeAction(element, state)
+ profiler.Start("OvaleBestAction_ComputeAction")
+ local nodeId = element.nodeId
+ local timeSpan = GetTimeSpan(element)
+ local priority, result
+
+ Ovale:Logf("[%d] evaluating action: %s(%s)", element.nodeId, element.name, element.paramsAsString)
+ local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
+ actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId = self:GetActionInfo(element, state)
+
+ local action = element.params[1]
+ if not actionTexture then
+ Ovale:Logf("[%s] Action %s not found.", nodeId, action)
+ elseif not (actionEnable and actionEnable > 0) then
+ Ovale:Logf("[%s] Action %s not enabled.", nodeId, action)
+ elseif element.params.usable == 1 and not actionUsable then
+ Ovale:Logf("[%s] Action %s not usable.", nodeId, action)
else
- local timeSpanB = self:ComputeBool(element.b, state)
- -- Take intersection of A and B.
- timeSpan:Reset()
- Intersect(timeSpanA, timeSpanB, timeSpan)
+ -- Set the cast time of the action.
+ if actionType == "spell" then
+ local spellId = actionId
+ local si = spellId and OvaleData.spellInfo[spellId]
+ if si and si.casttime then
+ element.castTime = si.casttime
+ else
+ local _, _, _, _, _, _, castTime = API_GetSpellInfo(spellId)
+ if castTime then
+ element.castTime = castTime / 1000
+ else
+ element.castTime = nil
+ end
+ end
+ else
+ element.castTime = 0
+ end
+
+ -- If the action is not on cooldown, then treat it like it's immediately ready.
+ local start
+ if actionCooldownDuration and actionCooldownStart and actionCooldownStart > 0 then
+ start = actionCooldownDuration + actionCooldownStart
+ else
+ start = state.currentTime
+ end
+
+ Ovale:Logf("[%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
+ -- 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
+ local spellId = state.currentSpellId
+ local si = spellId and OvaleData.spellInfo[spellId]
+ if si then
+ -- "channel=N" means that the channel has N total ticks and can be interrupted.
+ local channel = si.channel or si.canStopChannelling
+ if channel then
+ local hasteMultiplier = 1
+ if si.haste == "spell" then
+ hasteMultiplier = state:GetSpellHasteMultiplier()
+ elseif si.haste == "melee" then
+ hasteMultiplier = state:GetMeleeHasteMultiplier()
+ end
+ local numTicks = floor(channel * hasteMultiplier + 0.5)
+ local tick = (state.nextCast - state.startCast) / numTicks
+ local tickTime = state.startCast
+ for i = 1, numTicks do
+ tickTime = tickTime + tick
+ if start <= tickTime then
+ break
+ end
+ end
+ newStart = tickTime
+ Ovale:Logf("[%d] %s start=%f, numTicks=%d, tick=%f, tickTime=%f", nodeId, spellId, newStart, numTicks, tick, tickTime)
+ end
+ end
+ end
+ start = newStart
+ end
+ Ovale:Logf("[%d] Action %s can start at %f.", nodeId, action, start)
+ timeSpan[1], timeSpan[2] = start, math.huge
+
+ --[[
+ Allow for the return value of an to be "typecast" to a constant value by specifying
+ asValue=1 as a parameter.
+
+ Return 1 if the action is off of cooldown, or 0 if it is on cooldown.
+ --]]
+ local value
+ if element.params.asValue == 1 then
+ local atTime = state.currentTime
+ local value = HasTime(timeSpan, atTime) and 1 or 0
+ result = SetValue(element, value)
+ timeSpan[1], timeSpan[2] = 0, math.huge
+ Ovale:Logf("[%d] Action %s typecast to value %f.", nodeId, action, value)
+ else
+ result = element
+ end
+ priority = element.params.priority or OVALE_DEFAULT_PRIORITY
end
- Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan
+
+ profiler.Stop("OvaleBestAction_ComputeAction")
+ return timeSpan, priority, result
end
-local function ComputeArithmetic(element, state)
+function OvaleBestAction:ComputeArithmetic(element, state)
profiler.Start("OvaleBestAction_Compute")
- local self = OvaleBestAction
- local timeSpanA, _, elementA = self:Compute(element.a, state)
- local timeSpanB, _, elementB = self:Compute(element.b, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
+ local timeSpanA, _, elementA = self:Compute(element.child[1], state)
+ local timeSpanB, _, elementB = self:Compute(element.child[2], state)
+ local timeSpan = GetTimeSpan(element)
+ local result
-- Take intersection of A and B.
Intersect(timeSpanA, timeSpanB, timeSpan)
if Measure(timeSpan) == 0 then
- Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId)
- local result = PutValue(element, 0, 0, 0)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, OVALE_DEFAULT_PRIORITY, result
- end
-
- --[[
- A(t) = a + (t - b)*c
- B(t) = x + (t - y)*z
-
- Silently "typecast" non-values to a constant value of 0.
- --]]
- local a = elementA and elementA.value or 0
- local b = elementA and elementA.origin or 0
- local c = elementA and elementA.rate or 0
- local x = elementB and elementB.value or 0
- local y = elementB and elementB.origin or 0
- local z = elementB and elementB.rate or 0
- local atTime = state.currentTime
+ Ovale:Logf("[%d] arithmetic '%s' returns %s with zero measure", element.nodeId, element.operator, tostring(timeSpan))
+ result = SetValue(element, 0)
+ else
+ --[[
+ A(t) = a + (t - b)*c
+ B(t) = x + (t - y)*z
- Ovale:Logf("%f+(t-%f)*%f %s %f+(t-%f)*%f [%d]", a, b, c, element.operator, x, y, z, element.nodeId)
+ Silently "typecast" non-values to a constant value of 0.
+ --]]
+ local a = elementA and elementA.value or 0
+ local b = elementA and elementA.origin or 0
+ local c = elementA and elementA.rate or 0
+ local x = elementB and elementB.value or 0
+ local y = elementB and elementB.origin or 0
+ local z = elementB and elementB.rate or 0
+ local operator = element.operator
+ local atTime = state.currentTime
- -- result(t) = l + (t - m) * n
- local l, m, n
+ Ovale:Logf("[%d] %f+(t-%f)*%f %s %f+(t-%f)*%f", element.nodeId, a, b, c, operator, x, y, z)
- --[[
- A(t) = a + (t - b)*c = a + (t - t0 + t0 - b)*c = [a + (t0 - b)*c] + (t - t0)*c = A(t0) + (t - t0)*c
- B(t) = x + (t - y)*z = x + (t - t0 + t0 - y)*z = [x + (t0 - y)*z] + (t - t0)*z = B(t0) + (t - t0)*z
- --]]
- local A = a + (atTime - b)*c
- local B = x + (atTime - y)*z
+ -- result(t) = l + (t - m)*n
+ local l, m, n
- if element.operator == "+" then
--[[
- A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
- B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
-
- A(t) + B(t) = (A + B) + (t - t0)*(c + z)
+ A(t) = a + (t - b)*c = a + (t - t0 + t0 - b)*c = [a + (t0 - b)*c] + (t - t0)*c = A(t0) + (t - t0)*c
+ B(t) = x + (t - y)*z = x + (t - t0 + t0 - y)*z = [x + (t0 - y)*z] + (t - t0)*z = B(t0) + (t - t0)*z
--]]
- l = A + B
- m = atTime
- n = c + z
- elseif element.operator == "-" then
- --[[
- A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
- B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
+ local A = a + (atTime - b)*c
+ local B = x + (atTime - y)*z
- A(t) - B(t) = (A - B) + (t - t0)*(c - z)
- --]]
- l = A - B
- m = atTime
- n = c - z
- elseif element.operator == "*" then
- --[[
- A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
- B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
- A(t)*B(t) = A*B + (t - t0)*[A*z + B*c] + [(t - t0)^2]*(c*z)
- = A*B + (t - t0)*[A*z + B*c] + O(t^2) converges everywhere.
- --]]
- l = A*B
+ if operator == "+" then
+ --[[
+ A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
+ B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
+
+ A(t) + B(t) = (A + B) + (t - t0)*(c + z)
+ --]]
+ l = A + B
m = atTime
- n = A*z + B*c
- elseif element.operator == "/" then
- --[[
- A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
- B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
- A(t)/B(t) = A/B + (t - t0)*[(B*c - A*z)/B^2] + O(t^2) converges when |t - t0| < |B/z|.
- --]]
- l = A/B
- m = atTime
- n = (B*c - A*z)/(B^2)
- local bound
- if z == 0 then
- bound = math.huge
- else
- bound = abs(B/z)
- end
- local scratch = OvaleTimeSpan(self_pool:Get())
- scratch:Reset(timeSpan)
- timeSpan:Reset()
- IntersectInterval(scratch, atTime - bound, atTime + bound, timeSpan)
- self_pool:Release(scratch)
- elseif element.operator == "%" then
- if c == 0 and z == 0 then
- l = A % B
+ n = c + z
+ elseif operator == "-" then
+ --[[
+ A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
+ B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
+
+ A(t) - B(t) = (A - B) + (t - t0)*(c - z)
+ --]]
+ l = A - B
m = atTime
- n = 0
- else
- Ovale:Error("Parameters of % must be constants")
- l = 0
- m = 0
- n = 0
+ n = c - z
+ elseif operator == "*" then
+ --[[
+ A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
+ B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
+ A(t)*B(t) = A*B + (t - t0)*[A*z + B*c] + [(t - t0)^2]*(c*z)
+ = A*B + (t - t0)*[A*z + B*c] + O(t^2) converges everywhere.
+ --]]
+ l = A*B
+ m = atTime
+ n = A*z + B*c
+ elseif operator == "/" then
+ --[[
+ A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c
+ B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z
+ A(t)/B(t) = A/B + (t - t0)*[(B*c - A*z)/B^2] + O(t^2) converges when |t - t0| < |B/z|.
+ --]]
+ l = A/B
+ m = atTime
+ n = (B*c - A*z)/(B^2)
+ local bound
+ if z == 0 then
+ bound = math.huge
+ else
+ bound = abs(B/z)
+ end
+ local scratch = OvaleTimeSpan(self_timeSpanPool:Get())
+ scratch:Reset(timeSpan)
timeSpan:Reset()
+ IntersectInterval(scratch, atTime - bound, atTime + bound, timeSpan)
+ self_timeSpanPool:Release(scratch)
+ elseif operator == "%" then
+ if c == 0 and z == 0 then
+ l = A % B
+ m = atTime
+ n = 0
+ else
+ Ovale:Errorf("[%d] Parameters of modulus operator '%' must be constants.", element.nodeId)
+ l = 0
+ m = 0
+ n = 0
+ end
end
+ Ovale:Logf("[%d] arithmetic '%s' returns %f+(t-%f)*%f", element.nodeId, operator, l, m, n)
+ result = SetValue(element, l, m, n)
end
- Ovale:Logf("result = %f+(t-%f)*%f [%d]", l, m, n, element.nodeId)
- local result = PutValue(element, l, m, n)
profiler.Stop("OvaleBestAction_Compute")
return timeSpan, OVALE_DEFAULT_PRIORITY, result
end
-local function ComputeCompare(element, state)
+function OvaleBestAction:ComputeCompare(element, state)
profiler.Start("OvaleBestAction_Compute")
- local self = OvaleBestAction
- local timeSpanA, _, elementA = self:Compute(element.a, state)
- local timeSpanB, _, elementB = self:Compute(element.b, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
+ local timeSpanA, _, elementA = self:Compute(element.child[1], state)
+ local timeSpanB, _, elementB = self:Compute(element.child[2], state)
+ local timeSpan = GetTimeSpan(element)
-- Take intersection of A and B.
Intersect(timeSpanA, timeSpanB, timeSpan)
if Measure(timeSpan) == 0 then
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan
- end
-
- --[[
- A(t) = a + (t - b)*c
- B(t) = x + (t - y)*z
+ Ovale:Logf("[%d] compare '%s' returns %s with zero measure", element.nodeId, element.operator, tostring(timeSpan))
+ else
+ --[[
+ A(t) = a + (t - b)*c
+ B(t) = x + (t - y)*z
- Silently "typecast" non-values to a constant value of 0.
- --]]
- local a = elementA and elementA.value or 0
- local b = elementA and elementA.origin or 0
- local c = elementA and elementA.rate or 0
- local x = elementB and elementB.value or 0
- local y = elementB and elementB.origin or 0
- local z = elementB and elementB.rate or 0
- local operator = element.operator
+ Silently "typecast" non-values to a constant value of 0.
+ --]]
+ local a = elementA and elementA.value or 0
+ local b = elementA and elementA.origin or 0
+ local c = elementA and elementA.rate or 0
+ local x = elementB and elementB.value or 0
+ local y = elementB and elementB.origin or 0
+ local z = elementB and elementB.rate or 0
+ local operator = element.operator
- Ovale:Logf("%f+(t-%f)*%f %s %f+(t-%f)*%f [%d]", a, b, c, operator, x, y, z, element.nodeId)
+ Ovale:Logf("[%d] %f+(t-%f)*%f %s %f+(t-%f)*%f", element.nodeId, a, b, c, operator, x, y, z)
- --[[
- A(t) = B(t)
- a + (t - b)*c = x + (t - y)*z
- a + t*c - b*c = x + t*z - y*z
- t*c - t*z = (x - y*z) - (a - b*c)
- t*(c - z) = B(0) - A(0)
- --]]
- local A = a - b*c
- local B = x - y*z
- if c == z then
- if not ((operator == "==" and A == B)
- or (operator == "<" and A < B)
- or (operator == "<=" and A <= B)
- or (operator == ">" and A > B)
- or (operator == ">=" and A >= B)) then
+ --[[
+ A(t) = B(t)
+ a + (t - b)*c = x + (t - y)*z
+ a + t*c - b*c = x + t*z - y*z
+ t*c - t*z = (x - y*z) - (a - b*c)
+ t*(c - z) = B(0) - A(0)
+ --]]
+ local A = a - b*c
+ local B = x - y*z
+ if c == z then
+ if not ((operator == "==" and A == B)
+ or (operator == "!=" and A ~= B)
+ or (operator == "<" and A < B)
+ or (operator == "<=" and A <= B)
+ or (operator == ">" and A > B)
+ or (operator == ">=" and A >= B)) then
+ timeSpan:Reset()
+ end
+ else
+ local scratch = OvaleTimeSpan(self_timeSpanPool:Get())
+ scratch:Reset(timeSpan)
timeSpan:Reset()
+ local t = (B - A)/(c - z)
+ t = (t > 0) and t or 0
+ Ovale:Logf("[%d] intersection at t = %f", element.nodeId, t)
+ if (c > z and operator == "<")
+ or (c > z and operator == "<=")
+ or (c < z and operator == ">")
+ or (c < z and operator == ">=") then
+ IntersectInterval(scratch, 0, t, timeSpan)
+ elseif (c < z and operator == "<")
+ or (c < z and operator == "<=")
+ or (c > z and operator == ">")
+ or (c > z and operator == ">=") then
+ IntersectInterval(scratch, t, math.huge, timeSpan)
+ end
+ self_timeSpanPool:Release(scratch)
end
- else
- local scratch = OvaleTimeSpan(self_pool:Get())
- scratch:Reset(timeSpan)
- timeSpan:Reset()
- local t = (B - A)/(c - z)
- t = (t > 0) and t or 0
- Ovale:Logf("t = %f", t)
- if (c > z and operator == "<")
- or (c > z and operator == "<=")
- or (c < z and operator == ">")
- or (c < z and operator == ">=") then
- IntersectInterval(scratch, 0, t, timeSpan)
- elseif (c < z and operator == "<")
- or (c < z and operator == "<=")
- or (c > z and operator == ">")
- or (c > z and operator == ">=") then
- IntersectInterval(scratch, t, math.huge, timeSpan)
- end
- self_pool:Release(scratch)
+ Ovale:Logf("[%d] compare '%s' returns %s", element.nodeId, operator, tostring(timeSpan))
end
- Ovale:Logf("compare %s returns %s [%d]", operator, tostring(timeSpan), element.nodeId)
profiler.Stop("OvaleBestAction_Compute")
return timeSpan
end
-local function ComputeCustomFunction(element, state)
+function OvaleBestAction:ComputeCustomFunction(element, state)
profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("custom function %s", element.name)
- local self = OvaleBestAction
- if not element.serial or element.serial < self_serial then
- -- Cache new values in element.
- element.timeSpanA, element.priorityA, element.elementA = self:Compute(element.a, state)
- element.serial = self_serial
- else
- Ovale:Logf("Using cached values for %s", element.name)
- end
-
- local timeSpanA, priorityA, elementA = element.timeSpanA, element.priorityA, element.elementA
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- if element.params.asValue and element.params.asValue == 1 then
- --[[
- Allow for the return value of a custom function to be "typecast" to a constant value.
-
- If the return value is a time span (a "boolean" value), then if the current time of
- the simulation is within the time span, then return 1, or 0 otherwise.
-
- If the return value is a linear function, then if the current time of the simulation
- is within the function's domain, then the function is simply evaluated at the current
- time, or 0 otherwise.
-
- If the return value is an action, then return 1 if the action is off of cooldown, or
- 0 if it is on cooldown.
- --]]
- local atTime = state.currentTime
- local value = 0
- if HasTime(timeSpanA, atTime) then
- if not elementA then -- boolean
- value = 1
- elseif elementA.type == "value" then
- value = elementA.value + (atTime - elementA.origin) * elementA.rate
- elseif elementA.type == "action" then
- value = 1
+ local timeSpan = GetTimeSpan(element)
+ local priority, result
+
+ local node = OvaleCompile:GetFunctionNode(element.name)
+ if node then
+ Ovale:Logf("[%d] evaluating function: %s(%s)", element.nodeId, node.name, node.paramsAsString)
+ local timeSpanA, priorityA, elementA = self:Compute(node.child[1], state)
+ if element.params.asValue == 1 or node.params.asValue == 1 then
+ --[[
+ Allow for the return value of a custom function to be "typecast" to a constant value.
+
+ If the return value is a time span (a "boolean" value), then if the current time of
+ the simulation is within the time span, then return 1, or 0 otherwise.
+
+ If the return value is a linear function, then if the current time of the simulation
+ is within the function's domain, then the function is simply evaluated at the current
+ time, or 0 otherwise.
+
+ If the return value is an action, then return 1 if the action is off of cooldown, or
+ 0 if it is on cooldown.
+ --]]
+ local atTime = state.currentTime
+ local value = 0
+ if HasTime(timeSpanA, atTime) then
+ if not elementA then -- boolean
+ value = 1
+ elseif elementA.type == "value" then
+ value = elementA.value + (atTime - elementA.origin) * elementA.rate
+ elseif elementA.type == "action" then
+ value = 1
+ end
end
+ Ovale:Logf("[%d] function '%s' typecast to value %f", element.nodeId, element.name, value)
+ timeSpan[1], timeSpan[2] = 0, math.huge
+ result = SetValue(element, value)
+ else
+ CopyTimeSpan(timeSpanA, timeSpan)
+ result = elementA
end
- timeSpan[1], timeSpan[2] = 0, math.huge
- local result = PutValue(element, value, 0, 0)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, priorityA, result
- else
- CopyTimeSpan(timeSpanA, timeSpan)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, priorityA, elementA
end
+
+ profiler.Stop("OvaleBestAction_Compute")
+ return timeSpan, priority, result
end
-local function ComputeFunction(element, state)
+function OvaleBestAction:ComputeFunction(element, state)
profiler.Start("OvaleBestAction_ComputeFunction")
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- if not OvaleCondition:IsCondition(element.func) then
- Ovale:Errorf("Condition %s not found", element.func)
- profiler.Stop("OvaleBestAction_ComputeFunction")
- return timeSpan
- end
+ local timeSpan = GetTimeSpan(element)
+ local priority, result
+ Ovale:Logf("[%d] evaluating condition: %s(%s)", element.nodeId, element.name, element.paramsAsString)
local start, ending, value, origin, rate = OvaleCondition:EvaluateCondition(element.func, element.params)
if start and ending then
timeSpan[1], timeSpan[2] = start, ending
end
-
- if Ovale.trace then
- local conditionCall = element.func .. "("
- for k, v in pairs(element.params) do
- conditionCall = conditionCall .. k .. "=" .. v .. ","
- end
- conditionCall = conditionCall .. ")"
- Ovale:FormatPrint("Condition %s returned %s, %s, %s, %s, %s", conditionCall, start, ending, value, origin, rate)
- end
+ Ovale:Logf("[%d] condition '%s' returns %s, %s, %s, %s, %s", element.nodeId, element.name, start, ending, value, origin, rate)
--[[
Allow for the return value of a script condition to be "typecast" to a constant value
@@ -532,7 +829,7 @@ local function ComputeFunction(element, state)
is within the function's domain, then the function is simply evaluated at the current
time, or 0 otherwise.
--]]
- if element.params.asValue and element.params.asValue == 1 then
+ if element.params.asValue == 1 then
local atTime = state.currentTime
if HasTime(timeSpan, atTime) then
if value then
@@ -543,44 +840,33 @@ local function ComputeFunction(element, state)
else
value = 0
end
- origin, rate = 0, 0
+ result = SetValue(element, value)
timeSpan[1], timeSpan[2] = 0, math.huge
+ priority = OVALE_DEFAULT_PRIORITY
+ Ovale:Logf("[%d] condition '%s' typecast to value %f", element.nodeId, element.name, value)
+ elseif value then
+ result = SetValue(element, value, origin, rate)
end
- if value then
- local result = PutValue(element, value, origin, rate)
- profiler.Stop("OvaleBestAction_ComputeFunction")
- return timeSpan, OVALE_DEFAULT_PRIORITY, result
- else
- profiler.Stop("OvaleBestAction_ComputeFunction")
- return timeSpan
- end
+ profiler.Stop("OvaleBestAction_ComputeFunction")
+ return timeSpan, priority, result
end
-local function ComputeGroup(element, state)
+function OvaleBestAction:ComputeGroup(element, state)
profiler.Start("OvaleBestAction_Compute")
- local self = OvaleBestAction
local bestTimeSpan, bestPriority, bestElement, bestCastTime
- local timeSpan = element.timeSpan
- timeSpan:Reset()
+ local timeSpan = GetTimeSpan(element)
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
-
- if #element.nodes == 1 then
- profiler.Stop("OvaleBestAction_Compute")
- return self:Compute(element.nodes[1], state)
- end
+ local best = OvaleTimeSpan(self_timeSpanPool:Get())
+ local current = OvaleTimeSpan(self_timeSpanPool:Get())
- local best = OvaleTimeSpan(self_pool:Get())
- local current = OvaleTimeSpan(self_pool:Get())
-
- for k, v in ipairs(element.nodes) do
- local currentTimeSpan, currentPriority, currentElement = self:Compute(v, state)
+ for _, node in ipairs(element.child) do
+ local currentTimeSpan, currentPriority, currentElement = self:Compute(node, state)
-- We only care about actions that are available at time t > state.currentTime.
current:Reset()
IntersectInterval(currentTimeSpan, state.currentTime, math.huge, current)
if Measure(current) > 0 then
- Ovale:Logf(" group checking %s [%d]", tostring(current), element.nodeId)
+ Ovale:Logf("[%d] group checking %s", element.nodeId, tostring(current))
local currentCastTime
if currentElement then
currentCastTime = currentElement.castTime
@@ -590,24 +876,24 @@ local function ComputeGroup(element, state)
currentCastTime = gcd
end
- local replace = false
+ local currentIsBetter = false
if Measure(best) == 0 then
- Ovale:Logf(" group first best %s [%d]", tostring(current), element.nodeId)
- replace = true
+ Ovale:Logf("[%d] group first best is %s", element.nodeId, tostring(current))
+ currentIsBetter = true
elseif not currentPriority or not bestPriority or currentPriority == bestPriority then
-- If the spells have the same priority, then pick the one with an earlier cast time.
local threshold = (bestElement and bestElement.params) and bestElement.params.wait or 0
if best[1] - current[1] > threshold then
- Ovale:Logf(" group new best %s [%d]", tostring(current), element.nodeId)
- replace = true
+ Ovale:Logf("[%d] group new best is %s", element.nodeId, tostring(current))
+ currentIsBetter = true
end
elseif currentPriority > bestPriority then
-- If the current spell has a higher priority than the best one found, then choose the
-- higher priority spell if its cast is pushed back too far by the lower priority one.
local threshold = (currentElement and currentElement.params) and currentElement.params.wait or (bestCastTime * 0.75)
if current[1] - best[1] < threshold then
- Ovale:Logf(" group new best (lower prio) %s [%d]", tostring(current), element.nodeId)
- replace = true
+ Ovale:Logf("[%d] group new best (lower prio) is %s", element.nodeId, tostring(current))
+ currentIsBetter = true
end
elseif currentPriority < bestPriority then
-- If the current spell has a lower priority than the best one found, then choose the
@@ -615,11 +901,11 @@ local function ComputeGroup(element, state)
-- one by too much.
local threshold = (bestElement and bestElement.params) and bestElement.params.wait or (currentCastTime * 0.75)
if best[1] - current[1] > threshold then
- Ovale:Logf(" group new best (higher prio) %s [%d]", tostring(current), element.nodeId)
- replace = true
+ Ovale:Logf("[%d] group new best (higher prio) is %s", element.nodeId, tostring(current))
+ currentIsBetter = true
end
end
- if replace then
+ if currentIsBetter then
best:Reset(current)
bestTimeSpan = currentTimeSpan
bestPriority = currentPriority
@@ -631,39 +917,30 @@ local function ComputeGroup(element, state)
end
end
- self_pool:Release(best)
- self_pool:Release(current)
+ self_timeSpanPool:Release(best)
+ self_timeSpanPool:Release(current)
- if not bestTimeSpan then
- Ovale:Logf("group return %s [%d]", tostring(timeSpan), element.nodeId)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan
- else
- CopyTimeSpan(bestTimeSpan, timeSpan)
- if bestElement then
- local id = bestElement.value
- if bestElement.params then
- id = bestElement.params[1]
- end
- Ovale:Logf("group best action %s remains %s [%d]", id, tostring(timeSpan), element.nodeId)
- else
- Ovale:Logf("group no best action returns %s [%d]", tostring(timeSpan), element.nodeId)
+ CopyTimeSpan(bestTimeSpan, timeSpan)
+ if bestElement then
+ local id = bestElement.value
+ if bestElement.params then
+ id = bestElement.params[1]
end
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, bestPriority, bestElement
+ Ovale:Logf("[%d] group best action %s remains %s", element.nodeId, id, tostring(timeSpan))
+ else
+ Ovale:Logf("[%d] group no best action returns %s", element.nodeId, tostring(timeSpan))
end
+
+ profiler.Stop("OvaleBestAction_Compute")
+ return timeSpan, bestPriority, bestElement
end
-local function ComputeIf(element, state)
+function OvaleBestAction:ComputeIf(element, state)
profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
- local self = OvaleBestAction
-
- local timeSpanA = self:ComputeBool(element.a, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- local conditionTimeSpan = OvaleTimeSpan(self_pool:Get())
+ local timeSpanA = self:ComputeBool(element.child[1], state)
+ local timeSpan = GetTimeSpan(element)
+ local priority, result
+ local conditionTimeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
if element.type == "if" then
conditionTimeSpan:Reset(timeSpanA)
elseif element.type == "unless" then
@@ -673,323 +950,102 @@ local function ComputeIf(element, state)
-- Short-circuit evaluation of left argument to IF.
if Measure(conditionTimeSpan) == 0 then
timeSpan:Reset(conditionTimeSpan)
- self_pool:Release(conditionTimeSpan)
- Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId)
- local result = PutValue(element, 0, 0, 0)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, OVALE_DEFAULT_PRIORITY, result
- end
-
- local timeSpanB, priorityB, elementB = self:Compute(element.b, state)
- -- If the "then" clause is a "wait" node, then only wait if the conditions are true.
- if elementB and elementB.wait and not HasTime(conditionTimeSpan, state.currentTime) then
- elementB.wait = nil
+ Ovale:Logf("[%d] '%s' returns %s with zero measure", element.nodeId, element.type, tostring(timeSpan))
+ priority = OVALE_DEFAULT_PRIORITY
+ result = SetValue(element, 0)
+ else
+ local timeSpanB, priorityB, elementB = self:Compute(element.child[2], state)
+ -- If the "then" clause is a "wait" node, then only wait if the conditions are true.
+ if elementB and elementB.wait and not HasTime(conditionTimeSpan, state.currentTime) then
+ elementB.wait = nil
+ end
+ -- Take intersection of the condition and B.
+ Intersect(conditionTimeSpan, timeSpanB, timeSpan)
+ Ovale:Logf("[%d] '%s' returns %s", element.nodeId, element.type, tostring(timeSpan))
+ priority = priorityB
+ result = elementB
end
- -- Take intersection of the condition and B.
- Intersect(conditionTimeSpan, timeSpanB, timeSpan)
- self_pool:Release(conditionTimeSpan)
+ self_timeSpanPool:Release(conditionTimeSpan)
- Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId)
profiler.Stop("OvaleBestAction_Compute")
- return timeSpan, priorityB, elementB
+ return timeSpan, priority, result
end
-local function ComputeLua(element, state)
- profiler.Start("OvaleBestAction_ComputeLua")
- local ret = loadstring(element.lua)()
- Ovale:Logf("lua %s [%d]", ret, element.nodeId)
-
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- timeSpan[1], timeSpan[2] = 0, math.huge
- local result = PutValue(element, ret, 0, 0)
- profiler.Stop("OvaleBestAction_ComputeLua")
- return timeSpan, OVALE_DEFAULT_PRIORITY, result
-end
-
-local function ComputeNot(element, state)
+function OvaleBestAction:ComputeLogical(element, state)
profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
- local self = OvaleBestAction
- local timeSpanA = self:ComputeBool(element.a, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- Complement(timeSpanA, timeSpan)
- Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId)
+ local timeSpanA = self:ComputeBool(element.child[1], state)
+ local timeSpan = GetTimeSpan(element)
+
+ if element.operator == "and" then
+ -- Short-circuit evaluation of left argument to AND.
+ if Measure(timeSpanA) == 0 then
+ timeSpan:Reset(timeSpanA)
+ Ovale:Logf("[%d] logical '%s' short-circuits with zero measure left argument", element.nodeId, element.operator, tostring(timeSpan))
+ else
+ local timeSpanB = self:ComputeBool(element.child[2], state)
+ -- Take intersection of A and B.
+ Intersect(timeSpanA, timeSpanB, timeSpan)
+ end
+ elseif element.operator == "not" then
+ Complement(timeSpanA, timeSpan)
+ elseif element.operator == "or" then
+ -- Short-circuit evaluation of left argument to OR.
+ if timeSpanA[1] == 0 and timeSpanA[1] == math.huge then
+ timeSpan:Reset(timeSpanA)
+ Ovale:Logf("[%d] logical '%s' short-circuits with universe as left argument", element.nodeId, element.operator, tostring(timeSpan))
+ else
+ local timeSpanB = self:ComputeBool(element.child[2], state)
+ -- Take union of A and B.
+ Union(timeSpanA, timeSpanB, timeSpan)
+ end
+ end
+
+ Ovale:Logf("[%d] logical '%s' returns %s", element.nodeId, element.operator, tostring(timeSpan))
profiler.Stop("OvaleBestAction_Compute")
return timeSpan
end
-local function ComputeOr(element, state)
- profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
- local self = OvaleBestAction
- local timeSpanA = self:ComputeBool(element.a, state)
- local timeSpanB = self:ComputeBool(element.b, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
- -- Take union of A and B.
- Union(timeSpanA, timeSpanB, timeSpan)
- Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId)
- profiler.Stop("OvaleBestAction_Compute")
- return timeSpan
+function OvaleBestAction:ComputeLua(element, state)
+ profiler.Start("OvaleBestAction_ComputeLua")
+ local value = loadstring(element.lua)()
+ Ovale:Logf("[%d] lua returns %s", element.nodeId, value)
+
+ local timeSpan = GetTimeSpan(element)
+ local priority, result
+ if value then
+ timeSpan[1], timeSpan[2] = 0, math.huge
+ result = SetValue(element, value)
+ priority = OVALE_DEFAULT_PRIORITY
+ end
+ profiler.Stop("OvaleBestAction_ComputeLua")
+ return timeSpan, priority, result
end
-local function ComputeValue(element, state)
+function OvaleBestAction:ComputeValue(element, state)
profiler.Start("OvaleBestAction_ComputeValue")
- Ovale:Logf("value %s", element.value)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
-
+ Ovale:Logf("[%d] value is %s", element.nodeId, element.value)
+ local timeSpan = GetTimeSpan(element)
timeSpan[1], timeSpan[2] = 0, math.huge
profiler.Stop("OvaleBestAction_ComputeValue")
return timeSpan, OVALE_DEFAULT_PRIORITY, element
end
-local function ComputeWait(element, state)
+function OvaleBestAction:ComputeWait(element, state)
profiler.Start("OvaleBestAction_Compute")
- Ovale:Logf("%s [%d]", element.type, element.nodeId)
- local self = OvaleBestAction
- local timeSpanA, priorityA, elementA = self:Compute(element.a, state)
- local timeSpan = element.timeSpan
- timeSpan:Reset()
+ local timeSpanA, priorityA, elementA = self:Compute(element.child[1], state)
+ local timeSpan = GetTimeSpan(element)
if elementA then
elementA.wait = true
CopyTimeSpan(timeSpanA, timeSpan)
- Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId)
+ Ovale:Logf("[%d] '%s' returns %s", element.nodeId, element.type, tostring(timeSpan))
end
profiler.Stop("OvaleBestAction_Compute")
return timeSpan, priorityA, elementA
end
---</private-static-methods>
-
---<private-static-properties>
-local OVALE_COMPUTE_VISITOR =
-{
- ["action"] = ComputeAction,
- ["and"] = ComputeAnd,
- ["arithmetic"] = ComputeArithmetic,
- ["compare"] = ComputeCompare,
- ["customfunction"] = ComputeCustomFunction,
- ["function"] = ComputeFunction,
- ["group"] = ComputeGroup,
- ["if"] = ComputeIf,
- ["lua"] = ComputeLua,
- ["not"] = ComputeNot,
- ["or"] = ComputeOr,
- ["unless"] = ComputeIf,
- ["value"] = ComputeValue,
- ["wait"] = ComputeWait,
-}
---</private-static-properties>
-
---<public-static-methods>
-function OvaleBestAction:OnInitialize()
- -- Resolve module dependencies.
- OvaleActionBar = Ovale.OvaleActionBar
- OvaleCooldown = Ovale.OvaleCooldown
- OvaleCondition = Ovale.OvaleCondition
- OvaleData = Ovale.OvaleData
- OvaleEquipement = Ovale.OvaleEquipement
- OvaleFuture = Ovale.OvaleFuture
- OvalePower = Ovale.OvalePower
- OvaleSpellBook = Ovale.OvaleSpellBook
- OvaleStance = Ovale.OvaleStance
-end
-
-function OvaleBestAction:StartNewAction(state)
- state:Reset()
- OvaleFuture:ApplyInFlightSpells(state)
- self_serial = self_serial + 1
-end
-
-function OvaleBestAction:GetActionInfo(element, state)
- if not element then
- return nil
- end
-
- profiler.Start("OvaleBestAction_GetActionInfo")
- local target = element.params.target or OvaleCondition.defaultTarget
- local action
- local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
- actionUsable, actionShortcut, actionIsCurrent, actionEnable,
- actionType, actionId
-
- if element.func == "spell" then
- local spellId = element.params[1]
- action = OvaleActionBar:GetForSpell(spellId)
- if not OvaleSpellBook:IsKnownSpell(spellId) and not action then
- Ovale:Logf("Spell %s not learnt", spellId)
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
-
- actionTexture = actionTexture or API_GetSpellTexture(spellId)
- actionInRange = API_IsSpellInRange(OvaleSpellBook:GetSpellName(spellId), target)
- actionCooldownStart, actionCooldownDuration, actionEnable = state:GetSpellCooldown(spellId)
- actionType = "spell"
- actionId = spellId
- -- Verify that the spell may be cast given restrictions specified in SpellInfo().
- local si = OvaleData.spellInfo[spellId]
- if si then
- if si.stance and not OvaleStance:IsStance(si.stance) then
- -- Spell requires a stance that player is not in.
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
- if si.combo then
- -- Spell requires combo points.
- local cost = state:ComboPointCost(spellId)
- if state.combo < cost then
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
- end
- for powerType in pairs(OvalePower.SECONDARY_POWER) do
- if si[powerType] then
- -- Spell requires "secondary" resources, e.g., chi, focus, rage, etc.,
- local cost = state:PowerCost(spellId, powerType)
- if state[powerType] < cost then
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
- end
- end
-
- if actionCooldownStart and actionCooldownDuration then
- -- Get the maximum time before all "primary" resources are ready.
- local atTime = state.currentTime
- for powerType in pairs(OvalePower.PRIMARY_POWER) do
- if si[powerType] then
- local t = state.currentTime + state:TimeToPower(spellId, powerType)
- if atTime < t then
- atTime = t
- end
- end
- end
- if actionCooldownStart > 0 then
- if atTime > actionCooldownStart + actionCooldownDuration then
- Ovale:Logf("Delaying spell %s for primary resource.", spellId)
- actionCooldownDuration = atTime - actionCooldownStart
- end
- else
- actionCooldownStart = state.currentTime
- actionCooldownDuration = atTime - actionCooldownStart
- end
-
- if si.blood or si.frost or si.unholy or si.death then
- -- Spell requires runes.
- local needRunes = true
- -- "buff_runes_none" is the spell ID of the buff that makes casting the spell cost no runes.
- local buffNoRunes = si.buff_runes_none
- if buffNoRunes then
- local aura = state:GetAura("player", buffNoRunes)
- if state:IsActiveAura(aura) then
- needRunes = false
- end
- end
- if needRunes then
- local ending = state.currentTime + state:GetRunesCooldown(si.blood, si.unholy, si.frost, si.death)
- if ending > actionCooldownStart + actionCooldownDuration then
- actionCooldownDuration = ending - actionCooldownStart
- end
- end
- end
- end
- -- Use a custom texture if given.
- if si.texture then
- actionTexture = "Interface\\Icons\\" .. si.texture
- end
- end
-
- actionUsable = OvaleSpellBook:IsUsableSpell(spellId)
-
- elseif element.func == "macro" then
- local macro = element.params[1]
- action = OvaleActionBar:GetForMacro(macro)
- if not action then
- Ovale:Logf("Unknown macro %s", macro)
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
- actionTexture = API_GetActionTexture(action)
- actionInRange = API_IsActionInRange(action, target)
- actionCooldownStart, actionCooldownDuration, actionEnable = API_GetActionCooldown(action)
- actionUsable = API_IsUsableAction(action)
- actionType = "macro"
- actionId = macro
-
- elseif element.func == "item" then
- local itemId = element.params[1]
- if itemId and type(itemId) ~= "number" then
- itemId = OvaleEquipement:GetEquippedItem(itemId)
- end
- if not itemId then
- Ovale:Logf("Unknown item %s", element.params[1])
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return nil
- end
- Ovale:Logf("Item %s", itemId)
- action = OvaleActionBar:GetForItem(itemId)
-
- actionTexture = API_GetItemIcon(itemId)
- actionInRange = API_IsItemInRange(itemId, target)
- actionCooldownStart, actionCooldownDuration, actionEnable = API_GetItemCooldown(itemId)
-
- local spellName = API_GetItemSpell(itemId)
- actionUsable = spellName and API_IsUsableItem(action)
- actionType = "item"
- actionId = itemId
-
- elseif element.func == "texture" then
- local texture = element.params[1]
- actionTexture = "Interface\\Icons\\" .. texture
- actionInRange = nil
- actionCooldownStart = API_GetTime()
- actionCooldownDuration = 0
- actionEnable = 1
- actionUsable = true
- actionType = "texture"
- actionId = texture
- end
-
- if action then
- actionShortcut = OvaleActionBar:GetBinding(action)
- actionIsCurrent = API_IsCurrentAction(action)
- end
-
- profiler.Stop("OvaleBestAction_GetActionInfo")
- return actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
- actionUsable, actionShortcut, actionIsCurrent, actionEnable, actionType, actionId, target, element.params.nored
-end
-
-function OvaleBestAction:Compute(element, state)
- if not element or (Ovale.bug and not Ovale.trace) then
- return nil
- end
-
- local visitor = OVALE_COMPUTE_VISITOR[element.type]
- if visitor then
- return visitor(element, state)
- end
-
- Ovale:Logf("unknown element %s, return nil", element.type)
- return nil
-end
-
-function OvaleBestAction:ComputeBool(element, state)
- local timeSpan, _, newElement = self:Compute(element, state)
- -- Match SimC: 0 is false, non-zero is true.
- -- (https://code.google.com/p/simulationcraft/wiki/ActionLists#Logical_operators)
- if newElement and newElement.type == "value" and newElement.value == 0 and newElement.rate == 0 then
- return nil
- else
- return timeSpan
- end
+function OvaleBestAction:Debug()
+ self_timeSpanPool:Debug()
+ self_valuePool:Debug()
end
--</public-static-methods>
diff --git a/OvaleCompile.lua b/OvaleCompile.lua
index 4456bd4..f5efc2b 100644
--- a/OvaleCompile.lua
+++ b/OvaleCompile.lua
@@ -13,11 +13,8 @@ local OvaleCompile = Ovale:NewModule("OvaleCompile", "AceEvent-3.0")
Ovale.OvaleCompile = OvaleCompile
--<private-static-properties>
-local L = Ovale.L
-local OvalePool = Ovale.OvalePool
-local OvaleTimeSpan = Ovale.OvaleTimeSpan
-
-- Forward declarations for module dependencies.
+local OvaleAST = nil
local OvaleCondition = nil
local OvaleCooldown = nil
local OvaleData = nil
@@ -32,913 +29,345 @@ local OvaleStance = nil
local ipairs = ipairs
local pairs = pairs
local tonumber = tonumber
-local strgmatch = string.gmatch
-local strgsub = string.gsub
-local strlen = string.len
-local strlower = string.lower
+local strfind = string.find
local strmatch = string.match
local strsub = string.sub
-local tinsert = table.insert
local wipe = table.wipe
-local API_GetItemInfo = GetItemInfo
-local API_GetSpellInfo = GetSpellInfo
-- Profiling set-up.
local Profiler = Ovale.Profiler
local profiler = nil
do
local group = OvaleCompile:GetName()
-
- local function EnableProfiling()
- API_GetItemInfo = Profiler:Wrap(group, "OvaleCompile_API_GetItemInfo", GetItemInfo)
- API_GetSpellInfo = Profiler:Wrap(group, "OvaleCompile_API_GetSpellInfo", GetSpellInfo)
- end
-
- local function DisableProfiling()
- API_GetItemInfo = GetItemInfo
- API_GetSpellInfo = GetSpellInfo
- end
-
- Profiler:RegisterProfilingGroup(group, EnableProfiling, DisableProfiling)
+ Profiler:RegisterProfilingGroup(group)
profiler = Profiler:GetProfilingGroup(group)
end
-local self_node = {}
-local self_pool = OvalePool("OvaleCompile_pool")
-local self_timeSpanPool = OvalePool("OvaleCompile_timeSpanPool")
-local self_defines = {}
-local self_sharedCooldownNames = {}
-local self_customFunctions = {}
-local self_missingSpellList = {}
--- table of functions called within the script: self_functionCalls[functionName] = node
-local self_functionCalls = {}
-
-- Whether to trigger a script compilation if items or stances change.
local self_compileOnItems = false
local self_compileOnStances = false
-- This module needs the information in other modules to be preloaded and ready for use.
-local self_canCompile = false
+local self_canEvaluate = false
local self_requirePreload = { "OvaleEquipement", "OvaleSpellBook", "OvaleStance" }
--- Current age of compilation state.
+-- Current age of the script evaluation state.
+-- This advances every time an event occurs that requires re-evaluating the script.
local self_serial = 0
--- Number of times the script has been compiled.
-local self_compileCount = 0
--- Master nodes of the current script (one node for each icon)
-local self_masterNodes = {}
+-- Number of times the script has been evaluated.
+local self_timesEvaluated = 0
+-- Icon nodes of the current script (one node for each icon)
+local self_icon = {}
--- Lua pattern to match a key=value pair, returning key and value.
-local KEY_VALUE_PATTERN = "([%w_]+)=(!?[-%w\\_%.]+)"
-- Lua pattern to match a floating-point number that may start with a minus sign.
local NUMBER_PATTERN = "^%-?%d+%.?%d*$"
local OVALE_COMPILE_DEBUG = "compile"
-local OVALE_MISSING_SPELL_DEBUG = "missing_spells"
-local OVALE_UNKNOWN_SPELL_DEBUG = "unknown_spells"
-
--- Parameters used as conditionals in script declarations.
-local OVALE_PARAMETER = {
- checkboxoff = true,
- checkboxon = true,
- glyph = true,
- if_spell = true,
- if_stance = true,
- item = true,
- itemcount = true,
- itemset = true,
- list = true,
- mastery = true,
- talent = true,
-}
-
--- Known script functions other than conditions.
-local OVALE_FUNCTIONS = {
- item = true,
- macro = true,
- spell = true,
- texture = true,
-}
--</private-static-properties>
--<public-static-properties>
--- Current age of the current compiled script.
+-- Current age of the script; this advances every time the script is evaluated.
OvaleCompile.serial = nil
-OvaleCompile.customFunctionNode = {}
+-- AST for the current script.
+OvaleCompile.ast = nil
--</public-static-properties>
--<private-static-methods>
-local function AddNode(node)
- tinsert(self_node, node)
- node.nodeId = #self_node
- return "node" .. #self_node
-end
-
--- Parse params string into key=value pairs and positional arguments stored in paramList table.
-local function ParseParameters(params, paramList)
- profiler.Start("OvaleCompile_ParseParameters")
- paramList = paramList or {}
- if params then
- -- Handle key=value pairs.
- for key, value in strgmatch(params, KEY_VALUE_PATTERN) do
- if strmatch(key, NUMBER_PATTERN) then
- key = tonumber(key)
- end
- if strmatch(value, NUMBER_PATTERN) then
- value = tonumber(value)
- end
- paramList[key] = value
- end
- -- Strip out all key=value pairs and handle positional arguments.
- params = strgsub(params, KEY_VALUE_PATTERN, "")
- local k = 1
- for word in strgmatch(params, "[-%w_\\%.]+") do
- if strmatch(word, NUMBER_PATTERN) then
- word = tonumber(word)
- end
- paramList[k] = word
- k = k + 1
- end
- end
- profiler.Stop("OvaleCompile_ParseParameters")
- return paramList
-end
-
local function HasTalent(talentId)
if OvaleSpellBook:IsKnownTalent(talentId) then
return OvaleSpellBook:GetTalentPoints(talentId) > 0
else
- Ovale:FormatPrint("Unknown talent %s", talentId)
+ Ovale:FormatPrint("Warning: unknown talent ID '%s'", talentId)
return false
end
end
local function RequireValue(value)
- local requireValue = (strsub(value, 1, 1) ~= "!")
- if not requireValue then
+ local required = (strsub(tostring(value), 1, 1) ~= "!")
+ if not required then
value = strsub(value, 2)
if strmatch(value, NUMBER_PATTERN) then
value = tonumber(value)
end
end
- return value, requireValue
+ return value, required
end
-local function TestConditions(paramList)
+local function TestConditions(parameters)
profiler.Start("OvaleCompile_TestConditions")
local boolean = true
- if boolean and paramList.glyph then
- local glyph, requireGlyph = RequireValue(paramList.glyph)
+ if boolean and parameters.glyph then
+ local glyph, required = RequireValue(parameters.glyph)
local hasGlyph = OvaleSpellBook:IsActiveGlyph(glyph)
- if (requireGlyph and not hasGlyph) or (not requireGlyph and hasGlyph) then
- boolean = false
- end
+ boolean = (required and hasGlyph) or (not required and not hasGlyph)
end
- if boolean and paramList.mastery then
- local spec, requireSpec = RequireValue(paramList.mastery)
+ if boolean and parameters.mastery then
+ local spec, required = RequireValue(parameters.mastery)
local isSpec = OvalePaperDoll:IsSpecialization(spec)
- if (requireSpec and not isSpec) or (not requireSpec and isSpec) then
- boolean = false
- end
+ boolean = (required and isSpec) or (not required and not isSpec)
end
- if boolean and paramList.if_stance then
+ if boolean and parameters.if_stance then
self_compileOnStances = true
- local stance, requireStance = RequireValue(paramList.if_stance)
+ local stance, required = RequireValue(parameters.if_stance)
local isStance = OvaleStance:IsStance(stance)
- if (requireStance and not isStance) or (not requireStance and isStance) then
- boolean = false
- end
+ boolean = (required and isStance) or (not required and not isStance)
end
- if boolean and paramList.if_spell then
- local spell, requireSpell = RequireValue(paramList.if_spell)
+ if boolean and parameters.if_spell then
+ local spell, required = RequireValue(parameters.if_spell)
local hasSpell = OvaleSpellBook:IsKnownSpell(spell)
- if (requireSpell and not hasSpell) or (not requireSpell and hasSpell) then
- boolean = false
- end
+ boolean = (required and hasSpell) or (not required and not hasSpell)
end
- if boolean and paramList.talent then
- local talent, requireTalent = RequireValue(paramList.talent)
+ if boolean and parameters.talent then
+ local talent, required = RequireValue(parameters.talent)
local hasTalent = HasTalent(talent)
- if (requireTalent and not hasTalent) or (not requireTalent and hasTalent) then
- boolean = false
- end
+ boolean = (required and hasTalent) or (not required and not hasTalent)
end
- if boolean and paramList.checkboxon then
- local cb = paramList.checkboxon
- if not Ovale.casesACocher[cb] then
- Ovale.casesACocher[cb] = {}
- end
- Ovale.casesACocher[cb].compile = true
- if not OvaleOptions:GetProfile().check[cb] then
- boolean = false
- end
- end
- if boolean and paramList.checkboxoff then
- local cb = paramList.checkboxoff
- if not Ovale.casesACocher[cb] then
- Ovale.casesACocher[cb] = {}
- end
- Ovale.casesACocher[cb].compile = true
- if OvaleOptions:GetProfile().check[cb] then
- boolean = false
- end
- end
- if boolean and paramList.list and paramList.item then
- local list = paramList.list
- local key = paramList.item
- if not Ovale.listes[list] then
- Ovale.listes[list] = { items = {}}
- end
- Ovale.listes[list].compile = true
- if OvaleOptions:GetProfile().list[list] ~= key then
- boolean = false
- end
- end
- if boolean and paramList.itemset and paramList.itemcount then
- local equippedCount = OvaleEquipement:GetArmorSetCount(paramList.itemset)
+ if boolean and parameters.itemset and parameters.itemcount then
+ local equippedCount = OvaleEquipement:GetArmorSetCount(parameters.itemset)
self_compileOnItems = true
- if equippedCount < paramList.itemcount then
- boolean = false
+ boolean = (equippedCount >= parameters.itemcount)
+ end
+ do
+ local profile
+ if boolean and parameters.checkbox then
+ local name, required = RequireValue(parameters.checkbox)
+ local checkBox = Ovale.casesACocher[name] or {}
+ checkBox.compile = true
+ Ovale.casesACocher[name] = checkBox
+ -- Check the value of the checkbox.
+ profile = profile or OvaleOptions:GetProfile()
+ local isChecked = (profile.check[name] ~= nil)
+ boolean = (required and isChecked) or (not required and not isChecked)
+ end
+ -- XXX Deprecated: checkboxon
+ if boolean and parameters.checkboxon then
+ -- Flag this checkbox as triggering a script evaluation.
+ local name = parameters.checkboxon
+ local checkBox = Ovale.casesACocher[name] or {}
+ checkBox.compile = true
+ Ovale.casesACocher[name] = checkBox
+ -- Check the value of the checkbox.
+ profile = profile or OvaleOptions:GetProfile()
+ boolean = (profile.check[name] ~= nil)
+ end
+ -- XXX Deprecated: checkboxoff
+ if boolean and parameters.checkboxoff then
+ -- Flag this checkbox as triggering a script evaluation.
+ local name = parameters.checkboxon
+ local checkBox = Ovale.casesACocher[name] or {}
+ checkBox.compile = true
+ Ovale.casesACocher[name] = checkBox
+ -- Check the value of the checkbox.
+ profile = profile or OvaleOptions:GetProfile()
+ boolean = (profile.check[name] == nil)
+ end
+ if boolean and parameters.list and parameters.item then
+ -- Flag this list as triggering a script evaluation.
+ local name = parameters.list
+ local item, required = RequireValue(parameters.item)
+ local list = Ovale.listes[name] or { items = {}, default = nil }
+ list.compile = true
+ Ovale.listes[name] = list
+ -- Check the selected item in the list.
+ profile = profile or OvaleOptions:GetProfile()
+ local isSelected = (profile.list[name] == item)
+ boolean = (required and isSelected) or (not required and not isSelected)
end
end
profiler.Stop("OvaleCompile_TestConditions")
return boolean
end
-local function ParseNumber(dummy, value)
- local node = self_pool:Get()
- node.type = "value"
- node.value = tonumber(value)
- node.origin = 0
- node.rate = 0
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return dummy..AddNode(node)
-end
-
-local function ParseFunction(prefix, func, params)
- local paramList = ParseParameters(params)
- if func ~= "" then
- paramList.target = prefix
- else
- func = prefix
- end
-
- if not paramList.target then
- if strsub(func, 1, 6) == "Target" then
- paramList.target = "target"
- func = strsub(func, 7)
- end
- end
-
- if self_customFunctions[func] then
- self_functionCalls[func] = self_customFunctions[func]
- return self_customFunctions[func]
- end
-
- func = strlower(func)
-
- -- "debuff" and "buff" conditions implicitly set their aura filter.
- if not paramList.filter then
- if strsub(func, 1, 6) == "debuff" then
- paramList.filter = "debuff"
- elseif strsub(func, 1, 4) == "buff" then
- paramList.filter = "buff"
- elseif strsub(func, 1, 11) == "otherdebuff" then
- paramList.filter = "debuff"
- elseif strsub(func, 1, 9) == "otherbuff" then
- paramList.filter = "buff"
- end
- end
-
- local node = self_pool:Get()
- if func == "spell" or func == "macro" or func == "item" or func == "texture" then
- node.type = "action"
- else
- node.type = "function"
- end
- node.func = func
- node.params = paramList
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- local nodeName = AddNode(node)
- self_functionCalls[func] = node
-
- local mine = true
- if paramList.any then
- mine = false
- end
-
- local spellId = paramList[1]
- if spellId then
- -- For the conditions that refer to player's spells, check if the spell ID
- -- 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:IsSpellbookCondition(func) then
- if not OvaleSpellBook:IsKnownSpell(spellId) and not self_missingSpellList[spellId] and not self_sharedCooldownNames[spellId] then
- local spellName
- if type(spellId) == "number" then
- spellName = API_GetSpellInfo(spellId)
- end
- if spellName then
- if spellName == API_GetSpellInfo(spellName) then
- Ovale:DebugPrintf(OVALE_MISSING_SPELL_DEBUG, "Learning spell %s with ID %d", spellName, spellId)
- self_missingSpellList[spellId] = spellName
- end
- else
- Ovale:DebugPrintf(OVALE_UNKNOWN_SPELL_DEBUG, "Unknown spell with ID %s", spellId)
- end
- end
- end
- end
-
- return nodeName
-end
-
---[[
- Parse the various Spell*{Buff,Debuff}() declarations.
- Check for test conditions to see whether this declaration is active.
- Filter out then test conditions and copy the rest of the key=value pairs
- into the aura table.
---]]
-local function ParseSpellAuraList(auraTable, filter, paramList)
- if TestConditions(paramList) then
- paramList[1] = nil
- if not auraTable[filter] then
- for k, v in pairs(paramList) do
- if OVALE_PARAMETER[k] then
- paramList[k] = nil
- end
- end
- auraTable[filter] = paramList
- else
- local tbl = auraTable[filter]
- for k, v in pairs(paramList) do
- if not OVALE_PARAMETER[k] then
- tbl[k] = v
- end
+local function EvaluateAddCheckBox(node)
+ local ok = true
+ local name, parameters = node.name, node.params
+ if TestConditions(parameters) then
+ --[[
+ If this control was not previously existing, then age the script evaluation state
+ so that anything that checks the value of this control are re-evaluated after the
+ current evaluation cycle.
+ --]]
+ if not Ovale.casesACocher[name] then
+ self_serial = self_serial + 1
+ end
+ local checkBox = Ovale.casesACocher[name] or {}
+ checkBox.text = node.description.value
+ for _, v in ipairs(parameters) do
+ if v == "default" then
+ checkBox.checked = true
+ break
end
end
+ Ovale.casesACocher[name] = checkBox
end
- return ""
-end
-
-local function ParseSpellAddBuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.player, "HELPFUL", paramList)
-end
-
-local function ParseSpellAddDebuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.player, "HARMFUL", paramList)
-end
-
-local function ParseSpellAddTargetBuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.target, "HELPFUL", paramList)
+ return ok
end
-local function ParseSpellAddTargetDebuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.target, "HARMFUL", paramList)
-end
-
-local function ParseSpellDamageBuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.damage, "HELPFUL", paramList)
-end
-
-local function ParseSpellDamageDebuff(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- local si = OvaleData:SpellInfo(spellId)
- return ParseSpellAuraList(si.aura.damage, "HARMFUL", paramList)
-end
-
-local function ParseSpellInfo(params)
- local paramList = ParseParameters(params)
- local spellId = paramList[1]
- if spellId and TestConditions(paramList) then
- local si = OvaleData:SpellInfo(spellId)
- for k,v in pairs(paramList) do
- if k == "addduration" then
- si.duration = si.duration + v
- elseif k == "addcd" then
- si.cd = si.cd + v
- elseif k == "addlist" then
- -- Add this buff to the named spell list.
- if not OvaleData.buffSpellList[v] then
- OvaleData.buffSpellList[v] = {}
- end
- OvaleData.buffSpellList[v][spellId] = true
- elseif k == "sharedcd" then
- OvaleCooldown:AddSharedCooldown(v, spellId)
- self_sharedCooldownNames[v] = true
- else
- si[k] = v
- end
- end
+local function EvaluateAddIcon(node)
+ local ok = true
+ if TestConditions(node.params) then
+ self_icon[#self_icon + 1] = node
end
- return ""
+ return ok
end
-local function ParseScoreSpells(params)
- for v in strgmatch(params, "(%d+)") do
- local spellId = tonumber(v)
- if spellId then
- OvaleScore:AddSpell(spellId)
- else
- Ovale:FormatPrint("ScoreSpell with unknown spell %s", v)
+local function EvaluateAddListItem(node)
+ local ok = true
+ local name, item, parameters = node.name, node.item, node.params
+ if TestConditions(parameters) then
+ --[[
+ If this control was not previously existing, then age the script evaluation state
+ so that anything that checks the value of this control are re-evaluated after the
+ current evaluation cycle.
+ --]]
+ if not (Ovale.listes[name] and Ovale.listes[name][item]) then
+ self_serial = self_serial + 1
end
- end
-end
-
-local function ParseSpellList(name, params)
- OvaleData.buffSpellList[name] = {}
- for v in strgmatch(params, "(%d+)") do
- v = tonumber(v)
- if v then
- OvaleData.buffSpellList[name][v] = true
+ local list = Ovale.listes[name] or { items = {}, default = nil }
+ list.items[item] = node.description.value
+ for _, v in ipairs(parameters) do
+ if v == "default" then
+ list.default = item
+ break
+ end
end
+ Ovale.listes[name] = list
end
+ return ok
end
-local function ParseItemInfo(params)
- local paramList = ParseParameters(params)
- local itemId = paramList[1]
- if itemId and TestConditions(paramList) then
- for k, v in pairs(paramList) do
+local function EvaluateItemInfo(node)
+ local ok = true
+ local itemId, parameters = node.itemId, node.params
+ if itemId and TestConditions(parameters) then
+ for k, v in pairs(parameters) do
if k == "proc" then
-- Add the buff for this item proc to the spell list "item_proc_<proc>".
- local buff = tonumber(paramList.buff)
+ local buff = tonumber(parameters.buff)
if buff then
- local listName = "item_proc_" .. v
- if not OvaleData.buffSpellList[listName] then
- OvaleData.buffSpellList[listName] = {}
- end
- OvaleData.buffSpellList[listName][buff] = true
+ local name = "item_proc_" .. v
+ local list = OvaleData.buffSpellList[name] or {}
+ list[buff] = true
+ OvaleData.buffSpellList[name] = list
+ else
+ ok = false
+ break
end
end
end
end
- return ""
-end
-
-local function ParseItemList(name, params)
- OvaleData.itemList[name] = {}
- local i = 1
- for v in strgmatch(params, "(%d+)") do
- OvaleData.itemList[name][i] = tonumber(v)
- i = i + 1
- end
-end
-
-local function ParseIf(a, b)
- local node = self_pool:Get()
- node.type = "if"
- node.a = self_node[tonumber(a)]
- node.b = self_node[tonumber(b)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseUnless(a, b)
- local node = self_pool:Get()
- node.type = "unless"
- node.a = self_node[tonumber(a)]
- node.b = self_node[tonumber(b)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseWait(a)
- local node = self_pool:Get()
- node.type = "wait"
- node.a = self_node[tonumber(a)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseAnd(a,b)
- local node = self_pool:Get()
- node.type = "and"
- node.a = self_node[tonumber(a)]
- node.b = self_node[tonumber(b)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseNot(a)
- local node = self_pool:Get()
- node.type = "not"
- node.a = self_node[tonumber(a)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseOr(a,b)
- local node = self_pool:Get()
- node.type = "or"
- node.a = self_node[tonumber(a)]
- node.b = self_node[tonumber(b)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local ParseOp
-do
- local operator = {
- ["+"] = "arithmetic",
- ["-"] = "arithmetic",
- ["*"] = "arithmetic",
- ["/"] = "arithmetic",
- ["%"] = "arithmetic",
- ["<"] = "compare",
- ["<="] = "compare",
- ["=="] = "compare",
- [">="] = "compare",
- [">"] = "compare",
- }
-
- function ParseOp(a, op, b)
- local node = self_pool:Get()
- node.type = operator[op]
- node.operator = op
- node.a = self_node[tonumber(a)]
- node.b = self_node[tonumber(b)]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
- end
-end
-
-local function ParseGroup(text)
- local nodes = {}
-
- for w in strgmatch(text, "node(%d+)") do
- tinsert(nodes, self_node[tonumber(w)])
- end
-
- text = strgsub(text, "node%d+", "")
-
- if (strmatch(text,"[^ ]")) then
- Ovale:FormatPrint("syntax error: %s", text)
- return nil
- end
-
- local node = self_pool:Get()
- node.type = "group"
- node.nodes = nodes
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseAddListItem(list, item, text, params)
- local paramList = ParseParameters(params)
- if not TestConditions(paramList) then
- return ""
- end
- if (not Ovale.listes[list]) then
- Ovale.listes[list] = {items={},default=nil}
- end
- Ovale.listes[list].items[item] = text
- if paramList[1] and paramList[1] == "default" then
- Ovale.listes[list].default=item
- end
- return ""
-end
-
-local function ParseAddCheckBox(item, text, params)
- local paramList = ParseParameters(params)
- if not TestConditions(paramList) then
- return ""
- end
- if not Ovale.casesACocher[item] then
- Ovale.casesACocher[item] = {}
- end
- Ovale.casesACocher[item].text = text
- if paramList[1] and paramList[1]=="default" then
- Ovale.casesACocher[item].checked = true
- end
- return ""
+ return ok
end
-local function ParseDefine(key, value)
- self_defines[key] = value
- return ""
-end
-
-local function ReplaceDefine(key)
- return self_defines[key]
-end
-
-local function ParseLua(text)
- local node = self_pool:Get()
- node.type = "lua"
- node.lua = strsub(text, 2, strlen(text)-1)
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
-end
-
-local function ParseInclude(name)
- local code
- local script = OvaleScripts.script[name]
- if script then
- code = script.code
- end
- if not code then
- Ovale:FormatPrint("Cannot Include(...): script named \"%s\" not found", name)
- end
- return code or ""
-end
-
-local function ParseCommands(text)
- local original = text
- text = strgsub(text,"(%b[])", ParseLua)
-
- while true do
- local was = text
- text = strgsub(text, "(%w+)%.?(%w*)%s*%((.-)%)", ParseFunction)
- text = strgsub(text, "([^%w])(%d+%.?%d*)", ParseNumber)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
-
- while true do
- local was = text
- while true do
- local was = text
- text = strgsub(text, "node(%d+)%s*([%*%/%%])%s*node(%d+)", ParseOp)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
- while true do
- local was = text
- text = strgsub(text, "node(%d+)%s*([%+%-])%s*node(%d+)", ParseOp)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
- if was == text then
- break
- end
+local function EvaluateList(node)
+ local ok = true
+ local name, parameters = node.name, node.params
+ local listDB
+ if node.keyword == "ItemList" then
+ listDB = "itemList"
+ else -- if node.keyword == "SpellList" then
+ listDB = "buffSpellList"
end
-
- while true do
- local was = text
- text = strgsub(text, "node(%d+)%s*([%>%<]=?)%s*node(%d+)", ParseOp)
- text = strgsub(text, "node(%d+)%s*(==)%s*node(%d+)", ParseOp)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
-
- while true do
- local was = text
- while true do
- local was = text
- text = strgsub(text, "not%s+node(%d+)", ParseNot)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
- while true do
- local was = text
- text = strgsub(text, "node(%d+)%s+and%s+node(%d+)", ParseAnd)
- text = strgsub(text, "node(%d+)%s+or%s+node(%d+)", ParseOr)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
- break
- end
- end
- if was == text then
+ local list = OvaleData[listDB][name] or {}
+ for i, id in ipairs(parameters) do
+ id = tonumber(id)
+ if id then
+ list[id] = true
+ else
+ ok = false
break
end
end
+ OvaleData[listDB][name] = list
+ return ok
+end
- while true do
- local was = text
- text = strgsub(text, "if%s+node(%d+)%s+node(%d+)",ParseIf)
- text = strgsub(text, "unless%s+node(%d+)%s+node(%d+)",ParseUnless)
- text = strgsub(text, "wait%s+node(%d+)",ParseWait)
- text = strgsub(text, "{([node%d ]*)}", ParseGroup)
- if was == text then
+local function EvaluateScoreSpells(node)
+ local ok = true
+ for _, spellId in ipairs(node.params) do
+ spellId = tonumber(spellId)
+ if spellId then
+ OvaleScore:AddSpell(tonumber(spellId))
+ else
+ ok = false
break
end
end
-
- local nodeId
- if text then
- nodeId = tonumber(strmatch(text, "node(%d+)"))
- end
- if nodeId then
- -- If there is anything other than spaces, this is a syntax error.
- text = strgsub(text, "node%d+", "", 1)
- if strmatch(text,"[^ ]") then
- Ovale:FormatPrint("Group: %s", original)
- Ovale:FormatPrint("syntax error: %s", text)
- nodeId = nil
- end
- else
- Ovale:Print("no master node")
- end
- return nodeId
+ return ok
end
-local function ParseAddFunction(name, params, text)
- local paramList = ParseParameters(params)
- if TestConditions(paramList) then
- local nodeId = ParseCommands(text)
- if nodeId then
- local node = self_pool:Get()
- node.type = "customfunction"
- node.name = name
- node.params = paramList
- node.a = self_node[nodeId]
- node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get())
- return AddNode(node)
+local function EvaluateSpellAuraList(node)
+ local ok = true
+ local spellId, parameters = node.spellId, node.params
+ if TestConditions(parameters) then
+ local keyword = node.keyword
+ local si = OvaleData:SpellInfo(spellId)
+ local auraTable
+ if strfind(keyword, "^SpellAddTarget") then
+ auraTable = si.aura.target
+ elseif strfind(keyword, "^SpellDamage") then
+ auraTable = si.aura.damage
+ else
+ auraTable = si.aura.player
+ end
+ local filter = strfind(node.keyword, "Debuff") and "HARMFUL" or "HELPFUL"
+ local tbl = auraTable[filter] or {}
+ local count = 0
+ for k, v in pairs(parameters) do
+ if not OvaleAST.PARAMETER_KEYWORD[k] then
+ tbl[k] = v
+ count = count + 1
+ end
end
- end
-end
-
-local function ParseAddIcon(params, text, secure)
- local paramList = ParseParameters(params)
- if TestConditions(paramList) then
- local masterNodeId = ParseCommands(text)
- if masterNodeId then
- local masterNode = self_node[masterNodeId]
- masterNode.params = paramList
- masterNode.secure = secure
- return masterNode
+ if count > 0 then
+ auraTable[filter] = tbl
end
end
+ return ok
end
-local function ParseItemName(text)
- local itemId = tonumber(text)
- if itemId then
- local item = API_GetItemInfo(itemId) or "Item " .. itemId
- return '"' .. item .. '"'
- else
- Ovale:FormatPrint("ItemName of %s unknown\n", text)
- return nil
- end
-end
-
-local function ParseSpellName(text)
- local spellId = tonumber(text)
- local spell = OvaleSpellBook:GetSpellName(spellId)
- if spell then
- return '"' .. spell .. '"'
- else
- Ovale:FormatPrint("SpellName of %s unknown", text)
- return nil
- end
-end
-
-local function ParseL(text)
- return '"'..L[text]..'"'
-end
-
--- On compile les AddCheckBox et AddListItem
-local function CompileInputs(text)
- Ovale.casesACocher = {}
- Ovale.listes = {}
-
- text = strgsub(text, "AddListItem%s*%(%s*([%w_]+)%s+([%w_]+)%s+\"(.-)\"%s*(.-)%s*%)", ParseAddListItem)
- text = strgsub(text, "AddCheckBox%s*%(%s*([%w_]+)%s+\"(.-)\"%s*(.-)%s*%)", ParseAddCheckBox)
- return text
-end
-
--- Compile non-function and non-icon declarations.
-local function CompileDeclarations(text)
- -- Define(CONSTANTE valeur)
- text = strgsub(text, "Define%s*%(%s*([%w_]+)%s+([%w_.=]+)%s*%)", ParseDefine)
- -- On remplace les constantes par leur valeur
- text = strgsub(text, "([%w_]+)", ReplaceDefine)
-
- -- Fonctions
- text = strgsub(text, "ItemName%s*%(%s*(%w+)%s*%)", ParseItemName)
- text = strgsub(text, "SpellName%s*%(%s*(%w+)%s*%)", ParseSpellName)
- text = strgsub(text, "L%s*%(%s*(%w+)%s*%)", ParseL)
-
- -- Options diverses
- OvaleData:ResetSpellInfo()
- text = strgsub(text, "SpellAddBuff%s*%((.-)%)", ParseSpellAddBuff)
- text = strgsub(text, "SpellAddDebuff%s*%((.-)%)", ParseSpellAddDebuff)
- text = strgsub(text, "SpellAddTargetBuff%s*%((.-)%)", ParseSpellAddTargetBuff)
- text = strgsub(text, "SpellAddTargetDebuff%s*%((.-)%)", ParseSpellAddTargetDebuff)
- text = strgsub(text, "SpellDamageBuff%s*%((.-)%)", ParseSpellDamageBuff)
- text = strgsub(text, "SpellDamageDebuff%s*%((.-)%)", ParseSpellDamageDebuff)
- text = strgsub(text, "SpellInfo%s*%((.-)%)", ParseSpellInfo)
- text = strgsub(text, "ItemInfo%s*%((.-)%)", ParseItemInfo)
- text = strgsub(text, "ScoreSpells%s*%((.-)%)", ParseScoreSpells)
- text = strgsub(text, "SpellList%s*%(%s*([%w_]+)%s*(.-)%)", ParseSpellList)
- text = strgsub(text, "ItemList%s*%(%s*([%w_]+)%s*(.-)%)", ParseItemList)
-
- -- On vire les espaces en trop
- text = strgsub(text, "\n", " ")
- text = strgsub(text, "%s+", " ")
-
- return text
-end
-
-local function CompileScript(text)
- profiler.Start("OvaleCompile_CompileScript")
- local self = OvaleCompile
- self_compileOnItems = false
- self_compileOnStances = false
- Ovale.bug = false
-
- wipe(self_defines)
- wipe(self_sharedCooldownNames)
- wipe(self_customFunctions)
- wipe(self_missingSpellList)
- wipe(self_functionCalls)
- wipe(self.customFunctionNode)
- OvaleCooldown:ResetSharedCooldowns()
-
- -- Return all existing nodes to the node pool.
- for i, node in pairs(self_node) do
- self_node[i] = nil
- self_timeSpanPool:Release(node.timeSpan)
- self_pool:Release(node)
- end
- wipe(self_node)
-
- -- Loop and strip out comments and replace Include() directives until there
- -- are no more inclusions to make.
- while true do
- local was = text
- text = strgsub(text, "#.-\n","")
- text = strgsub(text, "#.*$","")
- text = strgsub(text, "Include%s*%(%s*([%w_]+)%s*%)", ParseInclude)
- if was == text then
- break
- end
- end
-
- text = CompileDeclarations(text)
- text = CompileInputs(text)
-
- for name, p, t in strgmatch(text, "AddFunction%s+(%w+)%s*(.-)%s*(%b{})") do
- local node = ParseAddFunction(name, p, t)
- if node then
- self_customFunctions[name] = node
- local nodeId = strmatch(node, "node(%d+)")
- self.customFunctionNode[name] = self_node[tonumber(nodeId)]
- end
- end
-
- -- On compile les AddIcon
- wipe(self_masterNodes)
-
- for p,t in strgmatch(text, "AddActionIcon%s*(.-)%s*(%b{})") do
- local node = ParseAddIcon(p,t,true)
- if node then
- tinsert(self_masterNodes, node)
- end
- end
-
- for p,t in strgmatch(text, "AddIcon%s*(.-)%s*(%b{})") do
- local node = ParseAddIcon(p,t)
- if node then
- tinsert(self_masterNodes, node)
- end
- end
-
- -- Verify that all the functions called within the script are defined.
- for p, v in pairs(self_functionCalls) do
- if not (OVALE_FUNCTIONS[p] or self_customFunctions[p] or OvaleCondition:IsCondition(p)) then
- Ovale:Errorf("Unknown function call: %s (node%s)", p, v.nodeId)
+local function EvaluateSpellInfo(node)
+ local ok = true
+ local spellId, parameters = node.spellId, node.params
+ if spellId and TestConditions(parameters) then
+ local si = OvaleData:SpellInfo(spellId)
+ for k, v in pairs(parameters) do
+ if k == "addduration" then
+ local value = tonumber(v)
+ if value then
+ si.duration = si.duration + value
+ else
+ ok = false
+ break
+ end
+ elseif k == "addcd" then
+ local value = tonumber(v)
+ if value then
+ si.cd = si.cd + value
+ else
+ ok = false
+ break
+ end
+ elseif k == "addlist" then
+ -- Add this buff to the named spell list.
+ local list = OvaleData.buffSpellList[v] or {}
+ list[spellId] = true
+ OvaleData.buffSpellList[v] = list
+ elseif k == "sharedcd" then
+ OvaleCooldown:AddSharedCooldown(v, spellId)
+ elseif not OvaleAST.PARAMETER_KEYWORD[k] then
+ si[k] = v
+ end
end
end
-
- -- Add any missing spells found while compiling the script into the spellbook.
- for k, v in pairs(self_missingSpellList) do
- OvaleSpellBook:AddSpell(k, v)
- end
- profiler.Stop("OvaleCompile_CompileScript")
+ return ok
end
--</private-static-methods>
--<public-static-methods>
function OvaleCompile:OnInitialize()
-- Resolve module dependencies.
+ OvaleAST = Ovale.OvaleAST
OvaleCondition = Ovale.OvaleCondition
OvaleCooldown = Ovale.OvaleCooldown
OvaleData = Ovale.OvaleData
@@ -952,15 +381,15 @@ function OvaleCompile:OnInitialize()
end
function OvaleCompile:OnEnable()
- self:RegisterMessage("PLAYER_REGEN_ENABLED")
self:RegisterMessage("Ovale_CheckBoxValueChanged", "EventHandler")
self:RegisterMessage("Ovale_EquipmentChanged")
self:RegisterMessage("Ovale_GlyphsChanged", "EventHandler")
self:RegisterMessage("Ovale_ListValueChanged", "EventHandler")
- self:RegisterMessage("Ovale_ScriptChanged", "EventHandler")
+ self:RegisterMessage("Ovale_ScriptChanged", "CompileScript")
self:RegisterMessage("Ovale_SpellsChanged", "EventHandler")
self:RegisterMessage("Ovale_StanceChanged")
self:RegisterMessage("Ovale_TalentsChanged", "EventHandler")
+ self:SendMessage("Ovale_ScriptChanged")
end
function OvaleCompile:OnDisable()
@@ -972,11 +401,6 @@ function OvaleCompile:OnDisable()
self:UnregisterMessage("Ovale_SpellsChanged")
self:UnregisterMessage("Ovale_StanceChanged")
self:UnregisterMessage("Ovale_TalentsChanged")
- self_pool:Drain()
-end
-
-function OvaleCompile:PLAYER_REGEN_ENABLED(event)
- self_pool:Drain()
end
function OvaleCompile:Ovale_EquipmentChanged(event)
@@ -992,93 +416,92 @@ function OvaleCompile:Ovale_StanceChanged(event)
end
function OvaleCompile:EventHandler(event)
- Ovale:DebugPrint(OVALE_COMPILE_DEBUG, event)
- -- Advance age of current compilation state.
+ -- Advance age of the script evaluation state.
self_serial = self_serial + 1
+ Ovale:DebugPrintf(OVALE_COMPILE_DEBUG, "%s: advance age to %d.", event, self_serial)
Ovale.refreshNeeded["player"] = true
end
-function OvaleCompile:Compile()
- self_canCompile = self_canCompile or Ovale:IsPreloaded(self_requirePreload)
- if self_canCompile then
- local profile = OvaleOptions:GetProfile()
- local source = profile.source
- local code
- if source and OvaleScripts.script[source] then
- code = OvaleScripts.script[source].code
- else
- code = ""
+function OvaleCompile:CompileScript(event)
+ local profile = OvaleOptions:GetProfile()
+ local source = profile.source
+ Ovale:DebugPrintf(OVALE_COMPILE_DEBUG, "Compiling script '%s'.", source)
+ if self.ast then
+ OvaleAST:Release(self.ast)
+ self.ast = nil
+ end
+ local ast = OvaleAST:ParseScript(source)
+ if ast then
+ OvaleAST:Optimize(ast)
+ self.ast = ast
+ end
+ self:EventHandler(event)
+end
+
+function OvaleCompile:EvaluateScript()
+ profiler.Start("OvaleCompile_EvaluateScript")
+ self_canEvaluate = self_canEvaluate or Ovale:IsPreloaded(self_requirePreload)
+ if self_canEvaluate and self.ast then
+ Ovale:DebugPrint(OVALE_COMPILE_DEBUG, "Evaluating script.")
+ -- Reset compilation state.
+ local ok = true
+ self_compileOnItems = false
+ self_compileOnStances = false
+ wipe(self_icon)
+ OvaleCooldown:ResetSharedCooldowns()
+ self_timesEvaluated = self_timesEvaluated + 1
+
+ -- Evaluate every declaration node of the script.
+ for _, node in ipairs(self.ast.child) do
+ local nodeType = node.type
+ if nodeType == "checkbox" then
+ ok = EvaluateAddCheckBox(node)
+ elseif nodeType == "icon" then
+ ok = EvaluateAddIcon(node)
+ elseif nodeType == "list_item" then
+ ok = EvaluateAddListItem(node)
+ elseif nodeType == "item_info" then
+ ok = EvaluateItemInfo(node)
+ elseif nodeType == "list" then
+ ok = EvaluateList(node)
+ elseif nodeType == "score_spells" then
+ ok = EvaluateScoreSpells(node)
+ elseif nodeType == "spell_aura_list" then
+ ok = EvaluateSpellAuraList(node)
+ elseif nodeType == "spell_info" then
+ ok = EvaluateSpellInfo(node)
+ else
+ -- Any other top-level node types are no-ops when evaluating the script.
+ end
+ if not ok then
+ break
+ end
+ end
+ if ok then
+ Ovale:UpdateFrame()
end
- CompileScript(code)
- self_compileCount = self_compileCount + 1
- Ovale:UpdateFrame()
end
+ profiler.Stop("OvaleCompile_EvaluateScript")
end
function OvaleCompile:GetFunctionNode(name)
- return self.customFunctionNode[name]
+ local node
+ if self.ast and self.ast.annotation and self.ast.annotation.customFunction then
+ node = self.ast.annotation.customFunction[name]
+ end
+ return node
end
-function OvaleCompile:GetMasterNodes()
- -- Compile the script if it is outdated.
+function OvaleCompile:GetIconNodes()
+ -- Evaluate the script if it is outdated.
if not self.serial or self.serial < self_serial then
self.serial = self_serial
- self:Compile()
+ self:EvaluateScript()
end
- return self_masterNodes
+ return self_icon
end
-function OvaleCompile:Debug(iconNumber)
- iconNumber = iconNumber or 1
- self_pool:Debug()
- local masterNodes = self:GetMasterNodes()
- Ovale:Print(self:DebugNode(masterNodes[iconNumber]))
- Ovale:FormatPrint("Total number of script compilations: %d", self_compileCount)
-end
-
-function OvaleCompile:DebugNode(node)
- local text
- if (not node) then
- return "#nil"
- end
- if (node.type == "group") then
- text = "{"
- for k,n in ipairs(node.nodes) do
- text = text .. self:DebugNode(n) .. " "
- end
- text = text .. "}\n"
- elseif (node.type == "action" or node.type == "function") then
- text = node.func.."("
- for k,p in pairs(node.params) do
- text = text .. k.."=" .. p .. " "
- end
- text = text .. ")"
- elseif (node.type == "customfunction") then
- text = self:DebugNode(node.a)
- elseif (node.type == "if") then
- text = "if "..self:DebugNode(node.a).." "..self:DebugNode(node.b)
- elseif (node.type == "unless") then
- text = "unless "..self:DebugNode(node.a).." "..self:DebugNode(node.b)
- elseif (node.type == "wait") then
- text = "wait "..self:DebugNode(node.a)
- elseif (node.type == "and") then
- text = self:DebugNode(node.a).." and "..self:DebugNode(node.b)
- elseif (node.type == "or") then
- text = self:DebugNode(node.a).." or "..self:DebugNode(node.b)
- elseif (node.type == "not") then
- text = "not "..self:DebugNode(node.a)
- elseif node.type == "compare" then
- text = self:DebugNode(node.a)..node.operator..self:DebugNode(node.b)
- elseif node.type == "arithmetic" then
- text = self:DebugNode(node.a)..node.operator..self:DebugNode(node.b)
- elseif node.type == "lua" then
- text = "["..node.lua.."]"
- elseif node.type == "value" then
- text = node.value
- else
- text = "#unknown node type "..node.type.."#"
- end
-
- return text
+function OvaleCompile:Debug()
+ Ovale:FormatPrint("Total number of times the script was evaluated: %d", self_timesEvaluated)
end
--</public-static-methods>
diff --git a/OvaleFrame.lua b/OvaleFrame.lua
index b5cebd0..ec62251 100644
--- a/OvaleFrame.lua
+++ b/OvaleFrame.lua
@@ -171,26 +171,25 @@ do
local refresh = forceRefresh or next(Ovale.refreshNeeded)
if not refresh then return end
- local masterNodes = OvaleCompile:GetMasterNodes()
- if not masterNodes then return end
+ local iconNodes = OvaleCompile:GetIconNodes()
+ if not iconNodes then return end
self.lastUpdate = now
local state = OvaleState.state
state:Initialize()
- for k,node in pairs(masterNodes) do
- local target
+ for k, node in ipairs(iconNodes) do
+ -- Set the true target of "target" references in the icon's body.
if node.params and node.params.target then
- target = node.params.target
+ OvaleCondition.defaultTarget = node.params.target
else
- target = "target"
+ OvaleCondition.defaultTarget = "target"
end
- OvaleCondition.defaultTarget = target
if refresh then
- Ovale:Logf("****Master Node %d", k)
+ Ovale:Logf("+++ Icon %d", k)
OvaleBestAction:StartNewAction(state)
- local timeSpan, _, element = OvaleBestAction:Compute(node, state)
+ local timeSpan, _, element = OvaleBestAction:Compute(node.child[1], state)
local start = NextTime(timeSpan, state.currentTime)
if start then
Ovale:Logf("Compute start = %f", start)
@@ -213,8 +212,11 @@ do
else
local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration,
actionUsable, actionShortcut, actionIsCurrent, actionEnable,
- actionType, actionId, actionTarget, noRed = OvaleBestAction:GetActionInfo(element, state)
- if noRed then
+ actionType, actionId, actionTarget = OvaleBestAction:GetActionInfo(element, state)
+
+ -- Use the start time of the best action instead of the intersection of its start time
+ -- with any conditions used to determine the best action.
+ if element and element.params and element.params.nored == 1 then
start = actionCooldownStart + actionCooldownDuration
if start < state.currentTime then
start = state.currentTime
@@ -238,9 +240,7 @@ do
action.spellId = nil
end
if start and start <= now and actionUsable then
- if not action.waitStart then
- action.waitStart = now
- end
+ action.waitStart = action.waitStart or now
else
action.waitStart = nil
end
@@ -269,7 +269,7 @@ do
spellTarget = OvaleCondition.defaultTarget
end
state:ApplySpell(spellId, OvaleGUID:GetGUID(spellTarget))
- timeSpan, _, element = OvaleBestAction:Compute(node, state)
+ timeSpan, _, element = OvaleBestAction:Compute(node.child[1], state)
start = NextTime(timeSpan, state.currentTime)
icons[2]:Update(element, start, OvaleBestAction:GetActionInfo(element, state))
else
@@ -313,14 +313,14 @@ do
local maxWidth = 0
local top = 0
- local masterNodes = OvaleCompile:GetMasterNodes()
- if not masterNodes then return end
+ local iconNodes = OvaleCompile:GetIconNodes()
+ if not iconNodes then return end
local BARRE = 8
local margin = profile.apparence.margin
- for k,node in pairs(masterNodes) do
+ for k, node in ipairs(iconNodes) do
if not self.actions[k] then
self.actions[k] = {icons={}, secureIcons={}}
end
diff --git a/OvaleOptions.lua b/OvaleOptions.lua
index 8da2c4d..742d031 100644
--- a/OvaleOptions.lua
+++ b/OvaleOptions.lua
@@ -544,12 +544,6 @@ local self_options =
desc = L["Debug GUID"],
type = "toggle",
},
- missing_spells =
- {
- name = "Missing spells",
- desc = L["Debug missing spells"],
- type = "toggle",
- },
paper_doll =
{
name = "Paper doll updates",
@@ -562,12 +556,6 @@ local self_options =
desc = L["Debug stat snapshots"],
type = "toggle",
},
- unknown_spells =
- {
- name = "Unknown spells",
- desc = L["Debug unknown spells"],
- type = "toggle",
- },
},
get = function(info) return OvaleOptions.db.profile.debug[info[#info]] end,
set = function(info, value) OvaleOptions.db.profile.debug[info[#info]] = value end,
diff --git a/conditions/Damage.lua b/conditions/Damage.lua
index 42e5242..890eea3 100644
--- a/conditions/Damage.lua
+++ b/conditions/Damage.lua
@@ -27,7 +27,7 @@ do
local name = si[paramName]
local node = OvaleCompile:GetFunctionNode(name)
if node then
- local timeSpan, priority, element = OvaleBestAction:Compute(node, state)
+ local timeSpan, priority, element = OvaleBestAction:Compute(node.child[1], state)
if element and element.type == "value" then
local value = element.value + (state.currentTime - element.origin) * element.rate
return value
diff --git a/conditions/LastEstimatedDamage.lua b/conditions/LastEstimatedDamage.lua
index 0c7d623..fe9d95f 100644
--- a/conditions/LastEstimatedDamage.lua
+++ b/conditions/LastEstimatedDamage.lua
@@ -29,7 +29,7 @@ do
local name = si[paramName]
local node = OvaleCompile:GetFunctionNode(name)
if node then
- local timeSpan, priority, element = OvaleBestAction:Compute(node, state)
+ local timeSpan, priority, element = OvaleBestAction:Compute(node.child[1], state)
if element and element.type == "value" then
local value = element.value + (state.currentTime - element.origin) * element.rate
return value