--[[-------------------------------------------------------------------- Ovale Spell Priority Copyright (C) 2009, 2010, 2011, 2012 Sidoine Copyright (C) 2012, 2013, 2014 Johnny C. Lam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License in the LICENSE file accompanying this program. --]]-------------------------------------------------------------------- local _, Ovale = ... local OvaleCompile = Ovale:NewModule("OvaleCompile", "AceEvent-3.0") Ovale.OvaleCompile = OvaleCompile --<private-static-properties> -- Forward declarations for module dependencies. local OvaleAST = nil local OvaleCondition = nil local OvaleCooldown = nil local OvaleData = nil local OvaleEquipement = nil local OvaleOptions = nil local OvalePaperDoll = nil local OvaleScore = nil local OvaleScripts = nil local OvaleSpellBook = nil local OvaleStance = nil local ipairs = ipairs local pairs = pairs local tonumber = tonumber local strfind = string.find local strmatch = string.match local strsub = string.sub local wipe = table.wipe -- Profiling set-up. local Profiler = Ovale.Profiler local profiler = nil do local group = OvaleCompile:GetName() Profiler:RegisterProfilingGroup(group) profiler = Profiler:GetProfilingGroup(group) end -- 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_canEvaluate = false local self_requirePreload = { "OvaleEquipement", "OvaleSpellBook", "OvaleStance" } -- 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 evaluated. local self_timesEvaluated = 0 -- Icon nodes of the current script (one node for each icon) local self_icon = {} -- 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" --</private-static-properties> --<public-static-properties> -- Current age of the script; this advances every time the script is evaluated. OvaleCompile.serial = nil -- AST for the current script. OvaleCompile.ast = nil --</public-static-properties> --<private-static-methods> local function HasTalent(talentId) if OvaleSpellBook:IsKnownTalent(talentId) then return OvaleSpellBook:GetTalentPoints(talentId) > 0 else Ovale:FormatPrint("Warning: unknown talent ID '%s'", talentId) return false end end local function RequireValue(value) 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, required end local function TestConditions(parameters) profiler.Start("OvaleCompile_TestConditions") local boolean = true if boolean and parameters.glyph then local glyph, required = RequireValue(parameters.glyph) local hasGlyph = OvaleSpellBook:IsActiveGlyph(glyph) boolean = (required and hasGlyph) or (not required and not hasGlyph) end if boolean and parameters.specialization then local spec, required = RequireValue(parameters.specialization) local isSpec = OvalePaperDoll:IsSpecialization(spec) boolean = (required and isSpec) or (not required and not isSpec) end if boolean and parameters.if_stance then self_compileOnStances = true local stance, required = RequireValue(parameters.if_stance) local isStance = OvaleStance:IsStance(stance) boolean = (required and isStance) or (not required and not isStance) end if boolean and parameters.if_spell then local spell, required = RequireValue(parameters.if_spell) local hasSpell = OvaleSpellBook:IsKnownSpell(spell) boolean = (required and hasSpell) or (not required and not hasSpell) end if boolean and parameters.talent then local talent, required = RequireValue(parameters.talent) local hasTalent = HasTalent(talent) boolean = (required and hasTalent) or (not required and not hasTalent) end if boolean and parameters.itemset and parameters.itemcount then local equippedCount = OvaleEquipement:GetArmorSetCount(parameters.itemset) self_compileOnItems = true boolean = (equippedCount >= parameters.itemcount) end do local profile if boolean and parameters.checkbox then for _, checkbox in ipairs(parameters.checkbox) do local name, required = RequireValue(checkbox) local control = Ovale.casesACocher[name] or {} control.compile = true Ovale.casesACocher[name] = control -- 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 end if boolean and parameters.listitem then for list, listitem in pairs(parameters.listitem) do local item, required = RequireValue(listitem) local control = Ovale.listes[list] or { items = {}, default = nil } control.compile = true Ovale.listes[list] = control -- Check the selected item in the list. profile = profile or OvaleOptions:GetProfile() local isSelected = (profile.list[list] == item) boolean = (required and isSelected) or (not required and not isSelected) end end end profiler.Stop("OvaleCompile_TestConditions") return boolean 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 ok end local function EvaluateAddIcon(node) local ok = true if TestConditions(node.params) then self_icon[#self_icon + 1] = node end return ok end 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 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 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(parameters.buff) if buff then 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 ok 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 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 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 return ok end 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 if count > 0 then auraTable[filter] = tbl end end return ok end 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 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 OvaleEquipement = Ovale.OvaleEquipement OvaleOptions = Ovale.OvaleOptions OvalePaperDoll = Ovale.OvalePaperDoll OvaleScore = Ovale.OvaleScore OvaleScripts = Ovale.OvaleScripts OvaleSpellBook = Ovale.OvaleSpellBook OvaleStance = Ovale.OvaleStance end function OvaleCompile:OnEnable() self:RegisterMessage("Ovale_CheckBoxValueChanged", "EventHandler") self:RegisterMessage("Ovale_EquipmentChanged") self:RegisterMessage("Ovale_GlyphsChanged", "EventHandler") self:RegisterMessage("Ovale_ListValueChanged", "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() self:UnregisterMessage("Ovale_CheckBoxValueChanged") self:UnregisterMessage("Ovale_EquipmentChanged") self:UnregisterMessage("Ovale_GlyphsChanged") self:UnregisterMessage("Ovale_ListValueChanged") self:UnregisterMessage("Ovale_ScriptChanged") self:UnregisterMessage("Ovale_SpellsChanged") self:UnregisterMessage("Ovale_StanceChanged") self:UnregisterMessage("Ovale_TalentsChanged") end function OvaleCompile:Ovale_EquipmentChanged(event) if self_compileOnItems then self:EventHandler(event) end end function OvaleCompile:Ovale_StanceChanged(event) if self_compileOnStances then self:EventHandler(event) end end function OvaleCompile:EventHandler(event) -- 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: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 end profiler.Stop("OvaleCompile_EvaluateScript") end function OvaleCompile:GetFunctionNode(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:GetIconNodes() -- Evaluate the script if it is outdated. if not self.serial or self.serial < self_serial then self.serial = self_serial self:EvaluateScript() end return self_icon end function OvaleCompile:Debug() Ovale:FormatPrint("Total number of times the script was evaluated: %d", self_timesEvaluated) end --</public-static-methods>