Mercurial > wow > reaction
diff classes/State.lua @ 240:98d7ad4a1158
move state.lua into classes for now, removed obsolete functions
author | Flick |
---|---|
date | Fri, 25 Mar 2011 16:42:21 -0700 |
parents | modules/State.lua@2669f737d9d7 |
children | 09c8e9baa35a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/classes/State.lua Fri Mar 25 16:42:21 2011 -0700 @@ -0,0 +1,874 @@ +--[[ + ReAction bar state driver interface + +--]] + +-- local imports +local addonName, addonTable = ... +local ReAction = addonTable.ReAction +local L = ReAction.L +local _G = _G +local format = string.format +local InCombatLockdown = InCombatLockdown +local RegisterStateDriver = RegisterStateDriver + +-- module declaration +local moduleID = "State" +local module = ReAction:NewModule( moduleID, "AceEvent-3.0" ) + +-- 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 + +-- 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 ApplyStates, CleanupStates, SetProperty, GetProperty + +-- PRIVATE -- +do + function GetProperty( bar, state, propname ) + return tfetch(bar:GetConfig(), "states", state, propname) + end + + function SetProperty( bar, state, propname, value ) + local s = tbuild(bar:GetConfig(), "states", state) + s[propname] = value + bar:SetSecureStateData(state, propname, value) + end + + function ApplyStates( bar ) + local states = tfetch(bar:GetConfig(), "states") + if states then + bar:SetStateDriver(states) + end + end + + function CleanupStates( bar ) + bar:SetStateDriver(nil) + end +end + + + +-- module event handlers -- + +function module:OnInitialize() + self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") + + ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") + + ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") + ReAction.RegisterCallback(self, "OnDestroyBar") + ReAction.RegisterCallback(self, "OnRefreshBar") +end + +function module:OnEnable() + self:UPDATE_SHAPESHIFT_FORMS() -- it doesn't fire on a /reloadui +end + +function module:UPDATE_SHAPESHIFT_FORMS() + -- Re-parse the rules table according to the new form list. + -- This happens both at initial login (after PLAYER_ENTERING_WORLD) + -- as well as when gaining new abilities. + ReAction.Bar.InitRuleFormats() + for _, bar in ReAction:IterateBars() do + ApplyStates(bar) + end +end + +function module:OnRefreshBar(event, bar, name) + ApplyStates(bar) +end + +function module:OnDestroyBar(event, bar, name) + CleanupStates(bar) +end + + + + +-- Options -- + +do + -- pre-sorted by the order they should appear in + local rules = { + -- rule fields + { "stance", { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, + { "form", { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {tree = L["Tree of Life"]}, {moonkin = L["Moonkin Form"]} } }, + { "stealth", { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]}, {shadowdance = L["Shadow Dance"]} } }, + { "shadow", { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, + { "demon", { {demon = L["Demon Form"]}, {nodemon = L["No Demon Form"]} } }, + { "pet", { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, + { "target", { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, + { "focus", { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, + { "possess", { {possess = L["Mind Control"]} } }, + { "vehicle", { {vehicle = L["In a Vehicle"]} } }, + { "group", { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, + { "combat", { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, + } + + local ruleSelect = { } + local ruleMap = { } + local optionMap = setmetatable({},{__mode="k"}) + + local pointTable = { + NONE = " ", + CENTER = L["Center"], + LEFT = L["Left"], + RIGHT = L["Right"], + TOP = L["Top"], + BOTTOM = L["Bottom"], + TOPLEFT = L["Top Left"], + TOPRIGHT = L["Top Right"], + BOTTOMLEFT = L["Bottom Left"], + BOTTOMRIGHT = L["Bottom Right"], + } + + -- unpack rules table into ruleSelect and ruleMap + for _, c in ipairs(rules) do + local rule, fields = unpack(c) + for _, field in ipairs(fields) do + local key, label = next(field) + table.insert(ruleSelect, label) + table.insert(ruleMap, key) + end + end + + local stateOptions = { + ordering = { + name = L["Info"], + order = 1, + type = "group", + args = { + delete = { + name = L["Delete this State"], + order = -1, + type = "execute", + func = "DeleteState", + }, + rename = { + name = L["Name"], + order = 1, + type = "input", + get = "GetName", + set = "SetStateName", + pattern = "^%w*$", + usage = L["State names must be alphanumeric without spaces"], + }, + ordering = { + 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, + type = "group", + inline = true, + args = { + up = { + name = L["Up"], + order = 1, + type = "execute", + width = "half", + func = "MoveStateUp", + }, + down = { + name = L["Down"], + order = 2, + type = "execute", + width = "half", + func = "MoveStateDown", + } + } + } + } + }, + properties = { + name = L["Properties"], + order = 2, + type = "group", + args = { + desc = { + name = L["Set the properties for the bar when in this state"], + order = 1, + type = "description" + }, + page = { + name = L["Show Page #"], + order = 11, + type = "select", + width = "half", + disabled = "IsPageDisabled", + hidden = "IsPageHidden", + values = "GetPageValues", + set = "SetProp", + get = "GetPage", + }, + hide = { + name = L["Hide Bar"], + order = 90, + type = "toggle", + set = "SetProp", + get = "GetProp", + }, + --[[ BROKEN + keybindState = { + name = L["Override Keybinds"], + desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], + order = 91, + type = "toggle", + set = "SetProp", + get = "GetProp", + }, ]] + position = { + name = L["Position"], + order = 92, + type = "group", + inline = true, + args = { + anchorEnable = { + name = L["Reposition"], + order = 1, + type = "toggle", + set = "SetProp", + get = "GetProp", + }, + anchorFrame = { + name = L["Anchor Frame"], + order = 2, + type = "select", + values = "GetAnchorFrames", + set = "SetAnchorFrame", + get = "GetAnchorFrame", + disabled = "GetAnchorDisabled", + hidden = "GetAnchorDisabled", + }, + anchorPoint = { + name = L["Point"], + order = 3, + type = "select", + values = pointTable, + set = "SetAnchorPointProp", + get = "GetAnchorPointProp", + disabled = "GetAnchorDisabled", + hidden = "GetAnchorDisabled", + }, + anchorRelPoint = { + name = L["Relative Point"], + order = 4, + type = "select", + values = pointTable, + set = "SetAnchorPointProp", + get = "GetAnchorPointProp", + disabled = "GetAnchorDisabled", + hidden = "GetAnchorDisabled", + }, + anchorX = { + name = L["X Offset"], + order = 5, + type = "range", + min = -100, + max = 100, + step = 1, + set = "SetProp", + get = "GetProp", + disabled = "GetAnchorDisabled", + hidden = "GetAnchorDisabled", + }, + anchorY = { + name = L["Y Offset"], + order = 6, + type = "range", + min = -100, + max = 100, + step = 1, + set = "SetProp", + get = "GetProp", + disabled = "GetAnchorDisabled", + hidden = "GetAnchorDisabled", + }, + }, + }, + scale = { + name = L["Scale"], + order = 93, + type = "group", + inline = true, + args = { + enableScale = { + name = L["Set New Scale"], + order = 1, + type = "toggle", + set = "SetProp", + get = "GetProp", + }, + scale = { + name = L["Scale"], + order = 2, + type = "range", + min = 0.25, + max = 2.5, + step = 0.05, + isPercent = true, + set = "SetProp", + get = "GetScale", + disabled = "GetScaleDisabled", + hidden = "GetScaleDisabled", + }, + }, + }, + alpha = { + name = L["Transparency"], + order = 94, + type = "group", + inline = true, + args = { + enableAlpha = { + name = L["Set Transparency"], + order = 1, + type = "toggle", + set = "SetProp", + get = "GetProp", + }, + alpha = { + name = L["Transparency"], + order = 2, + type = "range", + min = 0, + max = 1, + step = 0.01, + bigStep = 0.05, + isPercent = true, + set = "SetProp", + get = "GetAlpha", + disabled = "GetAlphaDisabled", + hidden = "GetAlphaDisabled", + }, + }, + }, + }, + plugins = { } + }, + rules = { + name = L["Rule"], + order = 3, + type = "group", + args = { + mode = { + name = L["Select this state"], + order = 2, + type = "select", + style = "radio", + values = { + default = L["by default"], + any = L["when ANY of these"], + all = L["when ALL of these"], + custom = L["via custom rule"], + keybind = L["via keybinding"], + }, + set = "SetType", + get = "GetType", + }, + clear = { + name = L["Clear All"], + order = 3, + type = "execute", + hidden = "GetClearAllDisabled", + disabled = "GetClearAllDisabled", + func = "ClearAllConditions", + }, + inputs = { + name = L["Conditions"], + order = 4, + type = "multiselect", + hidden = "GetConditionsDisabled", + disabled = "GetConditionsDisabled", + values = ruleSelect, + set = "SetCondition", + get = "GetCondition", + }, + custom = { + name = L["Custom Rule"], + order = 5, + type = "input", + multiline = true, + hidden = "GetCustomDisabled", + disabled = "GetCustomDisabled", + desc = L["Syntax like macro rules: see preset rules for examples"], + set = "SetCustomRule", + get = "GetCustomRule", + validate = "ValidateCustomRule", + }, + keybind = { + name = L["Keybinding"], + order = 6, + inline = true, + hidden = "GetKeybindDisabled", + disabled = "GetKeybindDisabled", + type = "group", + args = { + desc = { + name = L["Invoking a state keybind toggles an override of all other transition rules."], + order = 1, + type = "description", + }, + keybind = { + name = L["State Hotkey"], + desc = L["Define an override toggle keybind"], + order = 2, + type = "keybinding", + set = "SetKeybind", + get = "GetKeybind", + }, + }, + }, + }, + }, + } + + local StateHandler = { } + local meta = { __index = StateHandler } + + function StateHandler:New( bar, opts ) + local self = setmetatable( + { + bar = bar + }, + meta ) + + function self:GetName() + return opts.name + end + + function self:SetName(name) + opts.name = name + end + + function self:GetOrder() + return opts.order + end + + -- get reference to states table: even if the bar + -- name changes the states table ref won't + self.states = tbuild(bar:GetConfig(), "states") + self.state = tbuild(self.states, opts.name) + + opts.order = self:GetRuleField("order") + if opts.order == nil then + -- add after the highest + opts.order = 100 + for _, state in pairs(self.states) do + local x = tonumber(tfetch(state, "rule", "order")) + if x and x >= opts.order then + opts.order = x + 1 + end + end + self:SetRuleField("order",opts.order) + end + + return self + end + + -- helper methods + + function StateHandler:SetRuleField( key, value, ... ) + tbuild(self.state, "rule", ...)[key] = value + end + + function StateHandler:GetRuleField( ... ) + return tfetch(self.state, "rule", ...) + end + + function StateHandler: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 + if self:GetRuleField("type") == "all" then + for _, c in ipairs(rules) do + local rule, fields = unpack(c) + local once = false + if setkey then + for idx, field in ipairs(fields) do + if next(field) == setkey then + once = true + end + end + end + for idx, field in ipairs(fields) do + local key = next(field) + if self:GetRuleField("values",key) then + if once and key ~= setkey then + self:SetRuleField(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 + once = true + end + end + end + end + end + + function StateHandler:GetNeighbors() + local before, after + for k, v in pairs(self.states) do + local o = tonumber(tfetch(v, "rule", "order")) + if o and k ~= self:GetName() then + local obefore = tfetch(self.states,before,"rule","order") + local oafter = tfetch(self.states,after,"rule","order") + if o < self:GetOrder() and (not obefore or obefore < o) then + before = k + end + if o > self:GetOrder() and (not oafter or oafter > o) then + after = k + end + end + end + return before, after + end + + function StateHandler:SwapOrder( a, b ) + -- do options table + local args = optionMap[self.bar].args + args[a].order, args[b].order = args[b].order, args[a].order + -- do profile + a = tbuild(self.states, a, "rule") + b = tbuild(self.states, b, "rule") + a.order, b.order = b.order, a.order + end + + -- handler methods + + function StateHandler:GetProp( info ) + -- gets property of the same name as the options arg + return GetProperty(self.bar, self:GetName(), info[#info]) + end + + function StateHandler:SetProp( info, value ) + -- sets property of the same name as the options arg + SetProperty(self.bar, self:GetName(), info[#info], value) + end + + function StateHandler:DeleteState() + if self.states[self:GetName()] then + self.states[self:GetName()] = nil + ApplyStates(self.bar) + end + optionMap[self.bar].args[self:GetName()] = nil + end + + function StateHandler:SetStateName(info, value) + -- check for existing state name + if self.states[value] then + ReAction:UserError(format(L["State named '%s' already exists"],value)) + return + end + local args = optionMap[self.bar].args + local name = self:GetName() + self.states[value], args[value], self.states[name], args[name] = self.states[name], args[name], nil, nil + self:SetName(value) + ApplyStates(self.bar) + ReAction:ShowEditor(self.bar, moduleID, value) + end + + function StateHandler:MoveStateUp() + local before, after = self:GetNeighbors() + if before then + self:SwapOrder(before, self:GetName()) + ApplyStates(self.bar) + end + end + + function StateHandler:MoveStateDown() + local before, after = self:GetNeighbors() + if after then + self:SwapOrder(self:GetName(), after) + ApplyStates(self.bar) + end + end + + function StateHandler:GetAnchorDisabled() + return not GetProperty(self.bar, self:GetName(), "anchorEnable") + end + + function StateHandler:IsPageDisabled() + local n = self.bar:GetConfig().nPages or 1 + return not (n > 1) + end + + function StateHandler:IsPageHidden() + return not self.bar:GetConfig().nPages + end + + function StateHandler:GetPageValues() + if not self._pagevalues then + self._pagevalues = { } + end + local n = self.bar:GetConfig().nPages + -- cache the results + if self._npages ~= n then + self._npages = n + wipe(self._pagevalues) + for i = 1, n do + self._pagevalues["page"..i] = i + end + end + return self._pagevalues + end + + function StateHandler:GetPage(info) + return self:GetProp(info) or 1 + end + + function StateHandler:GetAnchorFrames(info) + self._anchorframes = self._anchorframes or { } + table.wipe(self._anchorframes) + + table.insert(self._anchorframes, "UIParent") + for name, bar in ReAction:IterateBars() do + table.insert(self._anchorframes, bar:GetFrame():GetName()) + end + return self._anchorframes + end + + function StateHandler:GetAnchorFrame(info) + local value = self:GetProp(info) + for k,v in pairs(self._anchorframes) do + if v == value then + return k + end + end + end + + function StateHandler:SetAnchorFrame(info, value) + local f = _G[self._anchorframes[value]] + if f then + self.bar:SetFrameRef("anchor-"..self:GetName(), f) + self:SetProp(info, f:GetName()) + end + end + + function StateHandler:SetAnchorPointProp(info, value) + self:SetProp(info, value ~= "NONE" and value or nil) + end + + function StateHandler:GetAnchorPointProp(info) + return self:GetProp(info) or "NONE" + end + + function StateHandler:GetScale(info) + return self:GetProp(info) or 1.0 + end + + function StateHandler:GetScaleDisabled() + return not GetProperty(self.bar, self:GetName(), "enableScale") + end + + function StateHandler:GetAlpha(info) + return self:GetProp(info) or 1.0 + end + + function StateHandler:GetAlphaDisabled() + return not GetProperty(self.bar, self:GetName(), "enableAlpha") + end + + function StateHandler:SetType(info, value) + self:SetRuleField("type", value) + self:FixAll() + ApplyStates(self.bar) + end + + function StateHandler:GetType() + return self:GetRuleField("type") + end + + function StateHandler:GetClearAllDisabled() + local t = self:GetRuleField("type") + return not( t == "any" or t == "all" or t == "custom") + end + + function StateHandler:ClearAllConditions() + local t = self:GetRuleField("type") + if t == "custom" then + self:SetRuleField("custom","") + elseif t == "any" or t == "all" then + self:SetRuleField("values", {}) + end + ApplyStates(self.bar) + end + + function StateHandler:GetConditionsDisabled() + local t = self:GetRuleField("type") + return not( t == "any" or t == "all") + end + + function StateHandler:SetCondition(info, key, value) + self:SetRuleField(ruleMap[key], value or nil, "values") + if value then + self:FixAll(ruleMap[key]) + end + ApplyStates(self.bar) + end + + function StateHandler:GetCondition(info, key) + return self:GetRuleField("values", ruleMap[key]) or false + end + + function StateHandler:GetCustomDisabled() + return self:GetRuleField("type") ~= "custom" + end + + function StateHandler:SetCustomRule(info, value) + self:SetRuleField("custom",value) + ApplyStates(self.bar) + end + + function StateHandler:GetCustomRule() + return self:GetRuleField("custom") or "" + end + + function StateHandler:ValidateCustomRule(info, value) + local s = value: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 format(L["Invalid custom rule '%s': each clause must appear within [brackets]"],value or "") + end + s = r + until c == nil + return true + end + + function StateHandler:GetKeybindDisabled() + return self:GetRuleField("type") ~= "keybind" + end + + function StateHandler:GetKeybind() + return self:GetRuleField("keybind") + end + + function StateHandler:SetKeybind(info, value) + if value and #value == 0 then + value = nil + end + self:SetRuleField("keybind",value) + ApplyStates(self.bar) + end + + local function CreateStateOptions(bar, name) + local opts = { + type = "group", + name = name, + childGroups = "tab", + args = stateOptions + } + + opts.handler = StateHandler:New(bar,opts) + + return opts + end + + function module:GetBarOptions(bar) + local private = { } + local states = tbuild(bar:GetConfig(), "states") + local options = { + name = L["Dynamic State"], + type = "group", + order = -1, + childGroups = "tree", + disabled = InCombatLockdown, + args = { + __desc__ = { + name = L["States are evaluated in the order they are listed"], + order = 1, + type = "description", + }, + __new__ = { + name = L["New State..."], + order = 2, + type = "group", + args = { + name = { + name = L["State Name"], + desc = L["Set a name for the new state"], + order = 1, + type = "input", + 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"], + }, + create = { + name = L["Create State"], + order = 2, + type = "execute", + func = function () + local name = private.newstatename + if states[name] then + ReAction:UserError(format(L["State named '%s' already exists"],name)) + else + -- TODO: select default state options and pass as final argument + states[name] = { } + optionMap[bar].args[name] = CreateStateOptions(bar,name) + ReAction:ShowEditor(bar, moduleID, name) + private.newstatename = "" + end + end, + disabled = function() + local name = private.newstatename or "" + return #name == 0 or name:find("%W") + end, + } + } + } + } + } + for name, config in pairs(states) do + options.args[name] = CreateStateOptions(bar,name) + end + optionMap[bar] = options + return options + end +end + + +-- Export methods to Bar class -- + +ReAction.Bar.GetStateProperty = GetProperty +ReAction.Bar.SetStateProperty = SetProperty