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!: Add health bar 16bcad2d2169a7caa2a0f17513202673d178fa98 Vladyslav Bondarenko 2023-08-14 21:59:43
feat!: Add target aura tracker 96105088fc793845cde21639e0221b82de897262 Vladyslav Bondarenko 2023-08-12 21:49:26
Commit 16bcad2d2169a7caa2a0f17513202673d178fa98 - feat!: Add health bar
Author: Vladyslav Bondarenko
Author date (UTC): 2023-08-14 21:59
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2023-08-14 21:59
Parent(s): 96105088fc793845cde21639e0221b82de897262
Signer:
Signing key: EFF9624877D25D02
Signing status: E
Tree: ef7d1769d6fefa4964a428307a4595d5db5cc785
File Lines added Lines deleted
chorus.toc 1 0
etc/luacheckrc.lua 7 0
src/ChorusProgressFrameTemplate.lua 261 0
src/ChorusProgressFrameTemplate.xml 39 0
src/ChorusTestFrame.xml 12 2
File chorus.toc changed (mode: 100644) (index b39728b..a2b902b)
5 5 src\Chorus.xml src\Chorus.xml
6 6 src\ChorusAuraButtonTemplate.xml src\ChorusAuraButtonTemplate.xml
7 7 src\ChorusAuraFrameTemplate.xml src\ChorusAuraFrameTemplate.xml
8 src\ChorusProgressFrameTemplate.xml
8 9 src\ChorusTestFrame.xml src\ChorusTestFrame.xml
File etc/luacheckrc.lua changed (mode: 100644) (index f91abab..d1de12f)
... ... stds.wow = {
12 12 'UnitAura', 'UnitAura',
13 13 'UnitExists', 'UnitExists',
14 14 'UnitGUID', 'UnitGUID',
15 'UnitHealth',
16 'UnitHealthMax',
17 'UnitIsCorpse',
18 'UnitIsDead',
19 'UnitIsGhost',
20 'UnitPower',
21 'UnitPowerMax',
15 22 'date', 'date',
16 23 'strtrim', 'strtrim',
17 24 'tContains', 'tContains',
File src/ChorusProgressFrameTemplate.lua added (mode: 100644) (index 0000000..e6744fb)
1 local GetTime = GetTime
2 local UnitExists = UnitExists
3 local UnitHealth = UnitHealth
4 local UnitHealthMax = UnitHealthMax
5 local UnitIsCorpse = UnitIsCorpse
6 local UnitIsDead = UnitIsDead
7 local UnitIsGhost = UnitIsGhost
8 local UnitPower = UnitHealth
9 local UnitPowerMax = UnitHealthMax
10
11 local Chorus = Chorus
12
13 local function validateProgressFrame(self)
14 assert(self ~= nil)
15
16 local artwork = self.artwork
17 assert(artwork ~= nil)
18
19 local label = self.label
20 assert(label ~= nil)
21
22 local ratio = self.ratio
23 assert(ratio ~= nil)
24 assert('number' == type(ratio))
25 assert(ratio >= 0.0)
26 assert(ratio <= 1.0)
27
28 local strategy = self.strategy
29 assert(strategy ~= nil)
30 assert('string' == type(strategy))
31 assert('UNIT_HEALTH' == strategy or 'UNIT_POWER' == strategy)
32 if 'UNIT_HEALTH' == strategy or 'UNIT_POWER' == strategy then
33 local unitDesignation = self.unit
34 assert('string' == type(unitDesignation))
35 unitDesignation = string.lower(strtrim(unitDesignation))
36 assert(string.len(unitDesignation) >= 1)
37 assert(string.len(unitDesignation) <= 256)
38 end
39 end
40
41 local function progressFrameUpdateProcessor(self)
42 assert(self ~= nil)
43
44 --[[ Reduce update frequency to roughly 24 frames per second. ]]--
45 if self.lastUpdateInstance and 'number' == type(self.lastUpdateInstance) then
46 local now = GetTime()
47 if now - self.lastUpdateInstance > 0.0417 then
48 self.lastUpdateInstance = now
49 else
50 return
51 end
52 end
53
54 local ratio = self.ratio
55 if not ratio then
56 return
57 end
58 if 'number' ~= type(ratio) then
59 return
60 end
61 ratio = math.min(math.max(0, math.abs(ratio)), 1)
62
63 local artwork = self.artwork or _G[self:GetName() .. 'Artwork']
64 assert(artwork ~= nil)
65
66 if ratio > 0 then
67 artwork:Show()
68 artwork:SetPoint('BOTTOMLEFT', 0, 0)
69 artwork:SetPoint('TOPLEFT', 0, 0)
70 artwork:SetWidth(self:GetWidth() * ratio)
71 else
72 artwork:Hide()
73 end
74 end
75
76 local function getRatio(a, b)
77 assert(a ~= nil)
78 assert('number' == type(a))
79 assert(a >= 0)
80
81 assert(b ~= nil)
82 assert('number' == type(b))
83 assert(b > 0)
84
85 assert(a <= b)
86
87 a = math.min(math.abs(a), math.abs(b))
88 b = math.max(math.abs(a), math.abs(b))
89 assert(b > 0)
90
91 local ratio = a / b
92 ratio = math.min(math.max(0, math.abs(ratio)), 1)
93
94 return ratio
95 end
96
97 local function applyRatio(self, a, b)
98 self.ratio = getRatio(a, b)
99 end
100
101 local function applyOverlay(self, a, b)
102 assert(self ~= nil)
103
104 assert(a ~= nil)
105 assert('number' == type(a))
106 assert(a >= 0)
107
108 assert(b ~= nil)
109 assert('number' == type(b))
110 assert(b > 0)
111
112 assert(a <= b)
113
114 local ratio = getRatio(a, b)
115
116 local t
117 if a < 1000 then
118 t = string.format('%d\n%.0f%%', a, ratio * 100)
119 elseif a < 1000000 then
120 t = string.format('%.2f K\n%.0f%%', a / 1000, ratio * 100)
121 else
122 t = string.format('%.2f M\n%.0f%%', a / 1000000, ratio * 100)
123 end
124
125 local label = self.label or _G[self:GetName() .. 'Text']
126 assert(label ~= nil)
127 label:SetText(t)
128 end
129
130 local function applyRatioHealth(self, unitDesignation)
131 assert(self ~= nil)
132
133 assert(unitDesignation ~= nil)
134
135 local a = UnitHealth(unitDesignation) or 0
136 local b = UnitHealthMax(unitDesignation) or 1
137
138 applyRatio(self, a, b)
139 end
140
141 local function applyOverlayHealth(self, unitDesignation)
142 assert(self ~= nil)
143
144 assert(unitDesignation ~= nil)
145
146 local label = self.label
147 assert(label ~= nil)
148
149 local artwork = self.artwork
150 assert(artwork ~= nil)
151
152 --[[ TODO Use indicators instead of localized text for character alive statuses. ]]--
153 if UnitIsCorpse(unitDesignation) then
154 label:SetText('(Corpse)')
155 elseif UnitIsGhost(unitDesignation) then
156 label:SetText('(Ghost)')
157 elseif UnitIsDead(unitDesignation) then
158 label:SetText('(Dead)')
159 else
160 local a = UnitHealth(unitDesignation) or 0
161 local b = UnitHealthMax(unitDesignation) or 1
162
163 applyOverlay(self, a, b)
164 end
165 end
166
167 local function applyRatioPower(self, unitDesignation)
168 assert(self ~= nil)
169
170 assert(unitDesignation ~= nil)
171
172 local a = UnitPower(unitDesignation) or 0
173 local b = UnitPowerMax(unitDesignation) or 1
174 applyRatio(self, a, b)
175 end
176
177 local function applyOverlayPower(self, unitDesignation)
178 assert(self ~= nil)
179
180 assert(unitDesignation ~= nil)
181
182 local a = UnitPower(unitDesignation) or 0
183 local b = UnitPowerMax(unitDesignation) or 1
184 applyOverlay(self, a, b)
185 end
186
187 local function progressFrameEventProcessor(self)
188 validateProgressFrame(self)
189
190 local p = self:GetParent()
191
192 local unitDesignation = self.unit
193 if not unitDesignation and p then
194 unitDesignation = unitDesignation or p.unit
195 end
196
197 assert(unitDesignation ~= nil)
198 assert('string' == type(unitDesignation))
199 unitDesignation = string.lower(strtrim(unitDesignation))
200 assert(string.len(unitDesignation) >= 1)
201 assert(string.len(unitDesignation) <= 256)
202
203 if UnitExists(unitDesignation) then
204 self:Show()
205 else
206 self:Hide()
207 return
208 end
209
210 --[[ TODO Add alternative flavours of progress bar for mana and other resources. ]]--
211 --[[ TODO Add different coloring strategies for progress bars. ]]--
212 local strategy = self.strategy
213 if 'UNIT_HEALTH' == strategy then
214 applyRatioHealth(self, unitDesignation)
215 applyOverlayHealth(self, unitDesignation)
216 elseif 'UNIT_POWER' == strategy then
217 applyRatioPower(self, unitDesignation)
218 applyOverlayPower(self, unitDesignation)
219 end
220 end
221
222 function Chorus.progressFrameMain(self)
223 assert(self ~= nil)
224
225 local frameDesignation = self:GetName()
226 assert(frameDesignation ~= nil)
227 assert('string' == type(frameDesignation))
228 frameDesignation = strtrim(frameDesignation)
229 assert(string.len(frameDesignation) >= 1)
230 assert(string.len(frameDesignation) <= 256)
231
232 local artwork = _G[frameDesignation .. 'Artwork']
233 assert(artwork ~= nil)
234 self.artwork = artwork
235
236 local label = _G[frameDesignation .. 'Text']
237 assert(label ~= nil)
238 self.label = label
239
240 self.ratio = 1
241
242 self:SetScript('OnEvent', progressFrameEventProcessor)
243 self:SetScript('OnUpdate', progressFrameUpdateProcessor)
244
245 self:RegisterEvent('PARTY_CONVERTED_TO_RAID')
246 self:RegisterEvent('PARTY_MEMBERS_CHANGED')
247 self:RegisterEvent('PARTY_MEMBER_DISABLE')
248 self:RegisterEvent('PARTY_MEMBER_ENABLE')
249 self:RegisterEvent('PLAYER_ALIVE')
250 self:RegisterEvent('PLAYER_FOCUS_CHANGED')
251 self:RegisterEvent('PLAYER_LOGIN')
252 self:RegisterEvent('PLAYER_TARGET_CHANGED')
253 self:RegisterEvent('RAID_ROSTER_UPDATE')
254 self:RegisterEvent('UNIT_ENERGY')
255 self:RegisterEvent('UNIT_HEALTH')
256 self:RegisterEvent('UNIT_MANA')
257 self:RegisterEvent('UNIT_MAXPOWER')
258 self:RegisterEvent('UNIT_POWER')
259 self:RegisterEvent('UNIT_RAGE')
260 self:RegisterEvent('UNIT_RUNIC_POWER')
261 end
File src/ChorusProgressFrameTemplate.xml added (mode: 100644) (index 0000000..37d788f)
1 <?xml version="1.0" encoding="UTF-8"?>
2 <Ui xmlns="http://www.blizzard.com/wow/ui/">
3 <Script file="ChorusProgressFrameTemplate.lua"/>
4 <Frame name="ChorusProgressFrameTemplate" virtual="true">
5 <Size>
6 <AbsDimension x="144" y="24"/>
7 </Size>
8 <Layers>
9 <Layer level="ARTWORK">
10 <Texture name="$parentArtwork" nonBlocking="true" setAllPoints="true">
11 <Color r="0.13" g="0.55" b="0.13" a="0.20"/>
12 <Anchors>
13 <Anchor point="BOTTOMLEFT">
14 <Offset>
15 <AbsDimension x="0" y="0"/>
16 </Offset>
17 </Anchor>
18 <Anchor point="TOPLEFT">
19 <Offset>
20 <AbsDimension x="0" y="0"/>
21 </Offset>
22 </Anchor>
23 </Anchors>
24 </Texture>
25 </Layer>
26 <Layer level="BACKGROUND">
27 <Texture name="$parentBackground" nonBlocking="true" setAllPoints="true">
28 <Color r="0" g="0" b="0" a="0.9"/>
29 </Texture>
30 </Layer>
31 <Layer level="OVERLAY">
32 <FontString name="$parentText" inherits="SystemFont_Outline_Small" setAllPoints="true"/>
33 </Layer>
34 </Layers>
35 <Scripts>
36 <OnLoad>Chorus.progressFrameMain(self);</OnLoad>
37 </Scripts>
38 </Frame>
39 </Ui>
File src/ChorusTestFrame.xml changed (mode: 100644) (index 39fa942..627f266)
2 2 <Ui xmlns="http://www.blizzard.com/wow/ui/"> <Ui xmlns="http://www.blizzard.com/wow/ui/">
3 3 <Frame name="ChorusTestFrame"> <Frame name="ChorusTestFrame">
4 4 <Size> <Size>
5 <AbsDimension x="216" y="76"/>
5 <AbsDimension x="216" y="108"/>
6 6 </Size> </Size>
7 7 <Anchors> <Anchors>
8 8 <Anchor point="CENTER"> <Anchor point="CENTER">
 
12 12 </Anchor> </Anchor>
13 13 </Anchors> </Anchors>
14 14 <Frames> <Frames>
15 <Frame name="ChorusTestTargetHealthFrame" inherits="ChorusProgressFrameTemplate">
16 <Anchors>
17 <Anchor point="BOTTOMLEFT">
18 <Offset>
19 <AbsDimension x="0" y="72"/>
20 </Offset>
21 </Anchor>
22 </Anchors>
23 </Frame>
15 24 <Frame name="ChorusTestTargetBuffFrame" inherits="ChorusAuraFrameTemplate"> <Frame name="ChorusTestTargetBuffFrame" inherits="ChorusAuraFrameTemplate">
16 25 <Anchors> <Anchors>
17 26 <Anchor point="BOTTOMLEFT"> <Anchor point="BOTTOMLEFT">
 
21 30 </Anchor> </Anchor>
22 31 </Anchors> </Anchors>
23 32 </Frame> </Frame>
24
25 33 <Frame name="ChorusTestTargetDebuffFrame" inherits="ChorusAuraFrameTemplate"> <Frame name="ChorusTestTargetDebuffFrame" inherits="ChorusAuraFrameTemplate">
26 34 <Anchors> <Anchors>
27 35 <Anchor point="BOTTOMLEFT"> <Anchor point="BOTTOMLEFT">
 
38 46 ChorusTestTargetBuffFrame.unit = 'target'; ChorusTestTargetBuffFrame.unit = 'target';
39 47 ChorusTestTargetDebuffFrame.filter = 'HARMFUL'; ChorusTestTargetDebuffFrame.filter = 'HARMFUL';
40 48 ChorusTestTargetDebuffFrame.unit = 'target'; ChorusTestTargetDebuffFrame.unit = 'target';
49 ChorusTestTargetHealthFrame.strategy = 'UNIT_HEALTH';
50 ChorusTestTargetHealthFrame.unit = 'target';
41 51 </OnLoad> </OnLoad>
42 52 </Scripts> </Scripts>
43 53 </Frame> </Frame>
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