vrtc / scepter (public) (License: Unspecified) (since 2022-07-30) (hash sha1)
WoW addon that helps to cast any spell on any raid member by a combination of key presses.
List of commits:
Subject Hash Author Date (UTC)
feat!: Add configuration frame 094aeb85927b28f44a25f9b3763d5679aaf8a25f Vladyslav Bondarenko 2022-05-19 17:40:54
fix: Allow toggling the spell eb6bee9a8516d9f8987388b0ddb70a79193e2281 Vladyslav Bondarenko 2022-05-16 08:26:55
feat: Initial commit 904be4af686e0ce4423daaf91a6f8e0cb0dacc07 Vladyslav Bondarenko 2022-05-16 08:12:12
Commit 094aeb85927b28f44a25f9b3763d5679aaf8a25f - feat!: Add configuration frame
Add configuration frame using native Interface Options feature.
The configuration settings allow to customize the spell list
and the key bindings used when Scepter spell casting mode is active.

It is not yet possible to customize the key bindings that relate to
units.

Also add separate profiles for unit key bindings for when the player is
in a raid group and in a party group. This is done for the player's
convenience, to allow reuse equal key bindigns for what is,
regarding implementation, distinct unit designations.

Signed-off-by: Vladyslav Bondarenko <vladyslavbond@protonmail.com>
Author: Vladyslav Bondarenko
Author date (UTC): 2022-05-19 17:40
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2022-05-19 17:45
Parent(s): eb6bee9a8516d9f8987388b0ddb70a79193e2281
Signer:
Signing key: EFF9624877D25D02
Signing status: E
Tree: 5730fb12dad911f6592694922d5f5f0f136566b0
File Lines added Lines deleted
conf/luacheckrc.lua 14 0
scepter.toc 3 0
src/Scepter.lua 1 0
src/ScepterConfFrame.lua 200 0
src/ScepterFrame.lua 146 19
File conf/luacheckrc.lua changed (mode: 100644) (index 42ad8ab..7bacebf)
1 1 read_globals = { read_globals = {
2 'BOOKTYPE_SPELL',
2 3 'CreateFrame', 'CreateFrame',
4 'GetNumSpellTabs',
5 'GetSpellInfo',
6 'GetSpellName',
7 'GetSpellTabInfo',
8 'InterfaceOptions_AddCategory',
9 'IsPassiveSpell',
3 10 'MAX_PARTY_MEMBERS', 'MAX_PARTY_MEMBERS',
4 11 'MAX_RAID_MEMBERS', 'MAX_RAID_MEMBERS',
12 'MAX_SKILLLINE_TABS',
5 13 'SetOverrideBindingClick', 'SetOverrideBindingClick',
6 14 'UIParent', 'UIParent',
7 15 'UnitClass', 'UnitClass',
8 16 'strtrim', 'strtrim',
9 17 'tContains', 'tContains',
18 'ClearOverrideBindings',
10 19 } }
11 20
12 21 globals = { globals = {
22 'Scepter',
23 'ScepterConfFrame',
24 'ScepterConfKeyFrame',
25 'ScepterConfSpellKeyMap',
26 'ScepterFrame',
13 27 } }
File scepter.toc changed (mode: 100644) (index b2fa7dd..49ecb2c)
2 2 ##Notes: Add custom key bindings to cast spells on raid ##Notes: Add custom key bindings to cast spells on raid
3 3 ##Title: Scepter ##Title: Scepter
4 4 ##Version: 0 ##Version: 0
5 ##SavedVariablesPerCharacter: ScepterConfSpellKeyMap
6 src\Scepter.lua
5 7 src\ScepterFrame.lua src\ScepterFrame.lua
8 src\ScepterConfFrame.lua
File src/Scepter.lua added (mode: 100644) (index 0000000..a6324de)
1 Scepter = {}
File src/ScepterConfFrame.lua added (mode: 100644) (index 0000000..9cdb5ee)
1 local GetNumSpellTabs = GetNumSpellTabs
2 local GetSpellInfo = GetSpellInfo
3 local GetSpellName = GetSpellName
4 local IsPassiveSpell = IsPassiveSpell
5 local tContains = tContains
6
7 local Scepter = Scepter
8
9 local function getAvailableSpellSet()
10 local t = {}
11 local i = 0
12 local j = 0
13 local booktypeDesignation = BOOKTYPE_SPELL or 'spell'
14 local maxi = math.min(math.max(1, GetNumSpellTabs() or MAX_SKILLLINE_TABS or 8), 32)
15 while (i < maxi) do
16 i = i + 1
17 local tabName, tabPictureFile, tabOffset, tabSpellQuantity = GetSpellTabInfo(i)
18 assert (tabName ~= nil)
19 assert (tabPictureFile ~= nil)
20 local q = 0
21 tabOffset = tabOffset or 0
22 tabSpellQuantity = tabSpellQuantity or 0
23 while (q < tabSpellQuantity) do
24 q = q + 1
25 local spellIndex = tabOffset + q
26 local spellName = GetSpellName(spellIndex, booktypeDesignation)
27 if spellName and
28 not tContains(t, spellName) and
29 not IsPassiveSpell(spellIndex, booktypeDesignation) then
30 j = j + 1
31 t[j] = spellName
32 end
33 end
34 end
35
36 assert (t ~= nil)
37 assert ('table' == type(t))
38 assert (#t >= 1)
39 return t
40 end
41
42 local function keyFrameRefresh(keyFrame)
43 assert (keyFrame ~= nil)
44
45 local spellKeyMap = ScepterConfSpellKeyMap or {}
46 local childrenList = {keyFrame:GetChildren()}
47 local i = 0
48 local j = 0
49 while (i < #childrenList) do
50 i = i + 1
51 local child = childrenList[i]
52 assert (child ~= nil)
53 local pictureFile = nil
54 local spellName = nil
55 local key = nil
56 if j < #spellKeyMap then
57 j = j + 1
58
59 local entry = spellKeyMap[j]
60 assert (entry ~= nil)
61 assert ('table' == type(entry))
62 assert (2 == #entry)
63
64 spellName = entry[1]
65 assert (spellName ~= nil)
66 assert ('string' == type(spellName))
67 spellName = strtrim(spellName)
68 assert (string.len(spellName) >= 1)
69
70 local infoName, _, infoPictureFile = GetSpellInfo(spellName)
71 if infoName then
72 key = entry[2]
73 assert (key ~= nil)
74 assert ('string' == type(key))
75 key = strtrim(key)
76 assert (string.len(key) >= 1)
77
78 pictureFile = infoPictureFile
79 end
80 end
81 if not pictureFile then
82 pictureFile = "Interface\\Icons\\Inv_Mace_15"
83 end
84 child.artwork:SetTexture(pictureFile)
85 child.nameEditBox:SetText(spellName)
86 child.keyEditBox:SetText(key)
87 end
88 end
89
90 local function keyFrameOkay(keyFrame)
91 assert (keyFrame ~= nil)
92
93 local childrenList = {keyFrame:GetChildren()}
94
95 local spellKeyMap = {}
96
97 local i = 0
98 local j = 0
99 while (i < #childrenList) do
100 i = i + 1
101 local child = childrenList[i]
102 assert (child ~= nil)
103
104 local spellName = child.nameEditBox:GetText()
105 local spellNameFlag = spellName ~= nil and 'string' == type(spellName) and string.len(spellName) >= 1
106 local key = child.keyEditBox:GetText()
107 local keyFlag = key ~= nil and 'string' == type(key) and string.len(key) >= 1
108 if spellNameFlag and keyFlag then
109 assert (spellName ~= nil)
110 assert ('string' == type(spellName))
111 spellName = strtrim(spellName)
112 assert (string.len(spellName) >= 1)
113
114 assert (key ~= nil)
115 assert ('string' == type(key))
116 key = strtrim(key)
117 assert (string.len(key) >= 1)
118
119 j = j + 1
120 spellKeyMap[j] = {spellName, key}
121 print(spellName, key)
122 end
123 end
124
125 ScepterConfSpellKeyMap = spellKeyMap
126 Scepter.applySpellKeyMap(spellKeyMap)
127 end
128
129 local function initConf()
130 local confFrame = _G['ScepterConfFrame']
131 local keyFrame = _G['ScepterConfKeyFrame']
132 if confFrame or keyFrame then
133 return confFrame, keyFrame
134 end
135
136 confFrame = CreateFrame('FRAME', 'ScepterConfFrame', UIParent)
137 confFrame:SetSize(360, 240)
138 confFrame:SetPoint('TOPLEFT', 0, 0)
139
140 keyFrame = CreateFrame('FRAME', 'ScepterConfKeyFrame', confFrame)
141 keyFrame:SetSize(360, 240)
142 keyFrame:SetPoint('TOPLEFT', 0, 0)
143
144 local editBoxQuantity = 9
145 local i = 0
146 while (i < editBoxQuantity) do
147 i = i + 1
148
149 local sectionName = string.format(keyFrame:GetName() .. 'Section%d', i)
150 local section = CreateFrame('FRAME', sectionName, keyFrame)
151 section:SetSize(360, 36)
152 section:SetPoint('TOPLEFT', 0, (i - 1) * -section:GetHeight())
153
154 local artwork = section:CreateTexture(sectionName .. 'Artwork', 'ARTWORK')
155 artwork:SetSize(24, 24)
156 artwork:SetPoint('BOTTOMLEFT', 12, 0)
157
158 local m = sectionName .. 'NameEditBox'
159 local nameEditBox = CreateFrame('EDITBOX', m, section, 'InputBoxTemplate')
160 nameEditBox:SetSize(144, 24)
161 nameEditBox:SetPoint('BOTTOMLEFT', 12 + 36, 0)
162
163 nameEditBox:SetAutoFocus(false)
164 nameEditBox:SetCursorPosition(0)
165
166 local n = sectionName .. 'KeyEditBox'
167 local keyEditBox = CreateFrame('EDITBOX', n, section, 'InputBoxTemplate')
168 keyEditBox:SetSize(144, 24)
169 keyEditBox:SetPoint('BOTTOMLEFT', 12 * 2 + 36 + nameEditBox:GetWidth(), 0)
170
171 keyEditBox:SetAutoFocus(false)
172 keyEditBox:SetCursorPosition(0)
173
174 section.artwork = artwork
175 section.nameEditBox = nameEditBox
176 section.keyEditBox = keyEditBox
177 end
178
179 confFrame.name = 'Scepter'
180
181 keyFrame.name = 'Spell key binding'
182 keyFrame.parent = confFrame.name
183 keyFrame.refresh = keyFrameRefresh
184 keyFrame.okay = keyFrameOkay
185
186 InterfaceOptions_AddCategory(confFrame)
187 InterfaceOptions_AddCategory(keyFrame)
188
189 confFrame:Hide()
190
191 return confFrame, keyFrame
192 end
193
194 do
195 assert (Scepter ~= nil)
196 assert ('table' == type(Scepter))
197 Scepter.initConf = function(...)
198 return initConf(...)
199 end
200 end
File src/ScepterFrame.lua changed (mode: 100644) (index ecd65ce..18b7524)
1 1 --[[ TODO Show the current active spell graphically ]]-- --[[ TODO Show the current active spell graphically ]]--
2 2 --[[ TODO Add spell GUI configuration menu ]]-- --[[ TODO Add spell GUI configuration menu ]]--
3 3 --[[ TODO Add unit key bindings GUI configuration menu ]]-- --[[ TODO Add unit key bindings GUI configuration menu ]]--
4 --[[ TODO When in party and not in a raid, rebind party keys to raid group one. ]]--
5 --[[ TODO Add unit buttons for pets ]]--
4 6 --[[ TODO Add LDoc comments ]]-- --[[ TODO Add LDoc comments ]]--
5 7
8 local Scepter = Scepter
9
6 10 local function trace(...) local function trace(...)
7 11 print('[Scepter]:', ...) print('[Scepter]:', ...)
8 12 end end
 
... ... local function getUnitList()
31 35 return unitList return unitList
32 36 end end
33 37
34 local function getUserKeyList()
35 local keyList = {
38 local function getUserRaidKeyList()
39 local raidKeyList = {
36 40 --[[ Player party ]]-- --[[ Player party ]]--
37 41 'CTRL-A', 'CTRL-S', 'CTRL-D', 'CTRL-F', 'CTRL-G', 'CTRL-A', 'CTRL-S', 'CTRL-D', 'CTRL-F', 'CTRL-G',
38 42 --[[ Raid group 1 ]]-- --[[ Raid group 1 ]]--
 
... ... local function getUserKeyList()
53 57 'ALT-Z', 'ALT-X', 'ALT-C', 'ALT-V', 'ALT-N', 'ALT-Z', 'ALT-X', 'ALT-C', 'ALT-V', 'ALT-N',
54 58 } }
55 59
56 return keyList
60 return raidKeyList
61 end
62
63 local function getUserPartyKeyList()
64 local partyKeyList = {
65 --[[ Player party ]]--
66 'A', 'S', 'D', 'F', 'G',
67 }
68 return partyKeyList
57 69 end end
58 70
59 71 local function getUserSpellList() local function getUserSpellList()
 
... ... local function createAttributeButton(n, rootFrame, attributeFrame, spellName)
94 106 return r return r
95 107 end end
96 108
109 --[[--
110 Scepter toggle button when shown will allow the user to cast spells on raid units
111 with keyboard key bindings alone.
112
113 Which spells can be cast in this manner exactly is determined by Scepter attribute buttons.
114
115 To show toggle button it must be clicked programmatically. User does this with a key binding.
116 To hide the button and thus exit the spell casting mode click the toggle button again
117 given it's shown.
118
119 @tparam n string toggle button designation
120 @param rootFrame toggle button parent frame
121 @return toggle button
122 ]]
97 123 local function createToggleButton(n, rootFrame) local function createToggleButton(n, rootFrame)
98 124 assert (n ~= nil) assert (n ~= nil)
99 125 assert (rootFrame ~= nil) assert (rootFrame ~= nil)
 
... ... local function createToggleButton(n, rootFrame)
112 138 specifically related override key bindings are applied. ]]-- specifically related override key bindings are applied. ]]--
113 139 f:WrapScript(f, 'OnShow', [=[ f:WrapScript(f, 'OnShow', [=[
114 140 self:SetBindingClick(true, 'ESCAPE', self) self:SetBindingClick(true, 'ESCAPE', self)
141 local groupCategory = PlayerInGroup()
142 local isRaid = 'raid' == groupCategory
115 143 local childrenList = self:GetChildList(newtable()) local childrenList = self:GetChildList(newtable())
116 144 local i = 0 local i = 0
117 145 while (i < #childrenList) do while (i < #childrenList) do
118 146 i = i + 1 i = i + 1
119 147 local unitButton = childrenList[i] local unitButton = childrenList[i]
120 148 local key = unitButton:GetAttribute('scepter-key') local key = unitButton:GetAttribute('scepter-key')
149
150 if not isRaid then
151 key = unitButton:GetAttribute('scepter-key-party')
152 end
153
121 154 if key then if key then
122 155 self:SetBindingClick(true, key, unitButton) self:SetBindingClick(true, key, unitButton)
123 156 end end
124 157 end end
125 print(self:GetName(), 1 == self:IsShown())
158 print(self:GetName(), 1 == self:IsShown(), groupCategory)
126 159 ]=]) ]=])
127 160
128 161 --[[ When the button is hidden it's features are disabled, --[[ When the button is hidden it's features are disabled,
 
... ... local function createToggleButton(n, rootFrame)
155 188 return f return f
156 189 end end
157 190
158 local function createUnitButton(toggleButton, unitDesignation, key)
191 local function createUnitButton(toggleButton, unitDesignation, raidKey, somePartyKey)
159 192 assert (toggleButton ~= nil) assert (toggleButton ~= nil)
193
160 194 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
161 assert (key ~= nil)
195 assert ('string' == type(unitDesignation))
196 unitDesignation = strtrim(unitDesignation)
197 assert (string.len(unitDesignation) >= 1)
198 assert (string.len(unitDesignation) <= 256)
199
200 assert (raidKey ~= nil)
201 assert ('string' == type(raidKey))
202 raidKey = strtrim(raidKey)
203 assert (string.len(raidKey) >= 1)
204 assert (string.len(raidKey) <= 256)
205
206 if somePartyKey then
207 assert ('string' == type(somePartyKey))
208 somePartyKey = strtrim(somePartyKey)
209 assert (string.len(somePartyKey) >= 1)
210 assert (string.len(somePartyKey) <= 256)
211 end
162 212
163 213 local n = 'ScepterUnitButton' local n = 'ScepterUnitButton'
164 214 .. string.upper(string.sub(unitDesignation, 1, 1)) .. string.upper(string.sub(unitDesignation, 1, 1))
 
... ... local function createUnitButton(toggleButton, unitDesignation, key)
166 216 local u = CreateFrame('BUTTON', n, toggleButton, 'SecureActionButtonTemplate') local u = CreateFrame('BUTTON', n, toggleButton, 'SecureActionButtonTemplate')
167 217 u:SetAttribute('type', 'spell') u:SetAttribute('type', 'spell')
168 218 u:SetAttribute('unit', unitDesignation) u:SetAttribute('unit', unitDesignation)
169 u:SetAttribute('scepter-key', key)
219 --[[ FIXME Pet targeting might not work at all ]]--
220 u:SetAttribute('ctrl-unit', unitDesignation .. 'pet')
221 u:SetAttribute('scepter-key', raidKey)
222 u:SetAttribute('scepter-key-party', somePartyKey)
170 223
171 224 assert (u ~= nil) assert (u ~= nil)
172 225 return u return u
173 226 end end
174 227
228 local function applySpellKeyMap(rootFrame, attributeButtonList, spellKeyMap)
229 rootFrame = rootFrame or ScepterFrame
230 spellKeyMap = spellKeyMap or ScepterConfSpellKeyMap or {}
231
232 assert (rootFrame ~= nil)
233
234 assert (attributeButtonList ~= nil)
235 assert ('table' == type(attributeButtonList))
236 assert (#attributeButtonList >= 1)
237
238 assert (spellKeyMap ~= nil)
239 assert ('table' == type(spellKeyMap))
240
241 ClearOverrideBindings(rootFrame)
242
243 local p = 0
244 while (p < #spellKeyMap) do
245 p = p + 1
246
247 local entry = spellKeyMap[p]
248 print('entry', entry[1], entry[2])
249 assert (entry ~= nil)
250 assert ('table' == type(entry))
251 assert (2 == #entry)
252
253 local spellName = entry[1]
254 assert (spellName ~= nil)
255 assert ('string' == type(spellName))
256 spellName = strtrim(spellName)
257 assert (string.len(spellName) >= 1)
258
259 local key = entry[2]
260 assert (key ~= nil)
261 assert ('string' == type(key))
262 key = strtrim(key)
263 assert (string.len(key) >= 1)
264
265 if spellName and key then
266 local a = attributeButtonList[p]
267 assert (a ~= nil)
268 a:SetAttribute('attribute-value', spellName)
269 SetOverrideBindingClick(rootFrame, true, key, a:GetName())
270 end
271 end
272 end
273
175 274 local function init(rootFrame) local function init(rootFrame)
176 275 assert (rootFrame ~= nil) assert (rootFrame ~= nil)
177 276
178 277 rootFrame:UnregisterAllEvents() rootFrame:UnregisterAllEvents()
179 278
279 if not ScepterConfSpellKeyMap then
280 ScepterConfSpellKeyMap = {}
281 end
282
180 283 local f = createToggleButton('ScepterToggleButtton', rootFrame) local f = createToggleButton('ScepterToggleButtton', rootFrame)
181 284 assert (f ~= nil) assert (f ~= nil)
182 285
183 286 local unitList = getUnitList() local unitList = getUnitList()
184 local keyList = getUserKeyList()
185 assert (#keyList == #unitList)
287
288 local raidKeyList = getUserRaidKeyList()
289 assert (#raidKeyList == #unitList)
290
291 local partyKeyList = getUserPartyKeyList()
292 assert (#partyKeyList <= #raidKeyList)
186 293
187 294 local i = 0 local i = 0
188 while (i < #keyList) do
295 while (i < #raidKeyList) do
189 296 i = i + 1 i = i + 1
297
190 298 local unitDesignation = unitList[i] local unitDesignation = unitList[i]
191 299 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
192 local key = keyList[i]
193 assert (key ~= nil)
194 createUnitButton(f, unitDesignation, key)
300
301 local raidKey = raidKeyList[i]
302 assert (raidKey ~= nil)
303
304 local somePartyKey = nil
305 if (i <= #partyKeyList) then
306 somePartyKey = partyKeyList[i]
307 end
308
309 local u = createUnitButton(f, unitDesignation, raidKey, somePartyKey)
310 assert (u ~= nil)
195 311 end end
196 312
197 313 --[[ FIXME Load user spell names from configuration settings ]]-- --[[ FIXME Load user spell names from configuration settings ]]--
198 local spellList = getUserSpellList()
314 local attributeButtonList = {}
315 local attributeButtonQuantity = 12
199 316 local q = 0 local q = 0
200 while (q < #spellList) do
317 while (q < attributeButtonQuantity) do
201 318 q = q + 1 q = q + 1
202 local spellName = spellList[q]
203 319 local n = string.format('ScepterAttributeButton%02d', q) local n = string.format('ScepterAttributeButton%02d', q)
204 local a = createAttributeButton(n, rootFrame, f, spellName)
205 local key = string.format('CTRL-%d', q)
206 SetOverrideBindingClick(rootFrame, true, key, a:GetName())
320 local a = createAttributeButton(n, rootFrame, f, 'Purify')
321 table.insert(attributeButtonList, a)
207 322 end end
208 323
324 applySpellKeyMap(rootFrame, attributeButtonList, ScepterConfSpellKeyMap)
325
326 assert (Scepter ~= nil)
327 assert ('table' == type(Scepter))
328 Scepter.applySpellKeyMap = function(spellKeyMap)
329 return applySpellKeyMap(rootFrame, attributeButtonList, spellKeyMap)
330 end
331
332 Scepter.initConf()
333
209 334 trace('init') trace('init')
335
336 return rootFrame, f
210 337 end end
211 338
212 339 local function main() local function main()
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/scepter

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/scepter

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/scepter

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main