List of commits:
Subject Hash Author Date (UTC)
fix(clearcasting)!: Reduce redundant updates e93ca1628e362276ef20d10bbc0b4314d8bb0ae9 Vladyslav Bondarenko 2021-11-09 07:18:49
fix(clearcasting): Lower priority of debuff type 5e74f3bb6adcf5ee9644a08f839ec6fdf7f59027 Vladyslav Bondarenko 2021-11-07 14:27:37
feat(clearcasting): Remove individual class profiles c7b4d8cf1ad3885e8f0bd84f33082a29330f1f18 Vladyslav Bondarenko 2021-11-05 18:41:10
fix(clearcasting): Correct subset button order when first allocated f3b52da4b40e436e7dc5fc5bda8c4cdbf2c3f4a7 Vladyslav Bondarenko 2021-11-05 13:15:09
fix(clearcasting): Subset button clickable and updates slower ec4768e7b925e52126140788fe866070ee3796c3 Vladyslav Bondarenko 2021-11-05 13:05:13
fix(clearcasting): Improve subset buttons readabilitiy a86de677ad80a16372b76c67d1f8baa457f72faa Vladyslav Bondarenko 2021-11-04 22:23:53
fix(clearcasting)!: Export createSubset correctly e4a0fdb48ac3e3e63d7e763460fb6ca2d95a1f9f Vladyslav Bondarenko 2021-11-04 22:02:35
feat(choir): Render debuff type color for subset cb13282dcb4c42732eccf46821d9ff49e6f8bdff Vladyslav Bondarenko 2021-11-04 20:12:49
fix(choir)!: Subset auras update in combat 081493e96ee7c72371f8770d8bbdc9b108db53ac Vladyslav Bondarenko 2021-11-04 19:02:16
feat(choir)!: Add alternative section creation 1aac7826a961509021679db26b95071edba77ad8 Vladyslav Bondarenko 2021-11-04 17:36:05
feat(clearcasting): Add death knight indicators 5c42814210b355e6de601faaf8e173222995cfa6 Vladyslav Bondarenko 2021-10-30 12:23:43
feat!: Show spell tooltip on indicator mouseover dd69f46f1522f49ce942d8e0b3756c609471be7a Vladyslav Bondarenko 2021-10-13 01:12:08
feat!: Indicator grouping feature df7e0579a51388dba73bc5d92b3d04369bb0e426 Vladyslav Bondarenko 2021-10-13 00:17:28
feat!: Redo the core functionality 085af1bc403b3a9d2d4b99ec460d67889239fabd Vladyslav Bondarenko 2021-07-09 11:15:31
feat: Track additional auras 958e52f9808d64ebb816663d2aa22a4eb39a2068 Vladyslav Bondarenko 2021-01-22 20:28:13
fix!: No longer crash on unknown spell 65061085f0f55b75910c9ed4d8249b1e8e30f9ee Vladyslav Bondarenko 2020-12-11 17:31:16
fix: Render indicators properly on first login bf102cc749cc23d3769a82f10fe93a05afc5277f Vladyslav Bondarenko 2020-12-11 14:25:11
feat: Add Serendipity and Surge of Light indicators ba1d0f32a4b8f4f5d4e2730f0a4e409e23b52974 Vladyslav Bondarenko 2020-12-10 16:19:55
Initial commit 9b3df418e373218125fec12271084afebb11cfc2 Vladyslav Bondarenko 2020-12-04 10:27:24
Commit e93ca1628e362276ef20d10bbc0b4314d8bb0ae9 - fix(clearcasting)!: Reduce redundant updates
When unit aura indicator button is hidden do not update it. Update
artwork only when it changes. Hopefully it improves performance.
Additionally remove all the legacy code.
Author: Vladyslav Bondarenko
Author date (UTC): 2021-11-09 07:18
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2021-11-09 07:18
Parent(s): e49d3a13365dce5ba70aa6e52cc58c080454ff6b
Signer:
Signing key:
Signing status: N
Tree: e42c2f868c74fb131d44358d99a4be3dc05203b2
File Lines added Lines deleted
.luacheckrc 22 99
clearcasting.lua 188 574
clearcasting.toc 1 1
File .luacheckrc changed (mode: 100644) (index a4687fa..370215f)
1 1 -- http://luacheck.readthedocs.io/en/stable/config.html -- http://luacheck.readthedocs.io/en/stable/config.html
2 2 stds.wow = { stds.wow = {
3 globals = {}, -- these globals can be set and accessed.
4 read_globals = {
5 "BuffFrame",
6 "ChatFrame1",
7 "CreateFrame",
8 "DEFAULT_CHAT_FRAME",
9 "DebuffTypeColor",
10 "GameFontNormal",
11 "GameMenuFrame",
12 "GameTooltip",
13 "GetBinding",
14 "GetChatWindowInfo",
15 "GetContainerItemID",
16 "GetContainerItemInfo",
17 "GetContainerNumSlots",
18 "GetInventoryItemCooldown",
19 "GetInventoryItemTexture",
20 "GetInventorySlotInfo",
21 "GetItemCooldown",
22 "GetItemInfo",
23 "GetLocale",
24 "GetLocale",
25 "GetLootSlotInfo",
26 "GetMacroInfo",
27 "GetMoney",
28 "GetNumBindings",
29 "GetNumLootItems",
30 "GetNumPartyMembers",
31 "GetNumRaidMembers",
32 "GetNumTalentTabs",
33 "GetNumTalents",
34 "GetPartyMember",
35 "GetPrimaryTalentTree",
36 "GetRealmName",
37 "GetSpellCooldown",
38 "GetSpellInfo",
39 "GetSpellLink",
40 "GetSpellName",
41 "GetSpellTexture",
42 "GetSubZoneText",
43 "GetTalentInfo",
44 "GetTime",
45 "GetTime",
46 "GetZoneText",
47 "HideUIPanel",
48 "IsSpellInRange",
49 "LootSlotIsCoin",
50 "LootSlotIsItem",
51 "MainMenuBar",
52 "MerchantFrame",
53 "NUM_CHAT_WINDOWS",
54 "NumberFont_OutlineThick_Mono_Small",
55 "NumberFont_Outline_Large",
56 "OpenAllBags",
57 "PaladinPowerBar",
58 "PaladinPowerBarBG",
59 "QuestLogFrame",
60 "SPELL_POWER_HOLY_POWER",
61 "SendChatMessage",
62 "SetCVar",
63 "ShowUIPanel",
64 "StaticPopup_Show",
65 "ToggleAchievementFrame",
66 "ToggleCharacter",
67 "ToggleFrame",
68 "ToggleFriendsFrame",
69 "ToggleHelpFrame",
70 "ToggleLFDParentFrame",
71 "TogglePVPFrame",
72 "ToggleSpellBook",
73 "ToggleTalentFrame",
74 "UIParent",
75 "UIParent",
76 "UnitAffectingCombat",
77 "UnitAura",
78 "UnitAura",
79 "UnitBuff",
80 "UnitClass",
81 "UnitFactionGroup",
82 "UnitIsDead",
83 "UnitIsEnemy",
84 "UnitName",
85 "UnitPlayerControlled",
86 "UnitPower",
87 "UnitSetRole",
88 "date",
89 "difftime",
90 "geterrorhandler",
91 "getglobal",
92 "hooksecurefunc",
93 "strtrim",
94 "time",
95 } -- these globals can only be accessed.
3 globals = {}, --[[ these globals can be set and accessed. ]]--
4 read_globals = {
5 'time',
6 'strtrim',
7 'GameTooltip',
8 'UnitAura',
9 'GetTime',
10 'UnitExists',
11 'date',
12 'DebuffTypeColor',
13 'UIParent',
14 'CreateFrame',
15 'BuffFrame',
16 'GetLocale',
17 'NumberFont_OutlineThick_Mono_Small',
18 } --[[ these globals can only be accessed. ]]--
96 19 } }
97 20
98 21 stds.clearcasting = { stds.clearcasting = {
99 globals = {
100 "ClearcastingDebugFlag",
101 },
102 read_globals = {
103 }
22 globals = {
23 'ClearcastingDebugFlag',
24 },
25 read_globals = {
26 }
104 27 } }
105 28
106 std = "min+wow+clearcasting"
29 std = 'min+wow+clearcasting'
File clearcasting.lua changed (mode: 100644) (index b98b02c..3aa2d46)
1 1 --[[-- --[[--
2 2 Clearcasting addon. Clearcasting addon.
3 3
4 TODO Show tooltip hint on mouseover.
5 TODO Separate profiles for different character classes.
6 TODO Sort debuff by time remaining and importance instead of first any.
4 FIXME Cancel aura on right click.
7 5
8 6 @script clearcasting @script clearcasting
9 7 ]] ]]
10 8
11 9 local function debug(...) local function debug(...)
12 10 if true == ClearcastingDebugFlag then if true == ClearcastingDebugFlag then
13 print('[Clearcasting]: ', ...)
11 print(date('%X'), '[Clearcasting]: ', ...)
14 12 end end
15 13 end end
16 14
17 local function findFirstFilterName(unitDesignation, filterDescriptor, eitherTargetNameOrId)
18 assert (unitDesignation ~= nil)
19 assert ('string' == type(unitDesignation))
20 assert (string.len(unitDesignation) >= 4)
21 assert (string.len(unitDesignation) <= 32)
22
23 assert (filterDescriptor ~= nil)
24 assert ('string' == type(filterDescriptor))
25 assert (string.len(filterDescriptor) >= 4)
26 assert (string.len(filterDescriptor) <= 64)
27
28 assert (eitherTargetNameOrId ~= nil)
29 if 'string' == type(eitherTargetNameOrId) then
30 local targetName = eitherTargetNameOrId
31 assert (targetName ~= nil)
32 assert ('string' == type(targetName))
33 --[[ The shortest spell name in English is "Hex" ]]--
34 assert (string.len(targetName) >= 2)
35 assert (string.len(targetName) <= 256)
36 elseif 'number' == type(eitherTargetNameOrId) then
37 local targetId = eitherTargetNameOrId
38 assert (targetId ~= nil)
39 assert (targetId > 0)
40 else
41 return nil
42 --[[error('illegal argument')]]--
43 end
44
45 local i = 0
46 while (i < 64) do
47 i = i + 1
48 local name, rank, pictureFile, stackQuantity, category,
49 duration, expirationInstance,
50 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, i, filterDescriptor)
51 if not name then
52 break
53 end
54 if eitherTargetNameOrId == name or eitherTargetNameOrId == id then
55 return name, rank, pictureFile, stackQuantity, category,
56 duration, expirationInstance,
57 caster, stealableFlag, consolidateFlag, id
58 end
59 end
60
61 return nil
62 end
63
64 local function findFirstFilterCategory(unitDesignation, filterDescriptor, targetCategory)
65 assert (unitDesignation ~= nil)
66 assert ('string' == type(unitDesignation))
67 assert (string.len(unitDesignation) >= 4)
68 assert (string.len(unitDesignation) <= 32)
69
70 assert (filterDescriptor ~= nil)
71 assert ('string' == type(filterDescriptor))
72 assert (string.len(filterDescriptor) >= 4)
73 assert (string.len(filterDescriptor) <= 64)
74
75 assert (targetCategory ~= nil)
76 if 'string' == type(targetCategory) then
77 assert (string.len(targetCategory) >= 2)
78 assert (string.len(targetCategory) <= 64)
79 else
80 return nil
81 --[[error('illegal argument')]]--
82 end
83
84 local i = 0
85 while (i < 64) do
86 i = i + 1
87 local name, rank, pictureFile, stackQuantity, category,
88 duration, expirationInstance,
89 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, i, filterDescriptor)
90 if not name then
91 break
92 end
93 if targetCategory == category then
94 return name, rank, pictureFile, stackQuantity, category,
95 duration, expirationInstance,
96 caster, stealableFlag, consolidateFlag, id
97 end
98 end
99
100 return nil
101 end
102
103 local function findAnyFilterName(unitDesignation, filterDescriptor, targetSet)
104 assert (unitDesignation ~= nil)
105 assert ('string' == type(unitDesignation))
106 assert (string.len(unitDesignation) >= 4)
107 assert (string.len(unitDesignation) <= 32)
108
109 assert (filterDescriptor ~= nil)
110 assert ('string' == type(filterDescriptor))
111 assert (string.len(filterDescriptor) >= 4)
112 assert (string.len(filterDescriptor) <= 64)
113
114 if 'table' == type(targetSet) then
115 assert (targetSet ~= nil)
116 assert (#targetSet >= 1)
117 else
118 return nil
119 end
120
121 local i = 0
122 debug('#begin findAny')
123 while (i < 64) do
124 i = i + 1
125 local name, rank, pictureFile, stackQuantity, category,
126 duration, expirationInstance,
127 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, i, filterDescriptor)
128 local j = 0
129 while (j < #targetSet) do
130 j = j + 1
131 local eitherTargetNameOrId = targetSet[j]
132 debug('expected: ', eitherTargetNameOrId, ', actual: ', name)
133 if eitherTargetNameOrId == name or eitherTargetNameOrId == id then
134 debug('return ', name)
135 return name, rank, pictureFile, stackQuantity, category,
136 duration, expirationInstance,
137 caster, stealableFlag, consolidateFlag, id
138 end
139 end
140 end
141 debug('-end findAny')
142
143 return nil
144 end
145
146 local function findAnyHarmful(unitDesignation, filterDescriptor, targetCategory)
147 assert (unitDesignation ~= nil)
148 assert ('string' == type(unitDesignation))
149 assert (string.len(unitDesignation) >= 4)
150 assert (string.len(unitDesignation) <= 32)
151
152 assert (filterDescriptor ~= nil)
153 assert ('string' == type(filterDescriptor))
154 assert (string.len(filterDescriptor) >= 4)
155 assert (string.len(filterDescriptor) <= 64)
156
157 assert (targetCategory ~= nil)
158 if 'string' == type(targetCategory) then
159 assert (string.len(targetCategory) >= 2)
160 assert (string.len(targetCategory) <= 64)
161 if 'HARMFUL' ~= targetCategory then
162 return nil
163 end
164 else
165 return nil
166 --[[error('illegal argument')]]--
167 end
168
169
170 local i = 0
171 while (i < 64) do
172 i = i + 1
173 local name, rank, pictureFile, stackQuantity, category,
174 duration, expirationInstance,
175 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, i, filterDescriptor)
176 if name and nil == category then
177 return name, rank, pictureFile, stackQuantity, category,
178 duration, expirationInstance,
179 caster, stealableFlag, consolidateFlag, id
180 end
181 end
182
183 return nil
184 end
185
186 local function applyBackground(f, pictureFile)
187 assert (f ~= nil)
188
189 f:SetBackdrop({bgFile = pictureFile,
190 edgeFile = "Interface\\AddOns\\clearcasting\\share\\2px_tooltip_border",
191 tile = false, tileSize = 24, edgeSize = 8,
192 insets = { left = 2, right = 2, top = 2, bottom = 2 }})
193
194 --f:SetBackdropColor(0.5, 0.5, 0.5, 0.5)
195 end
196
197 15 local function getAuraCategoryColor(categoryName) local function getAuraCategoryColor(categoryName)
198 16 local categoryTable = DebuffTypeColor[categoryName] local categoryTable = DebuffTypeColor[categoryName]
199 17 if not categoryTable then if not categoryTable then
 
... ... local function getAuraCategoryColor(categoryName)
202 20 return categoryTable.r, categoryTable.g, categoryTable.b return categoryTable.r, categoryTable.g, categoryTable.b
203 21 end end
204 22
205 local function applyBorder(f, category)
206 local r, g, b = getAuraCategoryColor(category)
207 local a = 1
208 f:SetBackdropBorderColor(r, g, b, a)
209 end
210
211 23 local function formatIndicatorText(duration, expirationInstance, stackQuantity) local function formatIndicatorText(duration, expirationInstance, stackQuantity)
212 24 local now = GetTime() local now = GetTime()
213 25 duration = duration or 0 duration = duration or 0
 
... ... local function formatIndicatorText(duration, expirationInstance, stackQuantity)
229 41 return t return t
230 42 end end
231 43
232 local function applyDuration(f, duration, expirationInstance, stackQuantity)
44 local function applySubsetButtonDuration(f, duration, expirationInstance, stackQuantity)
233 45 assert (f ~= nil) assert (f ~= nil)
234 46
235 47 local now = GetTime() local now = GetTime()
 
... ... local function applyDuration(f, duration, expirationInstance, stackQuantity)
246 58 f.stackQuantity = stackQuantity f.stackQuantity = stackQuantity
247 59 end end
248 60
249 local function applyIndicatorUpdate(f)
250 applyDuration(f, f.duration, f.expirationInstance, f.stackQuantity)
251 end
252
253 local function indicatorUpdateProcessor(rootFrame, elapsedSecs)
254 assert (rootFrame ~= nil)
255 assert (elapsedSecs ~= nil)
256 assert (type(elapsedSecs) == 'number')
257 elapsedSecs = math.min(math.max(0, elapsedSecs), 999)
258
259 local duration = rootFrame.elapsedSecs
260 if duration == nil then
261 duration = 0.0
262 elseif type(duration) ~= 'number' then
263 duration = 0.0
264 end
265 duration = math.min(math.max(0, duration + elapsedSecs), 999)
266 rootFrame.elapsedSecs = duration
267 if duration >= 0.08 then
268 applyIndicatorUpdate(rootFrame)
269 rootFrame.elapsedSecs = 0.0
270 end
271 end
272
273 local function attemptToApply(f, name, rank, pictureFile, stackQuantity, category,
274 duration, expirationInstance,
275 caster, stealableFlag, consolidateFlag, id)
276 assert (f ~= nil)
277
278 if not name then
279 f.spellId = nil
280 f.spellName = nil
281 f:Hide()
282 f:SetScript('OnUpdate', nil)
283 return
284 end
285
286 assert (name ~= nil)
287 assert ('string' == type(name))
288 assert (string.len(name) >= 2)
289 assert (string.len(name) <= 256)
290 f.spellName = name
291
292 assert (id ~= nil)
293 assert ('number' == type(id))
294 assert (id >= 1)
295 f.spellId = math.floor(id)
296
297 applyBackground(f, pictureFile)
298 applyBorder(f, category, caster)
299 applyDuration(f, duration, expirationInstance, stackQuantity)
300
301 f:Show()
302 f:SetScript('OnUpdate', indicatorUpdateProcessor)
303 end
304
305 local function indicatorEventProcessor(f)
306 assert (f ~= nil)
307
308 local unitDesignation = f.unit or 'player'
309 assert (unitDesignation ~= nil)
310 assert ('string' == type(unitDesignation))
311 assert (string.len(unitDesignation) >= 4)
312 assert (string.len(unitDesignation) <= 32)
313
314 local filterDescriptor = f.filter or 'HELPFUL'
315 assert (filterDescriptor ~= nil)
316 assert ('string' == type(filterDescriptor))
317 assert (string.len(filterDescriptor) >= 4)
318 assert (string.len(filterDescriptor) <= 64)
319
320 local target = f.target
321 assert (target ~= nil)
322
323 local name, rank, pictureFile, stackQuantity, category,
324 duration, expirationInstance,
325 caster, stealableFlag, consolidateFlag, id
326
327 name, rank, pictureFile, stackQuantity, category,
328 duration, expirationInstance,
329 caster, stealableFlag, consolidateFlag, id = findFirstFilterName(unitDesignation, filterDescriptor, target)
330
331 if not name then
332 name, rank, pictureFile, stackQuantity, category,
333 duration, expirationInstance,
334 caster, stealableFlag, consolidateFlag, id = findFirstFilterCategory(unitDesignation, filterDescriptor, target)
335 end
336
337 if not name then
338 name, rank, pictureFile, stackQuantity, category,
339 duration, expirationInstance,
340 caster, stealableFlag, consolidateFlag, id = findAnyFilterName(unitDesignation, filterDescriptor, target)
341 end
342
343 if not name then
344 name, rank, pictureFile, stackQuantity, category,
345 duration, expirationInstance,
346 caster, stealableFlag, consolidateFlag, id = findAnyHarmful(unitDesignation, filterDescriptor, target)
347 end
348
349 attemptToApply(f, name, rank, pictureFile, stackQuantity, category,
350 duration, expirationInstance,
351 caster, stealableFlag, consolidateFlag, id)
352 end
353
354 local function tooltipOverlayEventProcessor(tooltipOverlay)
355 assert (tooltipOverlay ~= nil)
356
357 GameTooltip:SetOwner(tooltipOverlay, 'ANCHOR_BOTTOMRIGHT')
358
359 local button = tooltipOverlay:GetParent()
360 assert (button ~= nil)
361 GameTooltip:SetText('spell description could not be found')
362 if button.unit and button.index and button.filter then
363 GameTooltip:SetUnitAura(button.unit, button.index, button.filter);
364 elseif button.spellId then
365 local t = GetSpellLink(button.spellId)
366 GameTooltip:SetHyperlink(t)
367 end
368 end
369
370 local function createTooltipOverlay(indicator)
371 assert (indicator ~= nil)
372
373 local p = indicator:GetName() or 'Clearcasting'
374 local n = p .. 'TooltipOverlay'
375 local o = CreateFrame('FRAME', n, indicator)
376 o:SetAllPoints()
377
378 --[[ It is critical to call EnableMouse method on a tooltip overlay frame ]]--
379 o:EnableMouse(true)
380
381 o:SetScript('OnEnter', tooltipOverlayEventProcessor)
382 o:SetScript('OnLeave', function() GameTooltip:Hide(); end)
383
384 return o
385 end
386
387 local function createIndicator(parentFrame, target, unitDesignation, filterDescriptor)
388 assert (parentFrame ~= nil)
389
390 assert (target ~= nil)
391
392 local maxColumnQuantity = 4
393 local maxRowQuantity = 8
394
395 local p = parentFrame:GetName() or 'Clearcasting'
396 local siblingSet = {parentFrame:GetChildren()}
397 local siblingQuantity = #siblingSet or 0
398 assert (siblingQuantity >= 0)
399 local maxIndicatorQuantity = math.min(64, maxRowQuantity * maxColumnQuantity)
400 assert (siblingQuantity < maxIndicatorQuantity, 'too many indicators ' .. tostring(siblingQuantity))
401 local i = siblingQuantity + 1
402 local n = p .. 'SpellActivationOverlay' .. tostring(i)
403
404 local f = CreateFrame('FRAME', n, parentFrame)
405 local size = 24
406 local padding = 4
407 local paddedSize = size + padding
408 local y = math.floor(siblingQuantity / maxColumnQuantity)
409 local x = siblingQuantity - (maxColumnQuantity * y)
410 f:SetSize(size, size)
411 f:SetPoint('BOTTOMLEFT', x * paddedSize, y * (paddedSize + size * 2 / 3))
412
413 local t = f:CreateFontString(n .. 'Text', 'OVERLAY')
414 local fontObject = NumberFont_OutlineThick_Mono_Small
415 assert (fontObject ~= nil)
416 t:SetFontObject(fontObject)
417 t:SetPoint('TOPRIGHT', f, 'TOPRIGHT', 6, -f:GetHeight())
418 t:SetPoint('TOPLEFT', f, 'TOPLEFT', -6, -f:GetHeight())
419 t:SetPoint('BOTTOMLEFT', f, 'BOTTOMLEFT', 0, -f:GetHeight() * 2 / 3)
420 t:SetPoint('BOTTOMRIGHT', f, 'BOTTOMRIGHT', 0, -f:GetHeight() * 2 / 3)
421 t:SetText('?')
422 f.text = t
423
424 local a = f:CreateTexture(n .. 'Background', 'ARTWORK')
425 a:SetAllPoints()
426 f.background = a
427
428 f.unit = unitDesignation
429 f.filter = string.upper(filterDescriptor or 'HELPFUL')
430 f.target = target
431 f.spellId = nil
432 f.spellName = nil
433
434 f.tooltipOverlay = createTooltipOverlay(f)
435
436 f:SetScript('OnEvent', indicatorEventProcessor)
437 f:SetScript('OnUpdate', indicatorUpdateProcessor)
438 f:RegisterEvent('PLAYER_ENTERING_WORLD')
439 f:RegisterEvent('PLAYER_FOCUS_CHANGED')
440 f:RegisterEvent('PLAYER_TARGET_CHANGED')
441 f:RegisterEvent('UNIT_AURA')
442
443 return f
444 end
445
446 local function sectionEventProcessor(section)
447 assert (section ~= nil)
448
449 local t = {section:GetChildren()}
450 if #t < 1 then
451 return
452 end
453
454 local width = section:GetWidth()
455
456 local rowHeight = 44
457 local columnWidth = 28
458 local columnQuantitiy = math.floor(width / columnWidth)
459
460 local x = 0
461 local y = 0
462
463
464 local i = 0
465 local j = math.min(math.max(0, #t), 64)
466 while (i < j) do
467 i = i + 1
468 local f = t[i]
469 assert (f ~= nil)
470 indicatorEventProcessor(f)
471 if 1 == f:IsShown() then
472 f:SetPoint('BOTTOMLEFT', x * columnWidth, y * rowHeight)
473 x = x + 1
474 if x >= columnQuantitiy then
475 x = 0
476 y = y + 1
477 end
478 end
479 end
480 end
481
482 local function createSection(name, parent, width, height)
483 assert (name ~= nil)
484 name = strtrim(name)
485 assert ('string' == type(name))
486 assert (string.len(name) >= 4)
487 assert (string.len(name) <= 128)
488
489 assert (parent ~= nil)
490
491 assert (width ~= nil)
492 assert ('number' == type(width))
493 assert (width >= 12)
494 assert (width <= 1024)
495
496 assert (height ~= nil)
497 assert ('number' == type(height))
498 assert (height >= 12)
499 assert (height <= 1024)
500
501 local section = CreateFrame('FRAME', name, parent)
502 section:SetSize(width, height)
503
504 local b = section:CreateTexture(name .. 'Background', 'BACKGROUND')
505 b:SetAllPoints()
506 b:SetTexture(0.02, 0.02, 0.02, 0.12)
507 section.background = b
508
509 section:SetScript('OnEvent', sectionEventProcessor)
510 section:RegisterEvent('PLAYER_FOCUS_CHANGED')
511 section:RegisterEvent('PLAYER_TARGET_CHANGED')
512 section:RegisterEvent('UNIT_AURA')
513 section:RegisterEvent('UNIT_DEATH')
514
515 assert (section ~= nil)
516 return section
517 end
518
519 61 local function unpackAuraTable(auraTable) local function unpackAuraTable(auraTable)
520 62 assert(auraTable ~= nil) assert(auraTable ~= nil)
521 63 assert("table" == type(auraTable)) assert("table" == type(auraTable))
 
... ... local function unpackAuraTable(auraTable)
531 73 local stealableFlag = auraTable[9] local stealableFlag = auraTable[9]
532 74 local consolidationFlag = auraTable[10] local consolidationFlag = auraTable[10]
533 75 local spellId = auraTable[11] local spellId = auraTable[11]
76 --[[ FIXME There is no 12th return value in UnitAura function.
77 -- This value is added by the script in requestUnitAuraTable function.
78 -- The purpose of the value is to enable game tooltip.
79 -- This is a source of potential compatibility issues,
80 -- especially when porting this add-on to newer client versions.
81 -- For now it works.]]--
82 local unitAuraIndex = auraTable[12]
534 83
535 84 return spellName, spellRank, pictureFile, stackQuantity, category, durationSec, expirationInstance, return spellName, spellRank, pictureFile, stackQuantity, category, durationSec, expirationInstance,
536 casterUnitDesignation, stealableFlag, consolidationFlag, spellId
85 casterUnitDesignation, stealableFlag, consolidationFlag, spellId, unitAuraIndex
537 86 end end
538 87
539 88 local function getAuraWeight( local function getAuraWeight(
 
... ... local function getAuraWeight(
550 99 spellId) spellId)
551 100
552 101 local spellName local spellName
553 if "table" == type(eitherAuraTableOrSpellName) then
102 if 'table' == type(eitherAuraTableOrSpellName) then
554 103 local auraTable = eitherAuraTableOrSpellName local auraTable = eitherAuraTableOrSpellName
555 104
556 105 spellName, spellName,
 
... ... local function getAuraWeight(
564 113 stealableFlag, stealableFlag,
565 114 consolidationFlag, consolidationFlag,
566 115 spellId = unpackAuraTable(auraTable) spellId = unpackAuraTable(auraTable)
567 elseif "string" == type(eitherAuraTableOrSpellName) then
116 elseif 'string' == type(eitherAuraTableOrSpellName) then
568 117 spellName = eitherAuraTableOrSpellName spellName = eitherAuraTableOrSpellName
569 118 else else
570 error("invalid argument")
119 debug('invalid argument', eitherAuraTableOrSpellName)
120 return
571 121 end end
572 122
123 assert (spellName ~= nil)
124 assert (nil == spellRank or 'string' == type(spellRank))
125 assert (pictureFile ~= nil)
126 assert (stackQuantity ~= nil)
127 assert (nil == stealableFlag or 1 == stealableFlag or 0 == stealableFlag)
128 assert (nil == consolidationFlag or 1 == consolidationFlag or 0 == consolidationFlag)
129 assert (spellId ~= nil)
130
573 131 local casterWeight = 0 local casterWeight = 0
574 if "player" == casterUnitDesignation then
132 if 'player' == casterUnitDesignation then
575 133 casterWeight = 1 casterWeight = 1
576 134 end end
577 135
578 local categoryMap = {["Magic"] = 4, ["Poison"] = 3, ["Disease"] = 2, ["Curse"] = 1}
136 local categoryMap = {['Magic'] = 4, ['Poison'] = 3, ['Disease'] = 2, ['Curse'] = 1}
579 137 local categoryWeight = 0 local categoryWeight = 0
580 138 if category then if category then
581 139 categoryWeight = categoryMap[category] or 0 categoryWeight = categoryMap[category] or 0
 
... ... local function requestUnitAuraTable(unitDesignation, filterDescriptor)
618 176 assert (string.len(filterDescriptor) >= 4) assert (string.len(filterDescriptor) >= 4)
619 177 assert (string.len(filterDescriptor) <= 128) assert (string.len(filterDescriptor) <= 128)
620 178
621 local j = 0
179 local index = 0
622 180 local q = 0 local q = 0
623 181 local e = {} local e = {}
624 while (j < 144) do
625 j = j + 1
182 local maxAuraQuantityPerUnit = 144
183 while (index < maxAuraQuantityPerUnit) do
184 index = index + 1
626 185 local name, rank, pictureFile, stackQuantity, category, local name, rank, pictureFile, stackQuantity, category,
627 186 duration, expirationInstance, duration, expirationInstance,
628 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, j, filterDescriptor)
187 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, index, filterDescriptor)
629 188 if name then if name then
630 189 local auraTable = {name, rank, pictureFile, stackQuantity, category, local auraTable = {name, rank, pictureFile, stackQuantity, category,
631 190 duration, expirationInstance, duration, expirationInstance,
632 caster, stealableFlag, consolidateFlag, id, j}
191 caster, stealableFlag, consolidateFlag, id, index}
633 192 q = q + 1 q = q + 1
634 193 e[q] = auraTable e[q] = auraTable
635 194 else else
 
... ... local function requestUnitAuraTable(unitDesignation, filterDescriptor)
641 200 return e return e
642 201 end end
643 202
644 local function subsetEventProcessor(subsetFrame)
203 local function subsetButtonUpdateProcessor(subsetButton, elapsedSec)
204 local updateDur = subsetButton.updateDur or 0
205 updateDur = updateDur - elapsedSec
206 if updateDur > 0 then
207 subsetButton.updateDur = updateDur
208 return
209 end
210 subsetButton.updateDur = 0.5
211
212 local unit = subsetButton.unit
213 if not unit then
214 return
215 end
216
217 local spell = subsetButton.spell
218 if not spell then
219 return
220 end
221
222 local filter = subsetButton.filter
223
224 local n, _, _, stackQuantity, _, durationSec, expirationInstance = UnitAura(unit, spell, nil, filter)
225 if not n then
226 return
227 end
228
229 applySubsetButtonDuration(subsetButton, durationSec, expirationInstance, stackQuantity)
230 end
231
232 local function applySubsetButtonArtwork(subsetButton, pictureFile)
233 assert (subsetButton ~= nil)
234 assert (pictureFile ~= nil)
235 assert ('string' == type(pictureFile))
236
237 local artwork = subsetButton.artwork
238 assert (artwork ~= nil)
239 artwork:SetTexture(pictureFile)
240 end
241
242 local function applySubsetButtonBackground(subsetButton, category)
243 assert (subsetButton ~= nil)
244 if nil == category then
245 category = 'none'
246 end
247 assert (category ~= nil)
248 assert ('string' == type(category))
249
250 local red, green, blue = getAuraCategoryColor(category)
251 local background = subsetButton.background
252 assert (background ~= nil)
253 background:SetTexture(red, green, blue)
254 end
255
256 local function applySubsetButtonTextColor(subsetButton, casterUnitDesignation)
257 assert (subsetButton ~= nil)
258 assert (nil == casterUnitDesignation or
259 (casterUnitDesignation ~= nil and 'string' == type(casterUnitDesignation)))
260
261 local label = subsetButton.text
262 assert (label ~= nil)
263 if 'player' == casterUnitDesignation then
264 label:SetTextColor(1, 0.8, 0)
265 else
266 label:SetTextColor(1, 1, 1)
267 end
268 end
269
270 local function unapplySubsetButtonSpell(subsetButton, unitDesignation, filterDescriptor)
271 assert (subsetButton ~= nil)
272 assert (unitDesignation ~= nil)
273 assert (filterDescriptor ~= nil)
274
275 applySubsetButtonArtwork(subsetButton, "Interface\\Icons\\spell_nature_wispsplode")
276
277 subsetButton.unit = unitDesignation
278 subsetButton.filter = filterDescriptor
279 subsetButton.spell = nil
280 subsetButton.index = nil
281
282 subsetButton:SetScript('OnUpdate', nil)
283 subsetButton:Hide()
284 end
285
286 local function applySubsetButtonSpell(subsetButton, unitDesignation, filterDescriptor,
287 spellName, spellRank, pictureFile, stackQuantity, category,
288 durationSec, expirationInstance,
289 casterUnitDesignation, stealableFlag, consolidationFlag, spellId, unitAuraIndex)
290 assert (subsetButton ~= nil)
291 assert (unitDesignation ~= nil)
292 assert (filterDescriptor ~= nil)
293 assert (spellName ~= nil)
294 assert (unitAuraIndex ~= nil)
295 assert (durationSec ~= nil)
296 assert (expirationInstance ~= nil)
297
298 assert (nil == spellRank or 'string' == type(spellRank))
299 assert (nil == stealableFlag or 1 == stealableFlag or 0 == stealableFlag)
300 assert (nil == consolidationFlag or 1 == consolidationFlag or 0 == consolidationFlag)
301 assert (spellId ~= nil)
302
303 local oldSpell = subsetButton.spell
304
305 if spellName == oldSpell then
306 applySubsetButtonDuration(subsetButton, durationSec, expirationInstance, stackQuantity)
307 return
308 end
309
310 applySubsetButtonDuration(subsetButton, durationSec, expirationInstance, stackQuantity)
311 applySubsetButtonArtwork(subsetButton, pictureFile)
312 applySubsetButtonBackground(subsetButton, category)
313 applySubsetButtonTextColor(subsetButton, casterUnitDesignation)
314
315 subsetButton.unit = unitDesignation
316 subsetButton.spell = spellName
317 subsetButton.index = unitAuraIndex
318 subsetButton.filter = filterDescriptor
319
320 subsetButton:SetScript('OnUpdate', subsetButtonUpdateProcessor)
321 subsetButton:Show()
322 end
323
324
325 local function subsetEventProcessorFactory(t)
326
327 return function(subsetFrame)
645 328 assert (subsetFrame ~= nil) assert (subsetFrame ~= nil)
646 329
647 local unitDesignation = subsetFrame.unit or 'player'
330 local unitDesignation = subsetFrame.unit
648 331 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
649 332 assert ('string' == type(unitDesignation)) assert ('string' == type(unitDesignation))
650 333 unitDesignation = strtrim(unitDesignation) unitDesignation = strtrim(unitDesignation)
651 334 assert (string.len(unitDesignation) >= 4) assert (string.len(unitDesignation) >= 4)
652 335 assert (string.len(unitDesignation) <= 64) assert (string.len(unitDesignation) <= 64)
653 336
337 if UnitExists(unitDesignation) then
338 subsetFrame:Show()
339 else
340 subsetFrame:Hide()
341 return
342 end
343
654 344 local filterDescriptor = subsetFrame.filter or 'HELPFUL' local filterDescriptor = subsetFrame.filter or 'HELPFUL'
655 345 assert (filterDescriptor ~= nil) assert (filterDescriptor ~= nil)
656 346 assert ('string' == type(filterDescriptor)) assert ('string' == type(filterDescriptor))
 
... ... local function subsetEventProcessor(subsetFrame)
661 351 local e = requestUnitAuraTable(unitDesignation, filterDescriptor) local e = requestUnitAuraTable(unitDesignation, filterDescriptor)
662 352
663 353 local i = 0 local i = 0
664 local t = {subsetFrame:GetChildren()}
354 --[[local t = {subsetFrame:GetChildren()}]]--
665 355 while (i < math.min(#e, #t)) do while (i < math.min(#e, #t)) do
666 356 i = i + 1 i = i + 1
357
667 358 local b = t[i] local b = t[i]
668 359 assert (b ~= nil) assert (b ~= nil)
669 360
670 361 local auraTable = e[i] local auraTable = e[i]
671 362 assert (auraTable ~= nil) assert (auraTable ~= nil)
672 363
673 local pictureFile = auraTable[3]
674 assert (pictureFile ~= nil)
675 local artwork = b.artwork
676 assert (artwork ~= nil, b:GetName() .. ' requires artwork field')
677 artwork:SetTexture(pictureFile)
678
679 local stackQuantity = auraTable[4]
680 local durationSec = auraTable[6]
681 local expirationInstance = auraTable[7]
682 applyDuration(b, durationSec, expirationInstance, stackQuantity)
683
684 local background = b.background
685 assert (background ~= nil)
686 local category = auraTable[5] or 'none'
687 assert (category ~= nil)
688 local red, green, blue = getAuraCategoryColor(category)
689 background:SetTexture(red, green, blue)
690
691 local casterDesignation = auraTable[8]
692 if 'player' == casterDesignation then
693 local label = b.text
694 label:SetTextColor(1, 0.8, 0)
695 else
696 local label = b.text
697 label:SetTextColor(1, 1, 1)
698 end
699
700 local spellName = auraTable[1]
701 assert (spellName ~= nil)
702 b.spell = spellName
703
704 local index = auraTable[12]
705 assert (index ~= nil)
706 b.index = index
707
708 b.filter = filterDescriptor
709 b.unit = unitDesignation
710
711 b:Show()
364 applySubsetButtonSpell(b, unitDesignation, filterDescriptor, unpackAuraTable(auraTable))
712 365 end end
713 366
714 367 local k = #e local k = #e
715 368 while (k < #t) do while (k < #t) do
716 369 k = k + 1 k = k + 1
370
717 371 local b = t[k] local b = t[k]
718 372 assert (b ~= nil) assert (b ~= nil)
719 373
720 local artwork = b.artwork
721 assert (artwork ~= nil, b:GetName() .. ' requires artwork field')
722 artwork:SetTexture("Interface\\Icons\\spell_nature_wispsplode")
723
724 b.spell = nil
725 b.index = nil
726
727 b.filter = filterDescriptor
728 b.unit = unitDesignation
729
730 b:Hide()
374 unapplySubsetButtonSpell(b, unitDesignation, filterDescriptor)
731 375 end end
732 376 end end
733 377
734 local function subsetButtonUpdateProcessor(subsetButton, elapsedSec)
735 local updateDur = subsetButton.updateDur or 0
736 updateDur = updateDur - elapsedSec
737 if updateDur > 0 then
738 subsetButton.updateDur = updateDur
739 return
740 end
741 subsetButton.updateDur = 0.5
742
743 local unit = subsetButton.unit
744 if not unit then
745 return
746 end
747
748 local spell = subsetButton.spell
749 if not spell then
750 return
751 end
752
753 local filter = subsetButton.filter
754
755 local n, _, _, stackQuantity, _, durationSec, expirationInstance = UnitAura(unit, spell, nil, filter)
756 if not n then
757 return
758 end
759
760 applyDuration(subsetButton, durationSec, expirationInstance, stackQuantity)
761 378 end end
762 379
763 380 local function createSubsetButtonArtwork(subsetButton) local function createSubsetButtonArtwork(subsetButton)
 
... ... local function createSubsetButtonArtwork(subsetButton)
776 393 artwork:SetPoint('BOTTOMRIGHT', padding, marginBottom) artwork:SetPoint('BOTTOMRIGHT', padding, marginBottom)
777 394 artwork:SetTexture("Interface\\Icons\\spell_nature_wispsplode") artwork:SetTexture("Interface\\Icons\\spell_nature_wispsplode")
778 395 local texMargin = 1.0 / 8.0 local texMargin = 1.0 / 8.0
779 --artwork:SetTexCoord(0, 0.5, 0.5, 1) -- bottom left
780 --artwork:SetTexCoord(0.5, 0.0, 0.5, 1) -- bottom right
781 --artwork:SetTexCoord(0.1, 0.9, 0.1, 0.9) -- bottom right
782 396 artwork:SetTexCoord(texMargin, 1 - texMargin, texMargin, 1 - texMargin) artwork:SetTexCoord(texMargin, 1 - texMargin, texMargin, 1 - texMargin)
783 397
784 398 return artwork return artwork
 
... ... local function createSubsetButtonBackground(subsetButton)
802 416 return background return background
803 417 end end
804 418
805
806 419 local function createSubsetButtonText(subsetButton) local function createSubsetButtonText(subsetButton)
807 420 assert (subsetButton ~= nil) assert (subsetButton ~= nil)
808 421
 
... ... local function createSubsetButtonText(subsetButton)
819 432 return t return t
820 433 end end
821 434
435 local function applyTooltip(subsetButton)
436 assert (subsetButton ~= nil)
437
438 local tooltip = GameTooltip
439 tooltip:SetOwner(subsetButton, 'ANCHOR_BOTTOMRIGHT')
440
441 tooltip:SetText('spell description could not be found')
442 tooltip:SetUnitAura(subsetButton.unit, subsetButton.index, subsetButton.filter);
443 end
444
445 local function unapplyTooltip()
446 GameTooltip:Hide()
447 end
448
822 449 local function createSubsetButton(subsetFrame, buttonDesignation, unitDesignation, spellName, filterDescriptor, local function createSubsetButton(subsetFrame, buttonDesignation, unitDesignation, spellName, filterDescriptor,
823 450 buttonWidth, buttonHeight) buttonWidth, buttonHeight)
824 451 assert (buttonDesignation ~= nil) assert (buttonDesignation ~= nil)
 
... ... local function createSubsetButton(subsetFrame, buttonDesignation, unitDesignatio
882 509 b.index = nil b.index = nil
883 510
884 511 b:EnableMouse(true) b:EnableMouse(true)
885 b:SetScript('OnEnter', function(button)
886 assert (button ~= nil)
887
888 GameTooltip:SetOwner(button, 'ANCHOR_BOTTOMRIGHT')
889
890 GameTooltip:SetText('spell description could not be found')
891 GameTooltip:SetUnitAura(button.unit, button.index, button.filter);
892 end)
893 b:SetScript('OnLeave', function() GameTooltip:Hide(); end)
512 b:SetScript('OnEnter', applyTooltip)
513 b:SetScript('OnLeave', unapplyTooltip)
894 514
895 515 b:SetScript('OnUpdate', subsetButtonUpdateProcessor) b:SetScript('OnUpdate', subsetButtonUpdateProcessor)
896 516
 
... ... local function createSubset(parentFrame, frameDesignation, unitDesignation, filt
965 585 local nameFormat = subsetFrame:GetName() .. 'Button%03d' local nameFormat = subsetFrame:GetName() .. 'Button%03d'
966 586 local k = 0 local k = 0
967 587 local i = 0 local i = 0
588 local t = {}
968 589 while (i < rowQuantity) do while (i < rowQuantity) do
969 590 i = i + 1 i = i + 1
970 591 local j = 0 local j = 0
 
... ... local function createSubset(parentFrame, frameDesignation, unitDesignation, filt
976 597 local b = createSubsetButton(subsetFrame, n, unitDesignation, emptySpellName, filterDescriptor, local b = createSubsetButton(subsetFrame, n, unitDesignation, emptySpellName, filterDescriptor,
977 598 buttonWidth, buttonHeight) buttonWidth, buttonHeight)
978 599 b:SetPoint('BOTTOMLEFT', buttonWidth * (j - 1) + padding * j, buttonHeight * (i - 1) + padding * i) b:SetPoint('BOTTOMLEFT', buttonWidth * (j - 1) + padding * j, buttonHeight * (i - 1) + padding * i)
600 t[k] = b
979 601 end end
980 602 end end
981 603
 
... ... local function createSubset(parentFrame, frameDesignation, unitDesignation, filt
998 620 subsetFrame:RegisterEvent('UNIT_AURA') subsetFrame:RegisterEvent('UNIT_AURA')
999 621 subsetFrame:RegisterEvent('UNIT_HEALTH') subsetFrame:RegisterEvent('UNIT_HEALTH')
1000 622 subsetFrame:RegisterEvent('UPDATE_BATTLEFIELD_SCORE') subsetFrame:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
623 local subsetEventProcessor = subsetEventProcessorFactory(t)
1001 624 subsetFrame:SetScript('OnEvent', subsetEventProcessor) subsetFrame:SetScript('OnEvent', subsetEventProcessor)
1002 625 subsetEventProcessor(subsetFrame) subsetEventProcessor(subsetFrame)
1003 626
 
... ... local function init(rootFrame)
1036 659 assert (t ~= nil) assert (t ~= nil)
1037 660 assert (#t >= 1) assert (#t >= 1)
1038 661
1039
1040 --rootFrame:SetScript('OnEvent', eventProcessor)
1041
1042 rootFrame.elapsedSecs = 0.0
1043 --rootFrame:SetScript('OnUpdate', updateProcessor)
1044
1045 --rootFrame:RegisterEvent('UNIT_AURA')
1046 --rootFrame:RegisterEvent('SPELLS_CHANGED')
1047 rootFrame.createIndicator = createIndicator
1048 662 rootFrame.createSubset = function(...) rootFrame.createSubset = function(...)
1049 663 return createSubset(...) return createSubset(...)
1050 664 end end
File clearcasting.toc changed (mode: 100644) (index f9e44cd..0b818ed)
1 1 ##Interface: 30300 ##Interface: 30300
2 2 ##Notes: Add separate indicators for specific buffs. ##Notes: Add separate indicators for specific buffs.
3 3 ##Title: Clearcasting ##Title: Clearcasting
4 ##Version: 0.0.3-SNAPSHOT
4 ##Version: 0.1.0-SNAPSHOT
5 5 ##SavedVariablesPerCharacter: ClearcastingDebugFlag ##SavedVariablesPerCharacter: ClearcastingDebugFlag
6 6 clearcasting.xml clearcasting.xml
7 7 clearcasting.lua clearcasting.lua
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/wowaddons

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

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

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