Mercurial > wow > reaction
diff State.lua @ 75:06cd74bdc7da
- Cleaned up Bar interface
- Move all attribute setting from Bar into State
- Separated Moonkin and Tree of Life
- Removed PossessBar module
- Added some infrastructure for paged/mind control support to Action
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 16 Jun 2008 18:46:08 +0000 |
parents | aa88aed52124 |
children | da8ba8783924 |
line wrap: on
line diff
--- a/State.lua Tue Jun 10 22:25:15 2008 +0000 +++ b/State.lua Mon Jun 16 18:46:08 2008 +0000 @@ -8,6 +8,7 @@ local L = ReAction.L local _G = _G local InCombatLockdown = InCombatLockdown +local format = string.format -- module declaration local moduleID = "State" @@ -33,9 +34,26 @@ return t end --- PRIVATE -- +-- return a new array of keys of table 't', sorted by comparing +-- sub-fields (obtained via tfetch) of the table values +local function fieldsort( t, ... ) + local r = { } + for k in pairs(t) do + table.insert(r,k) + end + local path = { ... } + table.sort(r, function(lhs, rhs) + local olhs = tfetch(t[lhs], unpack(path)) or 0 + local orhs = tfetch(t[rhs], unpack(path)) or 0 + return olhs < orhs + end) + return r +end + local InitRules, ApplyStates, SetProperty, GetProperty + +-- PRIVATE -- do -- As far as I can tell the macro clauses are NOT locale-specific. local ruleformats = { @@ -56,13 +74,14 @@ solo = "nogroup", combat = "combat", nocombat = "nocombat", + possess = "bonusbar:5", } -- Have to do these shenanigans instead of hardcoding the stances/forms because -- the ordering varies if the character is missing a form. For warriors -- this is rarely a problem (c'mon, who actually skips the level 10 def stance quest?) -- but for druids it can be. Some people never bother to do the aquatic form quest - -- until well past when they get cat form, and stance 5 can be flight, tree, or moonkin + -- until well past when they get cat form, and stance 5/6 can be flight, tree, or moonkin -- depending on talents. function InitRules() local forms = { } @@ -78,91 +97,109 @@ local aquatic = forms["Interface\\Icons\\Ability_Druid_AquaticForm"] or 9 local cat = forms["Interface\\Icons\\Ability_Druid_CatForm"] or 9 local travel = forms["Interface\\Icons\\Ability_Druid_TravelForm"] or 9 - local treekin = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9 + local tree = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or 9 + local moonkin = forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9 local flight = forms["Interface\\Icons\\Ability_Druid_FlightForm"] or 9 -- flight and swift flight share the same icon - ruleformats.battle = "stance:1" - ruleformats.defensive = ("stance:%d"):format(defensive) - ruleformats.berserker = ("stance:%d"):format(berserker) - ruleformats.caster = ("form:0/%d/%d/%d"):format(aquatic, travel, flight) - ruleformats.bear = ("form:%d"):format(bear) - ruleformats.cat = ("form:%d"):format(cat) - ruleformats.treeOrMoonkin = ("form:%d"):format(treekin) + ruleformats.battle = "stance:1" + ruleformats.defensive = format("stance:%d",defensive) + ruleformats.berserker = format("stance:%d",berserker) + ruleformats.caster = format("form:0/%d/%d/%d",aquatic, travel, flight) + ruleformats.bear = format("form:%d",bear) + ruleformats.cat = format("form:%d",cat) + ruleformats.tree = format("form:%d",tree) + ruleformats.moonkin = format("form:%d",moonkin) end - -- return a new array of keys of table 't', sorted by comparing - -- sub-fields (obtained via tfetch) of the table values - local function fieldsort( t, ... ) - local r = { } - for k in pairs(t) do - table.insert(r,k) + + -- state property functions + local ofskeys = { + anchorPoint = "point", + anchorRelPoint = "relpoint", + anchorX = "x", + anchorY = "y" + } + + local barofsidx = { + anchorPoint = 1, + anchorRelPoint = 3, + anchorX = 4, + anchorY = 5 + } + + local function UpdatePartialAnchor(bar, states, ckey) + local map = { } + local bc = bar.config + for state, c in pairs(states) do + if c.enableAnchor then + map[state] = c[ckey] + end end - local dotdotdot = { ... } - table.sort(r, function(lhs, rhs) - local olhs = tfetch(t[lhs], unpack(dotdotdot)) or 0 - local orhs = tfetch(t[rhs], unpack(dotdotdot)) or 0 - return olhs < orhs - end) - return r + local ofskey = ofskeys[ckey] + local default = select(barofsidx[ckey], bar:GetAnchor()) + bar:SetStateAttribute(format("headofs%s",ofskeys[ckey]), map, default) + end + + -- the name of the function maps to the name of the config element + local propertyFuncs = { + hide = function( bar, states ) + local hs = { } + for state, config in pairs(states) do + if config.hide then + table.insert(hs, state) + end + end + bar:SetStateAttribute("hidestates", nil, table.concat(hs,","), true) -- pass to buttons + end, + + page = function( bar, states ) + local map = { } + for state, config in pairs(states) do + if config.page then + map[state] = format("page%d",config.page) + end + end + bar:SetStateAttribute("statebutton", map) + end, + + keybindstate = function( bar, states ) + local map = { } + for state, config in pairs(states) do + local kbset = config.keybindstate and state + map[state] = kbset + for button in bar:IterateButtons() do + -- TODO: inform children they should maintain multiple binding sets + -- ?? button:UpdateBindingSet(kbset) + end + end + bar:SetStateAttribute("statebindings", map) + end, + + enableAnchor = function( bar, states ) + for ckey in pairs(ofskeys) do + UpdatePartialAnchor(bar, states, ckey) + end + end, + + enableScale = function( bar, states ) + local map = { } + for state, c in pairs(states) do + if c.enableScale then + map[state] = c.scale + end + end + bar:SetStateAttribute("headscale", map, 1.0) + end, + } + + -- generate some table entries + propertyFuncs.scale = propertyFuncs.enableScale + for ckey in pairs(ofskeys) do + propertyFuncs[ckey] = function( bar, states ) + UpdatePartialAnchor(bar, states, ckey) + end end - local function BuildRules(states) - local rules = { } - local keybinds = { } - local default - local fmt = "%s %s" - for idx, state in ipairs(fieldsort(states, "rule", "order")) do - local c = states[state].rule - local type = tfetch(c,"type") - if type == "default" then - default = default or state - elseif type == "keybind" then - keybinds[state] = c.keybind or false - elseif type == "custom" then - if c.custom then - -- strip out all spaces from the custom rule - table.insert(rules, fmt:format(c.custom:gsub("%s",""), state)) - end - elseif type == "any" then - if c.values then - local clauses = { } - for key, value in pairs(c.values) do - table.insert(clauses, ("[%s]"):format(ruleformats[key])) - end - if #clauses > 0 then - table.insert(rules, fmt:format(table.concat(clauses), state)) - end - end - elseif type == "all" then - if c.values then - local clauses = { } - for key, value in pairs(c.values) do - table.insert(clauses, ruleformats[key]) - end - if #clauses > 0 then - table.insert(rules, fmt:format(("[%s]"):format(table.concat(clauses, ",")), state)) - end - end - end - end - if default then - table.insert(rules, default) - end - return rules, keybinds - end - - local propertyFuncs = { } - - function ApplyStates( bar ) - local states = tfetch(module.db.profile.bars, bar:GetName(), "states") - if states then - local rules, keybinds = BuildRules(states) - bar:SetStateDriver(table.concat(rules,";"), states, keybinds) - for k, f in pairs(propertyFuncs) do - f(bar, states) - end - end - end function GetProperty( bar, state, propname ) return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) @@ -177,65 +214,111 @@ end end - -- state property functions - function propertyFuncs.hide( bar, states ) - local tmp = { } - for state, config in pairs(states) do - if config.hide then - table.insert(tmp, state) + + + -- + -- Build a state-transition spec string and statemap to be passed to + -- Bar:SetStateDriver(). + -- + -- The statemap building is complex: keybound states override all + -- other transitions, so must remain in their current state, but must + -- also remember other transitions that happen while they're stuck there + -- so that when the binding is toggled off it can return to the proper state + -- + local function BuildStateMap(states) + local rules = { } + local statemap = { } + local keybinds = { } + local default + + -- first grab all the keybind override states + -- and construct an override template + local override + do + local overrides = { } + for name, state in pairs(states) do + local type = tfetch(state, "rule", "type") + if type == "keybind" then + -- use the state-stack to remember the current transition + -- use $s as a marker for a later call to gsub() + table.insert(overrides, format("%s:$s set() %s", name, name)) + end + end + if #overrides > 0 then + table.insert(overrides, "") -- for a trailing ';' + end + override = table.concat(overrides, ";") or "" + end + + -- now iterate the rules in order + for idx, state in ipairs(fieldsort(states, "rule", "order")) do + local c = states[state].rule + local type = c.type + if type == "default" then + default = default or state + elseif type == "custom" then + if c.custom then + -- strip out all spaces from the custom rule + table.insert(rules, format("%s %s", c.custom:gsub("%s",""), state)) + end + elseif type == "any" then + if c.values then + local clauses = { } + for key, value in pairs(c.values) do + table.insert(clauses, format("[%s]", ruleformats[key])) + end + if #clauses > 0 then + table.insert(rules, format("%s %s", table.concat(clauses), state)) + end + end + elseif type == "all" then + if c.values then + local clauses = { } + for key, value in pairs(c.values) do + table.insert(clauses, ruleformats[key]) + end + if #clauses > 0 then + table.insert(rules, format("%s %s", format("[%s]", table.concat(clauses, ",")), state)) + end + end + end + + -- use a different virtual button for the actual keybind transition, + -- to implement a toggle. You have to clear it regardless of the type + -- (which is usually a no-op) to unbind state transitions when switching + -- transition types. + local bindbutton = format("%s_binding",state) + if type == "keybind" then + keybinds[bindbutton] = c.keybind or false + statemap[bindbutton] = format("%s:pop();*:set(%s)", state, state) + else + keybinds[bindbutton] = false + end + + -- construct the statemap. gsub() the state name into the override template. + statemap[state] = format("%s%s", override:gsub("%$s",state), state) + end + -- make sure that the default, if any, is last + if default then + table.insert(rules, default) + end + return table.concat(rules,";"), statemap, keybinds + end + + function ApplyStates( bar ) + local states = tfetch(module.db.profile.bars, bar:GetName(), "states") + if states then + local rule, statemap, keybinds = BuildStateMap(states) + bar:SetStateDriver("reaction", rule, statemap) + for state, key in pairs(keybinds) do + bar:SetAttributeBinding(state, key, "state-reaction", state) + end + for k, f in pairs(propertyFuncs) do + f(bar, states) end end - local s = table.concat(tmp,",") - bar:SetHideStates(s) end - function propertyFuncs.page( bar, states ) - local map = { } - for state, config in pairs(states) do - map[state] = config.page - end - bar:SetStatePageMap(state, map) - end - - function propertyFuncs.keybindstate( bar, states ) - local map = { } - for state, config in pairs(states) do - if config.keybindstate then - table.insert(map,state) - end - end - bar:SetStateKeybindOverrideMap(map) - end - - local function updateAnchor(bar, states) - local map = { } - for state, c in pairs(states) do - if c.enableAnchor then - map[state] = { point = c.anchorPoint, relpoint = c.anchorRelPoint, x = c.anchorX, y = c.anchorY } - end - end - bar:SetStateAnchorMap(map) - end - - propertyFuncs.enableAnchor = updateAnchor - propertyFuncs.anchorPoint = updateAnchor - propertyFuncs.anchorRelPoint = updateAnchor - propertyFuncs.anchorX = updateAnchor - propertyFuncs.anchorY = updateAnchor - - local function updateScale( bar, states ) - local map = { } - for state, c in pairs(states) do - if c.enablescale then - map[state] = c.scale - end - end - bar:SetStateScaleMap(map) - end - - propertyFuncs.enablescale = updateScale - propertyFuncs.scale = updateScale - end @@ -289,7 +372,7 @@ end function module:OnRenameBar(event, bar, oldname, newname) - local b = self.db.profile.bars + local bars = self.db.profile.bars bars[newname], bars[oldname] = bars[oldname], nil end @@ -317,12 +400,13 @@ 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"]} } }, + { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {tree = L["Tree of Life"]}, {moonkin = L["Moonkin Form"]} } }, { "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"]} } }, + { "possess", false, { {possess = L["Mind Control"]} } }, { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, } @@ -482,7 +566,7 @@ set = function(info, value) -- check for existing state name if states[value] then - L["State named '%s' already exists"]:format(value) + format(L["State named '%s' already exists"],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 @@ -551,27 +635,27 @@ order = 3, type = "select", disabled = function() - return bar:GetNumPages() < 2 + --return bar:GetNumPages() < 2 + return true end, hidden = function() - return bar:GetNumPages() < 2 + --return bar:GetNumPages() < 2 + return true end, values = function() - local pages = { none = " " } - for i = 1, bar:GetNumPages() do - pages[i] = i - end + -- use off-by-one ordering to put (none) first in the list + local pages = { [1] = L["(none)"] } + --for i = 1, bar:GetNumPages() do + -- pages[i+1] = i + --end return pages end, set = function(info, value) - if value == "none" then - setprop(info, nil) - else - setprop(info, value) - end + value = value - 1 + setprop(info, value > 0 and value or nil) end, get = function(info) - return getprop(info) or "none" + return getprop(info) or L["(none)"] end, }, keybindstate = { @@ -647,7 +731,7 @@ type = "group", inline = true, args = { - enablescale = { + enableScale = { name = L["Set New Scale"], order = 1, type = "toggle", @@ -664,8 +748,8 @@ isPercent = true, set = setprop, get = function(info) return getprop(info) or 1 end, - disabled = function() return not GetProperty(bar, opts.name, "enablescale") end, - hidden = function() return not GetProperty(bar, opts.name, "enablescale") end, + disabled = function() return not GetProperty(bar, opts.name, "enableScale") end, + hidden = function() return not GetProperty(bar, opts.name, "enableScale") end, }, }, }, @@ -771,7 +855,7 @@ 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) + return format(L["Invalid custom rule '%s': each clause must appear within [brackets]"],rule) end s = r until c == nil @@ -816,6 +900,7 @@ CreateBarOptions = function(bar) local private = { } + local states = tbuild(module.db.profile.bars, bar:GetName(), "states") local options = { type = "group", name = L["Dynamic State"], @@ -849,7 +934,7 @@ func = function () local name = private.newstatename if states[name] then - ReAction:UserError(L["State named '%s' already exists"]:format(name)) + ReAction:UserError(format(L["State named '%s' already exists"],name)) else -- TODO: select default state options and pass as final argument states[name] = { }