Mercurial > wow > reaction
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 |