WoWInterface SVN Critline

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /
    from Rev 12 to Rev 13
    Reverse comparison

Rev 12 → Rev 13

trunk/libs/LibDualSpec-1.0.lua
1,6 → 1,6
--[[
LibDualSpec-1.0 - Adds dual spec support to individual AceDB-3.0 databases
Copyright (C) 2009 Adirelle
Copyright (C) 2009-2011 Adirelle
 
All rights reserved.
 
31,7 → 31,7
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--]]
 
local MAJOR, MINOR = "LibDualSpec-1.0", 4
local MAJOR, MINOR = "LibDualSpec-1.0", 9
assert(LibStub, MAJOR.." requires LibStub")
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end
40,7 → 40,6
-- Library data
-- ----------------------------------------------------------------------------
 
lib.talentGroup = lib.talentGroup or GetActiveTalentGroup()
lib.eventFrame = lib.eventFrame or CreateFrame("Frame")
 
lib.registry = lib.registry or {}
76,37 → 75,47
 
local locale = GetLocale()
if locale == "frFR" then
L_DUALSPEC_DESC = "Lorsqu'elle est activée, cette fonctionnalité vous permet "..
"de choisir un profil différent pour chaque spécialisation de talents. "..
"Le second profil sera échangé avec le profil courant chaque fois que vous "..
"passerez d'une spécialisation à l'autre."
L_ENABLED = 'Activez le second profil'
L_ENABLED_DESC = "Cochez cette case pour échanger automatiquement les profils "..
"lors d'un changement de spécialisation."
L_DUAL_PROFILE = 'Second profil'
L_DUAL_PROFILE_DESC = "Sélectionnez le profil à échanger avec le profil courant "..
"lors du changement de spécialisation."
L_DUALSPEC_DESC = "Lorsqu'elle est activée, cette fonctionnalité vous permet de choisir un profil différent pour chaque spécialisation de talents. Le second profil sera échangé avec le profil courant chaque fois que vous passerez d'une spécialisation à l'autre."
L_DUAL_PROFILE = "Second profil"
L_DUAL_PROFILE_DESC = "Sélectionnez le profil à échanger avec le profil courant lors du changement de spécialisation."
L_ENABLED = "Activez le second profil"
L_ENABLED_DESC = "Cochez cette case pour échanger automatiquement les profils lors d'un changement de spécialisation."
elseif locale == "deDE" then
L_DUALSPEC_DESC = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel der dualen Talentspezialisierung das Profil. Das duale Profil wird beim Wechsel automatisch mit dem derzeit aktiven Profil getauscht."
L_DUAL_PROFILE = "Duales Profil"
L_DUAL_PROFILE_DESC = "W\195\164hle das Profil, das beim Wechsel der Talente aktiviert wird."
L_DUALSPEC_DESC = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel "..
"der dualen Talentspezialisierung das Profil. Das duale Profil wird beim "..
"Wechsel automatisch mit dem derzeit aktiven Profil getauscht."
L_DUAL_PROFILE_DESC = "Wähle das Profil, das beim Wechsel der Talente aktiviert wird."
L_ENABLED = "Aktiviere Duale Profile"
L_ENABLED_DESC = "Aktiviere diese Option, um beim Talentwechsel automatisch "..
"zwischen den Profilen zu wechseln."
L_ENABLED_DESC = "Aktiviere diese Option, um beim Talentwechsel automatisch zwischen den Profilen zu wechseln."
elseif locale == "koKR" then
L_DUALSPEC_DESC = "가능하면 사용합니다. 이중 특성에 의하여 다른 프로필을 선택할 수 있게 하니다. 이중 프로필은 현재 프로필과 번갈아서 특성이 변경될 때 같이 적용됩니다."
L_DUAL_PROFILE = "이중 프로필"
L_DUAL_PROFILE_DESC = "특성이 바뀔 때 프로필을 선택합니다."
L_ENABLED = "이중 프로필 사용"
L_ENABLED_DESC = "특성이 변경 될때 자동으로 프로필을 변경하도록 선택합니다."
elseif locale == "ruRU" then
L_DUALSPEC_DESC = "Двойной профиль позволяет вам выбрать различные профили для каждой раскладки талантов. Профили будут переключаться каждый раз, когда вы переключаете раскладку талантов."
L_DUAL_PROFILE = "Второй профиль"
L_DUAL_PROFILE_DESC = "Выберите профиль, который необходимо активировать при переключениии талантов."
L_ENABLED = "Включить двойной профиль"
L_ENABLED_DESC = "Включите эту опцию для автоматического переключения между профилями при переключении раскладки талантов."
elseif locale == "zhCN" then
L_DUALSPEC_DESC = "启时,你可以为你的双天赋设定另一组配置文件,你的双重配置文件将在你转换天赋时自动与目前使用配置文件交换。"
L_DUAL_PROFILE = "双重配置文件"
L_DUAL_PROFILE_DESC = "选择转换天赋时所要使用的配置文件"
L_DUALSPEC_DESC = "启时,你可以为你的双天赋设定另一组配置文件,你的双重配置文件将在你转换天赋时自动与目前使用配置文件交换。"
L_ENABLED = "开启双重配置文件"
L_ENABLED_DESC = "勾选以便转换天赋时自动交换配置文件。"
elseif locale == "zhTW" then
L_DUALSPEC_DESC = "啟用時,你可以為你的雙天賦設定另一組設定檔。你的雙設定檔將在你轉換天賦時自動與目前使用設定檔交換。"
L_DUAL_PROFILE = "雙設定檔"
L_DUAL_PROFILE_DESC = "選擇轉換天賦後所要使用的設定檔"
L_ENABLED = "啟用雙設定檔"
L_ENABLED_DESC = "勾選以在轉換天賦時自動交換設定檔"
L_DUAL_PROFILE = "雙設定檔"
L_DUAL_PROFILE_DESC = "選擇轉換天賦後所要使用的設定檔"
elseif locale == "esES" then
L_DUALSPEC_DESC = "Si está activa, esta característica te permite seleccionar un perfil distinto para cada configuración de talentos. El perfil secundario será intercambiado por el activo cada vez que cambies de una configuración de talentos a otra."
L_DUAL_PROFILE = "Perfil secundario"
L_DUAL_PROFILE_DESC = "Elige el perfil secundario que se usará cuando cambies de talentos."
L_ENABLED = "Activar perfil secundario"
L_ENABLED_DESC = "Activa esta casilla para alternar automáticamente entre prefiles cuando cambies de talentos."
end
end
 
114,7 → 123,7
-- Mixin
-- ----------------------------------------------------------------------------
 
--- Get dual spec feature status.
--- Get dual spec feature status.
-- @return (boolean) true is dual spec feature enabled.
-- @name enhancedDB:IsDualSpecEnabled
function mixin:IsDualSpecEnabled()
129,7 → 138,7
if enabled and not db.char.talentGroup then
db.char.talentGroup = lib.talentGroup
db.char.profile = self:GetCurrentProfile()
db.char.enabled = true
db.char.enabled = true
else
db.char.enabled = enabled
self:CheckDualSpecState()
146,7 → 155,7
 
--- Set the alternate profile name.
-- No validation are done to ensure the profile is valid.
-- @param profileName (string) the profile name to use.
-- @param profileName (string) the profile name to use.
-- @name enhancedDB:SetDualSpecProfile
function mixin:SetDualSpecProfile(profileName)
registry[self].db.char.profile = profileName
161,13 → 170,13
-- @name enhancedDB:CheckDualSpecState
function mixin:CheckDualSpecState()
local db = registry[self].db
if db.char.enabled and db.char.talentGroup ~= lib.talentGroup then
if lib.talentsLoaded and db.char.enabled and db.char.talentGroup ~= lib.talentGroup then
local currentProfile = self:GetCurrentProfile()
local newProfile = db.char.profile
db.char.talentGroup = lib.talentGroup
if newProfile ~= currentProfile then
db.char.profile = currentProfile
self:SetProfile(newProfile)
db.char.profile = currentProfile
end
end
end
187,6 → 196,14
EmbedMixin(target)
end
 
-- Actually enhance the database
-- This is used on first initialization and everytime the database is reset using :ResetDB
function lib:_EnhanceDatabase(event, target)
registry[target].db = target:GetNamespace(MAJOR, true) or target:RegisterNamespace(MAJOR)
EmbedMixin(target)
target:CheckDualSpecState()
end
 
--- Embed dual spec feature into an existing AceDB-3.0 database.
-- LibDualSpec specific methods are added to the instance.
-- @name LibDualSpec:EnhanceDatabase
205,10 → 222,9
elseif registry[target] then
return
end
local db = target:GetNamespace(MAJOR, true) or target:RegisterNamespace(MAJOR)
registry[target] = { name = name, db = db }
EmbedMixin(target)
target:CheckDualSpecState()
registry[target] = { name = name }
lib:_EnhanceDatabase("EnhanceDatabase", target)
target.RegisterCallback(lib, "OnDatabaseReset", "_EnhanceDatabase")
end
 
-- ----------------------------------------------------------------------------
301,6 → 317,7
 
lib.eventFrame:RegisterEvent('PLAYER_TALENT_UPDATE')
lib.eventFrame:SetScript('OnEvent', function()
lib.talentsLoaded = true
local newTalentGroup = GetActiveTalentGroup()
if lib.talentGroup ~= newTalentGroup then
lib.talentGroup = newTalentGroup
310,3 → 327,4
end
end)
 
 
trunk/libs/AceLocale-3.0.lua
1,8 → 1,8
--- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings.
-- @class file
-- @name AceLocale-3.0
-- @release $Id: AceLocale-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
local MAJOR,MINOR = "AceLocale-3.0", 2
-- @release $Id: AceLocale-3.0.lua 1005 2011-01-29 14:19:43Z mikk $
local MAJOR,MINOR = "AceLocale-3.0", 5
 
local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
 
79,7 → 79,7
-- @param application Unique name of addon / module
-- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc.
-- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS)
-- @param silent If true, the locale will not issue warnings for missing keys. Can only be set on the default locale.
-- @param silent If true, the locale will not issue warnings for missing keys. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used).
-- @usage
-- -- enUS.lua
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true)
92,28 → 92,29
-- @return Locale Table to add localizations to, or nil if the current locale is not required.
function AceLocale:NewLocale(application, locale, isDefault, silent)
 
if silent and not isDefault then
error("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' can only be specified for the default locale", 2)
end
 
-- GAME_LOCALE allows translators to test translations of addons without having that wow client installed
-- Ammo: I still think this is a bad idea, for instance an addon that checks for some ingame string will fail, just because some other addon
-- gives the user the illusion that they can run in a different locale? Ditch this whole thing or allow a setting per 'application'. I'm of the
-- opinion to remove this.
local gameLocale = GAME_LOCALE or gameLocale
 
if locale ~= gameLocale and not isDefault then
return -- nop, we don't need these translations
local app = AceLocale.apps[application]
 
if silent and app then
geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered")
end
 
local app = AceLocale.apps[application]
 
if not app then
app = setmetatable({}, silent and readmetasilent or readmeta)
if silent=="raw" then
app = {}
else
app = setmetatable({}, silent and readmetasilent or readmeta)
end
AceLocale.apps[application] = app
AceLocale.appnames[app] = application
end
 
 
if locale ~= gameLocale and not isDefault then
return -- nop, we don't need these translations
end
 
registering = app -- remember globally for writeproxy and writedefaultproxy
 
if isDefault then
trunk/minimap.lua
23,16 → 23,12
minimap:RegisterForDrag("LeftButton")
minimap:SetPoint("TOPLEFT", -15, 0)
minimap:SetSize(32, 32)
minimap:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
minimap:SetHighlightTexture([[Interface\Minimap\UI-Minimap-ZoomButton-Highlight]])
minimap:Hide()
minimap:SetScript("OnClick", function(self, button)
local display = addon.display
if button == "LeftButton" and display then
if display:IsShown() then
display:Hide()
else
display:Show()
end
display:Toggle()
elseif button == "RightButton" then
addon:OpenConfig()
end
60,7 → 56,7
icon:SetPoint("TOPLEFT", 6, -6)
 
local border = minimap:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetTexture([[Interface\Minimap\MiniMap-TrackingBorder]])
border:SetSize(54, 54)
border:SetPoint("TOPLEFT")
 
trunk/splash.lua
164,7 → 164,11
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags)
end
 
local function initialize(self)
local font = templates:CreateDropDownMenu("CritlineSplashFont", config)
font:SetFrameWidth(120)
font:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -28)
font.label:SetText(L["Font"])
font.initialize = function(self)
for _, v in ipairs(LSM:List("font")) do
local info = UIDropDownMenu_CreateInfo()
info.text = v
173,38 → 177,24
UIDropDownMenu_AddButton(info)
end
end
 
local font = templates:CreateDropDownMenu("CritlineSplashFont", config, nil, initialize)
font:SetFrameWidth(120)
font:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -28)
font.label:SetText(L["Font"])
options.font = font
 
local menu = {
onClick = function(self)
self.owner:SetSelectedValue(self.value)
local font = splash.profile.font
font.flags = self.value
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags)
end,
{
text = L["None"],
value = "",
},
{
text = L["Normal"],
value = "OUTLINE",
},
{
text = L["Thick"],
value = "THICKOUTLINE",
},
{text = L["None"], value = ""},
{text = L["Normal"], value = "OUTLINE"},
{text = L["Thick"], value = "THICKOUTLINE"},
}
 
local fontFlags = templates:CreateDropDownMenu("CritlineSplashFontFlags", config, menu)
fontFlags:SetFrameWidth(120)
fontFlags:SetPoint("TOP", font, "BOTTOM", 0, -16)
fontFlags.label:SetText(L["Font outline"])
fontFlags.onClick = function(self)
self.owner:SetSelectedValue(self.value)
local font = splash.profile.font
font.flags = self.value
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags)
end
options.fontFlags = fontFlags
 
local fontSize = templates:CreateSlider(config, {
239,7 → 229,7
flags = "OUTLINE",
},
colors = {
spell = {r = 1, g = 1, b = 0},
spell = {r = 1, g = 1, b = 0},
amount = {r = 1, g = 1, b = 1},
},
pos = {
298,17 → 288,21
local red1 = {r = 1, g = 0, b = 0}
local red255 = {r = 255, g = 0, b = 0}
 
function splash:NewRecord(event, spell, amount, crit, oldAmount)
spell = format(L["New %s record!"], spell)
if splash.profile.oldRecord and oldAmount > 0 then
amount = format("%d (%d)", amount, oldAmount)
function splash:NewRecord(event, tree, spellID, periodic, amount, crit, prevRecord, isFiltered)
if isFiltered then
return
end
 
local colors = splash.profile.colors
spell = format(L["New %s record!"], addon:GetFullSpellName(spellID, periodic, true))
if self.profile.oldRecord and prevRecord.amount > 0 then
amount = format("%s (%s)", addon:ShortenNumber(amount), addon:ShortenNumber(prevRecord.amount))
end
 
local colors = self.profile.colors
local spellColor = colors.spell
local amountColor = colors.amount
 
if splash.profile.sct then
if self.profile.sct then
-- check if any custom SCT addon is loaded and use it accordingly
if MikSBT then
if crit then
trunk/Critline.toc
1,6 → 1,6
## Interface: 40000
## Title: Critline
## Version: 3.1.3
## Version: 4.0.0
## Notes: Saves your normal and critical records and flashes a message if you break the record.
## Author: L'ombra
## SavedVariables: CritlineDB
trunk/templates.lua
168,8 → 168,8
 
do -- slider template
local backdrop = {
bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
bgFile = [[Interface\Buttons\UI-SliderBar-Background]],
edgeFile = [[Interface\Buttons\UI-SliderBar-Border]],
tile = true, tileSize = 8, edgeSize = 8,
insets = {left = 3, right = 3, top = 6, bottom = 6}
}
231,30 → 231,46
 
 
do -- swatch button template
local function setColor(self)
local ColorPickerFrame = ColorPickerFrame
 
local function swatchFunc()
local button = ColorPickerFrame.extraInfo
local r, g, b = ColorPickerFrame:GetColorRGB()
self.swatch:SetVertexColor(r, g, b)
local color = self.color
button.swatch:SetVertexColor(r, g, b)
if button.func then button:func(r, g, b) end
local color = button.color
color.r = r
color.g = g
color.b = b
end
 
local function cancel(self, prev)
local r, g, b = prev.r, prev.g, prev.b
self.swatch:SetVertexColor(r, g, b)
local color = self.color
local function cancelFunc(prev)
local button = ColorPickerFrame.extraInfo
local r, g, b, a = prev.r, prev.g, prev.b, prev.opacity
button.swatch:SetVertexColor(r, g, b)
if button.func then button:func(r, g, b) end
local color = button.color
color.r = r
color.g = g
color.b = b
end
 
-- local function opacityFunc()
-- local button = ColorPickerFrame.extraInfo
-- local alpha = 1.0 - OpacitySliderFrame:GetValue()
-- if button.opacityFunc then button:opacityFunc(alpha) end
-- end
 
local function onClick(self)
local info = UIDropDownMenu_CreateInfo()
local color = self.color
info.r, info.g, info.b = color.r, color.g, color.b
info.swatchFunc = function() setColor(self) end
info.cancelFunc = function(c) cancel(self, c) end
info.swatchFunc = swatchFunc
-- info.hasOpacity = self.hasOpacity
-- info.opacityFunc = opacityFunc
-- info.opacity = color.a
info.cancelFunc = cancelFunc
info.extraInfo = self
OpenColorPicker(info)
end
 
302,6 → 318,7
do -- editbox
function templates:CreateEditBox(parent)
local editbox = CreateFrame("EditBox", nil, parent)
editbox:SetAutoFocus(false)
editbox:SetHeight(20)
editbox:SetFontObject("ChatFontNormal")
editbox:SetTextInsets(5, 0, 0, 0)
337,10 → 354,6
UIDropDownMenu_SetText(self, self.menu and self.menu[value] or value)
end
 
local function getSelectedValue(self)
return self.selectedValue
end
 
local function setDisabled(self, disable)
if disable then
self:Disable()
350,7 → 363,7
end
 
local function initialize(self)
local onClick = self.menu.onClick
local onClick = self.onClick
for _, v in ipairs(self.menu) do
local info = UIDropDownMenu_CreateInfo()
info.text = v.text
362,15 → 375,18
end
end
 
function templates:CreateDropDownMenu(name, parent, menu, initFunc, valueLookup)
function templates:CreateDropDownMenu(name, parent, menu, valueLookup)
local frame = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate")
 
frame.SetFrameWidth = UIDropDownMenu_SetWidth
frame.SetSelectedValue = setSelectedValue
frame.GetSelectedValue = getSelectedValue
frame.GetSelectedValue = UIDropDownMenu_GetSelectedValue
frame.Refresh = UIDropDownMenu_Refresh
frame.SetText = UIDropDownMenu_SetText
frame.Enable = UIDropDownMenu_EnableDropDown
frame.Disable = UIDropDownMenu_DisableDropDown
frame.SetDisabled = setDisabled
frame.JustifyText = UIDropDownMenu_JustifyText
 
if menu then
for _, v in ipairs(menu) do
379,7 → 395,7
end
frame.menu = menu or valueLookup
 
frame.initialize = initFunc or initialize
frame.initialize = initialize
 
local label = frame:CreateFontString(name.."Label", "BACKGROUND", "GameFontNormalSmall")
label:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 16, 3)
391,28 → 407,14
 
 
do -- used in Reset and Announce
local MAXSPELLBUTTONS = 14
local ITEMHEIGHT = 22
local MAXSPELLBUTTONS = 8
local ITEMHEIGHT = 36
 
local spells = {}
 
local function update(self)
local selectedTree = self.tree:GetSelectedValue()
 
wipe(spells)
local spells = addon:GetSpellArray(selectedTree)
 
for i, v in ipairs(addon.percharDB.profile.spells[selectedTree]) do
if v.normal or v.crit then
spells[#spells + 1] = {
spellName = v.spellName,
isPeriodic = v.isPeriodic,
normal = v.normal,
crit = v.crit,
pos = i,
}
end
end
 
local size = #spells
 
FauxScrollFrame_Update(self.scrollFrame, size, MAXSPELLBUTTONS, ITEMHEIGHT)
420,30 → 422,49
local offset = FauxScrollFrame_GetOffset(self.scrollFrame)
 
for line = 1, MAXSPELLBUTTONS do
local button = self.buttons[line]
local item = self.buttons[line]
local lineplusoffset = line + offset
if lineplusoffset <= size then
local data = spells[lineplusoffset]
button.spell = data.spellName
button.isPeriodic = data.isPeriodic
item.data = data
item.button.data = data
local normal = data.normal and data.normal.amount
local crit = data.crit and data.crit.amount
button:SetText(addon:GetFullSpellName(selectedTree, data.spellName, data.isPeriodic))
button.record:SetFormattedText("%d/%d", (normal or 0), (crit or 0))
button:SetChecked(self.selectedSpells[data.pos])
button:Show()
item.icon:SetTexture(addon:GetSpellTexture(data.spellID))
item.spell:SetText(addon:GetFullSpellName(data.spellID, data.periodic))
item.target:SetFormattedText("%s / %s", data.normal and data.normal.target or "n/a", data.crit and data.crit.target or "n/a")
item.record:SetFormattedText("%s\n%s", addon:ShortenNumber(normal or 0), addon:ShortenNumber(crit or 0))
if self.history then
local prevRecord = self.history[selectedTree][data.spellID]
if prevRecord and prevRecord[data.periodic] then
item.button.bg:Hide()
item.button.texture:Hide()
item.button.action = "Undo"
else
item.button.bg:Show()
item.button.texture:Show()
item.button.action = "Reset"
end
end
item:Show()
else
button:Hide()
item:Hide()
end
end
end
 
 
local function onMouseDown(self)
self:SetPoint("RIGHT", -7, -1)
end
 
local function onMouseUp(self)
self:SetPoint("RIGHT", -8, 0)
end
 
-- this is used for creating the scroll frames for the Reset and Announce frames
function templates:CreateList(name, title)
function templates:CreateList(name, title, action)
local frame = templates:CreateConfigFrame(title, addonName)
 
frame.selectedSpells = {}
 
frame.Update = update
local function update()
frame:Update()
453,90 → 474,126
addon.RegisterCallback(frame, "RecordsChanged", "Update")
 
local scrollFrame = CreateFrame("ScrollFrame", name.."ScrollFrame", frame, "FauxScrollFrameTemplate")
scrollFrame:SetSize(300, (MAXSPELLBUTTONS * ITEMHEIGHT + 4))
scrollFrame:SetPoint("TOP", 0, -24)
scrollFrame:SetHeight(MAXSPELLBUTTONS * ITEMHEIGHT + 4)
scrollFrame:SetPoint("TOPLEFT", 32, -24)
scrollFrame:SetPoint("TOPRIGHT", -32, -24)
scrollFrame:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, ITEMHEIGHT, update) end)
frame.scrollFrame = scrollFrame
 
local function onClick(self)
frame[self.action](frame, self.data)
end
 
-- onClick for check buttons
local function onClick(self)
local _, pos = addon:GetSpellInfo(frame.tree.selectedValue, self.spell, self.isPeriodic)
local selectedSpells = frame.selectedSpells
if self:GetChecked() then
PlaySound("igMainMenuOptionCheckBoxOn")
selectedSpells[pos] = true
else
PlaySound("igMainMenuOptionCheckBoxOff")
selectedSpells[pos] = nil
local function onEnter(self)
GameTooltip.Critline = true
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetSpellByID(self.data.spellID)
GameTooltip:AddLine(" ")
addon:AddTooltipLine(self.data)
if frame.history then
local prevRecord = frame.history[frame.tree.selectedValue][self.data.spellID]
prevRecord = prevRecord and prevRecord[self.data.periodic]
if prevRecord then
GameTooltip:AddLine(" ")
GameTooltip:AddLine(L["Previous record:"])
addon:AddTooltipLine(prevRecord)
end
end
if next(selectedSpells) then
frame.button:Enable()
else
frame.button:Disable()
end
GameTooltip:Show()
end
 
-- create list of check buttons
local buttons = {}
for i = 1, MAXSPELLBUTTONS do
local btn = CreateFrame("CheckButton", nil, frame, "OptionsBaseCheckButtonTemplate")
local item = CreateFrame("Frame", nil, frame)
item:SetHeight(ITEMHEIGHT)
if i == 1 then
btn:SetPoint("TOPLEFT", scrollFrame)
item:SetPoint("TOPLEFT", scrollFrame)
else
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4)
item:SetPoint("TOP", buttons[i - 1], "BOTTOM")
end
btn:SetPushedTextOffset(0, 0)
btn:SetScript("OnClick", onClick)
item:SetPoint("RIGHT", scrollFrame)
item:SetScript("OnEnter", onEnter)
item:SetScript("OnLeave", GameTooltip_Hide)
 
-- set default font string for the check button (will contain the spell name)
local text = btn:CreateFontString(nil, nil, "GameFontHighlight")
text:SetPoint("LEFT", btn, "RIGHT", 0, 1)
text:SetJustifyH("LEFT")
btn:SetFontString(text)
local icon = item:CreateTexture()
icon:SetSize(32, 32)
icon:SetPoint("LEFT")
item.icon = icon
 
local spell = item:CreateFontString(nil, nil, "GameFontNormal")
spell:SetPoint("TOPLEFT", icon, "TOPRIGHT", 4, -4)
spell:SetJustifyH("LEFT")
item.spell = spell
 
local target = item:CreateFontString(nil, nil, "GameFontHighlightSmall")
target:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 4, 4)
target:SetPoint("RIGHT", -64, 0)
target:SetJustifyH("LEFT")
item.target = target
 
local button = CreateFrame("Button", nil, item)
button:SetSize(24, 24)
button:SetPoint("RIGHT", -8, 0)
button:SetScript("OnClick", onClick)
button:SetScript("OnMouseDown", onMouseDown)
button:SetScript("OnMouseUp", onMouseUp)
button.action = action
item.button = button
 
local border = button:CreateTexture(nil, "BORDER")
border:SetAllPoints()
border:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Undo]])
 
local bg = button:CreateTexture()
bg:SetSize(20, 20)
bg:SetPoint("CENTER")
bg:SetTexture(0, 0, 0)
button.bg = bg
 
local texture = button:CreateTexture()
texture:SetPoint("CENTER")
button.texture = texture
 
if action == "Announce" then
texture:SetSize(16, 16)
texture:SetTexture([[Interface\Buttons\UI-GuildButton-MOTD-Up]])
else
texture:SetSize(22, 22)
texture:SetTexture([[Interface\RaidFrame\ReadyCheck-NotReady]])
end
 
-- font string for record amounts
local record = btn:CreateFontString(nil, nil, "GameFontHighlight")
record:SetPoint("CENTER", text)
record:SetPoint("RIGHT", scrollFrame)
local record = item:CreateFontString(nil, nil, "GameFontHighlightSmall")
record:SetPoint("TOPRIGHT", -36, 0)
record:SetPoint("BOTTOMRIGHT", -36, 0)
record:SetJustifyH("RIGHT")
btn.record = record
record:SetSpacing(2)
item.record = record
 
buttons[i] = btn
buttons[i] = item
end
frame.buttons = buttons
 
do
local menu = {
onClick = function(self)
self.owner:SetSelectedValue(self.value)
wipe(frame.selectedSpells)
StaticPopup_Hide("CRITLINE_RESET_ALL")
FauxScrollFrame_SetOffset(scrollFrame, 0)
_G[scrollFrame:GetName().."ScrollBar"]:SetValue(0)
frame:Update()
frame.button:Disable()
end,
{text = L["Damage"], value = "dmg"},
{text = L["Healing"], value = "heal"},
{text = L["Pet"], value = "pet"},
}
 
local tree = templates:CreateDropDownMenu(name.."Tree", frame, menu)
tree:SetPoint("TOPLEFT", scrollFrame, "BOTTOMLEFT", -16, -4)
tree:SetFrameWidth(120)
tree:SetSelectedValue("dmg")
 
frame.tree = tree
local menu = {
{text = L["Damage"], value = "dmg"},
{text = L["Healing"], value = "heal"},
{text = L["Pet"], value = "pet"},
}
 
local tree = templates:CreateDropDownMenu(name.."Tree", frame, menu)
tree:SetPoint("TOPLEFT", scrollFrame, "BOTTOMLEFT", -16, -4)
tree:SetFrameWidth(120)
tree:SetSelectedValue("dmg")
tree.onClick = function(self)
self.owner:SetSelectedValue(self.value)
StaticPopup_Hide("CRITLINE_RESET_ALL")
FauxScrollFrame_SetOffset(scrollFrame, 0)
_G[scrollFrame:GetName().."ScrollBar"]:SetValue(0)
frame:Update()
end
frame.tree = tree
 
-- reset/announce button
local btn = templates:CreateButton(frame)
btn:SetPoint("TOPRIGHT", scrollFrame, "BOTTOMRIGHT", 0, -7)
btn:SetSize(100, 22)
btn:Disable()
btn:SetText(title)
frame.button = btn
 
return frame
end
end
\ No newline at end of file
trunk/locales/enUS.lua
4,13 → 4,16
 
L["Add by name"] = true
L["Add by spell ID"] = true
L["Add from spell book"] = true
L["Add target"] = true
L["Alpha"] = true
L["Alphabetically"] = true
L["Amount color"] = true
L["Announce"] = true
L["Are you sure you want to reset all %s records?"] = true
L["Aura filter"] = true
L["Basic options"] = true
L["Aura type"] = true
L["Buffs"] = true
L["By crit record"] = true
L["By normal record"] = true
L["Cannot add players to mob filter."] = true
22,8 → 25,12
L["critical "] = true
L["Critical!"] = true
L["Critline splash frame unlocked"] = true
L["Current fight"] = true
L["Current instance (%s)"] = true
L["Current session"] = true
L["damage"] = true
L["Damage"] = true
L["Debuffs"] = true
L["Delete aura"] = true
L["Delete mob"] = true
L["Detailed tooltip"] = true
31,36 → 38,35
L["Disable to ignore records where the target is a player."] = true
L["Display previous record along with \"New record\" messages."] = true
L["Don't filter magic"] = true
L[" (DoT)"] = true
L["Duration"] = true
L["Drag to move"] = true
L["Enable to filter out new spell entries by default."] = true
L["Enable to ignore additional damage due to vulnerability."] = true
L["Enable to ignore integrated aura filter."] = true
L["Enable to ignore integrated mob filter."] = true
L["Enable to ignore spells that are not in your (or your pet's) spell book."] = true
L["Enable to include rather than exclude selected spells in the spell filter."] = true
L["Enable to let magical damage ignore the level filter."] = true
L["Enable to show icon indicators instead of text."] = true
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = true
L["Enabled"] = true
L["Enter mob name:"] = true
L["Enter spell ID:"] = true
L["Filter new spells"] = true
L["Font"] = true
L["Font outline"] = true
L["Font size"] = true
L["healing"] = true
L["Healing"] = true
L[" (HoT)"] = true
L["If level difference between you and the target is greater than this setting, records will not be registered."] = true
L["Ignore aura filter"] = true
L["Ignore mob filter"] = true
L["Ignore vulnerability"] = true
L["Include (unfiltered) records in spell tooltips."] = true
L["Invalid channel. Please enter a valid channel name or ID."] = true
L["Invalid input. Please enter a spell ID."] = true
L["Invalid mob name."] = true
L["Invalid player name."] = true
L["Invalid spell ID. No such spell exists."] = true
L["Invert spell filter"] = true
L["Left-click to toggle summary frame"] = true
L["Level filter"] = true
L["Lock minimap button."] = true
71,6 → 77,7
L["Move splash screen"] = true
L["New %s record!"] = true
L["New %s%s record - %d"] = true
L["n/a"] = true
L["None"] = true
L["No records"] = true
L["Normal"] = true
80,12 → 87,14
L["Pet"] = true
L["Plays a sound on a new record."] = true
L["Play sound"] = true
L["Previous record:"] = true
L["Prints new record notifications to the chat frame."] = true
L["Record damage"] = true
L["Record healing"] = true
L["Record pet damage"] = true
L["Record PvE"] = true
L["Record PvP"] = true
L["Records in spell tooltips"] = true
L["Reset"] = true
L["Reset all"] = true
L["Reset all %s records."] = true
100,10 → 109,16
L["Sets the color for the amount text in the splash frame."] = true
L["Sets the color for the spell text in the splash frame."] = true
L["Sets the font size of the splash frame."] = true
L["Sets the opacity of the display."] = true
L["Sets the scale of the splash frame."] = true
L["Sets the scale of the summary frame."] = true
L["Sets the scale of the display."] = true
L["Sets the time (in seconds) the splash frame is visible before fading out."] = true
L["Shorten records"] = true
L["Show"] = true
L["Show auras cast by NPCs"] = true
L["Show auras cast by players"] = true
L["Show auras cast on hostile NPCs"] = true
L["Show auras cast on me"] = true
L["Show minimap button."] = true
L["Show icons"] = true
L["Show old record"] = true
113,13 → 128,20
L["%s is already in mob filter."] = true
L["Spell color"] = true
L["Spell filter"] = true
L["Spell ID: |cffffffff%d|r"] = true
L["Splash frame"] = true
L["%s removed from aura filter."] = true
L["%s removed from mob filter."] = true
L["Sort by aura name"] = true
L["Sort by source name"] = true
L["Summary frame scale"] = true
L["Suppress all records while mind controlled."] = true
L["Suppress mind control"] = true
L["Text filter"] = true
L["Thick"] = true
L["tick"] = true
L["Tick"] = true
L["Tooltip sorting:"] = true
L["Use combat text splash"] = true
L["Use detailed format in the summary tooltip."] = true
\ No newline at end of file +L["Use detailed format in the summary tooltip."] = true +L["Use shorter format for records numbers."] = true \ No newline at end of file
trunk/locales/zhTW.lua
27,7 → 27,6
L["Disable to ignore records where the target is a player."] = "禁用來忽略目標是一個玩家的記錄."
L["Display previous record along with \"New record\" messages."] = "在\"新的記錄\"訊息一起顯示前一條記錄."
L["Don't filter magic"] = "不過濾魔法"
L[" (DoT)"] = "(DoT)"
-- L["Duration"] = ""
L["Drag to move"] = "拖曳來移動"
L["Edit tooltip format"] = "編輯提示訊息格式"
40,7 → 39,6
-- L["Enabled"] = ""
L["Enter mob name:"] = "輸入怪物名字"
-- L["Enter spell ID:"] = ""
L[" (HoT)"] = "(HoT)"
L["If level difference between you and the target is greater than this setting, records will not be registered."] = "如果你和目標的等級差大于這個設定, 記錄將不會被注冊."
L["Ignore aura filter"] = "忽略光環過濾器"
L["Ignore mob filter"] = "忽略怪物過濾器"
trunk/locales/ruRU.lua
31,7 → 31,6
L["Disable to ignore records where the target is a player."] = "Отключите, чтобы игнорировать записи, когда цель - игрок"
L["Display previous record along with \"New record\" messages."] = "Показывать предыдущие рекорды вместе с сообщениями \"Новый рекорд\""
L["Don't filter magic"] = "Не фильтровать магию"
L[" (DoT)"] = "ДоТ" -- Needs review
L["Drag to move"] = "Зажмите, чтобы двигать"
L["Duration"] = "Длительность"
L["Enabled"] = "Включено."
50,7 → 49,6
L["Font size"] = "Размер шрифта"
L["healing"] = "лечение"
L["Healing"] = "Лечение"
L[" (HoT)"] = "ХоТ" -- Needs review
L["If level difference between you and the target is greater than this setting, records will not be registered."] = "Если разница в уровнях между вами и целью больше чем в настройках, рекорд не будет записан."
L["Ignore aura filter"] = "Игнорировать фильтр аур"
L["Ignore mob filter"] = "Игнорировать фильтр монстров"
trunk/locales/esES.lua
24,7 → 24,6
L["Disable to ignore records where the target is a player."] = "Inhabilitar: Ignorar las marcas cuando el objectivo es un jugador."
L["Display previous record along with \"New record\" messages."] = "Mostrar marca anterior junto con la nueva marca."
L["Don't filter magic"] = "No filtrar magia"
-- L[" (DoT)"] = "DP"
L["Drag to move"] = "Arrastrar para mover"
L["Edit tooltip format"] = "Editar el formato del tooltip" -- Needs review
L["Enable to ignore integrated aura filter."] = "Habilita para igonar todas la entradas en el filtro de auras."
33,7 → 32,6
L["Enable to let magical damage ignore the level filter."] = "Habilitar: el daño mágico igonara el ajuste de nivel."
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = "Mostrar los records en el marco de texto de combate de Blizzard."
L["Enter mob name:"] = "Entrar nombre de mob:"
-- L[" (HoT)"] = "CP"
L["If level difference between you and the target is greater than this setting, records will not be registered."] = "Diferencia máxima de nivel para registra las marcas (entre tu y el objectivo)."
L["Ignore aura filter"] = "Ignorar auras"
L["Ignore mob filter"] = "Ignorar mobs"
trunk/locales/deDE.lua
11,6 → 11,7
L["Are you sure you want to reset all %s records?"] = "Bist du sicher, dass du alle %s Rekorde zurücksetzen willst?" -- Needs review
L["Aura filter"] = "Aurafilter" -- Needs review
L["Basic options"] = "Einfache Optionen"
L["Buffs"] = "Stärkungszauber"
L["By crit record"] = "Nach kritischen Rekorden"
L["By normal record"] = "Nach normalen Rekorden"
L["Cannot add players to mob filter."] = "Es können keine Spieler zum Mob Filter hinzugefügt werden."
24,6 → 25,7
L["Critline splash frame unlocked"] = "Critline 'Splash Anzeige' freigestellt"
L["damage"] = "Schaden" -- Needs review
L["Damage"] = "Schaden"
L["Debuffs"] = "Schwächungszauber"
L["Delete aura"] = "Aura löschen" -- Needs review
L["Delete mob"] = "Mob löschen"
L["Detailed tooltip"] = "Detailierter Tooltip" -- Needs review
31,7 → 33,6
L["Disable to ignore records where the target is a player."] = "Deaktivieren um Rekorde zu ignorieren bei denen das Ziel ein Spieler ist."
L["Display previous record along with \"New record\" messages."] = "Zeige vorhergehenden Rekord zusammen mit Mitteilungen über \"Neuer Rekord\" an."
L["Don't filter magic"] = "Magie nicht filtern"
L[" (DoT)"] = " (DoT)" -- Needs review
L["Drag to move"] = "Ziehen zum bewegen"
L["Duration"] = "Dauer" -- Needs review
L["Enabled"] = "Aktiviert" -- Needs review
50,7 → 51,6
L["Font size"] = "Schriftgrösse" -- Needs review
L["healing"] = "Heilung" -- Needs review
L["Healing"] = "Heilung" -- Needs review
L[" (HoT)"] = " (HoT)" -- Needs review
L["If level difference between you and the target is greater than this setting, records will not be registered."] = "Falls der Stufenunterschied zwischen dir und dem Ziel größer ist als diese Einstellung, wird der Rekord nicht aufgezeichnet."
L["Ignore aura filter"] = "Aurenfilter ignorieren"
L["Ignore mob filter"] = "Mobfilter ignorieren"
trunk/reset.lua
4,81 → 4,112
local templates = addon.templates
 
local treeNames = addon.treeNames
-- history for undoing recent (last fight) records
local history = {dmg = {}, heal = {}, pet = {}}
 
 
local module = templates:CreateList("CritlineReset", L["Reset"])
module:RegisterEvent("PLAYER_REGEN_DISABLED")
module:SetScript("OnEvent", function(self)
-- previous records are wiped upon entering combat
self:ClearHistory()
end)
 
do
local button = module.button
button:SetScript("OnClick", function()
PlaySound("gsTitleOptionOK")
module:ResetRecords()
end)
 
local resetAll = templates:CreateButton(module)
resetAll:SetSize(100, 22)
resetAll:SetPoint("TOP", button, "BOTTOM", 0, -10)
resetAll:SetText(L["Reset all"])
resetAll:SetScript("OnClick", function(self)
PlaySound("gsTitleOptionOK")
StaticPopup_Show("CRITLINE_RESET_ALL", addon.treeNames[module.tree:GetSelectedValue()])
end)
module.history = history
 
-- "edit tooltip format" popup
StaticPopupDialogs["CRITLINE_RESET_ALL"] = {
text = L["Are you sure you want to reset all %s records?"],
button1 = OKAY,
button2 = CANCEL,
OnAccept = function(self)
module:ResetRecords(true)
end,
whileDead = true,
timeout = 0,
}
-- reset/announce button
local button = templates:CreateButton(module)
button:SetPoint("TOPRIGHT", module.scrollFrame, "BOTTOMRIGHT", 0, -7)
button:SetSize(100, 22)
button:SetText(L["Reset all"])
button:SetScript("OnClick", function(self)
PlaySound("gsTitleOptionOK")
StaticPopup_Show("CRITLINE_RESET_ALL", addon.treeNames[module.tree:GetSelectedValue()])
end)
 
-- "edit tooltip format" popup
StaticPopupDialogs["CRITLINE_RESET_ALL"] = {
text = L["Are you sure you want to reset all %s records?"],
button1 = OKAY,
button2 = CANCEL,
OnAccept = function(self)
module:ResetAll()
end,
whileDead = true,
timeout = 0,
}
 
 
function module:Reset(data)
local tree = self.tree.selectedValue
addon:DeleteSpell(tree, data.spellID, data.periodic)
addon:UpdateRecords(tree)
end
 
 
function module:ResetRecords(resetAll)
local selectedSpells = self.selectedSpells
function module:Undo(data)
local tree = self.tree.selectedValue
local history = history[tree][data.spellID]
local spell = addon:GetSpellInfo(tree, data.spellID, data.periodic)
for k, v in pairs(history[data.periodic]) do
local hitType = spell[k]
local amount, target = hitType.amount, hitType.target
for k, v in pairs(v) do
hitType[k] = v
end
addon:Message(format("Reverted %s (%d, %s) record on %s.", data.spellName, amount, tree, target))
end
history[data.periodic] = nil
addon:UpdateTopRecords(tree)
addon:UpdateRecords(tree)
end
 
 
function module:ResetAll()
local tree = self.tree:GetSelectedValue()
local spells = addon.percharDB.profile.spells[tree]
 
if resetAll then
for i = #spells, 1, -1 do
local data = spells[i]
if data.filtered then
data.normal = nil
data.crit = nil
else
tremove(spells, i)
end
end
addon:Message(format(L["Reset all %s records."], treeNames[tree]))
wipe(spells)
wipe(addon:GetSpellArray(tree))
addon:Message(format(L["Reset all %s records."], treeNames[tree]))
 
self:Update()
addon:UpdateTopRecords(tree)
addon:UpdateSpells(tree)
end
 
 
-- stores previous record for the undo feature
function module:NewRecord(event, tree, spellID, periodic, amount, crit, prevRecord)
-- do not store previous record if it was 0
if prevRecord.amount == 0 then
return
end
 
history[tree][spellID] = history[tree][spellID] or {}
local hitType = crit and "crit" or "normal"
local spell = history[tree][spellID]
spell[periodic] = spell[periodic] or {}
-- do not store previous records gained in current fight
if spell[periodic][hitType] then
return
else
-- remove selected spells from database
-- iterate first in ascending order to print chat messages in alphabetical order
for i = 1, table.maxn(selectedSpells) do
if selectedSpells[i] then
local data = spells[i]
if data.filtered then
-- don't remove filtered entries completely; save the 'filtered' flag
data.normal = nil
data.crit = nil
selectedSpells[i] = nil
end
addon:Message(format(L["Reset %s (%s) records."], addon:GetFullSpellName(tree, data.spellName, data.isPeriodic), treeNames[tree]))
end
end
-- then iterate in descending order not to mess up the loop with tremove
for i = table.maxn(selectedSpells), 1, -1 do
if selectedSpells[i] then
tremove(spells, i)
end
end
spell[periodic][hitType] = {}
end
 
wipe(selectedSpells)
for k, v in pairs(prevRecord) do
spell[periodic][hitType][k] = v
end
addon:Debug(format("Storing previous record for %s = %d (%s, %s, %s)", addon:GetSpellName(spellID), prevRecord.amount, tree, periodic == 2 and "periodic" or "direct", hitType))
end
 
addon.RegisterCallback(module, "NewRecord")
 
 
function module:ClearHistory()
for k, tree in pairs(history) do
wipe(tree)
end
self:Update()
self.button:Disable()
addon:UpdateSpells(tree)
end
\ No newline at end of file +end + +addon.RegisterCallback(module, "PerCharSettingsLoaded", "ClearHistory") \ No newline at end of file
trunk/profiles.lua
3,144 → 3,197
local templates = addon.templates
 
local L = {
profiles = "Profiles",
current = "Current profile:",
default = "Default",
-- intro = "You can change the active database profile, so you can have different settings for every character.",
-- reset_desc = "Reset the current profile back to its default values, in case your configuration is broken, or you simply want to start over.",
reset = "Reset profile",
-- reset_sub = "Reset the current profile to the default",
choose_desc = "You can either create a new profile by entering a name in the editbox, or choose one of the already existing profiles.",
new = "New",
-- new_sub = "Create a new empty profile.",
choose = "Existing profiles",
-- choose_sub = "Select one of your currently available profiles.",
copy_desc = "Copy the settings from one existing profile into the currently active profile.",
copy = "Copy from",
copy = "Copy From",
delete_desc = "Delete existing and unused profiles from the database to save space, and cleanup the SavedVariables file.",
delete = "Delete a profile",
-- delete_sub = "Deletes a profile from the database.",
delete_confirm = "Are you sure you want to delete the selected profile?",
 
dualspec_desc = "When enabled, this feature allow you to select a different "..
"profile for each talent spec. The dual profile will be swapped with the "..
"current profile each time you switch from a talent spec to the other.",
profiles = "Profiles",
-- profiles_sub = "Manage Profiles",
current = "Current profile:",
 
dualspec_desc = "When enabled, this feature allow you to select a different profile for each talent spec. The dual profile will be swapped with the current profile each time you switch from a talent spec to the other.",
dual_profile = "Dual profile",
enabled = "Enable dual profile",
enabled_desc = "Check this box to automatically swap profiles on talent switch.",
dual_profile = "Dual profile",
}
 
local LOCALE = GetLocale()
if LOCALE == "deDE" then
L["profiles"] = "Profile"
--L["current"] = "Current Profile:"
L["default"] = "Standard"
-- L["intro"] = "Hier kannst du das aktive Datenbankprofile \195\164ndern, damit du verschiedene Einstellungen f\195\188r jeden Charakter erstellen kannst, wodurch eine sehr flexible Konfiguration m\195\182glich wird."
-- L["reset_desc"] = "Setzt das momentane Profil auf Standardwerte zur\195\188ck, f\195\188r den Fall das mit der Konfiguration etwas schief lief oder weil du einfach neu starten willst."
L["reset"] = "Profil zur\195\188cksetzen"
-- L["reset_sub"] = "Das aktuelle Profil auf Standard zur\195\188cksetzen."
L["choose_desc"] = "Du kannst ein neues Profil erstellen, indem du einen neuen Namen in der Eingabebox 'Neu' eingibst, oder w\195\164hle eines der vorhandenen Profile aus."
L["new"] = "Neu"
-- L["new_sub"] = "Ein neues Profil erstellen."
L["choose"] = "Vorhandene Profile"
L["new"] = "Neu"
L["choose_desc"] = "Du kannst ein neues Profil erstellen, indem du einen neuen Namen in der Eingabebox 'Neu' eingibst, oder w\195\164hle eines der vorhandenen Profile aus."
-- L["choose_sub"] = "W\195\164hlt ein bereits vorhandenes Profil aus."
L["copy_desc"] = "Kopiere die Einstellungen von einem vorhandenen Profil in das aktive Profil."
L["copy"] = "Kopieren von..."
L["delete_desc"] = "L\195\182sche vorhandene oder unbenutzte Profile aus der Datenbank um Platz zu sparen und um die SavedVariables Datei 'sauber' zu halten."
L["delete"] = "Profil l\195\182schen"
-- L["delete_sub"] = "L\195\182scht ein Profil aus der Datenbank."
L["delete_confirm"] = "Willst du das ausgew\195\164hlte Profil wirklich l\195\182schen?"
L["profiles"] = "Profile"
-- L["profiles_sub"] = "Profile verwalten"
--L["current"] = "Current Profile:"
 
L["dualspec_desc"] = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel "..
"der dualen Talentspezialisierung das Profil. Das duale Profil wird beim "..
"Wechsel automatisch mit dem derzeit aktiven Profil getauscht."
L["dualspec_desc"] = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel der dualen Talentspezialisierung das Profil. Das duale Profil wird beim Wechsel automatisch mit dem derzeit aktiven Profil getauscht."
L["dual_profile"] = "Duales Profil"
L["enabled"] = "Aktiviere Duale Profile"
L["enabled_desc"] = "Aktiviere diese Option, um beim Talentwechsel automatisch zwischen den Profilen zu wechseln."
L["dual_profile"] = "Duales Profil"
elseif LOCALE == "frFR" then
L["profiles"] = "Profils"
--L["current"] = "Current Profile:"
L["default"] = "D\195\169faut"
-- L["intro"] = "Vous pouvez changer le profil actuel afin d'avoir des param\195\168tres diff\195\169rents pour chaque personnage, permettant ainsi d'avoir une configuration tr\195\168s flexible."
-- L["reset_desc"] = "R\195\169initialise le profil actuel au cas o\195\185 votre configuration est corrompue ou si vous voulez tout simplement faire table rase."
L["reset"] = "R\195\169initialiser le profil"
-- L["reset_sub"] = "R\195\169initialise le profil actuel avec les param\195\168tres par d\195\169faut."
L["choose_desc"] = "Vous pouvez cr\195\169er un nouveau profil en entrant un nouveau nom dans la bo\195\174te de saisie, ou en choississant un des profils d\195\169j\195\160 existants."
L["new"] = "Nouveau"
-- L["new_sub"] = "Cr\195\169\195\169e un nouveau profil vierge."
L["choose"] = "Profils existants"
-- L["choose_sub"] = "Permet de choisir un des profils d\195\169j\195\160 disponibles."
L["copy_desc"] = "Copie les param\195\168tres d'un profil d\195\169j\195\160 existant dans le profil actuellement actif."
L["copy"] = "Copier \195\160 partir de"
L["delete_desc"] = "Supprime les profils existants inutilis\195\169s de la base de donn\195\169es afin de gagner de la place et de nettoyer le fichier SavedVariables."
L["delete"] = "Supprimer un profil"
-- L["delete_sub"] = "Supprime un profil de la base de donn\195\169es."
L["delete_confirm"] = "Etes-vous s\195\187r de vouloir supprimer le profil s\195\169lectionn\195\169 ?"
 
L["dualspec_desc"] = "Lorsqu'elle est activée, cette fonctionnalité vous permet "..
"de choisir un profil différent pour chaque spécialisation de talents. "..
"Le second profil sera échangé avec le profil courant chaque fois que vous "..
"passerez d'une spécialisation à l'autre."
L["profiles"] = "Profils"
-- L["profiles_sub"] = "Gestion des profils"
 
L["dualspec_desc"] = "Lorsqu'elle est activée, cette fonctionnalité vous permet de choisir un profil différent pour chaque spécialisation de talents. Le second profil sera échangé avec le profil courant chaque fois que vous passerez d'une spécialisation à l'autre."
L["dual_profile"] = 'Second profil'
L["enabled"] = 'Activez le second profil'
L["enabled_desc"] = "Cochez cette case pour échanger automatiquement les profils lors d'un changement de spécialisation."
L["dual_profile"] = 'Second profil'
-- elseif LOCALE == "koKR" then
-- L["profiles"] = "프로필"
elseif LOCALE == "koKR" then
L["default"] = "기본값"
-- L["intro"] = "모든 캐릭터의 다양한 설정과 사용중인 데이터베이스 프로필, 어느것이던지 매우 다루기 쉽게 바꿀수 있습니다."
-- L["reset_desc"] = "단순히 다시 새롭게 구성을 원하는 경우, 현재 프로필을 기본값으로 초기화 합니다."
L["reset"] = "프로필 초기화"
-- L["reset_sub"] = "현재의 프로필을 기본값으로 초기화 합니다"
L["choose_desc"] = "새로운 이름을 입력하거나, 이미 있는 프로필중 하나를 선택하여 새로운 프로필을 만들 수 있습니다."
L["new"] = "새로운 프로필"
-- L["new_sub"] = "새로운 프로필을 만듭니다."
L["choose"] = "프로필 선택"
-- L["choose_sub"] = "당신이 현재 이용할수 있는 프로필을 선택합니다."
L["copy_desc"] = "현재 사용중인 프로필에, 선택한 프로필의 설정을 복사합니다."
L["copy"] = "복사"
L["delete_desc"] = "데이터베이스에 사용중이거나 저장된 프로파일 삭제로 SavedVariables 파일의 정리와 공간 절약이 됩니다."
L["delete"] = "프로필 삭제"
-- L["delete_sub"] = "데이터베이스의 프로필을 삭제합니다."
L["delete_confirm"] = "정말로 선택한 프로필의 삭제를 원하십니까?"
L["profiles"] = "프로필"
L["profiles_sub"] = "프로필 설정"
-- L["current"] = "Current Profile:"
-- L["default"] = "기본값"
-- L["reset"] = "프로필 초기화"
-- L["choose_desc"] = "새로운 이름을 입력하거나, 이미 있는 프로필중 하나를 선택하여 새로운 프로필을 만들 수 있습니다."
-- L["new"] = "새로운 프로필"
-- L["choose"] = "프로필 선택"
-- L["copy_desc"] = "현재 사용중인 프로필에, 선택한 프로필의 설정을 복사합니다."
-- L["copy"] = "복사"
-- L["delete_desc"] = "데이터베이스에 사용중이거나 저장된 프로파일 삭제로 SavedVariables 파일의 정리와 공간 절약이 됩니다."
-- L["delete"] = "프로필 삭제"
-- L["delete_confirm"] = "정말로 선택한 프로필의 삭제를 원하십니까?"
 
L["dualspec_desc"] = "가능하면 사용합니다. 이중 특성에 의하여 다른 프로필을 선택할 수 있게 하니다. 이중 프로필은 현재 프로필과 번갈아서 특성이 변경될 때 같이 적용됩니다."
L["dual_profile"] = "이중 프로필"
L["enabled"] = "이중 프로필 사용"
L["enabled_desc"] = "특성이 변경 될때 자동으로 프로필을 변경하도록 선택합니다."
elseif LOCALE == "esES" or LOCALE == "esMX" then
L["profiles"] = "Perfiles"
--L["current"] = "Current Profile:"
L["default"] = "Por defecto"
-- L["intro"] = "Puedes cambiar el perfil activo de tal manera que cada personaje tenga diferentes configuraciones."
-- L["reset_desc"] = "Reinicia el perfil actual a los valores por defectos, en caso de que se haya estropeado la configuración o quieras volver a empezar de nuevo."
L["reset"] = "Reiniciar Perfil"
-- L["reset_sub"] = "Reinicar el perfil actual al de por defecto"
L["choose_desc"] = "Puedes crear un nuevo perfil introduciendo un nombre en el recuadro o puedes seleccionar un perfil de los ya existentes."
L["new"] = "Nuevo"
-- L["new_sub"] = "Crear un nuevo perfil vacio."
L["choose"] = "Perfiles existentes"
-- L["choose_sub"] = "Selecciona uno de los perfiles disponibles."
L["copy_desc"] = "Copia los ajustes de un perfil existente al perfil actual."
L["copy"] = "Copiar de"
L["delete_desc"] = "Borra los perfiles existentes y sin uso de la base de datos para ganar espacio y limpiar el archivo SavedVariables."
L["delete"] = "Borrar un Perfil"
-- L["delete_sub"] = "Borra un perfil de la base de datos."
L["delete_confirm"] = "¿Estas seguro que quieres borrar el perfil seleccionado?"
L["profiles"] = "Perfiles"
-- L["profiles_sub"] = "Manejar Perfiles"
--L["current"] = "Current Profile:"
elseif LOCALE == "zhTW" then
L["profiles"] = "設定檔"
--L["current"] = "Current Profile:"
L["default"] = "預設"
-- L["intro"] = "你可以選擇一個活動的資料設定檔,這樣你的每個角色就可以擁有不同的設定值,可以給你的插件設定帶來極大的靈活性。"
-- L["reset_desc"] = "將當前的設定檔恢復到它的預設值,用於你的設定檔損壞,或者你只是想重來的情況。"
L["reset"] = "重置設定檔"
-- L["reset_sub"] = "將當前的設定檔恢復為預設值"
L["choose_desc"] = "你可以通過在文本框內輸入一個名字創立一個新的設定檔,也可以選擇一個已經存在的設定檔。"
L["new"] = "新建"
-- L["new_sub"] = "新建一個空的設定檔。"
L["choose"] = "現有的設定檔"
-- L["choose_sub"] = "從當前可用的設定檔裏面選擇一個。"
L["copy_desc"] = "從當前某個已保存的設定檔複製到當前正使用的設定檔。"
L["copy"] = "複製自"
L["delete_desc"] = "從資料庫裏刪除不再使用的設定檔,以節省空間,並且清理SavedVariables檔。"
L["delete"] = "刪除一個設定檔"
-- L["delete_sub"] = "從資料庫裏刪除一個設定檔。"
L["delete_confirm"] = "你確定要刪除所選擇的設定檔嗎?"
 
L["dualspec_desc"] = "啟用時,你可以為你的雙天賦設定另一組設定檔。你的雙設定檔將在你轉換天賦時自動與目前使用設定檔交換。"
L["enabled"] = "啟用雙設定檔"
L["enabled_desc"] = "勾選以在轉換天賦時自動交換設定檔"
L["dual_profile"] = "雙設定檔"
L["profiles"] = "設定檔"
-- L["profiles_sub"] = "管理設定檔"
--L["current"] = "Current Profile:"
elseif LOCALE == "zhCN" then
L["profiles"] = "配置文件"
--L["current"] = "Current Profile:"
L["default"] = "默认"
-- L["intro"] = "你可以选择一个活动的数据配置文件,这样你的每个角色就可以拥有不同的设置值,可以给你的插件配置带来极大的灵活性。"
-- L["reset_desc"] = "将当前的配置文件恢复到它的默认值,用于你的配置文件损坏,或者你只是想重来的情况。"
L["reset"] = "重置配置文件"
-- L["reset_sub"] = "将当前的配置文件恢复为默认值"
L["choose_desc"] = "你可以通过在文本框内输入一个名字创立一个新的配置文件,也可以选择一个已经存在的配置文件。"
L["new"] = "新建"
-- L["new_sub"] = "新建一个空的配置文件。"
L["choose"] = "现有的配置文件"
-- L["choose_sub"] = "从当前可用的配置文件里面选择一个。"
L["copy_desc"] = "从当前某个已保存的配置文件复制到当前正使用的配置文件。"
L["copy"] = "复制自"
L["delete_desc"] = "从数据库里删除不再使用的配置文件,以节省空间,并且清理SavedVariables文件。"
L["delete"] = "删除一个配置文件"
-- L["delete_sub"] = "从数据库里删除一个配置文件。"
L["delete_confirm"] = "你确定要删除所选择的配置文件么?"
L["profiles"] = "配置文件"
-- L["profiles_sub"] = "管理配置文件"
--L["current"] = "Current Profile:"
 
L["dualspec_desc"] = "启时,你可以为你的双天赋设定另一组配置文件,你的双重配置文件将在你转换天赋时自动与目前使用配置文件交换。"
L["dual_profile"] = "双重配置文件"
L["enabled"] = "开启双重配置文件"
L["enabled_desc"] = "勾选以便转换天赋时自动交换配置文件。"
L["dual_profile"] = "双重配置文件"
elseif LOCALE == "ruRU" then
L["profiles"] = "Профили"
--L["current"] = "Current Profile:"
L["default"] = "По умолчанию"
-- L["intro"] = "Изменяя активный профиль, вы можете задать различные настройки модификаций для каждого персонажа."
-- L["reset_desc"] = "Если ваша конфигурации испорчена или если вы хотите настроить всё заново - сбросьте текущий профиль на стандартные значения."
L["reset"] = "Сброс профиля"
-- L["reset_sub"] = "Сброс текущего профиля на стандартный"
L["choose_desc"] = "Вы можете создать новый профиль, введя название в поле ввода, или выбрать один из уже существующих профилей."
L["new"] = "Новый"
-- L["new_sub"] = "Создать новый чистый профиль"
L["choose"] = "Существующие профили"
-- L["choose_sub"] = "Выбор одиного из уже доступных профилей"
L["copy_desc"] = "Скопировать настройки из выбранного профиля в активный."
L["copy"] = "Скопировать из"
L["delete_desc"] = "Удалить существующий и неиспользуемый профиль из БД для сохранения места, и очистить SavedVariables файл."
L["delete"] = "Удалить профиль"
-- L["delete_sub"] = "Удаление профиля из БД"
L["delete_confirm"] = "Вы уверены, что вы хотите удалить выбранный профиль?"
L["profiles"] = "Профили"
-- L["profiles_sub"] = "Управление профилями"
--L["current"] = "Current Profile:"
 
L["dualspec_desc"] = "Двойной профиль позволяет вам выбрать различные профили для каждой раскладки талантов. Профили будут переключаться каждый раз, когда вы переключаете раскладку талантов."
L["dual_profile"] = "Второй профиль"
L["enabled"] = "Включить двойной профиль"
L["enabled_desc"] = "Включите эту опцию для автоматического переключения между профилями при переключении раскладки талантов."
end
 
 
333,7 → 386,6
chooseDesc:SetText(L.choose_desc)
 
local newProfile = templates:CreateEditBox(frame)
newProfile:SetAutoFocus(false)
newProfile:SetWidth(160)
newProfile:SetPoint("TOPLEFT", chooseDesc, "BOTTOMLEFT", 0, -16)
newProfile:SetScript("OnEscapePressed", newProfile.ClearFocus)
347,10 → 399,11
label:SetHeight(18)
label:SetText(L.new)
 
local choose = templates:CreateDropDownMenu("CritlineDBChooseProfile"..name, frame, nil, initializeDropdown, defaultProfiles)
local choose = templates:CreateDropDownMenu("CritlineDBChooseProfile"..name, frame, nil, defaultProfiles)
choose:SetFrameWidth(144)
choose:SetPoint("LEFT", newProfile, "RIGHT", 0, -2)
choose.label:SetText(L.choose)
choose.initialize = initializeDropdown
choose.func = chooseProfileOnClick
choose.common = true
objects.choose = choose
372,11 → 425,12
text:SetText(L.enabled)
objects.dualEnabled = enabled
 
local dualProfile = templates:CreateDropDownMenu("CritlineDBDualProfile"..name, frame, nil, initializeDropdown, defaultProfiles)
local dualProfile = templates:CreateDropDownMenu("CritlineDBDualProfile"..name, frame, nil, defaultProfiles)
dualProfile:SetFrameWidth(144)
dualProfile:SetPoint("LEFT", choose)
dualProfile:SetPoint("TOP", enabled)
dualProfile.label:SetText(L.dual_profile)
dualProfile.initialize = initializeDropdown
dualProfile.func = dualProfileOnClick
dualProfile.common = true
objects.dualProfile = dualProfile
390,10 → 444,11
copyDesc:SetWordWrap(true)
copyDesc:SetText(L.copy_desc)
 
local copy = templates:CreateDropDownMenu("CritlineDBCopyProfile"..name, frame, nil, initializeDropdown, defaultProfiles)
local copy = templates:CreateDropDownMenu("CritlineDBCopyProfile"..name, frame, nil, defaultProfiles)
copy:SetFrameWidth(144)
copy:SetPoint("TOPLEFT", copyDesc, "BOTTOMLEFT", -16, -8)
copy.label:SetText(L.copy)
copy.initialize = initializeDropdown
copy.func = copyProfileOnClick
copy.nocurrent = true
objects.copy = copy
404,10 → 459,11
deleteDesc:SetWordWrap(true)
deleteDesc:SetText(L.delete_desc)
 
local delete = templates:CreateDropDownMenu("CritlineDBDeleteProfile"..name, frame, nil, initializeDropdown, defaultProfiles)
local delete = templates:CreateDropDownMenu("CritlineDBDeleteProfile"..name, frame, nil, defaultProfiles)
delete:SetFrameWidth(144)
delete:SetPoint("TOPLEFT", deleteDesc, "BOTTOMLEFT", -16, -8)
delete.label:SetText(L.delete)
delete.initialize = initializeDropdown
delete.func = deleteProfileOnClick
delete.nocurrent = true
objects.delete = delete
trunk/Broker.lua
5,11 → 5,13
 
 
local feeds = {
dmg = L["Damage"],
dmg = L["Damage"],
heal = L["Healing"],
pet = L["Pet"],
pet = L["Pet"],
}
 
local msgFormat = format("%s: %%s - %s: %%s", L["Normal"], L["Crit"])
 
for k, v in pairs(feeds) do
feeds[k] = LDB:NewDataObject("Critline "..addon.treeNames[k], {
type = "data source",
17,10 → 19,26
icon = addon.icons[k],
OnClick = function()
if IsShiftKeyDown() then
local normalRecord, critRecord, normalSpell, critSpell = addon:GetHighest(k)
local normal = format("%s: %s", L["Normal"], (normalSpell and format("%d (%s)", normalRecord, normalSpell) or "n/a"))
local crit = format("%s: %s", L["Crit"], (critSpell and format("%d (%s)", critRecord, critSpell) or "n/a"))
ChatFrame_OpenChat(normal.." - "..crit)
local normalRecord, critRecord = addon:GetHighest(k)
local normalSpell, critSpell
local spells = addon:GetSpellArray(k)
for i = 1, #spells do
local v = spells[i]
local normal = v.normal
if normal and normal.amount == normalRecord then
normalSpell = v.spellName
end
local crit = v.crit
if crit and crit.amount == critRecord then
critSpell = v.spellName
end
if (normalSpell or not normalRecord) and (critSpell or not normalRecord) then
break
end
end
local normal = normalSpell and format("%s (%s)", addon:ShortenNumber(normalRecord), normalSpell) or L["n/a"]
local crit = critSpell and format("%s (%s)", addon:ShortenNumber(critRecord), critSpell) or L["n/a"]
ChatFrame_OpenChat(format(msgFormat, normal, crit))
else
addon:OpenConfig()
end
32,18 → 50,30
end
 
 
local function updateRecords(event, tree, isFiltered)
if not isFiltered then
if tree then
feeds[tree].text = format("%s/%s", addon:GetHighest(tree))
else
for k in pairs(feeds) do
updateRecords(nil, k)
end
local function updateRecords(event, tree)
if tree then
local normal, crit = addon:GetHighest(tree)
feeds[tree].text = format("%s/%s", addon:ShortenNumber(normal), addon:ShortenNumber(crit))
else
for k in pairs(feeds) do
updateRecords(event, k)
end
end
end
 
addon.RegisterCallback(feeds, "PerCharSettingsLoaded", updateRecords)
addon.RegisterCallback(feeds, "RecordsChanged", updateRecords)
addon.RegisterCallback(feeds, "SpellsChanged", updateRecords)
\ No newline at end of file + +local function onTreeStateChanged(event, tree, enabled) + if enabled then + updateRecords(event, tree) + else + feeds[tree].text = L["n/a"] + end +end + + +local function addonLoaded() + addon.RegisterCallback(feeds, "OnNewTopRecord", updateRecords) + addon.RegisterCallback(feeds, "OnTreeStateChanged", onTreeStateChanged) +end + +addon.RegisterCallback(feeds, "AddonLoaded", addonLoaded) \ No newline at end of file
trunk/changeLog.txt
1,3 → 1,36
4.0.0
Core:
* Rewrote database to store spells by ID rather than name. This is more flexible and opens up a few possibilities.
* Most spells will be carried over to the new database, but others (mostly procs and item effects) will be lost.
- Absorb effects are now stored as healing records. Uses the remaining absorb amount when you apply shields.
- Added option to use shorter number format for records. (eg 13 k instead of 13000)
- Added option to display records in its spell's tooltip.
- Spells that have different versions, but identical effects, (such as "Pyroblast!" and "Ravage!") are now treated as one and the same.
- Added compability for patch 4.1.
- Certain guardian type pets should once again have their records properly registered.
- Periodic damage is now annotated by an asterisk (*) or the word "tick" rather than DoT/HoT.
- Removed some legacy code.
- Some code and performance optimisation.
Filter:
- Implemented basic filtering based on target auras. For now you have to be in combat log range when an aura is applied or removed for the addon to register it. See filters.lua for details.
- Implemented a basic aura tracker (mostly for debugging purposes). Type '/critline aura' to show or hide it.
- Removed option to invert spell filter. From now on unchecked spells are filtered, and new spells are not filtered by default.
- Added option to automatically filter newly registered spells. (disabled by default)
- Spell filter now shows spell tooltip and associated records on mouseover.
- Spells with no records are no longer kept in the database for the purpose of saving its filter flag.
- Added Drakeadon Mongrel and Exposed Head of Magmaw to mob filter.
- Added Power Generator, Engulfing Magic, Lightning Charge and Blessing of the Sun (heroic) to aura filter.
Display:
- The different panels' background color can now be changed as desired.
- The frame can now be scaled up to 200%. (from 150%)
- The frame's alpha can now be changed.
- It should no longer be possible to force show the frame when no trees are enabled, resulting in a weird appearance.
 
- Reset and announce spell lists had their appearance redesigned. Click to directly to your thing, instead of checking and then clicking.
- New records achieved in the last fight can now be undone in the reset module. Eligible spells will have a different button.
- Spell profiles should now display the correct used profiles at all times.
- Updated libraries.
 
3.1.3
- Added Karsh Steelbender, Ragnaros (Mount Hyjal) and Debilitated Apexar to mob filter.
- Added Tidal Surge, Pyrogenics, Blessing of the Sun, Empowering Twilight,
trunk/display.lua
3,12 → 3,12
local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
local templates = addon.templates
 
local width, height
local width, height = 128, 22
 
local trees = {
dmg = L["Damage"],
dmg = L["Damage"],
heal = L["Healing"],
pet = L["Pet"],
pet = L["Pet"],
}
 
 
33,12 → 33,19
GameTooltip:Show()
end
 
local backdrop = {
bgFile = [[Interface\ChatFrame\ChatFrameBackground]],
insets = {left = -1, right = -1, top = -1, bottom = -1},
}
 
local function createDisplay(parent)
local frame = CreateFrame("Frame", nil, parent)
frame:SetFrameStrata("LOW")
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetPoint("LEFT", 4, 0)
frame:SetPoint("RIGHT", -4, 0)
frame:SetBackdrop(backdrop)
frame:SetScript("OnDragStart", onDragStart)
frame:SetScript("OnDragStop", onDragStop)
frame:SetScript("OnEnter", onEnter)
67,16 → 74,19
addon.display = display
display:SetMovable(true)
display:SetBackdrop({
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
edgeSize = 16,
insets = {left = 4, right = 4, top = 4, bottom = 4}
edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
edgeSize = 12,
})
display:SetBackdropColor(0, 0, 0, 0.8)
display:SetBackdropBorderColor(0.5, 0.5, 0.5, 1)
display.trees = {}
 
 
Critline.SlashCmdHandlers["reset"] = function()
display:ClearAllPoints()
display:SetPoint("CENTER")
end
 
 
for k, treeName in pairs(trees) do
local frame = createDisplay(display)
frame.icon:SetTexture(addon.icons[k])
90,10 → 100,6
 
local config = templates:CreateConfigFrame("Display", addonName, true)
 
local function displayFunc(self)
self.module:UpdateLayout(self.setting, self:GetChecked())
end
 
local options = {
db = {},
{
101,11 → 107,7
tooltipText = L["Show summary frame."],
setting = "show",
func = function(self)
if self:GetChecked() then
display:Show()
else
display:Hide()
end
display:UpdateLayout()
end,
},
{
156,14 → 158,16
end
 
 
local slider = templates:CreateSlider(config, {
local sliders = {}
 
sliders[1] = templates:CreateSlider(config, {
text = L["Scale"],
tooltipText = L["Sets the scale of the summary frame."],
tooltipText = L["Sets the scale of the display."],
minValue = 0.5,
maxValue = 1.5,
maxValue = 2,
valueStep = 0.05,
minText = "50%",
maxText = "150%",
maxText = "200%",
func = function(self)
local value = self:GetValue()
self.value:SetFormattedText("%.0f%%", value * 100)
174,15 → 178,58
display.profile.scale = value
end,
})
slider:SetPoint("TOPLEFT", options[#options], "BOTTOMLEFT", 4, -24)
sliders[1]:SetPoint("TOPLEFT", options[#options], "BOTTOMLEFT", 4, -24)
 
sliders[2] = templates:CreateSlider(config, {
text = L["Alpha"],
tooltipText = L["Sets the opacity of the display."],
minValue = 0,
maxValue = 1,
valueStep = 0.05,
minText = "0%",
maxText = "100%",
func = function(self)
local value = self:GetValue()
self.value:SetFormattedText("%.0f%%", value * 100)
display:SetAlpha(value)
display.profile.alpha = value
end,
})
sliders[2]:SetPoint("TOP", sliders[1], "BOTTOM", 0, -32)
 
 
local function swatchFunc(self, r, g, b)
display.trees[self.setting]:SetBackdropColor(r, g, b)
end
 
local colorButtons = {}
 
for i, v in ipairs({"dmg", "heal", "pet"}) do
local btn = templates:CreateColorButton(config)
if i == 1 then
btn:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -21)
else
btn:SetPoint("TOP", colorButtons[i - 1], "BOTTOM", 0, -18)
end
btn:SetText(trees[v])
btn.setting = v
btn.func = swatchFunc
btn.opacityFunc = opacityFunc
colorButtons[i] = btn
end
 
local defaults = {
profile = {
show = true,
locked = false,
icons = true,
scale = 1,
alpha = 1,
colors = {
dmg = {r = 0, g = 0, b = 0},
heal = {r = 0, g = 0, b = 0},
pet = {r = 0, g = 0, b = 0},
},
pos = {
point = "CENTER",
},
192,9 → 239,7
function display:AddonLoaded()
self.db = addon.db:RegisterNamespace("display", defaults)
addon.RegisterCallback(self, "SettingsLoaded")
addon.RegisterCallback(self, "PerCharSettingsLoaded", "UpdateRecords")
addon.RegisterCallback(self, "SpellsChanged", "UpdateRecords")
addon.RegisterCallback(self, "RecordsChanged", "UpdateRecords")
addon.RegisterCallback(self, "OnNewTopRecord", "UpdateRecords")
end
 
addon.RegisterCallback(display, "AddonLoaded")
203,12 → 248,19
function display:SettingsLoaded()
self.profile = self.db.profile
 
self:UpdateRecords()
 
for _, btn in ipairs(options.db) do
btn:LoadSetting()
end
 
local colors = self.profile.colors
for _, btn in ipairs(colorButtons) do
local color = colors[btn.setting]
local r, g, b = color.r, color.g, color.b
btn:func(r, g, b)
btn.swatch:SetVertexColor(r, g, b)
btn.color = color
end
 
-- restore stored position
local pos = self.profile.pos
self:ClearAllPoints()
217,18 → 269,19
local scale = self.profile.scale
-- need to set scale separately first to ensure proper behaviour in scale-friendly repositioning
self:SetScale(scale)
slider:SetValue(scale)
sliders[1]:SetValue(scale)
 
sliders[2]:SetValue(self.profile.alpha)
end
 
 
function display:UpdateRecords(event, tree, isFiltered)
if not isFiltered then
if tree then
self.trees[tree].text:SetFormattedText("%6d/%-6d", addon:GetHighest(tree))
else
for k in pairs(self.trees) do
self:UpdateRecords(nil, k)
end
function display:UpdateRecords(event, tree)
if tree then
local normal, crit = addon:GetHighest(tree)
self.trees[tree].text:SetFormattedText("%8s / %-8s", addon:ShortenNumber(normal), addon:ShortenNumber(crit))
else
for k in pairs(trees) do
self:UpdateRecords(nil, k)
end
end
end
244,6 → 297,14
end
 
 
function display:Toggle()
local show = not self.profile.show
self.profile.show = show
options[1]:SetChecked(show)
self:UpdateLayout()
end
 
 
-- rearrange display buttons when any of them is shown or hidden
function display:UpdateLayout()
local trees = self.trees
253,16 → 314,16
 
if heal:IsShown() then
if dmg:IsShown() then
heal:SetPoint("TOP", dmg, "BOTTOM")
heal:SetPoint("TOP", dmg, "BOTTOM", 0, -2)
else
heal:SetPoint("TOP", 0, -4)
end
end
if pet:IsShown() then
if heal:IsShown() then
pet:SetPoint("TOP", heal, "BOTTOM")
pet:SetPoint("TOP", heal, "BOTTOM", 0, -2)
elseif dmg:IsShown() then
pet:SetPoint("TOP", dmg, "BOTTOM")
pet:SetPoint("TOP", dmg, "BOTTOM", 0, -2)
else
pet:SetPoint("TOP", 0, -4)
end
280,12 → 341,12
n = n + 1
end
 
self:SetSize(width, n * height + 8)
self:SetSize(width, n * (height + 2) + 6)
 
-- hide the entire frame if it turns out none of the individual frames are shown
if n == 0 then
if n == 0 or not self.profile.show then
self:Hide()
elseif self.profile.show then
else
self:Show()
end
end
\ No newline at end of file
trunk/announce.lua
4,70 → 4,59
local templates = addon.templates
 
 
local module = templates:CreateList("CritlineAnnounce", L["Announce"])
local module = templates:CreateList("CritlineAnnounce", L["Announce"], "Announce")
 
do
local button = module.button
button:SetScript("OnClick", function()
PlaySound("gsTitleOptionOK")
module:AnnounceRecords()
end)
addon.RegisterCallback(module, "SpellsChanged", "Update")
 
local function onClick(self)
self.owner:SetSelectedValue(self.value)
local target = module.target
if self.value == "WHISPER" or self.value == "CHANNEL" then
target:Show()
target:SetFocus()
else
target:Hide()
end
local function onClick(self)
self.owner:SetSelectedValue(self.value)
local target = module.target
if self.value == "WHISPER" or self.value == "CHANNEL" then
target:Show()
target:SetFocus()
else
target:Hide()
end
end
 
local channels = {
"SAY",
"GUILD",
"PARTY",
"RAID",
"WHISPER",
"CHANNEL",
}
 
local function initialize(self)
for i, v in ipairs(channels) do
local info = UIDropDownMenu_CreateInfo()
info.text = _G[v]
info.value = v
info.func = onClick
info.owner = self
UIDropDownMenu_AddButton(info)
end
end
local channels = {
"SAY",
"GUILD",
"PARTY",
"RAID",
"WHISPER",
"CHANNEL",
}
 
local channel = templates:CreateDropDownMenu("CritlineAnnounceChannel", module, nil, initialize, _G)
channel:SetFrameWidth(120)
channel:SetPoint("TOPLEFT", module.tree, "BOTTOMLEFT")
channel:SetSelectedValue("SAY")
module.channel = channel
 
local target = templates:CreateEditBox(module)
target:SetAutoFocus(false)
target:SetWidth(144)
target:SetPoint("LEFT", channel, "RIGHT", 0, 2)
target:SetScript("OnEscapePressed", target.ClearFocus)
target:SetScript("OnEnterPressed", function(self) module:AnnounceRecords() end)
target:Hide()
module.target = target
local channel = templates:CreateDropDownMenu("CritlineAnnounceChannel", module, nil, _G)
channel:SetFrameWidth(120)
channel:SetPoint("TOPLEFT", module.tree, "BOTTOMLEFT")
channel:SetSelectedValue("SAY")
channel.initialize = function(self)
for i, v in ipairs(channels) do
local info = UIDropDownMenu_CreateInfo()
info.text = _G[v]
info.value = v
info.func = onClick
info.owner = self
UIDropDownMenu_AddButton(info)
end
end
module.channel = channel
 
local target = templates:CreateEditBox(module)
target:SetWidth(144)
target:SetPoint("LEFT", channel, "RIGHT", 0, 2)
target:SetScript("OnEnterPressed", target.ClearFocus)
target:SetScript("OnEscapePressed", target.ClearFocus)
target:Hide()
module.target = target
 
addon.RegisterCallback(module, "SpellsChanged", "Update")
 
local announceFormat = format("%%s - %s: %%s %s: %%s", L["Normal"], L["Crit"])
 
function module:AnnounceRecords()
function module:Announce(data)
local channel = self.channel:GetSelectedValue()
local tree = self.tree:GetSelectedValue()
local spells = addon.percharDB.profile.spells[tree]
local target = self.target:GetText():trim()
 
if channel == "WHISPER" then
83,21 → 72,10
end
end
 
local sent
 
for i in pairs(self.selectedSpells) do
local data = spells[i]
local normal = data.normal and data.normal.amount
local crit = data.crit and data.crit.amount
local text = format("%s - %s: %s %s: %s", addon:GetFullSpellName(tree, data.spellName, data.isPeriodic), L["Normal"], (normal or "n/a"), L["Crit"], (crit or "n/a"))
SendChatMessage(text, channel, nil, target)
sent = true
end
 
if sent then
wipe(self.selectedSpells)
self:Update()
self.button:Disable()
self.target:SetText("")
end
local normal = data.normal and addon:ShortenNumber(data.normal.amount)
local crit = data.crit and addon:ShortenNumber(data.crit.amount)
local text = format(announceFormat, addon:GetFullSpellName(data.spellID, data.periodic, true), (normal or L["n/a"]), (crit or L["n/a"]))
SendChatMessage(text, channel, nil, target)
 
self.target:SetText("")
end
\ No newline at end of file
trunk/core.lua
1,13 → 1,21
local addonName, Critline = ...
_G.Critline = Critline
 
-- local addon = { }
-- local mt_func = { __index = function() return function() end end }
-- local empty_tbl = { }
-- local mt = { __index = function() return setmetatable(empty_tbl, mt_func) end }
-- setmetatable(addon, mt)
-- print(addon.module.method())
 
local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
local templates = Critline.templates
local playerClass = select(2, UnitClass("player"))
local debugging
 
-- auto attack spell
local AUTOATK = GetSpellInfo(6603)
local AUTOATK_ID = 6603
local AUTOATK = GetSpellInfo(AUTOATK_ID)
 
-- local references to commonly used functions and variables for faster access
local HasPetUI = HasPetUI
25,17 → 33,17
 
 
local treeNames = {
dmg = L["damage"],
dmg = L["damage"],
heal = L["healing"],
pet = L["pet"],
pet = L["pet"],
}
Critline.treeNames = treeNames
 
 
Critline.icons = {
dmg = "Interface\\Icons\\Ability_SteelMelee",
heal = "Interface\\Icons\\Spell_Holy_FlashHeal",
pet = "Interface\\Icons\\Ability_Hunter_Pet_Bear",
dmg = [[Interface\Icons\Ability_SteelMelee]],
heal = [[Interface\Icons\Spell_Holy_FlashHeal]],
pet = [[Interface\Icons\Ability_Hunter_Pet_Bear]],
}
 
 
49,20 → 57,104
[37994] = true, -- Water Elemental (glyphed)
}
 
-- spells that are essentially the same, but has different IDs, we'll register them under the same ID
local similarSpells = {
[27285] = 27243, -- Seed of Corruption (direct)
[33778] = 33763, -- Lifebloom (direct)
[44461] = 44457, -- Living Bomb (direct)
[47960] = 47897, -- Shadowflame (tick)
[81170] = 6785, -- Ravage (Stampede)
[83853] = 11129, -- Combustion (tick)
[88148] = 2120, -- Flamestrike (Improved Flamestrike)
[92315] = 11366, -- Pyroblast (Hot Streak)
}
 
-- spells whose actual effect is the result of a different spell, eg seals, damage shields, used for displaying relevant records in spell tooltips
local indirectSpells = {
[324] = 26364, -- Lightning Shield
[724] = 7001, -- Lightwell
[740] = 44203, -- Tranquility
[772] = 94009, -- Rend
[974] = 379, -- Earth Shield
[1329] = 5374, -- Mutilate
[1535] = 8349, -- Fire Nova
[1949] = 5857, -- Hellfire
[5938] = 5940, -- Shiv
[8024] = 10444, -- Flametongue Weapon
[8033] = 8034, -- Frostbrand Weapon
[8232] = 25504, -- Windfury Weapon
[16857] = 60089, -- Faerie Fire (Feral)
[16914] = 42231, -- Hurricane
[17364] = 32175, -- Stormstrike
[20154] = 25742, -- Seal of Righteousness
[20164] = 20170, -- Seal of Justice
[20165] = 20167, -- Seal of Insight
[20473] = 25912, -- Holy Shock
[22842] = 22845, -- Frenzied Regeneration
[26573] = 81297, -- Consecration
[31801] = 42463, -- Seal of Truth
[31850] = 66235, -- Ardent Defender
[33076] = 33110, -- Prayer of Mending
[43265] = 52212, -- Death and Decay
[47540] = 47666, -- Penance
[47541] = 47632, -- Death Coil (death knight)
[47788] = 48153, -- Guardian Spirit
[48045] = 49821, -- Mind Sear
[51730] = 51945, -- Earthliving
[61882] = 77478, -- Earthquake
[64843] = 64844, -- Divine Hymn
[73920] = 73921, -- Healing Rain
[82327] = 86452, -- Holy Radiance
[88685] = 88686, -- Holy Word: Sanctuary
}
 
-- those that has both a damage and a healing component has their healing spell listed here
local indirectHeals = {
[15237] = 23455, -- Holy Nova
[20473] = 25914, -- Holy Shock
[47540] = 47750, -- Penance
[47541] = 47633, -- Death Coil (death knight)
[49998] = 45470, -- Death Strike
[53385] = 54172, -- Divine Storm
}
 
-- cache of spell ID -> spell name
local spellNameCache = {
-- add form name to hybrid druid abilities, so the user can tell which is cat and which is bear
[33878] = format("%s (%s)", GetSpellInfo(33878)), -- Mangle (Bear Form)
[33876] = format("%s (%s)", GetSpellInfo(33876)), -- Mangle (Cat Form)
[779] = format("%s (%s)", GetSpellInfo(779)), -- Swipe (Bear Form)
[62078] = format("%s (%s)", GetSpellInfo(62078)), -- Swipe (Cat Form)
}
 
-- cache of spell textures
local spellTextureCache = {
-- use a static icon for auto attack (otherwise uses your weapon's icon)
[AUTOATK_ID] = [[Interface\Icons\INV_Sword_04]],
-- "fix" some other misleading icons
[20253] = GetSpellTexture(20252), -- Intercept
[26364] = GetSpellTexture(324), -- use Lightning Shield icon for Lightning Shield damage
[66235] = GetSpellTexture(31850), -- Ardent Defender icon for Ardent Defender heal
}
 
 
local swingDamage = function(amount, _, school, resisted, _, _, critical)
return AUTOATK, amount, resisted, critical, school
return AUTOATK_ID, AUTOATK, amount, resisted, critical, school
end
 
local spellDamage = function(_, spellName, _, amount, _, school, resisted, _, _, critical)
return spellName, amount, resisted, critical, school
local spellDamage = function(spellID, spellName, _, amount, _, school, resisted, _, _, critical)
return spellID, spellName, amount, resisted, critical, school
end
 
local healing = function(_, spellName, _, amount, _, _, critical)
return spellName, amount, 0, critical, 0
local healing = function(spellID, spellName, _, amount, _, _, critical)
return spellID, spellName, amount, 0, critical, 0
end
 
local absorb = function(spellID, spellName, _, _, amount)
return spellID, spellName, amount, 0, critical, 0
end
 
 
local combatEvents = {
SWING_DAMAGE = swingDamage,
RANGE_DAMAGE = spellDamage,
70,60 → 162,59
SPELL_PERIODIC_DAMAGE = spellDamage,
SPELL_HEAL = healing,
SPELL_PERIODIC_HEAL = healing,
SPELL_AURA_APPLIED = absorb,
SPELL_AURA_REFRESH = absorb,
}
 
 
local recordSorters = {
-- alpha: sort by name
alpha = function(a, b)
if a == b then return end
if a.spellName == b.spellName then
-- alpha: sort by name
local alpha = function(a, b)
if a == b then return end
if a.spellName == b.spellName then
if a.spellID == b.spellID then
-- sort DoT entries after non DoT
return b.isPeriodic
return a.periodic < b.periodic
else
return a.spellName < b.spellName
return a.spellID < b.spellID
end
end,
-- crit: sort by crit > normal > name
crit = function(a, b)
if a == b then return end
else
return a.spellName < b.spellName
end
end
 
-- normal: sort by normal > crit > name
local normal = function(a, b)
if a == b then return end
local normalA, normalB = (a.normal and a.normal.amount or 0), (b.normal and b.normal.amount or 0)
if normalA == normalB then
-- equal normal amounts, sort by crit amount instead
local critA, critB = (a.crit and a.crit.amount or 0), (b.crit and b.crit.amount or 0)
if critA == critB then
-- equal crit amounts, sort by normal amount instead
local normalA, normalB = (a.normal and a.normal.amount or 0), (b.normal and b.normal.amount or 0)
if normalA == normalB then
-- equal normal amounts, sort by name instead
if a.spellName == b.spellName then
return b.isPeriodic
else
return a.spellName < b.spellName
end
else
return normalA > normalB
end
-- equal crit amounts too, sort by name instead
return alpha(a, b)
else
return critA > critB
end
end,
-- normal: sort by normal > crit > name
normal = function(a, b)
if a == b then return end
local normalA, normalB = (a.normal and a.normal.amount or 0), (b.normal and b.normal.amount or 0)
if normalA == normalB then
local critA, critB = (a.crit and a.crit.amount or 0), (b.crit and b.crit.amount or 0)
if critA == critB then
if a.spellName == b.spellName then
return b.isPeriodic
else
return a.spellName < b.spellName
end
else
return critA > critB
end
else
return normalA > normalB
end
end,
else
return normalA > normalB
end
end
 
-- crit: sort by crit > normal > name
local crit = function(a, b)
if a == b then return end
local critA, critB = (a.crit and a.crit.amount or 0), (b.crit and b.crit.amount or 0)
if critA == critB then
return normal(a, b)
else
return critA > critB
end
end
 
local recordSorters = {
alpha = alpha,
normal = normal,
crit = crit,
}
 
 
134,7 → 225,24
-- this will hold the text for the summary tooltip
local tooltips = {dmg = {}, heal = {}, pet = {}}
 
-- indicates whether a given tree will need to have its tooltip updated before next use
local doTooltipUpdate = {}
 
-- overall record for each tree
local topRecords = {
dmg = {normal = 0, crit = 0},
heal = {normal = 0, crit = 0},
pet = {normal = 0, crit = 0},
}
 
-- sortable spell tables
local spellArrays = {dmg = {}, heal = {}, pet = {}}
 
 
-- tooltip for level scanning
local tooltip = CreateFrame("GameTooltip", "CritlineTooltip", nil, "GameTooltipTemplate")
 
 
Critline.eventFrame = CreateFrame("Frame")
function Critline:RegisterEvent(event)
self.eventFrame:RegisterEvent(event)
143,6 → 251,7
self.eventFrame:UnregisterEvent(event)
end
Critline:RegisterEvent("ADDON_LOADED")
Critline:RegisterEvent("PLAYER_TALENT_UPDATE")
Critline:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
Critline.eventFrame:SetScript("OnEvent", function(self, event, ...)
return Critline[event] and Critline[event](Critline, ...)
157,6 → 266,7
Critline.options = options
 
local function toggleTree(self)
callbacks:Fire("OnTreeStateChanged", self.setting, self:GetChecked())
local display = Critline.display
if display then
display:UpdateTree(self.setting)
199,9 → 309,15
setting = "PvP",
},
{
text = L["Ignore vulnerability"],
tooltipText = L["Enable to ignore additional damage due to vulnerability."],
setting = "ignoreVulnerability",
},
{
text = L["Chat output"],
tooltipText = L["Prints new record notifications to the chat frame."],
setting = "chatOutput",
newColumn = true,
},
{
text = L["Play sound"],
212,19 → 328,25
text = L["Screenshot"],
tooltipText = L["Saves a screenshot on a new record."],
setting = "screenshot",
newColumn = true,
},
{
text = L["Shorten records"],
tooltipText = L["Use shorter format for records numbers."],
setting = "shortFormat",
func = function(self) callbacks:Fire("OnNewTopRecord") Critline:UpdateTooltips() end,
gap = 16,
},
{
text = L["Records in spell tooltips"],
tooltipText = L["Include (unfiltered) records in spell tooltips."],
setting = "spellTooltips",
},
{
text = L["Detailed tooltip"],
tooltipText = L["Use detailed format in the summary tooltip."],
setting = "detailedTooltip",
func = function(self) Critline:UpdateTooltips() end,
},
{
text = L["Ignore vulnerability"],
tooltipText = L["Enable to ignore additional damage due to vulnerability."],
setting = "ignoreVulnerability",
},
}
 
options.checkButtons = checkButtons
246,11 → 368,6
 
-- summary sort dropdown
local menu = {
onClick = function(self)
self.owner:SetSelectedValue(self.value)
Critline.db.profile.tooltipSort = self.value
Critline:UpdateTooltips()
end,
{
text = L["Alphabetically"],
value = "alpha",
269,21 → 386,24
sorting:SetFrameWidth(120)
sorting:SetPoint("TOPLEFT", checkButtons[#checkButtons], "BOTTOMLEFT", -15, -24)
sorting.label:SetText(L["Tooltip sorting:"])
 
sorting.onClick = function(self)
self.owner:SetSelectedValue(self.value)
Critline.db.profile.tooltipSort = self.value
Critline:UpdateTooltips()
end
options.tooltipSort = sorting
end
 
 
Critline.SlashCmdHandlers = {
debug = function() Critline:ToggleDebug() end,
}
 
SlashCmdList.CRITLINE = function(msg)
msg = msg:trim():lower()
if msg == "debug" then
Critline:ToggleDebug()
elseif msg == "reset" then
local display = Critline.display
if display then
display:ClearAllPoints()
display:SetPoint("CENTER")
end
local slashCmdHandler = Critline.SlashCmdHandlers[msg]
if slashCmdHandler then
slashCmdHandler()
else
Critline:OpenConfig()
end
297,11 → 417,13
profile = {
PvE = true,
PvP = true,
ignoreVulnerability = true,
chatOutput = false,
sound = false,
screenshot = false,
shortFormat = false,
spellTooltips = false,
detailedTooltip = false,
ignoreVulnerability = true,
tooltipSort = "normal",
},
}
356,18 → 478,6
self:UnregisterEvent("ADDON_LOADED")
callbacks:Fire("AddonLoaded")
 
-- import old records and mob filter
if CritlineSettings then
for i, v in ipairs({"dmg","heal","pet"}) do
percharDB.profile.spells[v] = CritlineDB[v]
CritlineDB[v] = nil
end
 
if self.filters and CritlineMobFilter then
self.filters.db.global.mobs = CritlineMobFilter
end
end
 
self:LoadSettings()
self:LoadPerCharSettings()
 
376,12 → 486,105
end
 
 
function Critline:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, ...)
-- import native spells to new database format (4.0)
function Critline:PLAYER_TALENT_UPDATE()
if GetMajorTalentTreeBonuses(1) then
self:UnregisterEvent("PLAYER_TALENT_UPDATE")
self.PLAYER_TALENT_UPDATE = nil
else
return
end
 
local tooltip = CreateFrame("GameTooltip", "CritlineImportScanTooltip", nil, "GameTooltipTemplate")
 
local function getID(query)
local link = GetSpellLink(query)
if link then
return tonumber(link:match("spell:(%d+)"))
end
for tab = 1, 3 do
local id = GetMajorTalentTreeBonuses(tab)
if GetSpellInfo(id) == query then
return id
end
for i = 1, GetNumTalents(tab) do
local name, _, _, _, _, _, _, _, _, isExceptional = GetTalentInfo(tab, i)
if name == query and isExceptional then
tooltip:SetOwner(UIParent)
tooltip:SetTalent(tab, i)
return select(3, tooltip:GetSpell())
end
end
end
end
 
for k, profile in pairs(self.percharDB.profiles) do
if profile.spells then
for k, tree in pairs(profile.spells) do
local spells = {}
for i, spell in pairs(tree) do
if not spell.spellName then
return
end
local id = getID(spell.spellName)
id = (tree == heal and indirectHeals[id]) or indirectSpells[id] or id
if id and (spell.normal or spell.crit) then
spells[id] = spells[id] or {}
spells[id][spell.isPeriodic and 2 or 1] = spell
spell.spellName = nil
spell.isPeriodic = nil
end
end
profile.spells[k] = spells
end
end
end
 
tooltip:Hide()
 
-- invert filter flag on all spells if inverted filter was enabled
if self.filters then
if self.filters.db.profile.invertFilter then
for k, profile in pairs(self.percharDB.profiles) do
if profile.spells then
for k, tree in pairs(profile.spells) do
for i, spell in pairs(tree) do
for i, spell in pairs(spell) do
spell.filtered = not spell.filtered
end
end
end
end
end
end
for k, profile in pairs(self.filters.db.profiles) do
profile.invertFilter = nil
end
end
 
self:LoadPerCharSettings()
end
 
 
local TOC
local dummyTable = {}
do
-- Because GetBuildInfo() still returns 40000 on the PTR
local major, minor, rev = strsplit(".", (GetBuildInfo()))
TOC = major * 10000 + minor * 100
end
 
function Critline:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, ...)
-- we seem to get events with standard arguments equal to nil, so they need to be ignored
if not (timestamp and eventType) then
self:Debug("nil errors on start")
return
end
 
if TOC < 40100 and hideCaster ~= dummyTable then
self:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, dummyTable, hideCaster, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, ...)
return
end
 
-- if we don't have a destName (who we hit or healed) and we don't have a sourceName (us or our pets) then we leave
if not (destName or sourceName) then
389,13 → 592,6
return
end
 
-- check for filtered auras
if self.filters then
if self.filters:IsAuraEvent(eventType, ..., sourceFlags, destFlags, destGUID) then
return
end
end
 
local isPet
 
-- if sourceGUID is not us or our pet, we leave
403,7 → 599,7
local isMyPet = CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_MY_PET)
local isGuardian = band(sourceFlags, COMBATLOG_OBJECT_TYPE_GUARDIAN) ~= 0
-- only register if it's a real pet, or a guardian tree pet that's included in the filter
if isMyPet and ((not isGuardian and HasPetUI()) or classPets[tonumber(sourceGUID:sub(6, 12), 16)]) then
if isMyPet and ((not isGuardian and HasPetUI()) or classPets[tonumber(sourceGUID:sub(7, 10), 16)]) then
isPet = true
-- self:Debug(format("This is my pet (%s)", sourceName))
else
418,23 → 614,35
return
end
 
local isHeal
local isPeriodic
if eventType:find("_HEAL$") then
isHeal = true
if isPet then
return
end
local periodic = 1
local isHeal = eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" or eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH"
-- we don't care about healing done by the pet
if isHeal and isPet then
self:Debug("Pet healing. Return.")
return
end
if eventType:find("_PERIODIC_") then
if eventType == "SPELL_PERIODIC_DAMAGE" or eventType == "SPELL_PERIODIC_HEAL" then
isPeriodic = true
periodic = 2
end
 
local spellName, amount, resisted, critical, school = combatEvents[eventType](...)
-- get the relevants arguments
local spellID, spellName, amount, resisted, critical, school = combatEvents[eventType](...)
 
-- below are some checks to see if we want to register the hit at all
local similarSpell = similarSpells[spellID]
if similarSpell then
spellID = similarSpell
spellName = self:GetSpellName(similarSpell)
end
 
-- return if the event has no amount (non-absorbing aura applied)
if not amount then
return
end
 
if amount <= 0 then
self:Debug(format("Amount <= 0. (%s) Return.", self:GetFullSpellName(tree, spellName, isPeriodic)))
self:Debug(format("Amount <= 0. (%s) Return.", self:GetFullSpellName(spellID, periodic)))
return
end
 
446,9 → 654,10
tree = "heal"
end
 
local passed, isFiltered, targetLevel
local targetLevel = self:GetLevelFromGUID(destGUID)
local passed, isFiltered
if self.filters then
passed, isFiltered, targetLevel = self.filters:SpellPassesFilters(tree, spellName, ..., isPeriodic, destGUID, destName, school)
passed, isFiltered = self.filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school, targetLevel)
if not passed then
return
end
480,12 → 689,6
return
end
 
-- we only want damage spells from the pet
if isHeal and isPet then
self:Debug("Pet healing. Return.")
return
end
 
-- exit if not recording tree dmg
if not self.percharDB.profile[tree] then
self:Debug(format("Not recording this tree (%s). Return.", tree))
499,42 → 702,72
end
 
local hitType = critical and "crit" or "normal"
local data = self:GetSpellInfo(tree, spellID, periodic)
local arrayData
 
local data = self:GetSpellInfo(tree, spellName, isPeriodic)
 
-- create spell database entries as required
if not data then
self:Debug(format("Creating data for %s (%s)", self:GetFullSpellName(tree, spellName, isPeriodic), tree))
data = {
spellName = spellName,
isPeriodic = isPeriodic,
}
self:AddSpell(tree, data)
self:Debug(format("Creating data for %s (%s)", self:GetFullSpellName(spellID, periodic), tree))
data, arrayData = self:AddSpell(tree, spellID, periodic, spellName, isFiltered)
self:UpdateSpells(tree)
end
 
if not data[hitType] then
data[hitType] = {amount = 0}
(arrayData or self:GetSpellArrayEntry(tree, spellID, periodic))[hitType] = data[hitType]
end
 
data = data[hitType]
 
-- if new amount is larger than the stored amount we'll want to store it
if amount > data.amount then
local oldAmount = data.amount
if not isFiltered then
self:NewRecord(self:GetFullSpellName(spellID, periodic, true), amount, critical)
 
-- update the highest record if needed
local topRecords = topRecords[tree]
if amount > topRecords[hitType] then
topRecords[hitType] = amount
callbacks:Fire("OnNewTopRecord", tree)
end
end
callbacks:Fire("NewRecord", tree, spellID, periodic, amount, critical, data, isFiltered)
 
data.amount = amount
data.target = destName
data.targetLevel = targetLevel
data.isPvPTarget = isPlayer
 
if not isFiltered then
self:NewRecord(self:GetFullSpellName(tree, spellName, isPeriodic), amount, critical, oldAmount)
end
 
self:UpdateRecords(tree, isFiltered)
end
end
 
 
function Critline:GetLevelFromGUID(destGUID)
tooltip:SetOwner(UIParent, "ANCHOR_NONE")
tooltip:SetHyperlink("unit:"..destGUID)
 
local level = -1
 
for i = 1, tooltip:NumLines() do
local text = _G["CritlineTooltipTextLeft"..i]:GetText()
if text then
if text:match(LEVEL) then -- our destGUID has the word Level in it.
level = text:match("(%d+)") -- find the level
if level then -- if we found the level, break from the for loop
level = tonumber(level)
else
-- well, the word Level is in this tooltip, but we could not find the level
-- either the destGUID is at least 10 levels higher than us, or we just couldn't find it.
level = -1
end
end
end
end
return level
end
 
 
function Critline:Message(msg)
if msg then
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00Critline:|r "..msg)
574,8 → 807,25
 
 
function Critline:LoadPerCharSettings()
for tree in pairs(treeNames) do
wipe(spellArrays[tree])
for spellID, spell in pairs(self.percharDB.profile.spells[tree]) do
for i, v in pairs(spell) do
if type(v) ~= "table" or v.spellName then return end -- avoid error in pre 4.0 DB
spellArrays[tree][#spellArrays[tree] + 1] = {
spellID = spellID,
spellName = self:GetSpellName(spellID),
filtered = v.filtered,
periodic = i,
normal = v.normal,
crit = v.crit,
}
end
end
end
 
callbacks:Fire("PerCharSettingsLoaded")
 
self:UpdateTopRecords()
self:UpdateTooltips()
 
for _, btn in ipairs(self.options.checkButtons.percharDB) do
584,9 → 834,7
end
 
 
function Critline:NewRecord(spell, amount, crit, oldAmount)
callbacks:Fire("NewRecord", spell, amount, crit, oldAmount)
 
function Critline:NewRecord(spell, amount, crit)
if self.db.profile.chatOutput then
self:Message(format(L["New %s%s record - %d"], crit and L["critical "] or "", spell, amount))
end
601,29 → 849,77
end
 
 
-- return spell table from database, given tree, spell name and isPeriodic value
function Critline:GetSpellInfo(tree, spellName, periodic)
for i, v in ipairs(self.percharDB.profile.spells[tree]) do
if v.spellName == spellName and v.isPeriodic == periodic then
return v, i
local FIRST_NUMBER_CAP = FIRST_NUMBER_CAP:lower()
 
function Critline:ShortenNumber(amount)
if tonumber(amount) and self.db.profile.shortFormat then
if amount >= 1e7 then
amount = (floor(amount / 1e5) / 10)..SECOND_NUMBER_CAP
elseif amount >= 1e6 then
amount = (floor(amount / 1e4) / 100)..SECOND_NUMBER_CAP
elseif amount >= 1e4 then
amount = (floor(amount / 100) / 10)..FIRST_NUMBER_CAP
end
end
return amount
end
 
 
function Critline:GetFullSpellName(tree, spellName, isPeriodic)
local suffix = ""
if isPeriodic then
if tree == "heal" then
suffix = L[" (HoT)"]
else
suffix = L[" (DoT)"]
function Critline:GetSpellArrayEntry(tree, spellID, periodic)
for i, v in ipairs(spellArrays[tree]) do
if v.spellID == spellID and v.periodic == periodic then
return v
end
end
return format("%s%s", spellName, suffix)
end
 
 
-- local previousTree
-- local previousSort
 
function Critline:GetSpellArray(tree, useProfileSort)
local array = spellArrays[tree]
local sortMethod = useProfileSort and self.db.profile.tooltipSort or "alpha"
-- no need to sort if it's already sorted the way we want it
-- if sortMethod ~= previousSort or tree ~= previousTree then
sort(array, recordSorters[sortMethod])
-- previousTree = tree
-- previousSort = sortMethod
-- end
return array
end
 
 
-- return spell table from database, given tree, spell name and isPeriodic value
function Critline:GetSpellInfo(tree, spellID, periodic)
local spell = self.percharDB.profile.spells[tree][spellID]
return spell and spell[periodic]
end
 
 
function Critline:GetSpellName(spellID)
local spellName = spellNameCache[spellID] or GetSpellInfo(spellID)
spellNameCache[spellID] = spellName
return spellName
end
 
 
function Critline:GetSpellTexture(spellID)
local spellTexture = spellTextureCache[spellID] or GetSpellTexture(spellID)
spellTextureCache[spellID] = spellTexture
return spellTexture
end
 
 
function Critline:GetFullSpellName(spellID, periodic, verbose)
local spellName = self:GetSpellName(spellID)
if periodic == 2 then
spellName = format("%s (%s)", spellName, verbose and L["tick"] or "*")
end
return spellName
end
 
 
function Critline:GetFullTargetName(spell)
local suffix = ""
if spell.isPvPTarget then
634,44 → 930,94
 
 
-- retrieves the top, non filtered record amounts and spell names for a given tree
function Critline:GetHighest(tree)
function Critline:UpdateTopRecords(tree)
if not tree then
for tree in pairs(topRecords) do
self:UpdateTopRecords(tree)
end
return
end
 
local normalRecord, critRecord = 0, 0
local normalSpell, critSpell
 
for _, data in ipairs(self.percharDB.profile.spells[tree]) do
if not (self.filters and self.filters:IsFilteredSpell(tree, data.spellName, data.isPeriodic)) then
local normal = data.normal
if normal and normal.amount > normalRecord then
normalRecord = normal.amount
normalSpell = data.spellName
for spellID, spell in pairs(self.percharDB.profile.spells[tree]) do
for i, v in pairs(spell) do
if type(v) ~= "table" then return end -- avoid error in pre 4.0 DB
if not (self.filters and v.filtered) then
local normal = v.normal
if normal then
normalRecord = max(normal.amount, normalRecord)
end
local crit = v.crit
if crit then
critRecord = max(crit.amount, critRecord)
end
end
local crit = data.crit
if crit and crit.amount > critRecord then
critRecord = crit.amount
critSpell = data.spellName
end
end
end
return normalRecord, critRecord, normalSpell, critSpell
local topRecords = topRecords[tree]
topRecords.normal = normalRecord
topRecords.crit = critRecord
 
callbacks:Fire("OnNewTopRecord", tree)
end
 
 
function Critline:AddSpell(tree, spell)
-- retrieves the top, non filtered record amounts and spell names for a given tree
function Critline:GetHighest(tree)
local topRecords = topRecords[tree]
return topRecords.normal, topRecords.crit
end
 
 
function Critline:AddSpell(tree, spellID, periodic, spellName, filtered)
local spells = self.percharDB.profile.spells[tree]
tinsert(spells, spell)
sort(spells, recordSorters.alpha)
 
local spell = spells[spellID] or {}
spells[spellID] = spell
spell[periodic] = {filtered = filtered}
 
local spellArray = spellArrays[tree]
local arrayData = {
spellID = spellID,
spellName = spellName,
filtered = filtered,
periodic = periodic,
}
spellArray[#spellArray + 1] = arrayData
 
return spell[periodic], arrayData
end
 
 
function Critline:DeleteSpell(tree, index)
tremove(self.percharDB.profile.spells[tree], index)
function Critline:DeleteSpell(tree, spellID, periodic)
do
local tree = self.percharDB.profile.spells[tree]
local spell = tree[spellID]
spell[periodic] = nil
 
-- remove this entire spell entry if neither direct nor tick entries remain
if not spell[3 - periodic] then
tree[spellID] = nil
end
end
 
for i, v in ipairs(spellArrays[tree]) do
if v.spellID == spellID and v.periodic == periodic then
tremove(spellArrays[tree], i)
self:Message(format(L["Reset %s (%s) records."], self:GetFullSpellName(v.spellID, v.periodic), treeNames[tree]))
break
end
end
 
self:UpdateTopRecords(tree)
end
 
 
-- this "fires" when spells are added to/removed from the database
function Critline:UpdateSpells(tree)
if tree then
self:UpdateTooltip(tree)
doTooltipUpdate[tree] = true
callbacks:Fire("SpellsChanged", tree)
else
for k in pairs(tooltips) do
684,7 → 1030,7
-- this "fires" when a new record has been registered
function Critline:UpdateRecords(tree, isFiltered)
if tree then
self:UpdateTooltip(tree)
doTooltipUpdate[tree] = true
callbacks:Fire("RecordsChanged", tree, isFiltered)
else
for k in pairs(tooltips) do
694,12 → 1040,39
end
 
 
function Critline:UpdateTooltips()
for k in pairs(tooltips) do
doTooltipUpdate[k] = true
end
end
 
 
local LETHAL_LEVEL = "??"
local leftFormat = "|cffc0c0c0%s:|r %s"
local leftFormatIndent = leftFormat
local rightFormat = format("%s%%s|r (%%s)", HIGHLIGHT_FONT_COLOR_CODE)
local recordFormat = format("%s%%s|r", GREEN_FONT_COLOR_CODE)
local r, g, b = HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b
 
function Critline:ShowTooltip(tree)
GameTooltip:AddLine("Critline "..treeNames[tree], HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
for i, v in ipairs(tooltips[tree]) do
local left, right = v:match("(.+)\t(.+)")
if left and right then
GameTooltip:AddDoubleLine(left, right)
if doTooltipUpdate[tree] then
self:UpdateTooltip(tree)
end
local r, g, b = r, g, b
local rR, gR, bR
GameTooltip:AddLine("Critline "..treeNames[tree], r, g, b)
if not self.db.profile.detailedTooltip then
-- advanced tooltip uses different text color
rR, gR, bR = r, g, b
r, g, b = nil
end
local tooltip = tooltips[tree]
for i = 1, #tooltips[tree] do
local v = tooltip[i]
-- v is either an array containing the left and right tooltip strings, or a single string
if type(v) == "table" then
local left, right = unpack(v)
GameTooltip:AddDoubleLine(left, right, r, g, b, rR, gR, bR)
else
GameTooltip:AddLine(v)
end
708,88 → 1081,152
end
 
 
function Critline:UpdateTooltips()
for k in pairs(tooltips) do
self:UpdateTooltip(k)
function Critline:UpdateTooltip(tree)
local tooltip = tooltips[tree]
wipe(tooltip)
 
local normalRecord, critRecord = self:GetHighest(tree)
local n = 1
 
for _, v in ipairs(self:GetSpellArray(tree, true)) do
if not (self.filters and self:GetSpellInfo(tree, v.spellID, v.periodic).filtered) then
local spellName = self:GetFullSpellName(v.spellID, v.periodic)
 
-- if this is a DoT/HoT, and a direct entry exists, add the proper suffix
-- if v.periodic == 2 and not (self.filters and self.filters:IsFilteredSpell(tree, v.spellID, 1)) then
-- spellName = self:GetFullSpellName(v.spellID, 2)
-- end
 
if self.db.profile.detailedTooltip then
tooltip[n] = spellName
n = n + 1
tooltip[n] = {self:GetTooltipLine(v, "normal", tree)}
n = n + 1
tooltip[n] = {self:GetTooltipLine(v, "crit", tree)}
else
local normalAmount, critAmount = 0, 0
 
-- color the top score amount green
local normal = v.normal
if normal then
normalAmount = self:ShortenNumber(normal.amount)
normalAmount = normal.amount == normalRecord and GREEN_FONT_COLOR_CODE..normalAmount..FONT_COLOR_CODE_CLOSE or normalAmount
end
 
local crit = v.crit
if crit then
critAmount = self:ShortenNumber(crit.amount)
critAmount = crit.amount == critRecord and GREEN_FONT_COLOR_CODE..critAmount..FONT_COLOR_CODE_CLOSE or critAmount
end
 
tooltip[n] = {spellName, crit and format("%s / %s", normalAmount, critAmount) or normalAmount}
end
 
n = n + 1
end
end
 
if #tooltip == 0 then
tooltip[1] = L["No records"]
end
 
doTooltipUpdate[tree] = nil
end
 
 
local sortedSpells = {}
local hitTypes = {
normal = L["Normal"],
crit = L["Crit"],
}
 
function Critline:UpdateTooltip(tree)
local line = " |cffc0c0c0%s:|r %s\t%s (%s)"
function Critline:GetTooltipLine(data, hitType, tree)
local leftFormat = tree and " "..leftFormat or leftFormat
data = data and data[hitType]
if data then
local amount = self:ShortenNumber(data.amount)
if tree and data.amount == topRecords[tree][hitType] then
amount = format(recordFormat, amount)
end
local level = data.targetLevel
level = level > 0 and level or LETHAL_LEVEL
return format(leftFormat, hitTypes[hitType], amount), format(rightFormat, self:GetFullTargetName(data), level), r, g, b
end
end
 
 
function Critline:AddTooltipLine(data, tree)
GameTooltip:AddDoubleLine(self:GetTooltipLine(data, "normal", tree))
GameTooltip:AddDoubleLine(self:GetTooltipLine(data, "crit", tree))
end
 
 
local funcset = {}
 
for k in pairs(treeNames)do
funcset[k] = function(spellID, num)
local data = Critline:GetSpellInfo(k, spellID, num)
return not (Critline.filters and data and data.filtered) and data
end
end
 
local function addLine(header, nonTick, tick)
if header then
GameTooltip:AddLine(L[header])
end
Critline:AddTooltipLine(nonTick)
if tick and nonTick then
GameTooltip:AddLine(" ")
GameTooltip:AddLine(L["Tick"])
end
Critline:AddTooltipLine(tick)
end
 
GameTooltip:HookScript("OnTooltipSetSpell", function(self)
if self.Critline or not Critline.db.profile.spellTooltips then
return
end
 
wipe(sortedSpells)
local spellName, rank, spellID = self:GetSpell()
 
local n = 1
local indirectSpell = indirectSpells[spellID]
local indirectHeal = indirectHeals[spellID]
 
for i, v in ipairs(self.percharDB.profile.spells[tree]) do
if (v.normal or v.crit) and not (self.filters and self.filters:IsFilteredSpell(tree, v.spellName, v.isPeriodic)) then
sortedSpells[n] = {
spellName = v.spellName,
isPeriodic = v.isPeriodic,
normal = v.normal,
crit = v.crit,
}
n = n + 1
end
end
local dmg1 = funcset.dmg(indirectSpell or spellID, 1)
local dmg2 = funcset.dmg(indirectSpell or spellID, 2)
local dmg = dmg1 or dmg2
 
sort(sortedSpells, recordSorters[self.db.profile.tooltipSort])
local heal1 = funcset.heal(indirectHeal or indirectSpell or spellID, 1)
local heal2 = funcset.heal(indirectHeal or indirectSpell or spellID, 2)
local heal = heal1 or heal2
 
local tooltip = tooltips[tree]
wipe(tooltip)
local pet1 = funcset.pet(indirectSpell or spellID, 1)
local pet2 = funcset.pet(indirectSpell or spellID, 2)
local pet = pet1 or pet2
 
local normalRecord, critRecord = self:GetHighest(tree)
n = 1
if dmg or heal or pet then
self:AddLine(" ")
end
 
for _, v in ipairs(sortedSpells) do
-- if this is a DoT/HoT, and a direct entry exists, add the proper suffix
if v.isPeriodic then
for _, v2 in ipairs(sortedSpells) do
if v2.spellName == v.spellName and not v2.isPeriodic then
v.spellName = self:GetFullSpellName(tree, v.spellName, true)
break
end
end
if dmg then
addLine((heal or pet) and "Damage", dmg1, dmg2)
end
 
if heal then
if dmg then
GameTooltip:AddLine(" ")
end
 
local normalAmount, critAmount = HIGHLIGHT_FONT_COLOR_CODE..(0)..FONT_COLOR_CODE_CLOSE, HIGHLIGHT_FONT_COLOR_CODE..(0)..FONT_COLOR_CODE_CLOSE
 
-- color the top score amount green
local normal = v.normal
if normal then
normalAmount = (normal.amount == normalRecord and GREEN_FONT_COLOR_CODE or HIGHLIGHT_FONT_COLOR_CODE)..normal.amount..FONT_COLOR_CODE_CLOSE
end
 
local crit = v.crit
if crit then
critAmount = (crit.amount == critRecord and GREEN_FONT_COLOR_CODE or HIGHLIGHT_FONT_COLOR_CODE)..crit.amount..FONT_COLOR_CODE_CLOSE
end
 
if self.db.profile.detailedTooltip then
tooltip[n] = v.spellName
if normal then
n = n + 1
local target = HIGHLIGHT_FONT_COLOR_CODE..self:GetFullTargetName(normal)..FONT_COLOR_CODE_CLOSE
local level = (normal.targetLevel > 0) and normal.targetLevel or "??"
tooltip[n] = format(line, L["Normal"], normalAmount, target, level)
end
if crit then
n = n + 1
local target = HIGHLIGHT_FONT_COLOR_CODE..self:GetFullTargetName(crit)..FONT_COLOR_CODE_CLOSE
local level = (crit.targetLevel > 0) and crit.targetLevel or "??"
tooltip[n] = format(line, L["Crit"], critAmount, target, level)
end
else
tooltip[n] = format("%s\t%s%s", v.spellName, normalAmount, (crit and "/"..critAmount or ""))
end
 
n = n + 1
addLine((dmg or pet) and "Healing", heal1, heal2)
end
 
if #tooltip == 0 then
tooltip[1] = L["No records"]
if pet then
if dmg or heal then
GameTooltip:AddLine(" ")
end
addLine((dmg or heal) and "Pet", pet1, pet2)
end
end
\ No newline at end of file +end) + + +GameTooltip:HookScript("OnTooltipCleared", function(self) + self.Critline = nil +end) \ No newline at end of file
trunk/filters.lua
3,16 → 3,16
local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
local templates = addon.templates
 
local IsSpellKnown = IsSpellKnown
local UnitAura = UnitAura
local UnitName = UnitName
local UnitGUID = UnitGUID
local CombatLog_Object_IsA = CombatLog_Object_IsA
local band = bit.band
 
local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME
local COMBATLOG_OBJECT_REACTION_FRIENDLY = COMBATLOG_OBJECT_REACTION_FRIENDLY
 
-- amount of buttons in the spell, mob and aura filter scroll lists
local NUMSPELLBUTTONS = 8
local SPELLBUTTONHEIGHT = 22
local NUMFILTERBUTTONS = 10
local FILTERBUTTONHEIGHT = 16
 
-- mobs whose received hits won't be tracked due to various vulnerabilities
local specialMobs = {
[12460] = true, -- Death Talon Wyrmguard
23,15 → 23,14
[16803] = true, -- Death Knight Understudy
[22841] = true, -- Shade of Akama
[33329] = true, -- Heart of the Deconstructor
[33670] = true, -- Aerial Command Unit
[34496] = true, -- Eydis Darkbane
[34497] = true, -- Fjola Lightbane
[34797] = true, -- Icehowl
[38567] = true, -- Phantom Hallucination
[39698] = true, -- Karsh Steelbender
[40484] = true, -- Erudax
[40793] = true, -- Ragnaros (Mount Hyjal)
[42347] = true, -- Exposed Head of Magmaw (Point of Vulnerability [79011]) *
[42803] = true, -- Drakeadon Mongrel (Brood Power: Red/Green/Black/Blue/Bronze [80368+80369+80370+80371+80372])
[46083] = true, -- Drakeadon Mongrel (Brood Power: Red/Green/Black/Blue/Bronze [80368+80369+80370+80371+80372])
[46273] = true, -- Debilitated Apexar
[48270] = true, -- Exposed Head of Magmaw (Point of Vulnerability [79011])
}
 
-- auras that when gained will suppress record tracking
71,12 → 70,39
[76159] = true, -- Pyrogenics (Sun-Touched Spriteling)
[76355] = true, -- Blessing of the Sun (Rajh)
[76693] = true, -- Empowering Twilight (Crimsonborne Warlord)
[79624] = true, -- Power Generator (Arcanotron) ?
[81096] = true, -- Red Mist (Red Mist)
[86622] = true, -- Engulfing Magic (Theralion) ?
[86872] = true, -- Frothing Rage (Thundermar Ale)
[89879] = true, -- Blessing of the Sun (Rajh - heroic)
[90933] = true, -- Ragezone (Defias Blood Wizard)
[91871] = true, -- Lightning Charge (Siamat)
[93777] = true, -- Invocation of Flame (Skullcrusher the Mountain)
[95639] = true, -- Engulfing Magic (Theralion) ?
[95640] = true, -- Engulfing Magic (Theralion) ?
[95641] = true, -- Engulfing Magic (Theralion) ?
}
 
-- these are auras that increases the target's damage or healing received
local targetAuras = {
[64436] = true, -- Magnetic Core (Aerial Command Unit) ?
[66758] = true, -- Staggered Daze (Icehowl) ?
[75664] = true, -- Shadow Gale (Erudax) ?
[75846] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
[76015] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
[76232] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ?
[77717] = true, -- Vertigo (Atramedes)
[80157] = true, -- Chemical Bomb (Toxitron) ?
[87683] = true, -- Dragon's Vengeance (Halfus Wyrmbreaker)
[87904] = true, -- Feedback (Al'Akir)
[90933] = true, -- Ragezone (Defias Blood Wizard) ?
[91086] = true, -- Shadow Gale (Erudax - heroic)
[92390] = true, -- Vertigo (Atramedes) ?
[92910] = true, -- Debilitating Slime (Maloriak) ?
[93567] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ?
[95723] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ?
}
 
-- these heals are treated as periodic, but has no aura associated with them, or is associated to an aura with a different name, need to add exceptions for them to filter properly
local directHoTs = {
[54172] = true, -- Divine Storm
85,8 → 111,36
 
local activeAuras = {}
local corruptSpells = {}
local corruptTargets = {}
 
local playerAuras = {
session = {},
instance = {},
lastFight = {},
}
local enemyAuras = {
session = {},
instance = {},
lastFight = {},
}
 
-- name of current instance
local currentInstance = L["n/a"]
 
-- amount of buttons in the spell, mob and aura filter scroll lists
local NUMSPELLBUTTONS = 8
local SPELLBUTTONHEIGHT = 22
local NUMFILTERBUTTONS = 10
local FILTERBUTTONHEIGHT = 16
 
 
local filters = templates:CreateConfigFrame(FILTERS, addonName, true)
filters:SetScript("OnEvent", function(self, event, ...)
return self[event] and self[event](self, ...)
end)
addon.filters = filters
 
 
local function filterButtonOnClick(self)
local module = self.module
local scrollFrame = module.scrollFrame
188,7 → 242,7
local filterName = scrollFrame.filter
local selection = scrollFrame.selected
if selection then
local filter = self.filters.db.global[filterName]
local filter = filters.db.global[filterName]
local selectedEntry = filter[selection]
tremove(filter, selection)
local prevHighlight = scrollFrame.buttons[selection - FauxScrollFrame_GetOffset(scrollFrame)]
233,7 → 287,6
delete:Disable()
delete:SetScript("OnClick", deleteButtonOnClick)
delete.scrollFrame = scrollFrame
delete.filters = parent
frame.delete = delete
end
 
241,24 → 294,15
end
 
 
-- tooltip for level scanning
local tooltip = CreateFrame("GameTooltip", "CritlineTooltip", nil, "GameTooltipTemplate")
 
 
local filters = templates:CreateConfigFrame(FILTERS, addonName, true)
addon.filters = filters
 
 
do
local options = {}
filters.options = options
 
local checkButtons = {
{
text = L["Invert spell filter"],
tooltipText = L["Enable to include rather than exclude selected spells in the spell filter."],
setting = "invertFilter",
func = function(self) addon:UpdateRecords() end,
text = L["Filter new spells"],
tooltipText = L["Enable to filter out new spell entries by default."],
setting = "filterNew",
},
{
text = L["Ignore mob filter"],
331,19 → 375,24
spellFilter:SetPoint("LEFT", 48, 0)
spellFilter:SetPoint("RIGHT", -48, 0)
filterTypes.spell = spellFilter
 
 
do -- spell filter buttons
local function spellButtonOnClick(self)
local module = self.module
if self:GetChecked() then
PlaySound("igMainMenuOptionCheckBoxOn")
module:AddSpell(module.spell.tree:GetSelectedValue(), self.spell, self.isPeriodic)
else
PlaySound("igMainMenuOptionCheckBoxOff")
module:DeleteSpell(module.spell.tree:GetSelectedValue(), self.spell, self.isPeriodic)
end
local checked = self:GetChecked() == 1
PlaySound(checked and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff")
filters:FilterSpell(not checked, filters.spell.tree:GetSelectedValue(), self.data)
end
 
local function spellButtonOnEnter(self)
-- prevent records being added twice
GameTooltip.Critline = true
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetSpellByID(self.data.spellID)
GameTooltip:AddLine(" ")
addon:AddTooltipLine(self.data)
GameTooltip:Show()
end
 
local buttons = {}
for i = 1, NUMSPELLBUTTONS do
local btn = templates:CreateCheckButton(spellFilter)
353,7 → 402,7
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4)
end
btn:SetScript("OnClick", spellButtonOnClick)
btn.module = filters
btn:SetScript("OnEnter", spellButtonOnEnter)
buttons[i] = btn
end
spellFilter.scrollFrame.buttons = buttons
364,12 → 413,6
 
-- spell filter tree dropdown
local menu = {
onClick = function(self)
self.owner:SetSelectedValue(self.value)
FauxScrollFrame_SetOffset(spellScrollFrame, 0)
spellScrollFrame.scrollBar:SetValue(0)
spellScrollFrame:Update()
end,
{text = L["Damage"], value = "dmg"},
{text = L["Healing"], value = "heal"},
{text = L["Pet"], value = "pet"},
379,6 → 422,12
spellFilterTree:SetFrameWidth(120)
spellFilterTree:SetPoint("BOTTOMRIGHT", spellFilter, "TOPRIGHT", 16, 0)
spellFilterTree:SetSelectedValue("dmg")
spellFilterTree.onClick = function(self)
self.owner:SetSelectedValue(self.value)
FauxScrollFrame_SetOffset(spellScrollFrame, 0)
spellScrollFrame.scrollBar:SetValue(0)
spellScrollFrame:Update()
end
spellFilter.tree = spellFilterTree
spellScrollFrame.tree = spellFilter.tree
 
441,6 → 490,12
add:SetPoint("TOPLEFT", auraFilter, "BOTTOMLEFT", 0, -8)
add:SetText(L["Add by spell ID"])
add.popup = "CRITLINE_ADD_AURA_BY_ID"
 
-- local addAura = templates:CreateButton(auraFilter)
-- addAura:SetSize(48, 22)
-- addAura:SetPoint("TOP", auraFilter, "BOTTOM")
-- addAura:SetText("Add")
-- addAura:SetScript("OnClick", function() if auraList:IsShown() then auraList:Hide() else auraList:Show() end end)
 
local delete = auraFilter.delete
delete:SetSize(128, 22)
457,16 → 512,6
 
do -- filter tree dropdown
local menu = {
onClick = function(self)
self.owner:SetSelectedValue(self.value)
for k, v in pairs(filterTypes) do
if k == self.value then
v:Show()
else
v:Hide()
end
end
end,
{
text = L["Spell filter"],
value = "spell",
485,11 → 530,361
filterType:SetPoint("BOTTOMLEFT", spellFilter, "TOPLEFT", -16, 0)
filterType:SetFrameWidth(120)
filterType:SetSelectedValue("spell")
filterType.onClick = function(self)
self.owner:SetSelectedValue(self.value)
for k, v in pairs(filterTypes) do
if k == self.value then
v:Show()
else
v:Hide()
end
end
end
filters.type = filterType
end
end
 
 
do
local auraList = CreateFrame("Frame", nil, UIParent)
auraList:SetFrameStrata("DIALOG")
auraList:EnableMouse(true)
auraList:SetSize(320, 360)
auraList:SetPoint("CENTER")
auraList:SetBackdrop({
bgFile = [[Interface\ChatFrame\ChatFrameBackground]],
edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
edgeSize = 16,
insets = {left = 4, right = 4, top = 4, bottom = 4},
})
auraList:SetBackdropColor(0, 0, 0)
auraList:SetBackdropBorderColor(0.5, 0.5, 0.5)
auraList:Hide()
 
local closeButton = CreateFrame("Button", nil, auraList, "UIPanelCloseButton")
closeButton:SetPoint("TOPRIGHT")
 
Critline.SlashCmdHandlers["aura"] = function() auraList:Show() end
 
local currentFilter = playerAuras.session
 
local function auraSort(a, b)
return currentFilter[a].spellName < currentFilter[b].spellName
end
 
local function sourceSort(a, b)
a, b = currentFilter[a], currentFilter[b]
if a.source == b.source then
return a.spellName < b.spellName
else
return a.source < b.source
end
end
 
local auraFilters = {
BUFF = true,
DEBUFF = true,
targetAffiliation = playerAuras,
sourceType = "npc",
sort = auraSort,
}
 
local function onClick(self, text)
self.owner:SetSelectedValue(self.value)
self.owner:SetText(text)
currentFilter = auraFilters.targetAffiliation[self.value]
CritlineAuraListScrollFrame:Update()
end
 
local menuList = {
{
text = L["Current fight"],
value = "lastFight",
},
{
text = L["Current instance (%s)"],
value = "instance",
},
{
text = L["Current session"],
value = "session",
},
}
 
local auraListFilter = templates:CreateDropDownMenu("CritlineAuraListFilter", auraList)
auraListFilter:SetPoint("TOP", 0, -16)
auraListFilter:SetFrameWidth(220)
auraListFilter:JustifyText("LEFT")
auraListFilter:SetSelectedValue("session")
auraListFilter:SetText(L["Current session"])
auraListFilter.initialize = function(self)
for i, v in ipairs(menuList) do
local info = UIDropDownMenu_CreateInfo()
info.text = format(v.text, currentInstance)
info.value = v.value
info.func = onClick
info.owner = self
info.arg1 = info.text
UIDropDownMenu_AddButton(info)
end
end
 
local auraListAuraType = templates:CreateDropDownMenu("CritlineAuraListAuraType", auraList)
auraListAuraType:SetPoint("TOPLEFT", auraListFilter, "BOTTOMLEFT")
auraListAuraType:SetFrameWidth(96)
auraListAuraType:JustifyText("LEFT")
auraListAuraType:SetText(L["Aura type"])
 
do
local function onClick(self)
auraFilters[self.value] = self.checked
CritlineAuraListScrollFrame:Update()
end
 
local menuList = {
{
text = L["Buffs"],
value = "BUFF",
},
{
text = L["Debuffs"],
value = "DEBUFF",
},
}
 
auraListAuraType.initialize = function(self)
for i, v in ipairs(menuList) do
local info = UIDropDownMenu_CreateInfo()
info.text = v.text
info.value = v.value
info.func = onClick
info.checked = auraFilters[v.value]
info.isNotRadio = true
info.keepShownOnClick = true
UIDropDownMenu_AddButton(info)
end
end
end
 
local auraListFilters = templates:CreateDropDownMenu("CritlineAuraListFilters", auraList)
auraListFilters:SetPoint("TOPRIGHT", auraListFilter, "BOTTOMRIGHT")
auraListFilters:SetFrameWidth(96)
auraListFilters:JustifyText("LEFT")
auraListFilters:SetText(FILTERS)
 
do
local function onClick(self, key)
auraFilters[key] = self.value
self.owner:Refresh()
self.owner:SetText(FILTERS)
currentFilter = auraFilters.targetAffiliation[auraListFilter:GetSelectedValue()]
CritlineAuraListScrollFrame:Update()
end
 
local function checked(self)
return auraFilters[self.arg1] == self.value
end
 
local menuList = {
{
text = L["Show auras cast on me"],
value = playerAuras,
arg1 = "targetAffiliation",
},
{
text = L["Show auras cast on hostile NPCs"],
value = enemyAuras,
arg1 = "targetAffiliation",
},
{
text = L["Show auras cast by NPCs"],
value = "npc",
arg1 = "sourceType",
},
{
text = L["Show auras cast by players"],
value = "pvp",
arg1 = "sourceType",
},
{
text = L["Sort by aura name"],
value = auraSort,
arg1 = "sort",
},
{
text = L["Sort by source name"],
value = sourceSort,
arg1 = "sort",
},
}
 
auraListFilters.initialize = function(self)
for i, v in ipairs(menuList) do
local info = UIDropDownMenu_CreateInfo()
info.text = v.text
info.value = v.value
info.func = onClick
info.checked = checked
info.owner = self
info.keepShownOnClick = true
info.arg1 = v.arg1
UIDropDownMenu_AddButton(info)
end
end
end
 
local search = templates:CreateEditBox(auraList)
search:SetPoint("TOPLEFT", auraListAuraType, "BOTTOMLEFT", 18, -8)
search:SetPoint("TOPRIGHT", auraListFilters, "BOTTOMRIGHT", -18, -8)
search:SetWidth(192)
search:SetScript("OnTextChanged", function() CritlineAuraListScrollFrame:Update() end)
search:SetScript("OnEscapePressed", search.ClearFocus)
 
local label = search:CreateFontString(nil, nil, "GameFontNormalSmall")
label:SetPoint("BOTTOMLEFT", search, "TOPLEFT")
label:SetText(L["Text filter"])
 
local NUM_BUTTONS = 6
local BUTTON_HEIGHT = 36
 
local auraListScrollFrame = CreateFrame("ScrollFrame", "CritlineAuraListScrollFrame", auraList, "FauxScrollFrameTemplate")
auraListScrollFrame:SetHeight(NUM_BUTTONS * BUTTON_HEIGHT)
auraListScrollFrame:SetPoint("TOP", search, "BOTTOM", 0, -8)
auraListScrollFrame:SetPoint("LEFT", 32, 0)
auraListScrollFrame:SetPoint("RIGHT", -32, 0)
auraListScrollFrame:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, BUTTON_HEIGHT, self.Update) end)
 
local sortedAuras = {}
 
function auraListScrollFrame:Update()
if not auraList:IsShown() then
self.doUpdate = true
return
end
 
self.doUpdate = nil
 
wipe(sortedAuras)
 
local n = 0
local search = search:GetText():lower()
for spellID, v in pairs(currentFilter) do
if auraFilters[v.type] and v.sourceType == auraFilters.sourceType and (v.spellName:lower():find(search, nil, true) or v.sourceName:lower():find(search, nil, true)) then
n = n + 1
sortedAuras[n] = spellID
end
end
 
sort(sortedAuras, auraFilters.sort)
 
FauxScrollFrame_Update(self, n, NUM_BUTTONS, BUTTON_HEIGHT)
 
local offset = FauxScrollFrame_GetOffset(self)
local buttons = self.buttons
for line = 1, NUM_BUTTONS do
local button = buttons[line]
local lineplusoffset = line + offset
if lineplusoffset <= n then
local spellID = sortedAuras[lineplusoffset]
button:SetFormattedText("%s (%d)", currentFilter[spellID].spellName, spellID)
button.source:SetText(currentFilter[spellID].source)
button.icon:SetTexture(addon:GetSpellTexture(spellID))
button.spellID = spellID
-- local disabled = filters:IsFilteredAura(spellID)
-- button.icon:SetDesaturated(disabled)
-- button.text:SetFontObject(disabled and "GameFontDisable" or "GameFontNormal")
button:Show()
else
button:Hide()
end
end
end
 
auraList:SetScript("OnShow", function(self)
if auraListScrollFrame.doUpdate then
auraListScrollFrame:Update()
end
end)
 
local auraListButtons = {}
auraListScrollFrame.buttons = auraListButtons
 
-- local function onClick(self)
-- local disabled = filters:IsFilteredAura(self.spellID)
-- if disabled then
-- if specialAuras[self.spellID] then
-- addon:Message("Cannot delete integrated auras.")
-- return
-- else
-- local t = filters.db.global.auras
-- for i = 1, #t do
-- if t[i] == self.spellID then
-- tremove(t, i)
-- addon:Message(format("Removed aura (%s) from filter.", GetSpellInfo(self.spellID)))
-- break
-- end
-- end
-- end
-- else
-- filters:AddAura(self.spellID)
-- end
-- disabled = not disabled
-- self.icon:SetDesaturated(disabled)
-- self.text:SetFontObject(disabled and "GameFontDisable" or "GameFontNormal")
-- end
 
local function onEnter(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetSpellByID(self.spellID)
GameTooltip:AddLine(" ")
GameTooltip:AddLine(format(L["Spell ID: |cffffffff%d|r"], self.spellID))
GameTooltip:Show()
end
 
for i = 1, NUM_BUTTONS do
local btn = CreateFrame("Button", nil, auraList)
btn:SetHeight(BUTTON_HEIGHT)
if i == 1 then
btn:SetPoint("TOP", auraListScrollFrame)
else
btn:SetPoint("TOP", auraListButtons[i - 1], "BOTTOM")
end
btn:SetPoint("LEFT", auraListScrollFrame)
btn:SetPoint("RIGHT", auraListScrollFrame)
btn:SetPushedTextOffset(0, 0)
-- btn:SetScript("OnClick", onClick)
btn:SetScript("OnEnter", onEnter)
btn:SetScript("OnLeave", GameTooltip_Hide)
 
if i % 2 == 0 then
local bg = btn:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints()
bg:SetTexture(1, 1, 1, 0.1)
end
 
local icon = btn:CreateTexture()
icon:SetSize(32, 32)
icon:SetPoint("LEFT")
btn.icon = icon
 
local text = btn:CreateFontString(nil, nil, "GameFontNormal")
text:SetPoint("TOPLEFT", icon, "TOPRIGHT", 4, -4)
text:SetPoint("RIGHT")
text:SetJustifyH("LEFT")
btn:SetFontString(text)
btn.text = text
 
local source = btn:CreateFontString(nil, nil, "GameFontHighlightSmall")
source:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 4, 4)
source:SetPoint("RIGHT")
source:SetJustifyH("LEFT")
btn.source = source
 
auraListButtons[i] = btn
end
end
 
 
StaticPopupDialogs["CRITLINE_ADD_MOB_BY_NAME"] = {
text = L["Enter mob name:"],
button1 = OKAY,
563,7 → 958,7
 
local function updateSpellFilter(self)
local selectedTree = self.tree:GetSelectedValue()
local spells = addon.percharDB.profile.spells[selectedTree]
local spells = addon:GetSpellArray(selectedTree)
local size = #spells
 
FauxScrollFrame_Update(self, size, self.numButtons, self.buttonHeight)
575,10 → 970,9
local lineplusoffset = line + offset
if lineplusoffset <= size then
local data = spells[lineplusoffset]
button.spell = data.spellName
button.isPeriodic = data.isPeriodic
button:SetText(addon:GetFullSpellName(selectedTree, data.spellName, data.isPeriodic))
button:SetChecked(data.filtered)
button.data = data
button:SetText(addon:GetFullSpellName(data.spellID, data.periodic))
button:SetChecked(not data.filtered)
button:Show()
else
button:Hide()
618,10 → 1012,10
 
local defaults = {
profile = {
invertFilter = false,
filterNew = false,
onlyKnown = false,
ignoreMobFilter = false,
ignoreAuraFilter = false,
onlyKnown = false,
suppressMC = true,
dontFilterMagic = false,
levelFilter = -1,
638,6 → 1032,14
addon.RegisterCallback(self, "PerCharSettingsLoaded", "UpdateSpellFilter")
addon.RegisterCallback(self, "SpellsChanged", "UpdateSpellFilter")
 
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
self:RegisterEvent("PLAYER_REGEN_DISABLED")
self:RegisterEvent("PLAYER_ENTERING_WORLD")
self:RegisterEvent("PLAYER_LOGIN")
self:RegisterEvent("UNIT_NAME_UPDATE")
self:RegisterEvent("PLAYER_CONTROL_LOST")
self:RegisterEvent("PLAYER_CONTROL_GAINED")
 
-- mix in scroll frame update functions
self.spell.scrollFrame.Update = updateSpellFilter
self.mobs.scrollFrame.Update = updateFilter
647,10 → 1049,107
addon.RegisterCallback(filters, "AddonLoaded")
 
 
function filters:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, spellID, spellName, spellSchool, auraType)
if eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" then
if targetAuras[spellID] then
corruptTargets[destGUID] = corruptTargets[destGUID] or {}
corruptTargets[destGUID][spellID] = true
addon:Debug(format("Target (%s) gained filtered aura (%s). Ignore received damage.", destName, spellID))
end
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) then
self:RegisterAura(playerAuras, sourceName, sourceGUID, spellID, spellName, auraType)
if self:IsFilteredAura(spellID) then
-- if we gain any aura in the filter we can just stop tracking records
if not (self:IsEmpowered() or self.profile.ignoreAuraFilter) then
addon:Debug("Filtered aura gained. Disabling combat log tracking.")
end
activeAuras[spellID] = true
end
else
if CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then
corruptSpells[spellID] = corruptSpells[spellID] or {}
corruptSpells[spellID][destGUID] = self:IsEmpowered() or self:IsVulnerableTarget(destGUID)
end
-- only non friendly NPC units
local unitType = band(destGUID:sub(1, 5), 0x007)
if (unitType ~= 0 and unitType ~= 4) and (band(destFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == 0) then
self:RegisterAura(enemyAuras, sourceName, sourceGUID, spellID, spellName, auraType)
end
end
elseif (eventType == "SPELL_AURA_REMOVED" or eventType == "SPELL_AURA_BROKEN" or eventType == "SPELL_AURA_BROKEN_SPELL" or eventType == "SPELL_AURA_STOLEN") then
if targetAuras[spellID] then
corruptTargets[destGUID] = corruptTargets[destGUID] or {}
corruptTargets[destGUID][spellID] = nil
addon:Debug(format("Filtered aura (%s) faded from %s.", spellName, destName))
end
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) then
if self:IsFilteredAura(spellID) then
addon:Debug(format("Filtered aura (%s) faded from player.", spellName))
-- if we lost a special aura we have to check if any other filtered auras remain
activeAuras[spellID] = nil
if not filters:IsEmpowered() then
addon:Debug("No filtered aura detected. Resuming record tracking.")
end
-- elseif CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then
-- corruptSpells[spellID] = corruptSpells[spellID] or {}
-- corruptSpells[spellID][destGUID] = nil
end
-- else
end
end
end
 
 
-- reset current fight auras upon entering combat
function filters:PLAYER_REGEN_DISABLED()
wipe(playerAuras.lastFight)
wipe(enemyAuras.lastFight)
CritlineAuraListScrollFrame:Update()
end
 
 
function filters:PLAYER_ENTERING_WORLD()
-- wipe instance buff data when entering a new instance
local instanceName = GetInstanceInfo()
if IsInInstance() and instanceName ~= currentInstance then
wipe(playerAuras.instance)
wipe(enemyAuras.instance)
currentInstance = instanceName
if CritlineAuraListFilter:GetSelectedValue() == "instance" then
CritlineAuraListFilter:SetText(format(L["Current instance (%s)"], currentInstance))
end
CritlineAuraListScrollFrame:Update()
end
end
 
 
function filters:PLAYER_LOGIN()
self:ScanAuras()
end
 
 
function filters:UNIT_NAME_UPDATE()
self:ScanAuras()
self:UnregisterEvent("UNIT_NAME_UPDATE")
end
 
 
function filters:PLAYER_CONTROL_LOST()
self.inControl = false
addon:Debug("Lost control. Disabling combat log tracking.")
end
 
 
function filters:PLAYER_CONTROL_GAINED()
self.inControl = true
addon:Debug("Regained control. Resuming combat log tracking.")
end
 
 
function filters:LoadSettings()
self.profile = self.db.profile
 
for _, v in ipairs(self.options.checkButtons) do
for i, v in ipairs(self.options.checkButtons) do
v:LoadSetting()
end
 
658,45 → 1157,38
end
 
 
do
-- local spellButton_OnModifiedClick = SpellButton_OnModifiedClick
local auraTypes = {
BUFF = "HELPFUL",
DEBUFF = "HARMFUL",
}
 
-- hooksecurefunc("SpellButton_OnModifiedClick", function(self, button, ...)
-- local slot = SpellBook_GetSpellBookSlot(self)
-- if ( slot > MAX_SPELLS ) then
-- return
-- end
-- if IsShiftKeyDown() and filters.spell:IsVisible() and filters:GetParent() then
-- local spellName, subSpellName = GetSpellBookItemName(slot, SpellBookFrame.bookType)
-- filters:AddSpell(filters.spell.tree:GetSelectedValue(), spellName)
-- return
-- end
-- return spellButton_OnModifiedClick(self, button)
-- end)
 
--[[
local function onClick(self, button)
if button == "LeftButton" and IsShiftKeyDown() and filters.auras:IsVisible() and filters:GetParent() then
filters:AddAura(select(11, UnitAura(self.unit, self:GetID(), self.filter)))
function filters:ScanAuras()
local auras = {}
for auraType, filter in pairs(auraTypes) do
for i = 1, 40 do
local spellName, _, _, _, _, _, _, source, _, _, spellID = UnitAura("player", i, filter)
if not spellID then break end
auras[spellID] = true
if specialAuras[spellID] then
activeAuras[spellID] = true
end
self:RegisterAura(playerAuras, source and UnitName(source), source and UnitGUID(source), spellID, spellName, auraType)
end
end
 
-- debuff buttons needs to have an onClick handler, and both buff and debuff buttons needs to monitor left clicks
hooksecurefunc("AuraButton_Update", function(buttonName, index, filter)
local name = UnitAura("player", index, filter)
if name then
local buff = _G[buttonName..index]
if buff and not buff.Critline then
buff:RegisterForClicks("AnyUp")
if not buff:HasScript("OnClick") then
buff:SetScript("OnClick", onClick)
end
buff.Critline = true
end
end
end)
 
hooksecurefunc("BuffButton_OnClick", onClick)]]
CritlineAuraListScrollFrame:Update()
if next(auras) then
self:UnregisterEvent("UNIT_NAME_UPDATE")
end
for i, v in ipairs(self.db.global.auras) do
activeAuras[v] = auras[v]
end
if next(activeAuras) then
addon:Debug("Filtered aura detected. Disabling combat log tracking.")
end
self.inControl = HasFullControl()
if not self.inControl then
addon:Debug("Lost control. Disabling combat log tracking.")
end
end
 
 
710,34 → 1202,15
end
 
 
function filters:AddSpell(tree, spell, isPeriodic)
local data = addon:GetSpellInfo(tree, spell, isPeriodic)
if not data then
addon:AddSpell(tree, {
spellName = spell,
isPeriodic = isPeriodic,
filtered = true,
})
self:UpdateSpellFilter()
return
end
data.filtered = true
function filters:FilterSpell(filter, tree, data)
data.filtered = filter
addon:GetSpellInfo(tree, data.spellID, data.periodic).filtered = filter
addon:UpdateTopRecords(tree)
addon:UpdateRecords(tree)
end
 
 
function filters:DeleteSpell(tree, spell, isPeriodic)
local data, index = addon:GetSpellInfo(tree, spell, isPeriodic)
if not (data.normal or data.crit) then
addon:DeleteSpell(tree, index)
self:UpdateSpellFilter()
return
end
data.filtered = nil
addon:UpdateRecords(tree)
end
 
 
-- adds a mob to the mob filter
function filters:AddMob(name)
if self:IsFilteredMob(name) then
addon:Message(L["%s is already in mob filter."]:format(name))
749,6 → 1222,7
end
 
 
-- adds an aura to the aura filter
function filters:AddAura(spellID)
local spellName = GetSpellInfo(spellID)
if self:IsFilteredAura(spellID) then
779,37 → 1253,8
end
 
 
function filters:IsAuraEvent(eventType, spellID, sourceFlags, destFlags, destGUID)
if eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" then
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) and self:IsFilteredAura(spellID) then
-- if we gain any aura in the filter we can just stop tracking records
if not (self:IsEmpowered() or self.profile.ignoreAuraFilter) then
addon:Debug("Filtered aura gained. Disabling combat log tracking.")
end
activeAuras[spellID] = true
elseif CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then
corruptSpells[spellID] = corruptSpells[spellID] or {}
corruptSpells[spellID][destGUID] = self:IsEmpowered()
end
return true
elseif (eventType == "SPELL_AURA_REMOVED" or eventType == "SPELL_AURA_BROKEN" or eventType == "SPELL_AURA_BROKEN_SPELL" or eventType == "SPELL_AURA_STOLEN") then
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) and self:IsFilteredAura(spellID) then
-- if we lost a special aura we have to check if any other filtered auras remain
activeAuras[spellID] = nil
if not filters:IsEmpowered() then
addon:Debug("No filtered aura detected. Resuming record tracking.")
end
-- elseif CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then
-- corruptSpells[spellID] = corruptSpells[spellID] or {}
-- corruptSpells[spellID][destGUID] = nil
end
return true
end
end
 
 
-- check if a spell passes the filter settings
function filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school)
function filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school, targetLevel)
if spellID and not IsSpellKnown(spellID, tree == "pet") and self.profile.onlyKnown then
addon:Debug(format("%s is not in your%s spell book. Return.", spellName, tree == "pet" and " pet's" or ""))
return
820,7 → 1265,11
return
end
 
local targetLevel = self:GetLevelFromGUID(destGUID)
if self:IsVulnerableTarget(destGUID) and not self.profile.ignoreAuraFilter then
addon:Debug("Target is vulnerable. Return.")
return
end
 
local levelDiff = 0
if (targetLevel > 0) and (targetLevel < UnitLevel("player")) then
levelDiff = (UnitLevel("player") - targetLevel)
837,14 → 1286,14
return
end
 
return true, self:IsFilteredSpell(tree, spellName, isPeriodic), targetLevel
return true, self:IsFilteredSpell(tree, spellID, isPeriodic and 2 or 1), targetLevel
end
 
 
-- check if a spell passes the filters depending on inverted setting
function filters:IsFilteredSpell(tree, spellName, periodic)
local spell = addon:GetSpellInfo(tree, spellName, periodic)
return ((spell and spell.filtered) ~= nil) ~= self.db.profile.invertFilter
-- check if a spell will be filtered out
function filters:IsFilteredSpell(tree, spellID, periodic)
local spell = addon:GetSpellInfo(tree, spellID, periodic)
return (not spell and self.db.profile.filterNew) or (spell and spell.filtered)
end
 
 
856,10 → 1305,19
end
 
 
function filters:IsFilteredMob(mobName, GUID)
-- checks if a target is affected by any vulnerability auras
function filters:IsVulnerableTarget(guid)
local corruptTarget = corruptTargets[guid]
if corruptTarget and next(corruptTarget) then
return true
end
end
 
 
function filters:IsFilteredMob(mobName, guid)
-- GUID is provided if the function was called from the combat event handler
if GUID and not self.profile.ignoreMobFilter then
if specialMobs[tonumber(GUID:sub(7, 10), 16)] then
if guid and not self.profile.ignoreMobFilter then
if specialMobs[tonumber(guid:sub(7, 10), 16)] then
addon:Debug("Mob ("..mobName..") is in integrated filter.")
return true
end
875,86 → 1333,46
 
function filters:IsFilteredAura(spellID)
if specialAuras[spellID] then
addon:Debug("Aura ("..GetSpellInfo(spellID)..") is in integrated filter.")
return true
end
for _, v in ipairs(self.db.global.auras) do
if v == spellID then
addon:Debug("Aura ("..GetSpellInfo(spellID)..") is in custom filter.")
return true
end
end
end
 
 
function filters:GetLevelFromGUID(destGUID)
tooltip:SetOwner(UIParent, "ANCHOR_NONE")
tooltip:SetHyperlink("unit:"..destGUID)
function filters:RegisterAura(auraTable, sourceName, sourceGUID, spellID, spellName, auraType)
local session = auraTable.session
if session[spellID] or IsSpellKnown(spellID) or not sourceName then
return
end
 
local source = L["n/a"]
local sourceType
 
local level = -1
local unitType = bit.band(sourceGUID:sub(1, 5), 0x007)
if unitType == 0 or unitType == 4 then
-- this is a player or a player's permanent pet
source = PVP
sourceType = "pvp"
else
source = tonumber(sourceGUID:sub(6, 10), 16)
sourceType = "npc"
end
 
for i = 1, tooltip:NumLines() do
local text = _G["CritlineTooltipTextLeft"..i]:GetText()
if text then
if text:match(LEVEL) then -- our destGUID has the word Level in it.
level = text:match("(%d+)") -- find the level
if level then -- if we found the level, break from the for loop
level = tonumber(level)
else
-- well, the word Level is in this tooltip, but we could not find the level
-- either the destGUID is at least 10 levels higher than us, or we just couldn't find it.
level = -1
end
end
end
end
return level
end
 
 
addon:RegisterEvent("PLAYER_LOGIN")
addon:RegisterEvent("PLAYER_CONTROL_LOST")
addon:RegisterEvent("PLAYER_CONTROL_GAINED")
 
function addon:PLAYER_LOGIN()
for i = 1, 40 do
local buffID = select(11, UnitBuff("player", i))
local debuffID = select(11, UnitDebuff("player", i))
if not (buffID or debuffID) then
break
elseif specialAuras[buffID] then
activeAuras[buffID] = true
elseif specialAuras[debuffID] then
activeAuras[debuffID] = true
else
for _, v in ipairs(filters.db.global.auras) do
if v == buffID then
activeAuras[buffID] = true
break
elseif v == debuffID then
activeAuras[debuffID] = true
break
end
end
end
local aura = {
source = format("%s (%s)", sourceName, source),
sourceName = sourceName,
spellName = spellName,
sourceType = sourceType,
type = auraType,
}
auraTable.lastFight[spellID] = aura
if IsInInstance() then
auraTable.instance[spellID] = aura
end
if next(activeAuras) then
addon:Debug("Filtered aura detected. Disabling combat log tracking.")
end
filters.inControl = HasFullControl()
if not filters.inControl then
self:Debug("Lost control. Disabling combat log tracking.")
end
end
 
 
function addon:PLAYER_CONTROL_LOST()
filters.inControl = false
self:Debug("Lost control. Disabling combat log tracking.")
end
 
 
function addon:PLAYER_CONTROL_GAINED()
filters.inControl = true
self:Debug("Regained control. Resuming combat log tracking.")
session[spellID] = aura
CritlineAuraListScrollFrame:Update()
end
\ No newline at end of file