--[[-------------------------------------------------------------------- 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> local L = Ovale.L local OvalePool = Ovale.OvalePool local OvaleTimeSpan = Ovale.OvaleTimeSpan -- Forward declarations for module dependencies. 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 strgmatch = string.gmatch local strgsub = string.gsub local strlen = string.len local strlower = string.lower 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 = 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_requirePreload = { "OvaleEquipement", "OvaleSpellBook", "OvaleStance" } -- Current age of compilation state. 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 = {} -- 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. OvaleCompile.serial = nil OvaleCompile.customFunctionNode = {} --</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) return false end end local function RequireValue(value) local requireValue = (strsub(value, 1, 1) ~= "!") if not requireValue then value = strsub(value, 2) if strmatch(value, NUMBER_PATTERN) then value = tonumber(value) end end return value, requireValue end local function TestConditions(paramList) profiler.Start("OvaleCompile_TestConditions") local boolean = true if boolean and paramList.glyph then local glyph, requireGlyph = RequireValue(paramList.glyph) local hasGlyph = OvaleSpellBook:IsActiveGlyph(glyph) if (requireGlyph and not hasGlyph) or (not requireGlyph and hasGlyph) then boolean = false end end if boolean and paramList.mastery then local spec, requireSpec = RequireValue(paramList.mastery) local isSpec = OvalePaperDoll:IsSpecialization(spec) if (requireSpec and not isSpec) or (not requireSpec and isSpec) then boolean = false end end if boolean and paramList.if_stance then self_compileOnStances = true local stance, requireStance = RequireValue(paramList.if_stance) local isStance = OvaleStance:IsStance(stance) if (requireStance and not isStance) or (not requireStance and isStance) then boolean = false end end if boolean and paramList.if_spell then local spell, requireSpell = RequireValue(paramList.if_spell) local hasSpell = OvaleSpellBook:IsKnownSpell(spell) if (requireSpell and not hasSpell) or (not requireSpell and hasSpell) then boolean = false end end if boolean and paramList.talent then local talent, requireTalent = RequireValue(paramList.talent) local hasTalent = HasTalent(talent) if (requireTalent and not hasTalent) or (not requireTalent and hasTalent) then boolean = false end 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) self_compileOnItems = true if equippedCount < paramList.itemcount then boolean = false 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 end end 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) 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 end return "" 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) 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 end end 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 if k == "proc" then -- Add the buff for this item proc to the spell list "item_proc_<proc>". local buff = tonumber(paramList.buff) if buff then local listName = "item_proc_" .. v if not OvaleData.buffSpellList[listName] then OvaleData.buffSpellList[listName] = {} end OvaleData.buffSpellList[listName][buff] = true 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 "" 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 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 break end 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 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 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) 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 end end 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) 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") end --</private-static-methods> --<public-static-methods> function OvaleCompile:OnInitialize() -- Resolve module dependencies. 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("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_SpellsChanged", "EventHandler") self:RegisterMessage("Ovale_StanceChanged") self:RegisterMessage("Ovale_TalentsChanged", "EventHandler") 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") self_pool:Drain() end function OvaleCompile:PLAYER_REGEN_ENABLED(event) self_pool:Drain() 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) Ovale:DebugPrint(OVALE_COMPILE_DEBUG, event) -- Advance age of current compilation state. self_serial = self_serial + 1 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 = "" end CompileScript(code) self_compileCount = self_compileCount + 1 Ovale:UpdateFrame() end end function OvaleCompile:GetMasterNodes() -- Compile the script if it is outdated. if not self.serial or self.serial < self_serial then self.serial = self_serial self:Compile() end return self_masterNodes 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 end --</public-static-methods>