Mercurial > wow > reaction
comparison modules/ReAction_State/ReAction_State.lua @ 64:2000f4f4c6af
Redesigned state interface. The only thing missing now is the actual state properties.
| author | Flick <flickerstreak@gmail.com> |
|---|---|
| date | Wed, 28 May 2008 00:20:04 +0000 |
| parents | f9cdb920470a |
| children | 5ea65ec7d162 |
comparison
equal
deleted
inserted
replaced
| 63:768be7eb22a0 | 64:2000f4f4c6af |
|---|---|
| 18 function module:OnInitialize() | 18 function module:OnInitialize() |
| 19 self.db = ReAction.db:RegisterNamespace( moduleID, | 19 self.db = ReAction.db:RegisterNamespace( moduleID, |
| 20 { | 20 { |
| 21 profile = { | 21 profile = { |
| 22 bars = { }, | 22 bars = { }, |
| 23 presets = { } | |
| 24 } | 23 } |
| 25 } | 24 } |
| 26 ) | 25 ) |
| 27 self.states = { } | 26 |
| 28 self.options = setmetatable({},{__mode="k"}) | 27 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") |
| 29 end | 28 |
| 30 | 29 ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") |
| 31 | 30 ReAction.RegisterCallback(self, "OnRefreshBar") |
| 32 | 31 ReAction.RegisterCallback(self, "OnEraseBar") |
| 33 -- ReAction module interface | 32 ReAction.RegisterCallback(self, "OnRenameBar") |
| 34 function module:ApplyToBar(bar) | 33 ReAction.RegisterCallback(self, "OnConfigModeChanged") |
| 35 self:RefreshBar(bar) | 34 end |
| 36 end | 35 |
| 37 | 36 function module:OnRefreshBar(event, bar, name) |
| 38 function module:RefreshBar(bar) | 37 local c = self.db.profile.bars[name] |
| 39 local c = self.db.profile.bars[bar:GetName()] | |
| 40 if c then | 38 if c then |
| 41 --self:BuildStates(bar) | 39 self:UpdateStates(bar) |
| 42 --self:BuildRules(bar) | 40 end |
| 43 end | 41 end |
| 44 end | 42 |
| 45 | 43 function module:OnEraseBar(event, bar, name) |
| 46 function module:RemoveFromBar(bar) | 44 self.db.profile.bars[name] = nil |
| 47 end | 45 end |
| 48 | 46 |
| 49 function module:EraseBarConfig(barName) | 47 function module:OnRenameBar(event, bar, oldname, newname) |
| 50 self.db.profile.bars[barName] = nil | |
| 51 end | |
| 52 | |
| 53 function module:RenameBarConfig(oldname, newname) | |
| 54 local b = self.db.profile.bars | 48 local b = self.db.profile.bars |
| 55 bars[newname], bars[oldname] = bars[oldname], nil | 49 bars[newname], bars[oldname] = bars[oldname], nil |
| 56 end | 50 end |
| 57 | 51 |
| 58 function module:ApplyConfigMode(mode,bars) | 52 function module:OnConfigModeChanged(event, mode) |
| 59 -- swap out hidestates | 53 -- TODO: unregister all state drivers (temporarily) and hidestates |
| 60 end | 54 end |
| 61 | 55 |
| 62 | 56 |
| 63 | 57 |
| 64 | 58 |
| 65 -- Private -- | 59 -- Utility -- |
| 66 | 60 |
| 67 -- traverse a table tree by key list and fetch the result or first nil | 61 -- traverse a table tree by key list and fetch the result or first nil |
| 68 local function tfetch(t, ...) | 62 local function tfetch(t, ...) |
| 69 for i = 1, select('#', ...) do | 63 for i = 1, select('#', ...) do |
| 70 t = t and t[select(i, ...)] | 64 t = t and t[select(i, ...)] |
| 80 t = t[key] | 74 t = t[key] |
| 81 end | 75 end |
| 82 return t | 76 return t |
| 83 end | 77 end |
| 84 | 78 |
| 85 | 79 -- PRIVATE -- |
| 86 local rules | 80 local BuildRuleString, ApplyStates |
| 87 local BuildRuleString | 81 do |
| 82 local forms = { } | |
| 83 for i = 1, GetNumShapeshiftForms() do | |
| 84 local icon, name = GetShapeshiftFormInfo(i) | |
| 85 forms[name] = i; | |
| 86 end | |
| 87 -- TODO: need to find out if form name is localized, it probably is | |
| 88 local dStance = forms["Defensive Stance"] or 2 | |
| 89 local zStance = forms["Berserker Stance"] or 3 | |
| 90 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 | |
| 91 local cForm = forms["Cat Form"] or 3 | |
| 92 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 | |
| 93 local aForm = forms["Aquatic Form"] or 2 | |
| 94 local trForm = forms["Travel Form"] or 4 | |
| 95 local fForm = forms["Flight Form"] or forms["Swift Flight Form"] or 6 | |
| 96 | |
| 97 -- TODO: do the macro conditional strings need to be localized? | |
| 98 -- they're not contained in GlobalStrings.lua, but the parsing is done | |
| 99 -- via the C function SecureCmdOptionParse(), so it's not clear | |
| 100 local ruleformats = { | |
| 101 battle = "stance:1", | |
| 102 defensive = ("stance:%d"):format(dStance), | |
| 103 berserker = ("stance:%d"):format(zStance), | |
| 104 caster = ("form:0/%d/%d/%d"):format(aForm, trForm, fForm), | |
| 105 bear = ("form:%d"):format(bForm), | |
| 106 cat = ("form:%d"):format(cForm), | |
| 107 treeOrMoonkin = ("form:%d"):format(tForm), | |
| 108 stealth = "stealth", | |
| 109 nostealth = "nostealth", | |
| 110 shadowform = "form:1", | |
| 111 noshadowform = "noform", | |
| 112 pet = "pet", | |
| 113 nopet = "nopet", | |
| 114 harm = "target=target,harm", | |
| 115 help = "target=target,help", | |
| 116 notarget = "target=target,noexists", | |
| 117 focusharm = "target=focus,harm", | |
| 118 focushelp = "target=focus,help", | |
| 119 nofocus = "target=focus,noexists", | |
| 120 raid = "group:raid", | |
| 121 party = "group:party", | |
| 122 solo = "nogroup", | |
| 123 combat = "combat", | |
| 124 nocombat = "nocombat", | |
| 125 } | |
| 126 | |
| 127 function BuildRuleString(states) | |
| 128 local s = "" | |
| 129 local default | |
| 130 local sorted = { } | |
| 131 for name in pairs(states) do | |
| 132 table.insert(sorted,name) | |
| 133 end | |
| 134 table.sort(sorted, function(lhs, rhs) | |
| 135 local olhs = tfetch(states[lhs],"rule","order") or 0 | |
| 136 local orhs = tfetch(states[rhs],"rule","order") or 0 | |
| 137 return olhs < orhs | |
| 138 end) | |
| 139 for idx, name in ipairs(sorted) do | |
| 140 local state = states[name] | |
| 141 local semi = #s > 0 and "; " or "" | |
| 142 local mode = tfetch(state,"rule","type") | |
| 143 if mode == "default" then | |
| 144 default = name | |
| 145 elseif mode == "custom" then | |
| 146 if state.rule.custom then | |
| 147 -- strip out all spaces from the custom rule | |
| 148 s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name) | |
| 149 end | |
| 150 elseif mode == "any" then | |
| 151 if state.rule.values then | |
| 152 local clause = "" | |
| 153 for key, value in pairs(state.rule.values) do | |
| 154 clause = ("%s[%s]"):format(clause,ruleformats[key]) | |
| 155 end | |
| 156 if #clause > 0 then | |
| 157 s = ("%s%s%s %s"):format(s, semi, clause, name) | |
| 158 end | |
| 159 end | |
| 160 elseif mode == "all" then | |
| 161 if state.rule.values then | |
| 162 local clause = "" | |
| 163 for key, value in pairs(state.rule.values) do | |
| 164 clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key]) | |
| 165 end | |
| 166 if #clause > 0 then | |
| 167 s = ("%s%s[%s] %s"):format(s, semi, clause, name) | |
| 168 end | |
| 169 end | |
| 170 end | |
| 171 end | |
| 172 if default then | |
| 173 s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default) | |
| 174 end | |
| 175 return s | |
| 176 end | |
| 177 | |
| 178 local drivers = setmetatable({},{__mode="k"}) | |
| 179 | |
| 180 function ApplyStates( bar ) | |
| 181 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") | |
| 182 if states then | |
| 183 local frame = bar:GetFrame() | |
| 184 local string = BuildRuleString(states) | |
| 185 ReAction:Print("'"..string.."'") | |
| 186 if string and #string > 0 then | |
| 187 -- register a handler to set the value of attribute "state-reaction" | |
| 188 -- in response to events as per the rule string | |
| 189 RegisterStateDriver(frame, "reaction", string) | |
| 190 drivers[bar] = true | |
| 191 -- register a trivial map for each "statemap-reaction-XXX" to set 'state' to 'XXX' | |
| 192 for state in pairs(states) do | |
| 193 frame:SetAttribute(("statemap-reaction-%s"):format(state), state) | |
| 194 end | |
| 195 elseif drivers[bar] then | |
| 196 UnregisterStateDriver(frame, "reaction") | |
| 197 drivers[bar] = nil | |
| 198 end | |
| 199 end | |
| 200 end | |
| 201 end | |
| 202 | |
| 203 | |
| 204 | |
| 205 -- API -- | |
| 206 | |
| 207 function module:UpdateStates( bar ) | |
| 208 ApplyStates(bar) | |
| 209 end | |
| 210 | |
| 211 function module:CreateState( bar, name ) | |
| 212 local states = tbuild(self.db.profile.bars, bar:GetName(), "states") | |
| 213 if states[name] then | |
| 214 ReAction:UserError(L["State named '%s' already exists"]:format(name)) | |
| 215 else | |
| 216 states[name] = { } | |
| 217 end | |
| 218 end | |
| 219 | |
| 220 function module:DeleteState( bar, name ) | |
| 221 local states = tfetch(self.db.profile.bars, bar:GetName(), "states") | |
| 222 if states[name] then | |
| 223 states[name] = nil | |
| 224 ApplyStates(bar) | |
| 225 end | |
| 226 end | |
| 227 | |
| 228 -- Options -- | |
| 229 | |
| 230 local CreateBarOptions | |
| 88 do | 231 do |
| 89 local function ClassCheck(...) | 232 local function ClassCheck(...) |
| 90 for i = 1, select('#',...) do | 233 for i = 1, select('#',...) do |
| 91 local _, c = UnitClass("player") | 234 local _, c = UnitClass("player") |
| 92 if c == select(i,...) then | 235 if c == select(i,...) then |
| 94 end | 237 end |
| 95 end | 238 end |
| 96 return true | 239 return true |
| 97 end | 240 end |
| 98 | 241 |
| 99 -- The structure of this table is important: each row is designed to be unpack()ed | 242 -- pre-sorted by the order they should appear in |
| 100 -- into variables as defined in the header comment row. | 243 local rules = { |
| 101 -- The 'fields' subtable (index 4) structure is also important: its subtables | 244 -- rule hidden fields |
| 102 -- are ordered to match appearances of '%s' (string-substitutions) in the corresponding | 245 { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, |
| 103 -- rules[] format-string entry. Each subtable's single element's key is the storage key used | 246 { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } }, |
| 104 -- in the user db, and the name of the group-element table in the config tree, so it too | 247 { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, |
| 105 -- is important. | 248 { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, |
| 106 -- | 249 { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, |
| 107 -- While this allows for very compact code and data storage, the scheme is admittedly obtuse, | 250 { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, |
| 108 -- so I document it here for my own sanity. | 251 { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, |
| 109 -- | 252 { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, |
| 110 rules = { | 253 { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, |
| 111 -- rule name hidden fields | |
| 112 { "stance", L["Warrior Stance"], ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, | |
| 113 { "form", L["Druid Form"], ClassCheck("DRUID"), { {bear = L["Bear"]}, {cat = L["Cat"]}, {treeOrMoonkin = L["Tree/Moonkin"]}, {caster = L["Normal"]}, } }, | |
| 114 { "stealth", L["Stealth"], ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {normal = L["Normal"]} } }, | |
| 115 { "shadow", L["Shadowform"], ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {normal = L["Normal"]} } }, | |
| 116 { "pet", L["Pet"], ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, | |
| 117 { "target", L["Target"], false, { {hostile = L["Hostile Target"]}, {friendly = L["Friendly Target"]}, {none = L["No Target"]} } }, | |
| 118 { "focus", L["Focus"], false, { {hostile = L["Hostile Focus"]}, {friendly = L["Friendly Focus"]}, {none = L["No Focus"]} } }, | |
| 119 { "group", L["In Group"], false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, | |
| 120 { "key", L["Key Press"], false, { {state = L["On Key Press"]}, {keybinding = false} } }, -- keybinding has no state-selector, it's implemented elsewhere | |
| 121 { "combat", L["Combat"], false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, | |
| 122 { "custom", L["Custom"], false, { {rule = false} } }, -- custom has no state-selector, it's implemented elsewhere | |
| 123 } | 254 } |
| 124 | 255 |
| 125 do | 256 local ruleSelect = { } |
| 126 local forms = { } | 257 local ruleMap = { } |
| 127 for i = 1, GetNumShapeshiftForms() do | 258 local optionMap = setmetatable({},{__mode="k"}) |
| 128 local icon, name = GetShapeshiftFormInfo(i) | 259 |
| 129 -- TODO: need to find out if name is localized, it probably is | 260 -- unpack rules table into ruleSelect and ruleMap |
| 130 forms[name] = i; | 261 for _, c in ipairs(rules) do |
| 131 end | 262 local rule, hidden, fields = unpack(c) |
| 132 local dStance = forms["Defensive Stance"] or 2 | 263 if not hidden then |
| 133 local zStance = forms["Berserker Stance"] or 3 | 264 for _, field in ipairs(fields) do |
| 134 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 | 265 local key, label = next(field) |
| 135 local cForm = forms["Cat Form"] or 3 | 266 table.insert(ruleSelect, label) |
| 136 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 | 267 table.insert(ruleMap, key) |
| 137 | 268 end |
| 138 -- TODO: do the macro conditional strings need to be localized? | 269 end |
| 139 local ruleformats = { | |
| 140 stance = ("[stance:1] %%s; [stance:%d] %%s; [stance:%d] %%s"):format(dStance,zStance), | |
| 141 form = ("[form:%d] %%s; [form:%d] %%s; [form:%d] %%s; %%s"):format(bForm,cForm,tForm), | |
| 142 stealth = "[stealth] %s; %s", | |
| 143 shadow = "[stance:1] %s; %s", | |
| 144 pet = "[pet] %s; %s", | |
| 145 target = "[harm] %s; [help] %s; %s", | |
| 146 focus = "[target=focus,harm] %s; [target=focus,help] %s; %s", | |
| 147 group = "[group:raid] %s; [group] %s; %s", | |
| 148 combat = "[combat] %s; %s", | |
| 149 custom = "%s", | |
| 150 } | |
| 151 | |
| 152 local fieldmap = { } | |
| 153 for i = 1, #rules do | |
| 154 fieldmap[rules[i][1]] = rules[i][4] | |
| 155 end | |
| 156 | |
| 157 local _scratch = { } | |
| 158 function BuildRuleString(bar, name) | |
| 159 local rule, data = module:GetRule(bar,name) | |
| 160 if not rule then return "" end | |
| 161 local fields = fieldmap[rule] | |
| 162 for i = 1, #fields do | |
| 163 _scratch[i] = data[next(fields[i])] -- TODO: insert default state here | |
| 164 end | |
| 165 for i = #fields+1, #_scratch do | |
| 166 _scratch[i] = nil | |
| 167 end | |
| 168 local success, value = pcall(string.format, ruleformats[rule], unpack(_scratch)) | |
| 169 return success and value or "<error>" | |
| 170 end | |
| 171 end | |
| 172 | |
| 173 | |
| 174 end | |
| 175 | |
| 176 | |
| 177 | |
| 178 -- API -- | |
| 179 | |
| 180 function module:BuildStates( bar ) | |
| 181 local c = tfetch(self.db.profile.bars, bar:GetName(), "states") | |
| 182 if c then | |
| 183 for name, s in pairs(c) do | |
| 184 -- TODO: new state here | |
| 185 end | |
| 186 end | |
| 187 end | |
| 188 | |
| 189 function module:CreateState( bar, name ) | |
| 190 local c = tbuild(self.db.profile.bars, bar:GetName(), "states") | |
| 191 if c[name] then | |
| 192 ReAction:UserError(L["State named '%s' already exists"]:format(name)) | |
| 193 else | |
| 194 c[name] = { } | |
| 195 -- TODO: new state here | |
| 196 end | |
| 197 end | |
| 198 | |
| 199 function module:DeleteState( bar, name ) | |
| 200 local c = tfetch(self.db.profile.bars, bar:GetName(), "states") | |
| 201 if c[name] then | |
| 202 -- TODO: delete state | |
| 203 c[name] = nil | |
| 204 end | |
| 205 end | |
| 206 | |
| 207 function module:BuildRules( bar ) | |
| 208 for bar, c in pairs(self.db.profile.bars) do | |
| 209 if c.rules then | |
| 210 for name, t in pairs(c.rules) do | |
| 211 local rule, config = next(t) | |
| 212 self:SetRule(bar, name, rule, config) | |
| 213 end | |
| 214 end | |
| 215 end | |
| 216 end | |
| 217 | |
| 218 function module:CreateRule( bar, name, rule, config ) | |
| 219 local c = tbuild(self.db.profile.bars, bar:GetName(), "rules") | |
| 220 if c[name] then | |
| 221 ReAction:UserError(L["Rule named '%s' already exists"]:format(name)) | |
| 222 else | |
| 223 tbuild(self.db.profile.bars, bar:GetName(), "rules", name) | |
| 224 if rule then | |
| 225 self:SetRule(bar,name,rule,config) | |
| 226 end | |
| 227 end | |
| 228 end | |
| 229 | |
| 230 function module:DeleteRule( bar, name ) | |
| 231 local c = tfetch(self.db.profile.bars, bar:GetName(), "rules") | |
| 232 if c[name] then | |
| 233 local f = bar:GetFrame() | |
| 234 -- TODO: delete rule | |
| 235 c[name] = nil | |
| 236 end | |
| 237 end | |
| 238 | |
| 239 function module:UpdateRule( bar, name ) | |
| 240 local rule, c = self:GetRule(bar,name) | |
| 241 -- TODO: remove all relevant outdated attributes | |
| 242 -- TODO: set new attributes | |
| 243 end | |
| 244 | |
| 245 function module:GetRule(bar, name) | |
| 246 local c = tfetch(self.db.profile.bars, bar:GetName(), "rules", name) | |
| 247 if c then | |
| 248 return next(c) -- returns key, value (= rulename, configtable) | |
| 249 end | |
| 250 end | |
| 251 | |
| 252 function module:SetRule(bar, name, rule, config) | |
| 253 tbuild(self.db.profile.bars, bar:GetName(), "rules")[name] = { [rule] = (config or {}) } | |
| 254 self:UpdateRule(bar,name) | |
| 255 end | |
| 256 | |
| 257 | |
| 258 -- options -- | |
| 259 local CreateBarOptions | |
| 260 do | |
| 261 local function GetRuleConfig(bar, name, rule, field) | |
| 262 return tfetch(module.db.profile.bars, bar:GetName(), "rules", name, rule, field) | |
| 263 end | |
| 264 | |
| 265 local function SetRuleConfig( bar, name, rule, field, value ) | |
| 266 tbuild(module.db.profile.bars, bar:GetName(), "rules", name, rule)[field] = value | |
| 267 end | 270 end |
| 268 | 271 |
| 269 local function CreateStateOptions(bar, name) | 272 local function CreateStateOptions(bar, name) |
| 270 return { | |
| 271 type = "group", | |
| 272 name = name, | |
| 273 args = { | |
| 274 -- show/hide would go here | |
| 275 -- page # would go here | |
| 276 -- anchoring would go here | |
| 277 __delete__ = { | |
| 278 type = "execute", | |
| 279 name = L["Delete this State"], | |
| 280 func = function(info) | |
| 281 module:DeleteState(bar,name) | |
| 282 module.options[bar].args.states.args[name] = nil | |
| 283 end, | |
| 284 order = -1 | |
| 285 }, | |
| 286 } | |
| 287 } | |
| 288 end | |
| 289 | |
| 290 | |
| 291 -- display rule string setting is shared between all rule opts and is transient | |
| 292 -- (mostly used for debugging) | |
| 293 local display = { show = false } | |
| 294 | |
| 295 local function CreateRuleOptions(bar, name) | |
| 296 local function get(info) | |
| 297 local rule = info[#info-1] | |
| 298 local field = info[#info] | |
| 299 return GetRuleConfig(bar,name,rule,field) or "" | |
| 300 end | |
| 301 | |
| 302 local function set(info, value) | |
| 303 local rule = info[#info-1] | |
| 304 local field = info[#info] | |
| 305 SetRuleConfig(bar,name,rule,field,value) | |
| 306 module:UpdateRule(bar,name) | |
| 307 end | |
| 308 | |
| 309 local opts = { | 273 local opts = { |
| 310 type = "group", | 274 type = "group", |
| 311 name = name, | 275 name = name, |
| 312 childGroups = "inline", | 276 childGroups = "tab", |
| 313 args = { | 277 } |
| 314 __select__ = { | 278 |
| 315 type = "select", | 279 local states = tbuild(module.db.profile.bars, bar:GetName(), "states") |
| 316 name = L["Select Rule Type"], | 280 |
| 317 get = function(info) return module:GetRule(bar,name) or "" end, | 281 local function put( key, value, ... ) |
| 318 set = function(info,value) module:SetRule(bar,name,value) end, -- TODO: get default rule config and pass as final value | 282 tbuild(states, opts.name, "rule", ...)[key] = value |
| 319 values = function() | 283 end |
| 320 local v = { } | 284 |
| 321 for i = 1, #rules do | 285 local function fetch( ... ) |
| 322 local rule, name, hidden = unpack(rules[i]) | 286 return tfetch(states, opts.name, "rule", ...) |
| 323 if not hidden then | 287 end |
| 324 v[rule] = name | 288 |
| 325 end | 289 local function fixall(setkey) |
| 290 -- if multiple selections in the same group are chosen when 'all' is selected, | |
| 291 -- keep only one of them. If changing the mode, the first in the fields list will | |
| 292 -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, | |
| 293 -- it will be retained. | |
| 294 local notified = false | |
| 295 for _, c in ipairs(rules) do | |
| 296 local rule, hidden, fields = unpack(c) | |
| 297 local found = false | |
| 298 for key in ipairs(fields) do | |
| 299 if fetch("values",key) then | |
| 300 if (found or setkey) and key ~= setkey then | |
| 301 put(key,false,"values") | |
| 302 if not setkey and not notified then | |
| 303 ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) | |
| 304 notified = true | |
| 326 end | 305 end |
| 327 return v | 306 end |
| 328 end, | 307 found = true |
| 329 order = 1, | 308 end |
| 330 }, | 309 end |
| 331 __delete__ = { | 310 end |
| 332 type = "execute", | 311 end |
| 333 name = L["Delete this Rule"], | 312 |
| 334 func = function(info) | 313 local function getNeighbors() |
| 335 module:DeleteRule(bar,name) | 314 local before, after |
| 336 module.options[bar].args.rules.args[name] = nil | 315 for k, v in pairs(states) do |
| 337 end, | 316 local o = tonumber(tfetch(v, "rule", "order")) |
| 338 order = -3 | 317 if o and k ~= opts.name then |
| 339 }, | 318 local obefore = tfetch(states,before,"rule","order") |
| 340 -- | 319 local oafter = tfetch(states,after,"rule","order") |
| 341 -- rule selection groups will be inserted here | 320 if o < opts.order and (not obefore or obefore < o) then |
| 342 -- | 321 before = k |
| 343 __show__ = { | 322 end |
| 344 type = "toggle", | 323 if o > opts.order and (not oafter or oafter > o) then |
| 345 name = L["Show Rule String"], | 324 after = k |
| 346 desc = L["Toggles display of the raw rule string"], | 325 end |
| 347 get = function() return display.show end, | 326 end |
| 348 set = function(info,value) display.show = value end, | 327 end |
| 349 order = -2 | 328 return before, after |
| 350 }, | 329 end |
| 351 __rule__ = { | 330 |
| 352 type = "input", | 331 local function swapOrder( a, b ) |
| 353 name = L["Rule String"], | 332 -- do options table |
| 354 disabled = true, | 333 local args = optionMap[bar].args |
| 355 multiline = true, | 334 args[a].order, args[b].order = args[b].order, args[a].order |
| 356 width = "double", | 335 -- do profile |
| 357 hidden = function() return not display.show end, | 336 a = tbuild(states, a, "rule") |
| 358 get = function() return BuildRuleString(bar,name) end, | 337 b = tbuild(states, b, "rule") |
| 359 set = function() end, | 338 a.order, b.order = b.order, a.order |
| 360 order = -1 | 339 end |
| 340 | |
| 341 local function update() | |
| 342 module:UpdateStates(bar) | |
| 343 end | |
| 344 | |
| 345 | |
| 346 opts.order = fetch("order") | |
| 347 if opts.order == nil then | |
| 348 -- add after the highest | |
| 349 opts.order = 100 | |
| 350 for _, state in pairs(states) do | |
| 351 local x = tonumber(tfetch(state, "rule", "order")) | |
| 352 if x and x >= opts.order then | |
| 353 opts.order = x + 1 | |
| 354 end | |
| 355 end | |
| 356 put("order",opts.order) | |
| 357 end | |
| 358 | |
| 359 opts.args = { | |
| 360 properties = { | |
| 361 type = "group", | |
| 362 name = L["Properties"], | |
| 363 order = 1, | |
| 364 args = { | |
| 365 delete = { | |
| 366 type = "execute", | |
| 367 name = L["Delete this State"], | |
| 368 func = function(info) | |
| 369 module:DeleteState(bar,opts.name) | |
| 370 optionMap[bar].args[opts.name] = nil | |
| 371 end, | |
| 372 order = -1 | |
| 373 }, | |
| 374 rename = { | |
| 375 type = "input", | |
| 376 name = L["Name"], | |
| 377 order = 1, | |
| 378 get = function() return opts.name end, | |
| 379 set = function(info, value) | |
| 380 -- check for existing state name | |
| 381 if states[value] then | |
| 382 L["State named '%s' already exists"]:format(value) | |
| 383 end | |
| 384 local args = optionMap[bar].args | |
| 385 states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil | |
| 386 opts.name = value | |
| 387 update() | |
| 388 end, | |
| 389 pattern = "^%w*$", | |
| 390 usage = L["State names must be alphanumeric without spaces"], | |
| 391 }, | |
| 392 ordering = { | |
| 393 type = "group", | |
| 394 inline = true, | |
| 395 name = L["Evaluation Order"], | |
| 396 desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"], | |
| 397 order = 2, | |
| 398 args = { | |
| 399 up = { | |
| 400 type = "execute", | |
| 401 name = L["Up"], | |
| 402 width = "half", | |
| 403 order = 1, | |
| 404 func = function() | |
| 405 local before, after = getNeighbors() | |
| 406 if before then | |
| 407 swapOrder(before, opts.name) | |
| 408 update() | |
| 409 end | |
| 410 end, | |
| 411 }, | |
| 412 down = { | |
| 413 type = "execute", | |
| 414 name = L["Down"], | |
| 415 width = "half", | |
| 416 order = 2, | |
| 417 func = function() | |
| 418 local before, after = getNeighbors() | |
| 419 if after then | |
| 420 ReAction:Print(opts.name, after) | |
| 421 swapOrder(opts.name, after) | |
| 422 update() | |
| 423 end | |
| 424 end, | |
| 425 } | |
| 426 } | |
| 427 }, | |
| 428 -- keybinding for show-this-state would go here | |
| 429 -- show/hide would go here | |
| 430 -- page # would go here | |
| 431 -- anchoring would go here | |
| 432 } | |
| 433 }, | |
| 434 rules = { | |
| 435 type = "group", | |
| 436 name = L["Rules"], | |
| 437 order = 2, | |
| 438 args = { | |
| 439 mode = { | |
| 440 type = "select", | |
| 441 style = "radio", | |
| 442 name = L["Select this state"], | |
| 443 values = { | |
| 444 default = L["by default"], | |
| 445 any = L["when ANY of these"], | |
| 446 all = L["when ALL of these"], | |
| 447 custom = L["via custom rule"] | |
| 448 }, | |
| 449 set = function( info, value ) | |
| 450 put("type", value) | |
| 451 fixall() | |
| 452 update() | |
| 453 end, | |
| 454 get = function( info ) | |
| 455 return fetch("type") | |
| 456 end, | |
| 457 order = 2 | |
| 458 }, | |
| 459 clear = { | |
| 460 type = "execute", | |
| 461 name = L["Clear All"], | |
| 462 func = function() | |
| 463 local type = fetch("type") | |
| 464 if type == "custom" then | |
| 465 put("custom","") | |
| 466 elseif type == "any" or type == "all" then | |
| 467 put("values", {}) | |
| 468 end | |
| 469 update() | |
| 470 end, | |
| 471 order = 3 | |
| 472 }, | |
| 473 inputs = { | |
| 474 type = "multiselect", | |
| 475 name = L["Rules"], | |
| 476 hidden = function() | |
| 477 return fetch("type") == "custom" | |
| 478 end, | |
| 479 disabled = function() | |
| 480 return fetch("type") == "default" | |
| 481 end, | |
| 482 values = ruleSelect, | |
| 483 set = function(info, key, value ) | |
| 484 put(ruleMap[key], value or nil, "values") | |
| 485 if value then | |
| 486 fixall(ruleMap[key]) | |
| 487 end | |
| 488 update() | |
| 489 end, | |
| 490 get = function(info, key) | |
| 491 return fetch("values", ruleMap[key]) or false | |
| 492 end, | |
| 493 order = 4 | |
| 494 }, | |
| 495 custom = { | |
| 496 type = "input", | |
| 497 multiline = true, | |
| 498 hidden = function() | |
| 499 return fetch("type") ~= "custom" | |
| 500 end, | |
| 501 disabled = function() | |
| 502 return fetch("type") == "default" | |
| 503 end, | |
| 504 name = L["Custom Rule"], | |
| 505 desc = L["Syntax like macro rules: see preset rules for examples"], | |
| 506 set = function(info, value) | |
| 507 put("custom",value) | |
| 508 update() | |
| 509 end, | |
| 510 get = function(info) | |
| 511 return fetch("custom") or "" | |
| 512 end, | |
| 513 validate = function (info, rule) | |
| 514 local s = rule:gsub("%s","") -- remove all spaces | |
| 515 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler | |
| 516 repeat | |
| 517 if s == "" then | |
| 518 return true | |
| 519 end | |
| 520 local c, r = s:match("(%b[])(.*)") | |
| 521 if c == nil and s and #s > 0 then | |
| 522 return L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule) | |
| 523 end | |
| 524 s = r | |
| 525 until c == nil | |
| 526 return true | |
| 527 end, | |
| 528 order = 5, | |
| 529 } | |
| 361 } | 530 } |
| 362 } | 531 } |
| 363 } | 532 } |
| 364 | |
| 365 -- unpack rules table | |
| 366 for i = 1, #rules do | |
| 367 local rule, label, _, fields = unpack(rules[i]) | |
| 368 local hidden = function() | |
| 369 return module:GetRule(bar,name) ~= rule | |
| 370 end | |
| 371 opts.args[rule] = { | |
| 372 type = "group", | |
| 373 name = label, | |
| 374 hidden = hidden, | |
| 375 disabled = hidden, | |
| 376 inline = true, | |
| 377 args = { } | |
| 378 } | |
| 379 for j = 1, #fields do | |
| 380 local field, label = next(fields[j]) -- extract from table of single key-value pair | |
| 381 if field and label then | |
| 382 opts.args[rule].args[field] = { | |
| 383 type = "select", | |
| 384 name = L["Select State (%s):"]:format(label), | |
| 385 values = function () | |
| 386 local states = tfetch(module.db.profile.bars,bar:GetName(),"states") | |
| 387 local v = { } | |
| 388 if states then | |
| 389 for k in pairs(states) do | |
| 390 v[k] = k | |
| 391 end | |
| 392 end | |
| 393 return v | |
| 394 end, | |
| 395 set = set, | |
| 396 get = get, | |
| 397 order = 100 + j | |
| 398 } | |
| 399 end | |
| 400 end | |
| 401 end | |
| 402 | |
| 403 -- set up special entry for keybinding | |
| 404 opts.args.key.args.binding = { | |
| 405 type = "keybinding", | |
| 406 name = L["Key Binding"], | |
| 407 get = get, | |
| 408 set = set, | |
| 409 order = -1 | |
| 410 } | |
| 411 | |
| 412 -- set up special entry for custom | |
| 413 opts.args.custom.args.rule = { | |
| 414 type = "input", | |
| 415 name = L["Rule"], | |
| 416 desc = L["Syntax like macro conditions: see preset rules for examples"], | |
| 417 get = get, | |
| 418 set = set, | |
| 419 validate = function (info, rule) | |
| 420 local s = rule:gsub("%s","") -- remove all spaces | |
| 421 if s:match(";$") then -- can't end with semicolon | |
| 422 return L["Invalid custom rule '%s': Rule cannot end with ';'"]:format(rule) | |
| 423 end | |
| 424 if s == "" then | |
| 425 return true | |
| 426 end | |
| 427 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler | |
| 428 repeat | |
| 429 repeat | |
| 430 local c, r = s:match("(%b[])(.*)") | |
| 431 if r then s = r end | |
| 432 until c == nil | |
| 433 local state, s = s:match("(%w+)(.*)") | |
| 434 if not state then | |
| 435 return L["Invalid custom rule '%s': Each expression must have a state"]:format(rule) | |
| 436 end | |
| 437 if not tfetch(module.db.profile.bars,bar:GetName(),"states",state) then | |
| 438 return L["Invalid custom rule '%s': '%s' is not a state"]:format(rule,state) | |
| 439 end | |
| 440 if s:match("^[^;]") then | |
| 441 return L["Invalid custom rule '%s': Expressions must be separated by ';'"]:format(rule) | |
| 442 end | |
| 443 until #s == 0 | |
| 444 return true | |
| 445 end, | |
| 446 multiline = true, | |
| 447 order = 1, | |
| 448 } | |
| 449 | |
| 450 return opts | 533 return opts |
| 451 end | 534 end |
| 535 | |
| 452 | 536 |
| 453 CreateBarOptions = function(bar) | 537 CreateBarOptions = function(bar) |
| 454 local private = { } | 538 local private = { } |
| 455 local options = { | 539 local options = { |
| 456 type = "group", | 540 type = "group", |
| 457 name = L["Dynamic State"], | 541 name = L["Dynamic State"], |
| 458 childGroups = "tab", | 542 childGroups = "tree", |
| 459 disabled = InCombatLockdown, | 543 disabled = InCombatLockdown, |
| 460 args = { | 544 args = { |
| 461 states = { | 545 __desc__ = { |
| 546 type = "description", | |
| 547 name = L["States are evaluated in the order they are listed"], | |
| 548 order = 1 | |
| 549 }, | |
| 550 __new__ = { | |
| 462 type = "group", | 551 type = "group", |
| 463 name = L["States"], | 552 name = L["New State..."], |
| 464 childGroups = "tree", | 553 order = 2, |
| 465 order = 1, | |
| 466 args = { | 554 args = { |
| 467 __new__ = { | 555 name = { |
| 468 type = "group", | 556 type = "input", |
| 469 name = L["New State..."], | 557 name = L["State Name"], |
| 470 order = 1, | 558 desc = L["Set a name for the new state"], |
| 471 args = { | 559 get = function() return private.newstatename or "" end, |
| 472 name = { | 560 set = function(info,value) private.newstatename = value end, |
| 473 type = "input", | 561 pattern = "^%w*$", |
| 474 name = L["State Name"], | 562 usage = L["State names must be alphanumeric without spaces"], |
| 475 desc = L["Set a name for the new state"], | 563 order = 1 |
| 476 get = function() return private.newstatename or "" end, | 564 }, |
| 477 set = function(info,value) private.newstatename = value end, | 565 create = { |
| 478 pattern = "^%w*$", | 566 type = "execute", |
| 479 usage = L["State names must be alphanumeric without spaces"], | 567 name = L["Create State"], |
| 480 order = 1 | 568 func = function () |
| 481 }, | 569 local name = private.newstatename |
| 482 create = { | 570 module:CreateState(bar,name) -- TODO: select default state options and pass as final argument |
| 483 type = "execute", | 571 optionMap[bar].args[name] = CreateStateOptions(bar,name) |
| 484 name = L["Create State"], | 572 private.newstatename = "" |
| 485 func = function () | 573 end, |
| 486 local name = private.newstatename | 574 disabled = function() |
| 487 module:CreateState(bar,name) -- TODO: select default state options and pass as final argument | 575 local name = private.newstatename or "" |
| 488 module.options[bar].args.states.args[name] = CreateStateOptions(bar,name) | 576 return #name == 0 or name:find("%W") |
| 489 private.newstatename = "" | 577 end, |
| 490 end, | 578 order = 2, |
| 491 disabled = function() | |
| 492 local name = private.newstatename or "" | |
| 493 return #name == 0 or name:find("%W") | |
| 494 end, | |
| 495 order = 2, | |
| 496 } | |
| 497 } | |
| 498 } | 579 } |
| 499 } | 580 } |
| 500 }, | 581 } |
| 501 rules = { | |
| 502 type = "group", | |
| 503 name = L["Transition Rules"], | |
| 504 childGroups = "tree", | |
| 505 order = 2, | |
| 506 args = { | |
| 507 __new__ = { | |
| 508 type = "group", | |
| 509 name = L["New Rule..."], | |
| 510 order = 1, | |
| 511 args = { | |
| 512 name = { | |
| 513 type = "input", | |
| 514 name = L["Rule Name"], | |
| 515 desc = L["Set a name for the new transition rule"], | |
| 516 get = function() return private.newtransname or "" end, | |
| 517 set = function(info,value) private.newtransname = value end, | |
| 518 pattern = "^%w*$", | |
| 519 usage = L["Rule names must be alphanumeric without spaces"], | |
| 520 order = 1 | |
| 521 }, | |
| 522 create = { | |
| 523 type = "execute", | |
| 524 name = L["Create Rule"], | |
| 525 func = function () | |
| 526 local name = private.newtransname | |
| 527 module:CreateRule(bar,name) -- TODO: select default rule and add as final argument | |
| 528 module.options[bar].args.rules.args[name] = CreateRuleOptions(bar,name) | |
| 529 private.newtransname = "" | |
| 530 end, | |
| 531 disabled = function () | |
| 532 local name = private.newtransname or "" | |
| 533 return #name == 0 or name:find("%W") | |
| 534 end, | |
| 535 order = 2, | |
| 536 }, | |
| 537 } | |
| 538 } | |
| 539 } | |
| 540 }, | |
| 541 presets = { | |
| 542 type = "group", | |
| 543 name = L["Presets"], | |
| 544 order = 3, | |
| 545 args = { | |
| 546 desc = { | |
| 547 type = "description", | |
| 548 name = L["Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults."], | |
| 549 order = 1, | |
| 550 }, | |
| 551 select = { | |
| 552 type = "select", | |
| 553 name = L["Select Preset"], | |
| 554 set = function(info,value) end, | |
| 555 get = function() return "" end, | |
| 556 values = function() return { } end, | |
| 557 order = 2, | |
| 558 }, | |
| 559 load = { | |
| 560 type = "execute", | |
| 561 name = L["Load"], | |
| 562 func = function() end, | |
| 563 width = "half", | |
| 564 order = 3, | |
| 565 }, | |
| 566 delete = { | |
| 567 type = "execute", | |
| 568 name = L["Delete"], | |
| 569 disabled = function() return false end, | |
| 570 func = function() end, | |
| 571 width = "half", | |
| 572 order = 4, | |
| 573 }, | |
| 574 hdr = { | |
| 575 type = "header", | |
| 576 name = " ", | |
| 577 order = 5, | |
| 578 }, | |
| 579 save = { | |
| 580 type = "input", | |
| 581 name = L["Save As..."], | |
| 582 get = function() return "" end, | |
| 583 set = function(info,name) end, | |
| 584 order = 6, | |
| 585 }, | |
| 586 }, | |
| 587 }, | |
| 588 } | 582 } |
| 589 } | 583 } |
| 590 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") | 584 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") |
| 591 if states then | 585 if states then |
| 592 for name, config in pairs(states) do | 586 for name, config in pairs(states) do |
| 593 options.args.states.args[name] = CreateStateOptions(bar,name) | 587 options.args[name] = CreateStateOptions(bar,name) |
| 594 end | 588 end |
| 595 end | 589 end |
| 596 local rules = tfetch(module.db.profile.bars, bar:GetName(), "rules") | 590 optionMap[bar] = options |
| 597 if rules then | |
| 598 for name, config in pairs(rules) do | |
| 599 options.args.rules.args[name] = CreateRuleOptions(bar,name) | |
| 600 end | |
| 601 end | |
| 602 return options | 591 return options |
| 603 end | 592 end |
| 604 end | 593 end |
| 605 | 594 |
| 606 function module:GetBarOptions(bar) | 595 function module:GetBarOptions(bar) |
| 607 if not self.options[bar] then | 596 return CreateBarOptions(bar) |
| 608 self.options[bar] = CreateBarOptions(bar) | 597 end |
| 609 end | |
| 610 return self.options[bar] | |
| 611 end |
