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] = { }