vrtc / chorus (public) (License: CC0) (since 2023-08-12) (hash sha1)
World of Warcraft add-on stub. The overall goal is to create a specialized raid frame.
List of commits:
Subject Hash Author Date (UTC)
feat: upgrade cast bar f050d4444480983bfe7475bdafbfeb2c291c3472 Vladyslav Bondarenko 2024-06-18 18:11:20
fix!: only update cast bar when it's visible 7c2081b92a1ec323911520a977c00f68078bae18 Vladyslav Bondarenko 2024-06-18 17:09:56
fix: cast bar always reports ongoing cast d69d60f524a7871053daa2d914badc59e1189e15 Vladyslav Bondarenko 2024-06-18 15:37:59
feat: weigh priest effects 99341dd5c6cb03861fdfa802a1cde520887b29b5 Vladyslav Bondarenko 2024-06-18 14:01:45
feat: make cast bar prettier visually 8ef25268de9a70ce741f0e9b4c105d4969afe7ff Vladyslav Bondarenko 2024-06-18 14:01:03
doc: show todo and fixme annotations in the docs a880f593e674df82682c295375319b7a4cf13b20 Vladyslav Bondarenko 2024-06-18 11:58:53
fix: make raid frames protected c94bb2d05a404e2a1e802cf03daa6325cf38827b Vladyslav Bondarenko 2024-06-18 11:57:42
feat: cast bar shows instants and failures c08b3be1c0d8d63b298a71ddf7af734703ffcb42 Vladyslav Bondarenko 2024-06-16 20:42:42
feat: spell channel bar moves in reverse 64e0512304854e28df8ad94363a048fd997dc502 Vladyslav Bondarenko 2024-06-16 19:19:53
doc: valid references to github 6aba3821b71216c07d8f02b24f75006e556dca61 Vladyslav Bondarenko 2024-06-16 17:28:59
build: allow either GNUmake or pdpmake to be used 4f3cd088299dc09f5207caeeddedfcca461d439e Vladyslav Bondarenko 2024-06-16 15:52:32
doc: add minimal documentation 125835dbbef41528e4f545642f62e519e7e8f9fc Vladyslav Bondarenko 2024-06-16 15:34:58
doc: descript aura button API 7d110e3d397a335d2dc0ffbf699acc487730d0d8 Vladyslav Bondarenko 2024-06-16 13:24:52
feat: add ldoc custom see tag handler d28dc9809939a99694d87cf1d0654eb0e2fcd314 Vladyslav Bondarenko 2024-06-16 13:23:24
feat: add optional xmlstarlet support b2db46335cb4aa461c45915d235c6d8d340a3dea Vladyslav Bondarenko 2024-06-16 09:57:27
fix: indent Chorus.xml with tabs and not spaces bf4091ab69c75aafa9256b0503b5addd316e214f Vladyslav Bondarenko 2024-06-16 09:39:28
fix: format generated XML with tabs and not spaces d0018a28b184add22ea69f86c8b334a72310004e Vladyslav Bondarenko 2024-06-16 09:26:54
doc: raid frame profile generator script 22deb6f002463583ef5fd5c1acfcfb5777329aa8 Vladyslav Bondarenko 2024-06-15 21:55:01
feat: add fallback offline label a621e23fe7dc5668f0525dcc2bc60d916c2a1c14 Vladyslav Bondarenko 2024-06-15 19:08:05
fix: add test mocks for background and backdrop 2b22abbcb266bb02bded0633301e1669ed1b040d Vladyslav Bondarenko 2024-06-15 19:07:40
Commit f050d4444480983bfe7475bdafbfeb2c291c3472 - feat: upgrade cast bar
Apply heuristics to guess most appropriate artwork for spell cast or
channeling. Add borders. Color code interrupts and successful casts.

The code is unmaintainable. There are errors currently present. Prepare
to redo the whole module.
Author: Vladyslav Bondarenko
Author date (UTC): 2024-06-18 18:11
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2024-06-18 21:40
Parent(s): 7c2081b92a1ec323911520a977c00f68078bae18
Signer:
Signing key: EFF9624877D25D02
Signing status: E
Tree: 64cbcb9ff37b76b0f8e40d8bd2d56fc8fee3e500
File Lines added Lines deleted
conf/lua-check/luacheckrc.lua 2 0
src/ChorusCastFrameTemplate.lua 313 133
File conf/lua-check/luacheckrc.lua changed (mode: 100644) (index fc88691..66e0380)
... ... stds.wrath = {
24 24 'GetPlayerInfoByGUID', 'GetPlayerInfoByGUID',
25 25 'GetQuestDifficultyColor', 'GetQuestDifficultyColor',
26 26 'GetRaidTargetIndex', 'GetRaidTargetIndex',
27 'GetSpellCooldown',
27 28 'GetSpellInfo', 'GetSpellInfo',
28 29 'GetSpellName', 'GetSpellName',
29 30 'GetSpellTexture', 'GetSpellTexture',
 
... ... stds.wrath = {
74 75 'tContains', 'tContains',
75 76 'table', 'table',
76 77 'time', 'time',
78 'tostring',
77 79 'type', 'type',
78 80 'unpack', 'unpack',
79 81 }, },
File src/ChorusCastFrameTemplate.lua changed (mode: 100644) (index 9374486..dbe12dc)
... ... local UnitIsUnit = Chorus.test.UnitIsUnit or UnitIsUnit
13 13
14 14 local SecureButton_GetUnit = Chorus.test.SecureButton_GetUnit or SecureButton_GetUnit local SecureButton_GetUnit = Chorus.test.SecureButton_GetUnit or SecureButton_GetUnit
15 15
16 local function getDurationRemainingSec(nowMili, startInstanceMili, endInstanceMili)
17 assert(nowMili ~= nil)
18 assert('number' == type(nowMili))
19
20 assert(startInstanceMili ~= nil)
21 assert('number' == type(startInstanceMili))
22
23 assert(endInstanceMili ~= nil)
24 assert('number' == type(endInstanceMili))
25
26 local durationRemainingSec = (endInstanceMili - nowMili) / 1000
27
28 assert(durationRemainingSec ~= nil)
29 assert('number' == type(durationRemainingSec))
30 assert(durationRemainingSec >= 0 or durationRemainingSec <= 0)
31
32 return durationRemainingSec
33 end
34
35 local function castFrameEventIsRelevant(self, eventCategory, ...)
36 assert(self ~= nil)
37 assert(eventCategory ~= nil)
38
39 local u = SecureButton_GetUnit(self)
40 local eu = select(1, ...)
41 if eu and u then
42 return UnitIsUnit(u, eu)
43 else
44 return true
45 end
46 end
47
48 16 local function applyArtworkSpellIcon(self, pictureFile) local function applyArtworkSpellIcon(self, pictureFile)
49 17 assert(self ~= nil) assert(self ~= nil)
50 18
 
... ... local function applyDurationRemainingSec(self, durationRemainingSec)
125 93 label2:SetText(t) label2:SetText(t)
126 94 end end
127 95
128 local function applyBounds(self, startInstance, endInstance)
129 assert(self ~= nil)
96 local function unitFilter(castFrame, targetUnit)
97 assert(castFrame ~= nil)
98
99 if not targetUnit then
100 return false
101 end
102
103 local u = SecureButton_GetUnit(castFrame)
104 if u then
105 if UnitIsUnit(u, targetUnit) then
106 return true
107 end
108 end
109 return false
110 end
130 111
131 assert(startInstance ~= nil)
132 assert('number' == type(startInstance))
133 startInstance = math.abs(startInstance)
134 assert(startInstance >= 0)
112 local function durationUpdateProcessor(castFrame)
113 assert(castFrame ~= nil)
135 114
136 assert(endInstance ~= nil)
137 assert('number' == type(endInstance))
138 endInstance = math.abs(endInstance)
139 assert(endInstance >= 0)
115 local _, b = castFrame:GetMinMaxValues()
116 local now = GetTime()
140 117
141 local x = math.min(startInstance, endInstance)
142 local y = math.max(startInstance, endInstance)
143 assert(x <= y)
118 local durationRemainingSec = b - now
144 119
145 self:SetMinMaxValues(x, y)
120 applyDurationRemainingSec(castFrame, durationRemainingSec)
121 if durationRemainingSec < -2 then
122 castFrame:Hide()
123 elseif durationRemainingSec < 0 then
124 castFrame:SetAlpha(2 - math.abs(durationRemainingSec))
125 end
146 126 end end
147 127
148 local function applyCurrentInstanceMili(self, nowMili)
149 assert(self ~= nil)
128 local function castingUpdateProcessor(castFrame)
129 assert(castFrame ~= nil)
130
131 local now = GetTime()
132 castFrame:SetValue(now)
133
134 durationUpdateProcessor(castFrame)
135 end
136
137 local function channelUpdateProcessor(castFrame)
138 assert(castFrame ~= nil)
150 139
151 assert(nowMili ~= nil)
152 assert('number' == type(nowMili))
153 nowMili = math.abs(nowMili)
154 assert(nowMili >= 0)
140 local a, b = castFrame:GetMinMaxValues()
141 local now = GetTime()
142 castFrame:SetValue(a + b - now)
155 143
156 if self.strategy and 2 == self.strategy then
157 local a, b = self:GetMinMaxValues()
158 self:SetValue((a + b) - nowMili)
144 durationUpdateProcessor(castFrame)
145 end
146
147 local function spellcastBegin(castFrame, eventCategory, targetUnit)
148 assert(castFrame ~= nil)
149
150 local t = {
151 'ADDON_LOADED',
152 'PLAYER_ENTERING_WORLD',
153 'PLAYER_FOCUS_CHANGED',
154 'PLAYER_LOGIN',
155 'PLAYER_TARGET_CHANGED',
156 'UNIT_SPELLCAST_CHANNEL_START',
157 'UNIT_SPELLCAST_START',
158 'ZONE_CHANGED',
159 }
160
161 if not tContains(t, eventCategory) then
162 return false
163 end
164
165 targetUnit = targetUnit or SecureButton_GetUnit(castFrame) or 'none'
166 if 'ADDON_LOADED' == eventCategory then
167 targetUnit = SecureButton_GetUnit(castFrame)
168 elseif 'PLAYER_ENTERING_WORLD' == eventCategory then
169 targetUnit = SecureButton_GetUnit(castFrame)
170 elseif 'PLAYER_LOGIN' == eventCategory then
171 targetUnit = SecureButton_GetUnit(castFrame)
172 elseif 'ZONE_CHANGED' == eventCategory then
173 targetUnit = SecureButton_GetUnit(castFrame)
174 end
175
176 if not unitFilter(castFrame, targetUnit) then
177 return false
178 end
179
180 local spellName0, _, _, pictureFile0, startInstanceMili0,
181 endInstanceMili0, _, _, shieldedFlag0 = UnitCastingInfo(targetUnit)
182
183 local spellName1, _, _, pictureFile1, startInstanceMili1,
184 endInstanceMili1, _, _, shieldedFlag1 = UnitChannelInfo(targetUnit)
185
186 if spellName0 then
187 castFrame:SetScript('OnUpdate', castingUpdateProcessor)
188 elseif spellName1 then
189 castFrame:SetScript('OnUpdate', channelUpdateProcessor)
159 190 else else
160 self:SetValue(nowMili)
191 castFrame:SetScript('OnUpdate', durationUpdateProcessor)
192 end
193
194 if not spellName0 and not spellName1 then
195 castFrame:Hide()
196 return false
161 197 end end
198
199 local spellName = spellName0 or spellName1
200 local pictureFile = pictureFile0 or pictureFile1
201 local startInstanceMili = startInstanceMili0 or startInstanceMili1
202 local endInstanceMili = endInstanceMili0 or endInstanceMili1
203 local shieldedFlag = shieldedFlag0 or shieldedFlag1
204
205 local endInstanceSec = endInstanceMili / 1000
206 local startInstanceSec = startInstanceMili / 1000
207
208 castFrame:SetMinMaxValues(startInstanceSec, endInstanceSec)
209 local now = GetTime()
210 castFrame:SetValue(now)
211
212 applyArtworkSpellIcon(castFrame, pictureFile)
213 applyArtworkCastBar(castFrame, targetUnit, shieldedFlag)
214 applySpellName(castFrame, spellName)
215 castFrame:Show()
216 castFrame:SetAlpha(1)
217
218 return true
162 219 end end
163 220
164 local function castFrameEventProcessor(self, eventCategory, ...)
165 assert(self ~= nil)
221 local function getSpellName(castFrame)
222 assert(castFrame ~= nil)
166 223
167 local u = SecureButton_GetUnit(self)
168 if not u or not UnitExists(u) then
169 self:Hide()
170 return
224 --[[ Dirty reads are forbidden. ]]--
225
226 if not castFrame:IsShown() then
227 return nil
171 228 end end
172 229
173 --[[-- @fixme Find a more graceful approach to unit spellcast handling
174 in cast bar.]]
175
176 local castOngoingFlag = UnitCastingInfo(u) or UnitChannelInfo(u)
177
178 if not castOngoingFlag and
179 ('UNIT_SPELLCAST_SUCCEEDED' == eventCategory or
180 'UNIT_SPELLCAST_FAILED' == eventCategory) and UnitIsUnit(u, ...) then
181
182 local a, _ = self:GetMinMaxValues()
183 local now = GetTime() * 1000
184 self:SetMinMaxValues(a, now)
185 self:SetValue(now)
186
187 local spellName = select(2, ...)
188 applySpellName(self, spellName)
189 self:Show()
190 self:SetAlpha(1)
191
192 --[[-- @warning There is no easy way to query the spell
193 artwork here. Combat log is buggy and slow in Wrath client.
194 `UnitCastingInfo` is not available here either. Therefore,
195 simply use hardcoded stubs.]]
196
197 if 'UNIT_SPELLCAST_SUCCEEDED' == eventCategory then
198 self:SetStatusBarColor(1, 1, 0)
199 applyArtworkSpellIcon(self, "Interface\\Icons\\Spell_ChargePositive")
200 elseif 'UNIT_SPELLCAST_FAILED' == eventCategory then
201 self:SetStatusBarColor(1, 0, 1)
202 applyArtworkSpellIcon(self, "Interface\\Icons\\Spell_ChargeNegative")
203 else
204 error('must be either spellcast success or failure here')
230 local n = castFrame:GetName() or ''
231 assert(n ~= nil)
232
233 local label1 = castFrame.label1 or _G[tostring(n) .. 'Text1']
234 assert(label1 ~= nil)
235
236 local lastSpellName = label1:GetText()
237
238 if lastSpellName ~= nil then
239 assert('string' == type(lastSpellName))
240 lastSpellName = strtrim(lastSpellName)
241 assert(string.len(lastSpellName) >= 1)
242 assert(string.len(lastSpellName) <= 512)
243 end
244
245 return lastSpellName
246 end
247
248 local function spellcastSucceeded(castFrame, eventCategory, targetUnit, spellName)
249 assert(castFrame ~= nil)
250
251 assert(eventCategory ~= nil)
252
253 if not ('UNIT_SPELLCAST_SUCCEEDED' == eventCategory or
254 'UNIT_SPELLCAST_FAILED' == eventCategory or
255 'UNIT_SPELLCAST_INTERRUPTED' == eventCategory or
256 'UNIT_SPELLCAST_CHANNEL_STOP' == eventCategory) then
257 return false
258 end
259
260 local oldSpellName = getSpellName(castFrame)
261
262 local pictureFile = nil
263
264 if UnitIsUnit('player', targetUnit) then
265 local _, _, pictureFile0 = GetSpellInfo(spellName or oldSpellName)
266 pictureFile = pictureFile or pictureFile0
267
268 end
269
270 if 'UNIT_SPELLCAST_SUCCEEDED' == eventCategory or
271 'UNIT_SPELLCAST_CHANNEL_STOP' == eventCategory then
272 pictureFile = pictureFile or "Interface\\Icons\\Spell_ChargePositive"
273 elseif 'UNIT_SPELLCAST_FAILED' == eventCategory or
274 'UNIT_SPELLCAST_INTERRUPTED' == eventCategory then
275 pictureFile = pictureFile or "Interface\\Icons\\Spell_ChargeNegative"
276 end
277
278 local now = GetTime()
279 castFrame:SetMinMaxValues(now - 0.01, now + 0.01)
280 castFrame:SetValue(now)
281
282 --[[-- @todo Render shield pictogram for spell cast that cannot be interrupted by the user.
283 ]]
284
285 applyArtworkSpellIcon(castFrame, pictureFile)
286 applyArtworkCastBar(castFrame, targetUnit, false)
287 applySpellName(castFrame, spellName or oldSpellName)
288 castFrame:Show()
289 castFrame:SetAlpha(1)
290
291 return true
292 end
293
294 local function spellcastEnd(castFrame, eventCategory, targetUnit, spellName)
295 assert(castFrame ~= nil)
296
297 local t = {
298 'UNIT_SPELLCAST_CHANNEL_STOP',
299 'UNIT_SPELLCAST_FAILED',
300 'UNIT_SPELLCAST_INTERRUPTED',
301 'UNIT_SPELLCAST_STOP',
302 'UNIT_SPELLCAST_SUCCEEDED',
303 }
304
305 if not tContains(t, eventCategory) then
306 return false
307 end
308
309 if not unitFilter(castFrame, targetUnit) then
310 return false
311 end
312
313 --[[ Prevent cast bar being obscured by repeatedly pressing the
314 hotkey. ]]--
315
316 if 'UNIT_SPELLCAST_FAILED' == eventCategory and
317 UnitIsUnit('player', targetUnit) and
318 castFrame:IsShown() then
319
320 if spellName and GetSpellInfo(spellName) then
321 local cooldownDuration = GetSpellCooldown(spellName)
322 if cooldownDuration and cooldownDuration > 0 then
323 return false
324 end
205 325 end end
326 end
206 327
207 return
328 spellcastSucceeded(castFrame, eventCategory, targetUnit, spellName)
329
330 local _, boundaryMax = castFrame:GetMinMaxValues()
331 castFrame:SetValue(boundaryMax)
332 castFrame:SetScript('OnUpdate', durationUpdateProcessor)
333
334 local r = 1
335 local g = 1
336 local b = 0
337
338 if 'UNIT_SPELLCAST_FAILED' == eventCategory or
339 'UNIT_SPELLCAST_INTERRUPTED' == eventCategory then
340
341 r = 1
342 g = 0
343 b = 1
208 344 end end
345 castFrame:SetStatusBarColor(r, g, b)
209 346
210 if not castFrameEventIsRelevant(self, eventCategory, ...) then
211 return
347 return true
348 end
349
350 local function spellcastUpdate(castFrame, eventCategory, targetUnit)
351 assert(castFrame ~= nil)
352
353 local t = {
354 'UNIT_SPELLCAST_CHANNEL_UPDATE',
355 'UNIT_SPELLCAST_DELAYED',
356 'UNIT_SPELLCAST_INTERRUPTIBLE',
357 'UNIT_SPELLCAST_NOT_INTERRUPTIBLE',
358 }
359
360 if not tContains(t, eventCategory) then
361 return false
212 362 end end
213 363
214 local label2 = self.label2 or _G[self:GetName() .. 'Text2']
215 assert(label2 ~= nil)
364 targetUnit = targetUnit or SecureButton_GetUnit(castFrame) or 'none'
216 365
217 local spellName, _, _, pictureFile, startInstance, endInstance, _, _, shieldedFlag = UnitCastingInfo(u)
218 if spellName then
219 self.strategy = 1
220 else
221 spellName, _, _, pictureFile, startInstance, endInstance, _, _, shieldedFlag = UnitChannelInfo(u)
222 self.strategy = 2
366 if not unitFilter(castFrame, targetUnit) then
367 return false
223 368 end end
224 369
225 local castOngoingFlag = spellName ~= nil
226 if castOngoingFlag then
227 applyBounds(self, startInstance, endInstance)
370 local _, _, _, _, startInstanceMili0, endInstanceMili0 =
371 UnitCastingInfo(targetUnit)
228 372
229 local now = GetTime() * 1000
230 applyCurrentInstanceMili(self, now)
373 local _, _, _, _, startInstanceMili1, endInstanceMili1 =
374 UnitChannelInfo(targetUnit)
231 375
232 local dur = getDurationRemainingSec(now, startInstance, endInstance)
233 applyDurationRemainingSec(self, dur)
376 local endInstanceMili = endInstanceMili0 or endInstanceMili1
377 local startInstanceMili = startInstanceMili0 or startInstanceMili1
234 378
235 applyArtworkSpellIcon(self, pictureFile)
236 applyArtworkCastBar(self, u, shieldedFlag)
237 applySpellName(self, spellName)
238 self:Show()
239 self:SetAlpha(1)
240 else
241 self.strategy = nil
379 local endInstanceSec = endInstanceMili / 1000
380 local startInstanceSec = startInstanceMili / 1000
381
382 castFrame:SetMinMaxValues(startInstanceSec, endInstanceSec)
383 local now = GetTime()
384 castFrame:SetValue(now)
385
386 return true
387 end
388
389 local function guessTargetUnit(castFrame, eventCategory, targetUnit)
390 assert(castFrame ~= nil)
391
392 if targetUnit ~= nil then
393 return targetUnit
242 394 end end
395
396 if 'PLAYER_FOCUS_CHANGED' == eventCategory then
397 return 'focus'
398 elseif 'PLAYER_TARGET_CHANGED' == eventCategory then
399 return 'target'
400 end
401
402 return targetUnit
243 403 end end
244 404
245 local function castFrameUpdateProcessor(self)
246 assert(self ~= nil)
405 local function castFrameEventProcessor(castFrame, eventCategory, targetUnit, ...)
406 assert(castFrame ~= nil)
407
408 assert(eventCategory ~= nil)
409
410 local u = SecureButton_GetUnit(castFrame) or 'none'
247 411
248 local u = SecureButton_GetUnit(self)
249 if not u then
412 if not UnitExists(u) then
413 castFrame:Hide()
250 414 return return
251 415 end end
252 416
253 local now = GetTime() * 1000
254 applyCurrentInstanceMili(self, now)
417 targetUnit = targetUnit or guessTargetUnit(castFrame, eventCategory, targetUnit)
255 418
256 local a, b = self:GetMinMaxValues()
257 local durationRemainingSec = getDurationRemainingSec(now, a, b)
419 spellcastBegin(castFrame, eventCategory, targetUnit, ...)
420 spellcastEnd(castFrame, eventCategory, targetUnit, ...)
421 spellcastUpdate(castFrame, eventCategory, targetUnit, ...)
422 end
258 423
259 applyDurationRemainingSec(self, durationRemainingSec)
260 if durationRemainingSec < -2 then
261 self:Hide()
262 elseif durationRemainingSec < 0 then
263 self:SetAlpha(2 - math.abs(durationRemainingSec))
424 local function castFrameUpdateProcessor(castFrame)
425 assert(castFrame ~= nil)
426
427 local now = GetTime()
428
429 local a, b = castFrame:GetMinMaxValues()
430
431 if 2 == castFrame.strategy then
432 castFrame:SetValue(a + b - now)
433 else
434 castFrame:SetValue(now)
264 435 end end
265 436 end end
266 437
 
... ... local function castFrameMain(self)
296 467 return statusBar:SetValue(select(2, ...)) return statusBar:SetValue(select(2, ...))
297 468 end end
298 469
470 self.GetValue = function()
471 return statusBar:GetValue()
472 end
473
299 474 self.SetMinMaxValues = function(...) self.SetMinMaxValues = function(...)
300 475 return statusBar:SetMinMaxValues(select(2, ...)) return statusBar:SetMinMaxValues(select(2, ...))
301 476 end end
 
... ... local function castFrameMain(self)
313 488
314 489 self.artwork2 = _G[string.format('%sArtwork', statusBar:GetName())] self.artwork2 = _G[string.format('%sArtwork', statusBar:GetName())]
315 490
491 --[[-- @fixme When owner unit changes, the cast bar must update, but does not.
492 ]]
493
316 494 self:SetScript('OnEvent', castFrameEventProcessor) self:SetScript('OnEvent', castFrameEventProcessor)
317 495 self:SetScript('OnUpdate', castFrameUpdateProcessor) self:SetScript('OnUpdate', castFrameUpdateProcessor)
318 496
497 self:RegisterEvent("ADDON_LOADED");
319 498 self:RegisterEvent("PLAYER_ENTERING_WORLD"); self:RegisterEvent("PLAYER_ENTERING_WORLD");
320 499 self:RegisterEvent("PLAYER_FOCUS_CHANGED"); self:RegisterEvent("PLAYER_FOCUS_CHANGED");
500 self:RegisterEvent("PLAYER_LOGIN");
321 501 self:RegisterEvent("PLAYER_TARGET_CHANGED"); self:RegisterEvent("PLAYER_TARGET_CHANGED");
322 502 self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START"); self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START");
323 503 self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP"); self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP");
 
... ... local function castFrameMain(self)
330 510 self:RegisterEvent("UNIT_SPELLCAST_START"); self:RegisterEvent("UNIT_SPELLCAST_START");
331 511 self:RegisterEvent("UNIT_SPELLCAST_STOP"); self:RegisterEvent("UNIT_SPELLCAST_STOP");
332 512 self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
333 self:RegisterEvent("UNIT_SPELLCAST_FAILED");
513 self:RegisterEvent("ZONE_CHANGED");
334 514 end end
335 515
336 516 Chorus.castFrameMain = function(...) Chorus.castFrameMain = function(...)
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/chorus

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

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

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