comparison modules/ReAction_State/ReAction_State.lua @ 62:f9cdb920470a

Added first cut on State module. Menu system only, it doesn't do anything. The menu system and data storage will change substantially when the implementation takes shape.
author Flick <flickerstreak@gmail.com>
date Tue, 13 May 2008 16:42:52 +0000
parents 21bcaf8215ff
children 2000f4f4c6af
comparison
equal deleted inserted replaced
61:2ee41dcd673f 62:f9cdb920470a
1 --[[ 1 --[[
2 ReAction bar state machine 2 ReAction bar state driver interface
3 3
4 --]] 4 --]]
5 5
6 -- local imports 6 -- local imports
7 local ReAction = ReAction 7 local ReAction = ReAction
11 11
12 -- module declaration 12 -- module declaration
13 local moduleID = "State" 13 local moduleID = "State"
14 local module = ReAction:NewModule( moduleID ) 14 local module = ReAction:NewModule( moduleID )
15 15
16 -- module methods 16
17 -- module event handlers
17 function module:OnInitialize() 18 function module:OnInitialize()
18 self.db = ReAction:RegisterNamespace( moduleID, 19 self.db = ReAction.db:RegisterNamespace( moduleID,
19 { 20 {
20 profile = { } 21 profile = {
22 bars = { },
23 presets = { }
24 }
21 } 25 }
22 ) 26 )
23 end 27 self.states = { }
24 28 self.options = setmetatable({},{__mode="k"})
25 function module:OnEnable() 29 end
26 30
27 end 31
28 32
29 function module:OnDisable() 33 -- ReAction module interface
30 34 function module:ApplyToBar(bar)
31 end 35 self:RefreshBar(bar)
32 36 end
33 --function module:GetGlobalOptions( configModule ) 37
34 -- return {} 38 function module:RefreshBar(bar)
35 --end 39 local c = self.db.profile.bars[bar:GetName()]
36 40 if c then
37 --function module:GetGlobalBarOptions( configModule ) 41 --self:BuildStates(bar)
38 -- 42 --self:BuildRules(bar)
39 --end 43 end
40 44 end
41 --function module:GetModuleOptions( configModule ) 45
42 -- 46 function module:RemoveFromBar(bar)
43 --end 47 end
44 48
45 function module:GetBarConfigOptions( bar, configModule ) 49 function module:EraseBarConfig(barName)
46 if not bar.modConfigOpts[moduleID] then 50 self.db.profile.bars[barName] = nil
47 local IsEnabled = function() 51 end
48 return false 52
49 end 53 function module:RenameBarConfig(oldname, newname)
50 54 local b = self.db.profile.bars
51 bar.modConfigOpts[moduleID] = { 55 bars[newname], bars[oldname] = bars[oldname], nil
52 state = { 56 end
53 type = "group", 57
54 name = L["Dynamic Behavior"], 58 function module:ApplyConfigMode(mode,bars)
55 desc = L["Dynamic Behavior"], 59 -- swap out hidestates
56 args = { 60 end
57 enable = { 61
58 type = "toggle", 62
59 name = L["Enable dynamic behavior"], 63
60 desc = L["Toggles dynamic behavior for this bar"], 64
61 get = function() return false end, 65 -- Private --
62 set = function(x) end, 66
63 disabled = InCombatLockdown, 67 -- traverse a table tree by key list and fetch the result or first nil
64 order = 1 68 local function tfetch(t, ...)
65 }, 69 for i = 1, select('#', ...) do
66 70 t = t and t[select(i, ...)]
67 default = { 71 end
68 type = "text", 72 return t
69 name = L["Default State"], 73 end
70 desc = L["State when no conditions apply"], 74
71 get = function() return false end, 75 -- traverse a table tree by key list and build tree as necessary
72 set = function(x) end, 76 local function tbuild(t, ...)
73 disabled = IsEnabled, 77 for i = 1, select('#', ...) do
74 order = 2 78 local key = select(i, ...)
75 }, 79 if not t[key] then t[key] = { } end
76 80 t = t[key]
77 stealth = { 81 end
78 type = "text", 82 return t
79 name = L["Behavior when Stealthed"], 83 end
80 desc = L["Change bar state when stealthed"], 84
81 get = function() return false end, 85
82 set = function(x) end, 86 local rules
83 disabled = IsEnabled, 87 local BuildRuleString
84 validate = { }, 88 do
85 }, 89 local function ClassCheck(...)
86 90 for i = 1, select('#',...) do
91 local _, c = UnitClass("player")
92 if c == select(i,...) then
93 return false
94 end
95 end
96 return true
97 end
98
99 -- The structure of this table is important: each row is designed to be unpack()ed
100 -- into variables as defined in the header comment row.
101 -- The 'fields' subtable (index 4) structure is also important: its subtables
102 -- are ordered to match appearances of '%s' (string-substitutions) in the corresponding
103 -- rules[] format-string entry. Each subtable's single element's key is the storage key used
104 -- in the user db, and the name of the group-element table in the config tree, so it too
105 -- is important.
106 --
107 -- While this allows for very compact code and data storage, the scheme is admittedly obtuse,
108 -- so I document it here for my own sanity.
109 --
110 rules = {
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 }
124
125 do
126 local forms = { }
127 for i = 1, GetNumShapeshiftForms() do
128 local icon, name = GetShapeshiftFormInfo(i)
129 -- TODO: need to find out if name is localized, it probably is
130 forms[name] = i;
131 end
132 local dStance = forms["Defensive Stance"] or 2
133 local zStance = forms["Berserker Stance"] or 3
134 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1
135 local cForm = forms["Cat Form"] or 3
136 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5
137
138 -- TODO: do the macro conditional strings need to be localized?
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
268
269 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 = {
310 type = "group",
311 name = name,
312 childGroups = "inline",
313 args = {
314 __select__ = {
315 type = "select",
316 name = L["Select Rule Type"],
317 get = function(info) return module:GetRule(bar,name) or "" end,
318 set = function(info,value) module:SetRule(bar,name,value) end, -- TODO: get default rule config and pass as final value
319 values = function()
320 local v = { }
321 for i = 1, #rules do
322 local rule, name, hidden = unpack(rules[i])
323 if not hidden then
324 v[rule] = name
325 end
326 end
327 return v
328 end,
329 order = 1,
330 },
331 __delete__ = {
332 type = "execute",
333 name = L["Delete this Rule"],
334 func = function(info)
335 module:DeleteRule(bar,name)
336 module.options[bar].args.rules.args[name] = nil
337 end,
338 order = -3
339 },
340 --
341 -- rule selection groups will be inserted here
342 --
343 __show__ = {
344 type = "toggle",
345 name = L["Show Rule String"],
346 desc = L["Toggles display of the raw rule string"],
347 get = function() return display.show end,
348 set = function(info,value) display.show = value end,
349 order = -2
350 },
351 __rule__ = {
352 type = "input",
353 name = L["Rule String"],
354 disabled = true,
355 multiline = true,
356 width = "double",
357 hidden = function() return not display.show end,
358 get = function() return BuildRuleString(bar,name) end,
359 set = function() end,
360 order = -1
87 } 361 }
88 } 362 }
89 } 363 }
90 end 364
91 return bar.modConfigOpts[moduleID] 365 -- unpack rules table
92 end 366 for i = 1, #rules do
93 367 local rule, label, _, fields = unpack(rules[i])
94 function module:GetBarMenuOptions( bar, configModule ) 368 local hidden = function()
95 369 return module:GetRule(bar,name) ~= rule
96 end 370 end
97 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
451 end
452
453 CreateBarOptions = function(bar)
454 local private = { }
455 local options = {
456 type = "group",
457 name = L["Dynamic State"],
458 childGroups = "tab",
459 disabled = InCombatLockdown,
460 args = {
461 states = {
462 type = "group",
463 name = L["States"],
464 childGroups = "tree",
465 order = 1,
466 args = {
467 __new__ = {
468 type = "group",
469 name = L["New State..."],
470 order = 1,
471 args = {
472 name = {
473 type = "input",
474 name = L["State Name"],
475 desc = L["Set a name for the new state"],
476 get = function() return private.newstatename or "" end,
477 set = function(info,value) private.newstatename = value end,
478 pattern = "^%w*$",
479 usage = L["State names must be alphanumeric without spaces"],
480 order = 1
481 },
482 create = {
483 type = "execute",
484 name = L["Create State"],
485 func = function ()
486 local name = private.newstatename
487 module:CreateState(bar,name) -- TODO: select default state options and pass as final argument
488 module.options[bar].args.states.args[name] = CreateStateOptions(bar,name)
489 private.newstatename = ""
490 end,
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 }
499 }
500 },
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 }
589 }
590 local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
591 if states then
592 for name, config in pairs(states) do
593 options.args.states.args[name] = CreateStateOptions(bar,name)
594 end
595 end
596 local rules = tfetch(module.db.profile.bars, bar:GetName(), "rules")
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
603 end
604 end
605
606 function module:GetBarOptions(bar)
607 if not self.options[bar] then
608 self.options[bar] = CreateBarOptions(bar)
609 end
610 return self.options[bar]
611 end