Mercurial > wow > reaction
view modules/ReAction_State/ReAction_State.lua @ 62:f9cdb920470a
Added first cut on State module. Menu system only, it doesn't do anything.
The menu system and data storage will change substantially when the implementation takes shape.
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 13 May 2008 16:42:52 +0000 |
parents | 21bcaf8215ff |
children | 2000f4f4c6af |
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 = { }, presets = { } } } ) self.states = { } self.options = setmetatable({},{__mode="k"}) end -- ReAction module interface function module:ApplyToBar(bar) self:RefreshBar(bar) end function module:RefreshBar(bar) local c = self.db.profile.bars[bar:GetName()] if c then --self:BuildStates(bar) --self:BuildRules(bar) end end function module:RemoveFromBar(bar) end function module:EraseBarConfig(barName) self.db.profile.bars[barName] = nil end function module:RenameBarConfig(oldname, newname) local b = self.db.profile.bars bars[newname], bars[oldname] = bars[oldname], nil end function module:ApplyConfigMode(mode,bars) -- swap out hidestates end -- Private -- -- 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 local rules local BuildRuleString 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 -- The structure of this table is important: each row is designed to be unpack()ed -- into variables as defined in the header comment row. -- The 'fields' subtable (index 4) structure is also important: its subtables -- are ordered to match appearances of '%s' (string-substitutions) in the corresponding -- rules[] format-string entry. Each subtable's single element's key is the storage key used -- in the user db, and the name of the group-element table in the config tree, so it too -- is important. -- -- While this allows for very compact code and data storage, the scheme is admittedly obtuse, -- so I document it here for my own sanity. -- rules = { -- rule name hidden fields { "stance", L["Warrior Stance"], ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, { "form", L["Druid Form"], ClassCheck("DRUID"), { {bear = L["Bear"]}, {cat = L["Cat"]}, {treeOrMoonkin = L["Tree/Moonkin"]}, {caster = L["Normal"]}, } }, { "stealth", L["Stealth"], ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {normal = L["Normal"]} } }, { "shadow", L["Shadowform"], ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {normal = L["Normal"]} } }, { "pet", L["Pet"], ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, { "target", L["Target"], false, { {hostile = L["Hostile Target"]}, {friendly = L["Friendly Target"]}, {none = L["No Target"]} } }, { "focus", L["Focus"], false, { {hostile = L["Hostile Focus"]}, {friendly = L["Friendly Focus"]}, {none = L["No Focus"]} } }, { "group", L["In Group"], false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, { "key", L["Key Press"], false, { {state = L["On Key Press"]}, {keybinding = false} } }, -- keybinding has no state-selector, it's implemented elsewhere { "combat", L["Combat"], false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, { "custom", L["Custom"], false, { {rule = false} } }, -- custom has no state-selector, it's implemented elsewhere } do local forms = { } for i = 1, GetNumShapeshiftForms() do local icon, name = GetShapeshiftFormInfo(i) -- TODO: need to find out if name is localized, it probably is forms[name] = i; end 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 -- TODO: do the macro conditional strings need to be localized? local ruleformats = { stance = ("[stance:1] %%s; [stance:%d] %%s; [stance:%d] %%s"):format(dStance,zStance), form = ("[form:%d] %%s; [form:%d] %%s; [form:%d] %%s; %%s"):format(bForm,cForm,tForm), stealth = "[stealth] %s; %s", shadow = "[stance:1] %s; %s", pet = "[pet] %s; %s", target = "[harm] %s; [help] %s; %s", focus = "[target=focus,harm] %s; [target=focus,help] %s; %s", group = "[group:raid] %s; [group] %s; %s", combat = "[combat] %s; %s", custom = "%s", } local fieldmap = { } for i = 1, #rules do fieldmap[rules[i][1]] = rules[i][4] end local _scratch = { } function BuildRuleString(bar, name) local rule, data = module:GetRule(bar,name) if not rule then return "" end local fields = fieldmap[rule] for i = 1, #fields do _scratch[i] = data[next(fields[i])] -- TODO: insert default state here end for i = #fields+1, #_scratch do _scratch[i] = nil end local success, value = pcall(string.format, ruleformats[rule], unpack(_scratch)) return success and value or "<error>" end end end -- API -- function module:BuildStates( bar ) local c = tfetch(self.db.profile.bars, bar:GetName(), "states") if c then for name, s in pairs(c) do -- TODO: new state here end end end function module:CreateState( bar, name ) local c = tbuild(self.db.profile.bars, bar:GetName(), "states") if c[name] then ReAction:UserError(L["State named '%s' already exists"]:format(name)) else c[name] = { } -- TODO: new state here end end function module:DeleteState( bar, name ) local c = tfetch(self.db.profile.bars, bar:GetName(), "states") if c[name] then -- TODO: delete state c[name] = nil end end function module:BuildRules( bar ) for bar, c in pairs(self.db.profile.bars) do if c.rules then for name, t in pairs(c.rules) do local rule, config = next(t) self:SetRule(bar, name, rule, config) end end end end function module:CreateRule( bar, name, rule, config ) local c = tbuild(self.db.profile.bars, bar:GetName(), "rules") if c[name] then ReAction:UserError(L["Rule named '%s' already exists"]:format(name)) else tbuild(self.db.profile.bars, bar:GetName(), "rules", name) if rule then self:SetRule(bar,name,rule,config) end end end function module:DeleteRule( bar, name ) local c = tfetch(self.db.profile.bars, bar:GetName(), "rules") if c[name] then local f = bar:GetFrame() -- TODO: delete rule c[name] = nil end end function module:UpdateRule( bar, name ) local rule, c = self:GetRule(bar,name) -- TODO: remove all relevant outdated attributes -- TODO: set new attributes end function module:GetRule(bar, name) local c = tfetch(self.db.profile.bars, bar:GetName(), "rules", name) if c then return next(c) -- returns key, value (= rulename, configtable) end end function module:SetRule(bar, name, rule, config) tbuild(self.db.profile.bars, bar:GetName(), "rules")[name] = { [rule] = (config or {}) } self:UpdateRule(bar,name) end -- options -- local CreateBarOptions do local function GetRuleConfig(bar, name, rule, field) return tfetch(module.db.profile.bars, bar:GetName(), "rules", name, rule, field) end local function SetRuleConfig( bar, name, rule, field, value ) tbuild(module.db.profile.bars, bar:GetName(), "rules", name, rule)[field] = value end local function CreateStateOptions(bar, name) return { type = "group", name = name, args = { -- show/hide would go here -- page # would go here -- anchoring would go here __delete__ = { type = "execute", name = L["Delete this State"], func = function(info) module:DeleteState(bar,name) module.options[bar].args.states.args[name] = nil end, order = -1 }, } } end -- display rule string setting is shared between all rule opts and is transient -- (mostly used for debugging) local display = { show = false } local function CreateRuleOptions(bar, name) local function get(info) local rule = info[#info-1] local field = info[#info] return GetRuleConfig(bar,name,rule,field) or "" end local function set(info, value) local rule = info[#info-1] local field = info[#info] SetRuleConfig(bar,name,rule,field,value) module:UpdateRule(bar,name) end local opts = { type = "group", name = name, childGroups = "inline", args = { __select__ = { type = "select", name = L["Select Rule Type"], get = function(info) return module:GetRule(bar,name) or "" end, set = function(info,value) module:SetRule(bar,name,value) end, -- TODO: get default rule config and pass as final value values = function() local v = { } for i = 1, #rules do local rule, name, hidden = unpack(rules[i]) if not hidden then v[rule] = name end end return v end, order = 1, }, __delete__ = { type = "execute", name = L["Delete this Rule"], func = function(info) module:DeleteRule(bar,name) module.options[bar].args.rules.args[name] = nil end, order = -3 }, -- -- rule selection groups will be inserted here -- __show__ = { type = "toggle", name = L["Show Rule String"], desc = L["Toggles display of the raw rule string"], get = function() return display.show end, set = function(info,value) display.show = value end, order = -2 }, __rule__ = { type = "input", name = L["Rule String"], disabled = true, multiline = true, width = "double", hidden = function() return not display.show end, get = function() return BuildRuleString(bar,name) end, set = function() end, order = -1 } } } -- unpack rules table for i = 1, #rules do local rule, label, _, fields = unpack(rules[i]) local hidden = function() return module:GetRule(bar,name) ~= rule end opts.args[rule] = { type = "group", name = label, hidden = hidden, disabled = hidden, inline = true, args = { } } for j = 1, #fields do local field, label = next(fields[j]) -- extract from table of single key-value pair if field and label then opts.args[rule].args[field] = { type = "select", name = L["Select State (%s):"]:format(label), values = function () local states = tfetch(module.db.profile.bars,bar:GetName(),"states") local v = { } if states then for k in pairs(states) do v[k] = k end end return v end, set = set, get = get, order = 100 + j } end end end -- set up special entry for keybinding opts.args.key.args.binding = { type = "keybinding", name = L["Key Binding"], get = get, set = set, order = -1 } -- set up special entry for custom opts.args.custom.args.rule = { type = "input", name = L["Rule"], desc = L["Syntax like macro conditions: see preset rules for examples"], get = get, set = set, validate = function (info, rule) local s = rule:gsub("%s","") -- remove all spaces if s:match(";$") then -- can't end with semicolon return L["Invalid custom rule '%s': Rule cannot end with ';'"]:format(rule) end if s == "" then return true end -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler repeat repeat local c, r = s:match("(%b[])(.*)") if r then s = r end until c == nil local state, s = s:match("(%w+)(.*)") if not state then return L["Invalid custom rule '%s': Each expression must have a state"]:format(rule) end if not tfetch(module.db.profile.bars,bar:GetName(),"states",state) then return L["Invalid custom rule '%s': '%s' is not a state"]:format(rule,state) end if s:match("^[^;]") then return L["Invalid custom rule '%s': Expressions must be separated by ';'"]:format(rule) end until #s == 0 return true end, multiline = true, order = 1, } return opts end CreateBarOptions = function(bar) local private = { } local options = { type = "group", name = L["Dynamic State"], childGroups = "tab", disabled = InCombatLockdown, args = { states = { type = "group", name = L["States"], childGroups = "tree", order = 1, args = { __new__ = { type = "group", name = L["New State..."], order = 1, 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 module.options[bar].args.states.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, } } } } }, rules = { type = "group", name = L["Transition Rules"], childGroups = "tree", order = 2, args = { __new__ = { type = "group", name = L["New Rule..."], order = 1, args = { name = { type = "input", name = L["Rule Name"], desc = L["Set a name for the new transition rule"], get = function() return private.newtransname or "" end, set = function(info,value) private.newtransname = value end, pattern = "^%w*$", usage = L["Rule names must be alphanumeric without spaces"], order = 1 }, create = { type = "execute", name = L["Create Rule"], func = function () local name = private.newtransname module:CreateRule(bar,name) -- TODO: select default rule and add as final argument module.options[bar].args.rules.args[name] = CreateRuleOptions(bar,name) private.newtransname = "" end, disabled = function () local name = private.newtransname or "" return #name == 0 or name:find("%W") end, order = 2, }, } } } }, presets = { type = "group", name = L["Presets"], order = 3, args = { desc = { type = "description", name = L["Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults."], order = 1, }, select = { type = "select", name = L["Select Preset"], set = function(info,value) end, get = function() return "" end, values = function() return { } end, order = 2, }, load = { type = "execute", name = L["Load"], func = function() end, width = "half", order = 3, }, delete = { type = "execute", name = L["Delete"], disabled = function() return false end, func = function() end, width = "half", order = 4, }, hdr = { type = "header", name = " ", order = 5, }, save = { type = "input", name = L["Save As..."], get = function() return "" end, set = function(info,name) end, order = 6, }, }, }, } } local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then for name, config in pairs(states) do options.args.states.args[name] = CreateStateOptions(bar,name) end end local rules = tfetch(module.db.profile.bars, bar:GetName(), "rules") if rules then for name, config in pairs(rules) do options.args.rules.args[name] = CreateRuleOptions(bar,name) end end return options end end function module:GetBarOptions(bar) if not self.options[bar] then self.options[bar] = CreateBarOptions(bar) end return self.options[bar] end