Mercurial > wow > reaction
view modules/ReAction_State/ReAction_State.lua @ 64:2000f4f4c6af
Redesigned state interface. The only thing missing now is the actual state properties.
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Wed, 28 May 2008 00:20:04 +0000 |
parents | f9cdb920470a |
children | 5ea65ec7d162 |
line wrap: on
line source
--[[ ReAction bar state driver interface --]] -- local imports local ReAction = ReAction local L = ReAction.L local _G = _G local InCombatLockdown = InCombatLockdown -- module declaration local moduleID = "State" local module = ReAction:NewModule( moduleID ) -- module event handlers function module:OnInitialize() self.db = ReAction.db:RegisterNamespace( moduleID, { profile = { bars = { }, } } ) ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") ReAction.RegisterCallback(self, "OnRefreshBar") ReAction.RegisterCallback(self, "OnEraseBar") ReAction.RegisterCallback(self, "OnRenameBar") ReAction.RegisterCallback(self, "OnConfigModeChanged") end function module:OnRefreshBar(event, bar, name) local c = self.db.profile.bars[name] if c then self:UpdateStates(bar) end end function module:OnEraseBar(event, bar, name) self.db.profile.bars[name] = nil end function module:OnRenameBar(event, bar, oldname, newname) local b = self.db.profile.bars bars[newname], bars[oldname] = bars[oldname], nil end function module:OnConfigModeChanged(event, mode) -- TODO: unregister all state drivers (temporarily) and hidestates end -- Utility -- -- traverse a table tree by key list and fetch the result or first nil local function tfetch(t, ...) for i = 1, select('#', ...) do t = t and t[select(i, ...)] end return t end -- traverse a table tree by key list and build tree as necessary local function tbuild(t, ...) for i = 1, select('#', ...) do local key = select(i, ...) if not t[key] then t[key] = { } end t = t[key] end return t end -- PRIVATE -- local BuildRuleString, ApplyStates do local forms = { } for i = 1, GetNumShapeshiftForms() do local icon, name = GetShapeshiftFormInfo(i) forms[name] = i; end -- TODO: need to find out if form name is localized, it probably is local dStance = forms["Defensive Stance"] or 2 local zStance = forms["Berserker Stance"] or 3 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 local cForm = forms["Cat Form"] or 3 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 local aForm = forms["Aquatic Form"] or 2 local trForm = forms["Travel Form"] or 4 local fForm = forms["Flight Form"] or forms["Swift Flight Form"] or 6 -- TODO: do the macro conditional strings need to be localized? -- they're not contained in GlobalStrings.lua, but the parsing is done -- via the C function SecureCmdOptionParse(), so it's not clear local ruleformats = { battle = "stance:1", defensive = ("stance:%d"):format(dStance), berserker = ("stance:%d"):format(zStance), caster = ("form:0/%d/%d/%d"):format(aForm, trForm, fForm), bear = ("form:%d"):format(bForm), cat = ("form:%d"):format(cForm), treeOrMoonkin = ("form:%d"):format(tForm), stealth = "stealth", nostealth = "nostealth", shadowform = "form:1", noshadowform = "noform", pet = "pet", nopet = "nopet", harm = "target=target,harm", help = "target=target,help", notarget = "target=target,noexists", focusharm = "target=focus,harm", focushelp = "target=focus,help", nofocus = "target=focus,noexists", raid = "group:raid", party = "group:party", solo = "nogroup", combat = "combat", nocombat = "nocombat", } function BuildRuleString(states) local s = "" local default local sorted = { } for name in pairs(states) do table.insert(sorted,name) end table.sort(sorted, function(lhs, rhs) local olhs = tfetch(states[lhs],"rule","order") or 0 local orhs = tfetch(states[rhs],"rule","order") or 0 return olhs < orhs end) for idx, name in ipairs(sorted) do local state = states[name] local semi = #s > 0 and "; " or "" local mode = tfetch(state,"rule","type") if mode == "default" then default = name elseif mode == "custom" then if state.rule.custom then -- strip out all spaces from the custom rule s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name) end elseif mode == "any" then if state.rule.values then local clause = "" for key, value in pairs(state.rule.values) do clause = ("%s[%s]"):format(clause,ruleformats[key]) end if #clause > 0 then s = ("%s%s%s %s"):format(s, semi, clause, name) end end elseif mode == "all" then if state.rule.values then local clause = "" for key, value in pairs(state.rule.values) do clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key]) end if #clause > 0 then s = ("%s%s[%s] %s"):format(s, semi, clause, name) end end end end if default then s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default) end return s end local drivers = setmetatable({},{__mode="k"}) function ApplyStates( bar ) local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then local frame = bar:GetFrame() local string = BuildRuleString(states) ReAction:Print("'"..string.."'") if string and #string > 0 then -- register a handler to set the value of attribute "state-reaction" -- in response to events as per the rule string RegisterStateDriver(frame, "reaction", string) drivers[bar] = true -- register a trivial map for each "statemap-reaction-XXX" to set 'state' to 'XXX' for state in pairs(states) do frame:SetAttribute(("statemap-reaction-%s"):format(state), state) end elseif drivers[bar] then UnregisterStateDriver(frame, "reaction") drivers[bar] = nil end end end end -- API -- function module:UpdateStates( bar ) ApplyStates(bar) end function module:CreateState( bar, name ) local states = tbuild(self.db.profile.bars, bar:GetName(), "states") if states[name] then ReAction:UserError(L["State named '%s' already exists"]:format(name)) else states[name] = { } end end function module:DeleteState( bar, name ) local states = tfetch(self.db.profile.bars, bar:GetName(), "states") if states[name] then states[name] = nil ApplyStates(bar) end end -- Options -- local CreateBarOptions do local function ClassCheck(...) for i = 1, select('#',...) do local _, c = UnitClass("player") if c == select(i,...) then return false end end return true end -- pre-sorted by the order they should appear in local rules = { -- rule hidden fields { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } }, { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, } local ruleSelect = { } local ruleMap = { } local optionMap = setmetatable({},{__mode="k"}) -- unpack rules table into ruleSelect and ruleMap for _, c in ipairs(rules) do local rule, hidden, fields = unpack(c) if not hidden then for _, field in ipairs(fields) do local key, label = next(field) table.insert(ruleSelect, label) table.insert(ruleMap, key) end end end local function CreateStateOptions(bar, name) local opts = { type = "group", name = name, childGroups = "tab", } local states = tbuild(module.db.profile.bars, bar:GetName(), "states") local function put( key, value, ... ) tbuild(states, opts.name, "rule", ...)[key] = value end local function fetch( ... ) return tfetch(states, opts.name, "rule", ...) end local function fixall(setkey) -- if multiple selections in the same group are chosen when 'all' is selected, -- keep only one of them. If changing the mode, the first in the fields list will -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, -- it will be retained. local notified = false for _, c in ipairs(rules) do local rule, hidden, fields = unpack(c) local found = false for key in ipairs(fields) do if fetch("values",key) then if (found or setkey) and key ~= setkey then put(key,false,"values") if not setkey and not notified then ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) notified = true end end found = true end end end end local function getNeighbors() local before, after for k, v in pairs(states) do local o = tonumber(tfetch(v, "rule", "order")) if o and k ~= opts.name then local obefore = tfetch(states,before,"rule","order") local oafter = tfetch(states,after,"rule","order") if o < opts.order and (not obefore or obefore < o) then before = k end if o > opts.order and (not oafter or oafter > o) then after = k end end end return before, after end local function swapOrder( a, b ) -- do options table local args = optionMap[bar].args args[a].order, args[b].order = args[b].order, args[a].order -- do profile a = tbuild(states, a, "rule") b = tbuild(states, b, "rule") a.order, b.order = b.order, a.order end local function update() module:UpdateStates(bar) end opts.order = fetch("order") if opts.order == nil then -- add after the highest opts.order = 100 for _, state in pairs(states) do local x = tonumber(tfetch(state, "rule", "order")) if x and x >= opts.order then opts.order = x + 1 end end put("order",opts.order) end opts.args = { properties = { type = "group", name = L["Properties"], order = 1, args = { delete = { type = "execute", name = L["Delete this State"], func = function(info) module:DeleteState(bar,opts.name) optionMap[bar].args[opts.name] = nil end, order = -1 }, rename = { type = "input", name = L["Name"], order = 1, get = function() return opts.name end, set = function(info, value) -- check for existing state name if states[value] then L["State named '%s' already exists"]:format(value) end local args = optionMap[bar].args states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil opts.name = value update() end, pattern = "^%w*$", usage = L["State names must be alphanumeric without spaces"], }, ordering = { type = "group", inline = true, name = L["Evaluation Order"], desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"], order = 2, args = { up = { type = "execute", name = L["Up"], width = "half", order = 1, func = function() local before, after = getNeighbors() if before then swapOrder(before, opts.name) update() end end, }, down = { type = "execute", name = L["Down"], width = "half", order = 2, func = function() local before, after = getNeighbors() if after then ReAction:Print(opts.name, after) swapOrder(opts.name, after) update() end end, } } }, -- keybinding for show-this-state would go here -- show/hide would go here -- page # would go here -- anchoring would go here } }, rules = { type = "group", name = L["Rules"], order = 2, args = { mode = { type = "select", style = "radio", name = L["Select this state"], values = { default = L["by default"], any = L["when ANY of these"], all = L["when ALL of these"], custom = L["via custom rule"] }, set = function( info, value ) put("type", value) fixall() update() end, get = function( info ) return fetch("type") end, order = 2 }, clear = { type = "execute", name = L["Clear All"], func = function() local type = fetch("type") if type == "custom" then put("custom","") elseif type == "any" or type == "all" then put("values", {}) end update() end, order = 3 }, inputs = { type = "multiselect", name = L["Rules"], hidden = function() return fetch("type") == "custom" end, disabled = function() return fetch("type") == "default" end, values = ruleSelect, set = function(info, key, value ) put(ruleMap[key], value or nil, "values") if value then fixall(ruleMap[key]) end update() end, get = function(info, key) return fetch("values", ruleMap[key]) or false end, order = 4 }, custom = { type = "input", multiline = true, hidden = function() return fetch("type") ~= "custom" end, disabled = function() return fetch("type") == "default" end, name = L["Custom Rule"], desc = L["Syntax like macro rules: see preset rules for examples"], set = function(info, value) put("custom",value) update() end, get = function(info) return fetch("custom") or "" end, validate = function (info, rule) local s = rule:gsub("%s","") -- remove all spaces -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler repeat if s == "" then return true end local c, r = s:match("(%b[])(.*)") if c == nil and s and #s > 0 then return L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule) end s = r until c == nil return true end, order = 5, } } } } return opts end CreateBarOptions = function(bar) local private = { } local options = { type = "group", name = L["Dynamic State"], childGroups = "tree", disabled = InCombatLockdown, args = { __desc__ = { type = "description", name = L["States are evaluated in the order they are listed"], order = 1 }, __new__ = { type = "group", name = L["New State..."], order = 2, args = { name = { type = "input", name = L["State Name"], desc = L["Set a name for the new state"], get = function() return private.newstatename or "" end, set = function(info,value) private.newstatename = value end, pattern = "^%w*$", usage = L["State names must be alphanumeric without spaces"], order = 1 }, create = { type = "execute", name = L["Create State"], func = function () local name = private.newstatename module:CreateState(bar,name) -- TODO: select default state options and pass as final argument optionMap[bar].args[name] = CreateStateOptions(bar,name) private.newstatename = "" end, disabled = function() local name = private.newstatename or "" return #name == 0 or name:find("%W") end, order = 2, } } } } } local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then for name, config in pairs(states) do options.args[name] = CreateStateOptions(bar,name) end end optionMap[bar] = options return options end end function module:GetBarOptions(bar) return CreateBarOptions(bar) end