Mercurial > wow > reaction
changeset 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 | 2ee41dcd673f |
children | 768be7eb22a0 |
files | locale/enUS.lua modules/ReAction_State/ReAction_State.lua modules/modules.xml |
diffstat | 3 files changed, 649 insertions(+), 59 deletions(-) [+] |
line wrap: on
line diff
--- a/locale/enUS.lua Tue May 13 16:42:03 2008 +0000 +++ b/locale/enUS.lua Tue May 13 16:42:52 2008 +0000 @@ -101,7 +101,79 @@ "Use the mouse to arrange and resize the bars on screen. Tooltips on bars indicate additional functionality.", -- modules/ReAction_State -"Dynamic Behavior", +"Dynamic State", +"Hide Bar", +"Hide the bar while in this state", +"Select State (%s):", +"Key Binding", +"Delete this State", +"Select Rule Type", +"Delete this Rule", +"Show Rule String", +"Toggles display of the raw rule string", +"Rule String", +"Warrior Stance", +"Battle Stance", +"Defensive Stance", +"Berserker Stance", +"Druid Form", +"Normal", +"Bear", +"Cat", +"Tree/Moonkin", +"Caster", +"Stealth", +"Shadowform", +"Pet", +"Without Pet", +"With Pet", +"Target", +"No Target", +"Hostile Target", +"Friendly Target", +"Default", +"Focus", +"No Focus", +"Hostile Focus", +"Friendly Focus", +"In Group", +"Solo", +"Party", +"Raid", +"Key Press", +"On Key Press", +"Combat", +"In Combat", +"Out of Combat", +"Custom", +"Rule", +"Syntax like macro conditions: see preset rules for examples", +"States", +"New State...", +"Set a name for the new state", +"State Name", +"Create State", +"Options", +"Transition Rules", +"New Rule...", +"Rule Name", +"Set a name for the new transition rule", +"Create Rule", +"Presets", +"Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults.", +"Select Preset", +"Load", +"Delete", +"Save As...", +"State named '%s' already exists", +"Rule named '%s' already exists", +"State names must be alphanumeric without spaces", +"Rule names must be alphanumeric without spaces", +"Invalid custom rule '%s': Rule cannot end with ';'", +"Invalid custom rule '%s': Expressions must be separated by ';'", +"Invalid custom rule '%s': Each expression must have a state", +"Invalid custom rule '%s': '%s' is not a state", + }) do L[string] = true end
--- a/modules/ReAction_State/ReAction_State.lua Tue May 13 16:42:03 2008 +0000 +++ b/modules/ReAction_State/ReAction_State.lua Tue May 13 16:42:52 2008 +0000 @@ -1,5 +1,5 @@ --[[ - ReAction bar state machine + ReAction bar state driver interface --]] @@ -13,85 +13,599 @@ local moduleID = "State" local module = ReAction:NewModule( moduleID ) --- module methods + +-- module event handlers function module:OnInitialize() - self.db = ReAction:RegisterNamespace( moduleID, + self.db = ReAction.db:RegisterNamespace( moduleID, { - profile = { } + profile = { + bars = { }, + presets = { } + } } ) + self.states = { } + self.options = setmetatable({},{__mode="k"}) end -function module:OnEnable() + + +-- 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 -function module:OnDisable() + +-- 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:GetGlobalOptions( configModule ) --- return {} ---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:GetGlobalBarOptions( configModule ) --- ---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:GetModuleOptions( configModule ) --- ---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:GetBarConfigOptions( bar, configModule ) - if not bar.modConfigOpts[moduleID] then - local IsEnabled = function() - return false +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 - bar.modConfigOpts[moduleID] = { - state = { - type = "group", - name = L["Dynamic Behavior"], - desc = L["Dynamic Behavior"], - args = { - enable = { - type = "toggle", - name = L["Enable dynamic behavior"], - desc = L["Toggles dynamic behavior for this bar"], - get = function() return false end, - set = function(x) end, - disabled = InCombatLockdown, - order = 1 - }, + 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 - default = { - type = "text", - name = L["Default State"], - desc = L["State when no conditions apply"], - get = function() return false end, - set = function(x) end, - disabled = IsEnabled, - order = 2 - }, - - stealth = { - type = "text", - name = L["Behavior when Stealthed"], - desc = L["Change bar state when stealthed"], - get = function() return false end, - set = function(x) end, - disabled = IsEnabled, - validate = { }, - }, - + 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 - return bar.modConfigOpts[moduleID] + + 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:GetBarMenuOptions( bar, configModule ) - +function module:GetBarOptions(bar) + if not self.options[bar] then + self.options[bar] = CreateBarOptions(bar) + end + return self.options[bar] end -
--- a/modules/modules.xml Tue May 13 16:42:03 2008 +0000 +++ b/modules/modules.xml Tue May 13 16:42:52 2008 +0000 @@ -6,13 +6,15 @@ <Include file="ReAction_ConfigUI\ReAction_ConfigUI.xml"/> <Include file="ReAction_HideBlizzard\ReAction_HideBlizzard.xml"/> -<!-- action button modules --> +<!-- general utility modules --> +<Include file="ReAction_State\ReAction_State.xml"/> + +<!-- button modules --> <Include file="ReAction_Action\ReAction_Action.xml"/> <Include file="ReAction_PetAction\ReAction_PetAction.xml"/> <Include file="ReAction_PossessBar\Reaction_PossessBar.xml"/> <!-- not yet implemented -<Include file="ReAction_State\ReAction_State.xml"/> <Include file="ReAction_BagBar\ReAction_BagBar.xml"/> <Include file="ReAction_Label\ReAction_Label.xml"/> <Include file="ReAction_MicroMenu\ReAction_MicroMenu.xml"/>