· 8 years ago · Apr 08, 2018, 05:14 AM
1--[[
2 Afflicted 3, Shadow of US-Mal'Ganis
3]]
4
5Afflicted = LibStub("AceAddon-3.0"):NewAddon("Afflicted", "AceEvent-3.0")
6
7local L = AfflictedLocals
8local instanceType, arenaBracket
9local summonedTotems = {}
10local summonedObjects = {}
11
12function Afflicted:OnInitialize()
13 self.defaults = {
14 profile = {
15 showAnchors = true,
16 showIcons = false,
17 targetOnly = false,
18 cooldownMessage = L["READY *spell (*target)"],
19 barWidth = 180,
20 barNameOnly = false,
21 barName = "BantoBar",
22 fontSize = 12,
23 fontName = "Friz Quadrata TT",
24 spellVersion = 0,
25 inside = {["arena"] = true, ["pvp"] = true},
26 anchors = {},
27 spells = {},
28 arenas = {[2] = {}, [3] = {}, [5] = {}},
29 anchorDefault = {
30 enabled = true,
31 announce = false,
32 growUp = false,
33 scale = 1.0,
34 maxRows = 20,
35 fadeTime = 0.5,
36 icon = "LEFT",
37 redirect = "",
38 display = "bars",
39 startMessage = "USED *spell (*target)",
40 endMessage = "FADED *spell (*target)",
41 announceColor = { r = 1.0, g = 1.0, b = 1.0 },
42 announceDest = "1",
43 },
44 },
45 }
46
47 -- Load default anchors
48 local anchor = self.defaults.profile.anchorDefault
49 self.defaults.profile.anchors.interrupts = CopyTable(anchor)
50 self.defaults.profile.anchors.interrupts.text = "Interrupts"
51 self.defaults.profile.anchors.cooldowns = CopyTable(anchor)
52 self.defaults.profile.anchors.cooldowns.text = "Cooldowns"
53 self.defaults.profile.anchors.spells = CopyTable(anchor)
54 self.defaults.profile.anchors.spells.text = "Spells"
55 self.defaults.profile.anchors.buffs = CopyTable(anchor)
56 self.defaults.profile.anchors.buffs.text = "Buffs"
57 self.defaults.profile.anchors.defenses = CopyTable(anchor)
58 self.defaults.profile.anchors.defenses.text = "Defensive"
59 self.defaults.profile.anchors.damage = CopyTable(anchor)
60 self.defaults.profile.anchors.damage.text = "Damage"
61
62 -- Initialize DB
63 self.db = LibStub:GetLibrary("AceDB-3.0"):New("AfflictedDB", self.defaults, true)
64 self.db.RegisterCallback(self, "OnProfileChanged", "Reload")
65 self.db.RegisterCallback(self, "OnProfileCopied", "Reload")
66 self.db.RegisterCallback(self, "OnProfileReset", "Reload")
67 self.db.RegisterCallback(self, "OnDatabaseShutdown", "OnDatabaseShutdown")
68
69 -- Setup our spell cache
70 self.writeQueue = {}
71 self.spells = setmetatable({}, {
72 __index = function(tbl, index)
73 if( not index ) then return end
74
75 -- No data found, don't try and load this index again
76 if( not Afflicted.db.profile.spells[index] ) then
77 tbl[index] = false
78 return false
79 end
80
81 local spell = loadstring("return " .. Afflicted.db.profile.spells[index])()
82 if( type(spell) == "table" ) then
83 spell.cooldown = spell.cooldown or 0
84 spell.duration = spell.duration or 0
85
86 spell.name = spell.name and L.spells[spell.name]
87 spell.cooldownName = spell.cooldownName and L.spells[spell.cooldownName]
88 spell.configName = spell.configName and L.spells[spell.configName]
89 end
90
91 tbl[index] = spell
92 return spell
93 end
94 })
95
96 -- Load spell database
97 local spells = AfflictedSpells:GetData()
98
99 -- Fresh DB, copy it all in
100 if( self.db.profile.spellVersion == 0 ) then
101 self.db.profile.spellVersion = AfflictedSpells.version
102 self.db.profile.spells = CopyTable(spells)
103
104 -- Spell database changed
105 elseif( self.db.profile.spellVersion < AfflictedSpells.version ) then
106 self.db.profile.spellVersion = AfflictedSpells.version
107
108 -- Remove old spells
109 for spellID in pairs(self.db.profile.spells) do
110 if( type(spellID) == "number" and spells[spellID] == false ) then
111 self.db.profile.spells[spellID] = nil
112 end
113 end
114
115 -- Load new or changed spells in
116 for spellID, data in pairs(spells) do
117 -- No spell of this type exists, add a new one
118 if( not self.db.profile.spells[spellID] ) then
119 self.db.profile.spells[spellID] = data
120 -- We already have this, so selective merge
121 elseif( type(data) == "string" and self.db.profile.spells[spellID] ~= data ) then
122 local spell = loadstring("return" .. data)()
123 local oldSpell = self.spells[spellID]
124
125 oldSpell.duration = spell.duration
126 oldSpell.cooldown = spell.cooldown
127 oldSpell.type = spell.type
128 oldSpell.class = spell.class
129
130 self.writeQueue[spellID] = true
131 end
132 end
133 end
134
135 self:HelpFrame()
136
137 -- Load display libraries
138 self.bars = self.modules.Bars:LoadVisual()
139 self.icons = self.modules.Icons:LoadVisual()
140
141 self:RegisterEvent("PLAYER_ENTERING_WORLD", "ZONE_CHANGED_NEW_AREA")
142 self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
143end
144
145-- Quick function to get the linked spells easily and such
146function Afflicted:GetSpell(spellID, spellName)
147 if( self.spells[spellName] ) then
148 return self.spells[spellName]
149 elseif( tonumber(self.spells[spellID]) ) then
150 return self.spells[self.spells[spellID]]
151 end
152
153 return self.spells[spellID]
154end
155
156local COMBATLOG_OBJECT_REACTION_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE
157local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE
158local eventRegistered = {["SPELL_CAST_SUCCESS"] = true, ["SPELL_AURA_REMOVED"] = true, ["SPELL_SUMMON"] = true, ["SPELL_CREATE"] = true, ["PARTY_KILL"] = true, ["UNIT_DIED"] = true}
159function Afflicted:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, sourceFlags2, destGUID, destName, destFlags, destFlags2, ...)
160 if( not eventRegistered[eventType] ) then
161 return
162 end
163
164 -- Enemy buff faded
165 if( eventType == "SPELL_AURA_REMOVED" and bit.band(sourceFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE ) then
166 local spellID, spellName, spellSchool, auraType = ...
167 local spell = self:GetSpell(spellID, spellName)
168 if( auraType == "BUFF" and spell and spell.type == "buff" ) then
169 self:AbilityEarlyFade(sourceGUID, sourceName, spell, spellID)
170 end
171
172 -- Spell casted succesfully
173 elseif( eventType == "SPELL_CAST_SUCCESS" and bit.band(sourceFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE ) then
174 local spellID, spellName, spellSchool, auraType = ...
175 local spell = self:GetSpell(spellID, spellName)
176 if( spell and spell.resets ) then
177 self:ResetCooldowns(sourceGUID, spell.resets)
178 end
179
180 -- Totems and traps will be handled in SPELL_SUMMON and SPELL_CREATE, don't trigger them here
181 if( spell and ( spell.type == "totem" or spell.type == "trap" ) ) then
182 return
183 end
184
185 self:AbilityTriggered(sourceGUID, sourceName, spell, spellID)
186
187 -- Check for something being summoned (Pets, totems)
188 elseif( eventType == "SPELL_SUMMON" and bit.band(sourceFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE ) then
189 local spellID, spellName, spellSchool = ...
190
191 -- Fixes an issue with totems not being removed when they get redropped
192 local id = sourceGUID .. (AfflictedSpells:GetTotemClass(spellName) or spellName)
193 local spell = self:GetSpell(spellID, spellName)
194 if( spell and spell.type == "totem" ) then
195 -- We already had a totem of this timer up, remove the previous one first
196 if( summonedTotems[id] ) then
197 self[self.db.profile.anchors[spell.anchor].display]:RemoveTimerByID(summonedTotems[id])
198 end
199
200 -- Set it as summoned so the totem specifically dying removes its timers
201 summonedObjects[destGUID] = sourceGUID .. spellID
202
203 -- Now trigger
204 self:AbilityTriggered(sourceGUID, sourceName, spell, spellID)
205 end
206
207 -- Set this as the active totem of that type down
208 summonedTotems[id] = sourceGUID .. spellID
209
210 -- Check for something being created (Traps, ect)
211 elseif( eventType == "SPELL_CREATE" and bit.band(sourceFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE ) then
212 local spellID, spellName, spellSchool = ...
213
214 local spell = self:GetSpell(spellID, spellName)
215 if( spell and spell.type == "trap" ) then
216 -- Set it as summoned so the totem specifically dying removes its timers
217 summonedObjects[destGUID] = sourceGUID .. spellID
218
219 self:AbilityTriggered(sourceGUID, sourceName, spell, spellID)
220 end
221 -- Check if we should clear timers
222 elseif( ( eventType == "PARTY_KILL" or ( instanceType ~= "arena" and eventType == "UNIT_DIED" ) ) and bit.band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE ) then
223 -- If this is a summoned object (trap/totem) that was specifically killed, remove its timer
224 if( summonedObjects[destGUID] ) then
225 self.bars:RemoveTimerByID(summonedObjects[destGUID])
226 self.icons:RemoveTimerByID(summonedObjects[destGUID])
227 return
228 end
229
230 -- If the player has any totems, kill them off with the player
231 local offset = string.len(destGUID)
232 for guid in pairs(summonedTotems) do
233 if( string.sub(guid, 0, offset) == destGUID ) then
234 self.bars:UnitDied(guid)
235 self.icons:UnitDied(guid)
236
237 summonedTotems[guid] = nil
238 end
239 end
240
241 self.bars:UnitDied(destGUID)
242 self.icons:UnitDied(destGUID)
243 end
244end
245
246-- Reset spells
247function Afflicted:ResetCooldowns(sourceGUID, resets)
248 for spellID in pairs(resets) do
249 local spellData = Afflicted.spells[spellID]
250 if( spellData and spellData.cdAnchor ) then
251 self[self.db.profile.anchors[spellData.cdAnchor].display]:RemoveTimerByID(sourceGUID .. spellID .. "CD")
252 end
253 end
254end
255
256-- Timer started
257function Afflicted:AbilityTriggered(sourceGUID, sourceName, spellData, spellID)
258 -- No data found, it's disabled, or it's not in our interest cause it's not focus/target
259 if( not spellData or ( self.db.profile.targetOnly and UnitGUID("target") ~= sourceGUID and UnitGUID("focus") ~= sourceGUID ) ) then
260 return
261 end
262
263 -- Grab spell info
264 local spellName, _, spellIcon = GetSpellInfo(spellID)
265
266 -- We're in an arena, and we don't want this spell enabled in the bracket
267 if( arenaBracket and ( self.db.profile.arenas[arenaBracket][spellID] or self.db.profile.arenas[arenaBracket][spellName] ) ) then
268 return
269 end
270
271 -- Start duration timer (if any)
272 if( not spellData.disabled and spellData.anchor and spellData.duration > 0 ) then
273 self:CreateTimer(sourceGUID, sourceName, spellData.anchor, spellData.repeating, false, spellData.duration, spellID, spellData.name or spellName, spellIcon)
274
275 -- Announce timer used
276 self:Announce(spellData, self.db.profile.anchors[spellData.anchor], "startMessage", spellID, spellData.name or spellName, sourceName)
277 end
278
279 -- Start CD timer
280 if( not spellData.cdDisabled and spellData.cdAnchor and spellData.cooldown > 0 ) then
281 self:CreateTimer(sourceGUID, sourceName, spellData.cdAnchor, false, true, spellData.cooldown, spellID, spellData.cooldownName or spellName, spellIcon)
282
283 -- Only announce that a cooldown was used if we didn't announce a duration, it's implied that the cooldown started.
284 if( spellData.disabled or not spellData.anchor or spellData.duration == 0 ) then
285 self:Announce(spellData, self.db.profile.anchors[spellData.cdAnchor], "startMessage", spellID, spellData.cooldownName or spellName, sourceName)
286 end
287 end
288end
289
290-- Spell faded early, so announce that
291function Afflicted:AbilityEarlyFade(sourceGUID, sourceName, spellData, spellID, spellName, announce)
292 if( spellData and not spellData.disabled and spellData.type == "buff" ) then
293 local removed = self[self.db.profile.anchors[spellData.anchor].display]:RemoveTimerByID(sourceGUID .. spellID)
294 if( removed and announce ) then
295 self:Announce(spellData, self.db.profile.anchors[spellData.anchor], "endMessage", spellID, spellName, sourceName)
296 end
297 end
298end
299
300-- Timer faded naturally
301function Afflicted:AbilityEnded(sourceGUID, sourceName, spellData, spellID, spellName, isCooldown)
302 if( spellData ) then
303 if( not isCooldown and not spellData.disabled ) then
304 self:Announce(spellData, self.db.profile.anchors[spellData.anchor], "endMessage", spellID, spellData.name or spellName, sourceName)
305 elseif( isCooldown and not spellData.cdDisabled ) then
306 self:Announce(spellData, self.db.profile.anchors[spellData.cdAnchor], "cooldownMessage", spellID, spellData.cooldownName or spellName, sourceName)
307 end
308 end
309end
310
311-- Create a timer and shunt it to the correct display
312function Afflicted:CreateTimer(sourceGUID, sourceName, anchorName, repeating, isCooldown, duration, spellID, spellName, spellIcon)
313 local anchor = self.db.profile.anchors[anchorName]
314 if( anchor ) then
315 self[anchor.display]:CreateTimer(sourceGUID, sourceName, anchorName, repeating, isCooldown, duration, spellID, spellName, spellIcon)
316 end
317end
318
319-- Announce something
320function Afflicted:Announce(spellData, anchor, key, spellID, spellName, sourceName, isCooldown)
321 local msg
322 if( key == "cooldownMessage" and ( spellData.custom or ( anchor.enabled and anchor.announce ) ) ) then
323 msg = self.db.profile.cooldownMessage
324 elseif( spellData.custom ) then
325 msg = spellData[key]
326 elseif( anchor.enabled and anchor.announce ) then
327 msg = anchor[key]
328 end
329
330 if( not msg or msg == "" ) then
331 return
332 end
333
334 msg = string.gsub(msg, "*spell", spellName)
335 msg = string.gsub(msg, "*target", self:StripServer(sourceName))
336
337 self:SendMessage(msg, anchor.announceDest, anchor.announceColor, spellID)
338end
339
340-- Database is getting ready to be written, we need to convert any changed data back into text
341function Afflicted:OnDatabaseShutdown()
342 for id in pairs(self.writeQueue) do
343 -- We currently have a table, meaning we can write it out as a string
344 if( type(self.spells[id]) == "table" ) then
345 local data = ""
346 for key, value in pairs(self.spells[id]) do
347 local text = ""
348 -- Right now, the tables in the spells are resets which is a number indexed table
349 if( type(value) == "table" ) then
350 for _, subValue in pairs(value) do
351 text = string.format("%s%s;", text, subValue)
352 end
353
354 text = string.format("{%s}", text)
355 elseif( type(value) == "string" ) then
356 text = string.format("'%s'", value)
357 else
358 text = tostring(value)
359 end
360
361 data = string.format("%s%s=%s;", data, key, text)
362 end
363
364 self.db.profile.spells[id] = string.format("{%s}", data)
365
366 -- We have a linked spell setup (spellID -> spellID)
367 elseif( type(self.spells[id]) == "number" ) then
368 self.db.profile.spells[id] = self.spells[id]
369 -- Nothing found, so reset the value
370 else
371 self.db.profile.spells[id] = nil
372 end
373
374 self.writeQueue[id] = nil
375 end
376end
377
378-- Find the current arena bracket we are in
379function Afflicted:SaveArenaBracket()
380 arenaBracket = nil
381 for i=1, MAX_BATTLEFIELD_QUEUES do
382 local status, _, _, _, _, teamSize = GetBattlefieldStatus(i)
383 if( status == "active" and teamSize > 0 ) then
384 arenaBracket = teamSize
385 break
386 end
387 end
388end
389
390-- Couldn't find data on the arena bracket we were in, so keep checking up UBS until we find it (or, we leave the arena)
391function Afflicted:UPDATE_BATTLEFIELD_STATUS()
392 self:SaveArenaBracket()
393 if( arenaBracket ) then
394 self:UnregisterEvent("UPDATE_BATTLEFIELD_STATUS")
395 end
396end
397
398-- Enabling Afflicted based on zone type
399function Afflicted:ZONE_CHANGED_NEW_AREA()
400 local type = select(2, IsInInstance())
401
402 if( type ~= instanceType ) then
403 -- Clear anchors because we changed zones
404 for key, data in pairs(self.db.profile.anchors) do
405 self[data.display]:ClearTimers(key)
406 end
407
408 -- Reset bracket
409 arenaBracket = nil
410
411 -- Monitor spells?
412 if( self.db.profile.inside[type] ) then
413 -- Find arena bracket
414 if( type == "arena" ) then
415 self:SaveArenaBracket()
416 if( not arenaBracket ) then
417 self:RegisterEvent("UPDATE_BATTLEFIELD_STATUS")
418 end
419 end
420
421 -- Reset our summoned stuff since we don't care about anything before inside
422 for k in pairs(summonedObjects) do summonedObjects[k] = nil end
423 for k in pairs(summonedTotems) do summonedTotems[k] = nil end
424
425 self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
426 else
427 self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
428 self:UnregisterEvent("UPDATE_BATTLEFIELD_STATUS")
429 end
430 end
431
432 instanceType = type
433end
434
435function Afflicted:ReloadEnabled()
436 instanceType = nil
437 self:ZONE_CHANGED_NEW_AREA()
438end
439
440function Afflicted:Reload()
441 if( self.icons ) then
442 self.icons:ReloadVisual()
443 end
444
445 if( self.bars ) then
446 self.bars:ReloadVisual()
447 end
448
449 self:HelpFrame()
450end
451
452-- Strips server name
453function Afflicted:StripServer(text)
454 local name, server = string.match(text, "(.-)%-(.*)$")
455 if( not name and not server ) then
456 return text
457 end
458
459 return name
460end
461
462function Afflicted:WrapIcon(msg, spellID)
463 if( not self.db.profile.showIcons or not spellID ) then
464 return msg
465 end
466
467 -- Make sure we have a valid icon
468 local icon = select(3, GetSpellInfo(spellID))
469 if( not icon ) then
470 return msg
471 end
472
473 return string.format("|T%s:0:0|t %s", icon, msg)
474end
475
476local chatFrames = {}
477local _G = getfenv(0)
478function Afflicted:SendMessage(msg, dest, color, spellID)
479 -- We're not showing anything
480 if( dest == "none" ) then
481 return
482 -- We're undergrouped, so redirect it to our fake alert frame
483 elseif( dest == "rw" and GetNumRaidMembers() == 0 and GetNumPartyMembers() == 0 ) then
484 dest = "rwframe"
485 -- We're grouped, in a raid and not leader or assist
486 elseif( dest == "rw" and not IsRaidLeader() and not IsRaidOfficer() and GetNumRaidMembers() > 0 ) then
487 dest = "party"
488 end
489
490 -- Strip out any () leftover from no name being given
491 msg = string.trim(string.gsub(msg, "%(%)", ""))
492
493 -- Chat frame
494 if( tonumber(dest) ) then
495 if( not chatFrames[dest] ) then
496 chatFrames[dest] = _G["ChatFrame" .. dest]
497 end
498
499 local frame = chatFrames[dest] or DEFAULT_CHAT_FRAME
500 frame:AddMessage(string.format("|cff33ff99Afflicted|r|cffffffff:|r %s", self:WrapIcon(msg, spellID)), color.r, color.g, color.b)
501 -- Raid warning announcement to raid/party
502 elseif( dest == "rw" ) then
503 SendChatMessage(msg, "RAID_WARNING")
504 -- Raid warning frame, will not send it out to the party
505 elseif( dest == "rwframe" ) then
506 if( not self.alertFrame ) then
507 self.alertFrame = CreateFrame("MessageFrame", nil, UIParent)
508 self.alertFrame:SetInsertMode("TOP")
509 self.alertFrame:SetFrameStrata("HIGH")
510 self.alertFrame:SetWidth(UIParent:GetWidth())
511 self.alertFrame:SetHeight(60)
512 self.alertFrame:SetFadeDuration(0.5)
513 self.alertFrame:SetTimeVisible(2)
514 self.alertFrame:SetFont((GameFontNormal:GetFont()), 20, "OUTLINE")
515 self.alertFrame:SetPoint("CENTER", 0, 60)
516 end
517
518 self.alertFrame:AddMessage(self:WrapIcon(msg, spellID), color.r, color.g, color.b)
519 -- Party chat
520 elseif( dest == "party" ) then
521 SendChatMessage(msg, "PARTY")
522 -- Combat text
523 elseif( dest == "ct" ) then
524 self:CombatText(self:WrapIcon(msg, spellID), color)
525 end
526end
527
528function Afflicted:CombatText(text, color, spellID)
529 -- SCT
530 if( IsAddOnLoaded("sct") ) then
531 SCT:DisplayText(text, color, nil, "event", 1)
532 -- MSBT
533 elseif( IsAddOnLoaded("MikScrollingBattleText") ) then
534 MikSBT.DisplayMessage(text, MikSBT.DISPLAYTYPE_NOTIFICATION, false, color.r * 255, color.g * 255, color.b * 255)
535 -- Blizzard Combat Text
536 elseif( IsAddOnLoaded("Blizzard_CombatText") ) then
537 -- Haven't cached the movement function yet
538 if( not COMBAT_TEXT_SCROLL_FUNCTION ) then
539 CombatText_UpdateDisplayedMessages()
540 end
541
542 CombatText_AddMessage(text, COMBAT_TEXT_SCROLL_FUNCTION, color.r, color.g, color.b)
543 end
544end
545
546function Afflicted:Print(msg)
547 DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99Afflicted3|r: " .. msg)
548end
549
550function Afflicted:HelpFrame()
551 if( self.helpFrame ) then
552 if( Afflicted.db.profile.showAnchors ) then
553 self.helpFrame:Show()
554 else
555 self.helpFrame:Hide()
556 end
557 return
558 elseif( not Afflicted.db.profile.showAnchors ) then
559 return
560 end
561
562 local frame = CreateFrame("Frame", nil, UIParent)
563 frame:SetClampedToScreen(true)
564 frame:SetFrameStrata("LOW")
565 frame:SetWidth(300)
566 frame:SetHeight(100)
567 frame:RegisterForDrag("LeftButton")
568 frame:EnableMouse(true)
569 frame:SetMovable(true)
570 frame:SetScript("OnDragStart", function(self)
571 self:StartMoving()
572 end)
573 frame:SetScript("OnDragStop", function(self)
574 self:StopMovingOrSizing()
575 end)
576 frame:SetBackdrop({
577 bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
578 edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
579 edgeSize = 26,
580 insets = {left = 9, right = 9, top = 9, bottom = 9},
581 })
582 frame:SetBackdropColor(0, 0, 0, 0.85)
583 frame:SetPoint("CENTER", UIParent, "CENTER", 0, 225)
584
585 frame.titleBar = frame:CreateTexture(nil, "ARTWORK")
586 frame.titleBar:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
587 frame.titleBar:SetPoint("TOP", 0, 8)
588 frame.titleBar:SetWidth(200)
589 frame.titleBar:SetHeight(45)
590
591 frame.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
592 frame.title:SetPoint("TOP", 0, 0)
593 frame.title:SetText("Afflicted")
594
595 frame.text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
596 frame.text:SetText(L["The black anchor boxes are used to move timer anchors around for Afflicted. Type /afflicted ui and check \"Show timer anchors\" or click \"Hide anchors\" below to hide them."])
597 frame.text:SetPoint("TOPLEFT", 12, -22)
598 frame.text:SetWidth(frame:GetWidth() - 20)
599 frame.text:SetJustifyH("LEFT")
600
601 frame.lock = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
602 frame.lock:SetText(L["Hide anchors"])
603 frame.lock:SetHeight(20)
604 frame.lock:SetWidth(100)
605 frame.lock:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 6, 8)
606 frame.lock:SetScript("OnEnter", OnEnter)
607 frame.lock:SetScript("OnLeave", OnLeave)
608 frame.lock.tooltipText = L["Hides the drag anchors in Afflicted."]
609 frame.lock:SetScript("OnClick", function(self)
610 Afflicted.db.profile.showAnchors = false
611 Afflicted:Reload()
612 LibStub("AceConfigRegistry-3.0"):NotifyChange("Afflicted")
613 end)
614
615 self.helpFrame = frame
616end