/tags
local addonName, Critline = ... |
_G.Critline = Critline |
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 references to commonly used functions and variables for faster access |
local HasPetUI = HasPetUI |
local tonumber = tonumber |
local CombatLog_Object_IsA = CombatLog_Object_IsA |
local bor = bit.bor |
local band = bit.band |
local COMBATLOG_FILTER_MINE = COMBATLOG_FILTER_MINE |
local COMBATLOG_FILTER_MY_PET = COMBATLOG_FILTER_MY_PET |
local COMBATLOG_OBJECT_REACTION_FRIENDLY = COMBATLOG_OBJECT_REACTION_FRIENDLY |
local COMBATLOG_OBJECT_REACTION_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE |
local COMBATLOG_OBJECT_CONTROL_PLAYER = COMBATLOG_OBJECT_CONTROL_PLAYER |
local COMBATLOG_OBJECT_TYPE_GUARDIAN = COMBATLOG_OBJECT_TYPE_GUARDIAN |
local treeNames = { |
dmg = L["damage"], |
heal = L["healing"], |
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", |
} |
-- non hunter pets whose damage we may want to register |
local classPets = { |
[510] = true, -- Water Elemental |
[11859] = true, -- Doomguard |
[15438] = true, -- Greater Fire Elemental |
[27829] = true, -- Ebon Gargoyle |
[29264] = true, -- Spirit Wolf |
[37994] = true, -- Water Elemental (glyphed) |
} |
local swingDamage = function(amount, _, school, resisted, _, _, critical) |
return AUTOATK, amount, resisted, critical, school |
end |
local spellDamage = function(_, spellName, _, amount, _, school, resisted, _, _, critical) |
return spellName, amount, resisted, critical, school |
end |
local healing = function(_, spellName, _, amount, _, _, critical) |
return spellName, amount, 0, critical, 0 |
end |
local combatEvents = { |
SWING_DAMAGE = swingDamage, |
RANGE_DAMAGE = spellDamage, |
SPELL_DAMAGE = spellDamage, |
SPELL_PERIODIC_DAMAGE = spellDamage, |
SPELL_HEAL = healing, |
SPELL_PERIODIC_HEAL = healing, |
} |
local recordSorters = { |
-- alpha: sort by name |
alpha = function(a, b) |
if a == b then return end |
if a.spellName == b.spellName then |
-- sort DoT entries after non DoT |
return b.isPeriodic |
else |
return a.spellName < b.spellName |
end |
end, |
-- crit: sort by crit > normal > name |
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 |
-- 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 |
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, |
} |
local callbacks = LibStub("CallbackHandler-1.0"):New(Critline) |
Critline.callbacks = callbacks |
-- this will hold the text for the summary tooltip |
local tooltips = {dmg = {}, heal = {}, pet = {}} |
Critline.eventFrame = CreateFrame("Frame") |
function Critline:RegisterEvent(event) |
self.eventFrame:RegisterEvent(event) |
end |
function Critline:UnregisterEvent(event) |
self.eventFrame:UnregisterEvent(event) |
end |
Critline:RegisterEvent("ADDON_LOADED") |
Critline:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") |
Critline.eventFrame:SetScript("OnEvent", function(self, event, ...) |
return Critline[event] and Critline[event](Critline, ...) |
end) |
local config = templates:CreateConfigFrame(addonName, nil, true) |
do |
local options = {} |
Critline.options = options |
local function toggleTree(self) |
local display = Critline.display |
if display then |
display:UpdateTree(self.setting) |
end |
end |
local checkButtons = { |
db = {}, |
percharDB = {}, |
{ |
text = L["Record damage"], |
tooltipText = L["Check to enable damage events to be recorded."], |
setting = "dmg", |
perchar = true, |
func = toggleTree, |
}, |
{ |
text = L["Record healing"], |
tooltipText = L["Check to enable healing events to be recorded."], |
setting = "heal", |
perchar = true, |
func = toggleTree, |
}, |
{ |
text = L["Record pet damage"], |
tooltipText = L["Check to enable pet damage events to be recorded."], |
setting = "pet", |
perchar = true, |
func = toggleTree, |
}, |
{ |
text = L["Record PvE"], |
tooltipText = L["Disable to ignore records where the target is an NPC."], |
setting = "PvE", |
gap = 16, |
}, |
{ |
text = L["Record PvP"], |
tooltipText = L["Disable to ignore records where the target is a player."], |
setting = "PvP", |
}, |
{ |
text = L["Chat output"], |
tooltipText = L["Prints new record notifications to the chat frame."], |
setting = "chatOutput", |
}, |
{ |
text = L["Play sound"], |
tooltipText = L["Plays a sound on a new record."], |
setting = "sound", |
}, |
{ |
text = L["Screenshot"], |
tooltipText = L["Saves a screenshot on a new record."], |
setting = "screenshot", |
newColumn = true, |
}, |
{ |
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 |
for i, v in ipairs(checkButtons) do |
local btn = templates:CreateCheckButton(config, v) |
if i == 1 then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOMLEFT", -2, -16) |
elseif v.newColumn then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -16) |
else |
btn:SetPoint("TOP", checkButtons[i - 1], "BOTTOM", 0, -(v.gap or 8)) |
end |
btn.module = Critline |
local btns = checkButtons[btn.db] |
btns[#btns + 1] = btn |
checkButtons[i] = btn |
end |
-- 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", |
}, |
{ |
text = L["By normal record"], |
value = "normal", |
}, |
{ |
text = L["By crit record"], |
value = "crit", |
}, |
} |
local sorting = templates:CreateDropDownMenu("CritlineTooltipSorting", config, menu) |
sorting:SetFrameWidth(120) |
sorting:SetPoint("TOPLEFT", checkButtons[#checkButtons], "BOTTOMLEFT", -15, -24) |
sorting.label:SetText(L["Tooltip sorting:"]) |
options.tooltipSort = sorting |
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 |
else |
Critline:OpenConfig() |
end |
end |
SLASH_CRITLINE1 = "/critline" |
SLASH_CRITLINE2 = "/cl" |
local defaults = { |
profile = { |
PvE = true, |
PvP = true, |
chatOutput = false, |
sound = false, |
screenshot = false, |
detailedTooltip = false, |
ignoreVulnerability = true, |
tooltipSort = "normal", |
}, |
} |
-- which trees are enabled by default for a given class |
local treeDefaults = { |
DEATHKNIGHT = {dmg = true, heal = false, pet = false}, |
DRUID = {dmg = true, heal = true, pet = false}, |
HUNTER = {dmg = true, heal = false, pet = true}, |
MAGE = {dmg = true, heal = false, pet = false}, |
PALADIN = {dmg = true, heal = true, pet = false}, |
PRIEST = {dmg = true, heal = true, pet = false}, |
ROGUE = {dmg = true, heal = false, pet = false}, |
SHAMAN = {dmg = true, heal = true, pet = false}, |
WARLOCK = {dmg = true, heal = false, pet = true}, |
WARRIOR = {dmg = true, heal = false, pet = false}, |
} |
function Critline:ADDON_LOADED(addon) |
if addon == addonName then |
local AceDB = LibStub("AceDB-3.0") |
local db = AceDB:New("CritlineDB", defaults, nil) |
self.db = db |
local percharDefaults = { |
profile = treeDefaults[playerClass], |
} |
percharDefaults.profile.spells = { |
dmg = {}, |
heal = {}, |
pet = {}, |
} |
local percharDB = AceDB:New("CritlinePerCharDB", percharDefaults) |
self.percharDB = percharDB |
-- dual spec support |
local LibDualSpec = LibStub("LibDualSpec-1.0") |
LibDualSpec:EnhanceDatabase(self.db, addonName) |
LibDualSpec:EnhanceDatabase(self.percharDB, addonName) |
db.RegisterCallback(self, "OnProfileChanged", "LoadSettings") |
db.RegisterCallback(self, "OnProfileCopied", "LoadSettings") |
db.RegisterCallback(self, "OnProfileReset", "LoadSettings") |
percharDB.RegisterCallback(self, "OnProfileChanged", "LoadPerCharSettings") |
percharDB.RegisterCallback(self, "OnProfileCopied", "LoadPerCharSettings") |
percharDB.RegisterCallback(self, "OnProfileReset", "LoadPerCharSettings") |
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() |
self.ADDON_LOADED = nil |
end |
end |
function Critline:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, 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 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 |
self:Debug("nil source/dest") |
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 |
if not CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_MINE) then |
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 |
isPet = true |
-- self:Debug(format("This is my pet (%s)", sourceName)) |
else |
-- self:Debug("This is not me, my trap or my pet; return.") |
return |
end |
else |
-- self:Debug(format("This is me or my trap (%s)", sourceName)) |
end |
if not combatEvents[eventType] then |
return |
end |
local isHeal |
local isPeriodic |
if eventType:find("_HEAL$") then |
isHeal = true |
if isPet then |
return |
end |
end |
if eventType:find("_PERIODIC_") then |
isPeriodic = true |
end |
local spellName, amount, resisted, critical, school = combatEvents[eventType](...) |
-- below are some checks to see if we want to register the hit at all |
if amount <= 0 then |
self:Debug(format("Amount <= 0. (%s) Return.", self:GetFullSpellName(tree, spellName, isPeriodic))) |
return |
end |
local tree = "dmg" |
if isPet then |
tree = "pet" |
elseif isHeal then |
tree = "heal" |
end |
local passed, isFiltered, targetLevel |
if self.filters then |
passed, isFiltered, targetLevel = self.filters:SpellPassesFilters(tree, spellName, ..., isPeriodic, destGUID, destName, school) |
if not passed then |
return |
end |
end |
local isPlayer = band(destFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) ~= 0 |
local friendlyFire = band(destFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) ~= 0 |
local hostileTarget = band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) ~= 0 |
if not (isPlayer or self.db.profile.PvE or isHeal) then |
self:Debug(format("Target (%s) is an NPC and PvE damage is not registered.", destName)) |
return |
end |
if isPlayer and not (self.db.profile.PvP or isHeal or friendlyFire) then |
self:Debug(format("Target (%s) is a player and PvP damage is not registered.", destName)) |
return |
end |
-- ignore damage done to friendly targets |
if friendlyFire and not isHeal then |
self:Debug(format("Friendly fire (%s, %s).", spellName, destName)) |
return |
end |
-- ignore healing done to hostile targets |
if hostileTarget and isHeal then |
self:Debug(format("Healing hostile target (%s, %s).", spellName, destName)) |
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)) |
return |
end |
-- ignore vulnerability damage if necessary |
if self.db.profile.ignoreVulnerability and resisted and resisted < 0 then |
amount = amount + resisted |
self:Debug(format("%d vulnerability damage ignored for a real value of %d.", abs(resisted), amount)) |
end |
local hitType = critical and "crit" or "normal" |
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:UpdateSpells(tree) |
end |
if not data[hitType] then |
data[hitType] = {amount = 0} |
end |
data = data[hitType] |
if amount > data.amount then |
local oldAmount = data.amount |
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:Message(msg) |
if msg then |
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00Critline:|r "..msg) |
end |
end |
function Critline:Debug(msg) |
if debugging then |
DEFAULT_CHAT_FRAME:AddMessage("|cff56a3ffCritlineDebug:|r "..msg) |
end |
end |
function Critline:ToggleDebug() |
debugging = not debugging |
self:Message("Debugging "..(debugging and "enabled" or "disabled")) |
end |
function Critline:OpenConfig() |
InterfaceOptionsFrame_OpenToCategory(config) |
end |
function Critline:LoadSettings() |
callbacks:Fire("SettingsLoaded") |
local options = self.options |
for _, btn in ipairs(options.checkButtons.db) do |
btn:LoadSetting() |
end |
options.tooltipSort:SetSelectedValue(self.db.profile.tooltipSort) |
end |
function Critline:LoadPerCharSettings() |
callbacks:Fire("PerCharSettingsLoaded") |
self:UpdateTooltips() |
for _, btn in ipairs(self.options.checkButtons.percharDB) do |
btn:LoadSetting() |
end |
end |
function Critline:NewRecord(spell, amount, crit, oldAmount) |
callbacks:Fire("NewRecord", spell, amount, crit, oldAmount) |
if self.db.profile.chatOutput then |
self:Message(format(L["New %s%s record - %d"], crit and L["critical "] or "", spell, amount)) |
end |
if self.db.profile.sound then |
PlaySound("LEVELUP", 1, 1, 0, 1, 3) |
end |
if self.db.profile.screenshot then |
TakeScreenshot() |
end |
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 |
end |
end |
end |
function Critline:GetFullSpellName(tree, spellName, isPeriodic) |
local suffix = "" |
if isPeriodic then |
if tree == "heal" then |
suffix = L[" (HoT)"] |
else |
suffix = L[" (DoT)"] |
end |
end |
return format("%s%s", spellName, suffix) |
end |
function Critline:GetFullTargetName(spell) |
local suffix = "" |
if spell.isPvPTarget then |
suffix = format(" (%s)", PVP) |
end |
return format("%s%s", spell.target, suffix) |
end |
-- retrieves the top, non filtered record amounts and spell names for a given tree |
function Critline:GetHighest(tree) |
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 |
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 |
end |
function Critline:AddSpell(tree, spell) |
local spells = self.percharDB.profile.spells[tree] |
tinsert(spells, spell) |
sort(spells, recordSorters.alpha) |
end |
function Critline:DeleteSpell(tree, index) |
tremove(self.percharDB.profile.spells[tree], index) |
end |
-- this "fires" when spells are added to/removed from the database |
function Critline:UpdateSpells(tree) |
if tree then |
self:UpdateTooltip(tree) |
callbacks:Fire("SpellsChanged", tree) |
else |
for k in pairs(tooltips) do |
self:UpdateSpells(k) |
end |
end |
end |
-- this "fires" when a new record has been registered |
function Critline:UpdateRecords(tree, isFiltered) |
if tree then |
self:UpdateTooltip(tree) |
callbacks:Fire("RecordsChanged", tree, isFiltered) |
else |
for k in pairs(tooltips) do |
self:UpdateRecords(k, isFiltered) |
end |
end |
end |
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) |
else |
GameTooltip:AddLine(v) |
end |
end |
GameTooltip:Show() |
end |
function Critline:UpdateTooltips() |
for k in pairs(tooltips) do |
self:UpdateTooltip(k) |
end |
end |
local sortedSpells = {} |
function Critline:UpdateTooltip(tree) |
local line = " |cffc0c0c0%s:|r %s\t%s (%s)" |
wipe(sortedSpells) |
local n = 1 |
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 |
sort(sortedSpells, recordSorters[self.db.profile.tooltipSort]) |
local tooltip = tooltips[tree] |
wipe(tooltip) |
local normalRecord, critRecord = self:GetHighest(tree) |
n = 1 |
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 |
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 |
end |
if #tooltip == 0 then |
tooltip[1] = L["No records"] |
end |
end |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local CombatLog_Object_IsA = CombatLog_Object_IsA |
local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME |
-- 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 |
[12461] = true, -- Death Talon Overseer |
[14020] = true, -- Chromaggus |
[15339] = true, -- Ossirian the Unscarred |
[15928] = true, -- Thaddius |
[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 |
[40793] = true, -- Ragnaros (Mount Hyjal) |
[46273] = true, -- Debilitated Apexar |
} |
-- auras that when gained will suppress record tracking |
local specialAuras = { |
[18173] = true, -- Burning Adrenaline (Vaelastrasz the Corrupt) |
[41337] = true, -- Aura of Anger (Reliquary of Souls) |
[41350] = true, -- Aura of Desire (Reliquary of Souls) |
[44335] = true, -- Energy Feedback (Vexallus) |
[44406] = true, -- Energy Infusion (Vexallus) |
[53642] = true, -- Might of Mograine (Light's Hope Chapel) |
[55849] = true, -- Power Spark (Malygos) |
[56330] = true, -- Iron's Bane (Storm Peaks quest) |
[56648] = true, -- Potent Fungus (Amanitar) |
[57524] = true, -- Metanoia (Valkyrion Aspirant) |
[58026] = true, -- Blessing of the Crusade (Icecrown quest) |
[58361] = true, -- Might of Mograine (Patchwerk) |
[58549] = true, -- Tenacity (Lake Wintergrasp) |
[59641] = true, -- Warchief's Blessing (The Battle For The Undercity) |
[60964] = true, -- Strength of Wrynn (The Battle For The Undercity) |
[61888] = true, -- Overwhelming Power (Assembly of Iron - 10 man hard mode) |
[62243] = true, -- Unstable Sun Beam (Elder Brightleaf) |
[62650] = true, -- Fortitude of Frost (Yogg-Saron) |
[62670] = true, -- Resilience of Nature (Yogg-Saron) |
[62671] = true, -- Speed of Invention (Yogg-Saron) |
[62702] = true, -- Fury of the Storm (Yogg-Saron) |
[63277] = true, -- Shadow Crash (General Vezax) |
[63711] = true, -- Storm Power (Hodir 10 man) |
[64320] = true, -- Rune of Power (Assembly of Iron) |
[64321] = true, -- Potent Pheromones (Freya) |
[64637] = true, -- Overwhelming Power (Assembly of Iron - 25 man hard mode) |
[65134] = true, -- Storm Power (Hodir 25 man) |
[70867] = true, -- Essence of the Blood Queen (Blood Queen Lana'thel) |
[70879] = true, -- Essence of the Blood Queen (Blood Queen Lana'thel, bitten by a player) |
[72219] = true, -- Gastric Bloat (Festergut) |
[76133] = true, -- Tidal Surge (Neptulon) |
[76159] = true, -- Pyrogenics (Sun-Touched Spriteling) |
[76355] = true, -- Blessing of the Sun (Rajh) |
[76693] = true, -- Empowering Twilight (Crimsonborne Warlord) |
[81096] = true, -- Red Mist (Red Mist) |
[86872] = true, -- Frothing Rage (Thundermar Ale) |
[90933] = true, -- Ragezone (Defias Blood Wizard) |
[93777] = true, -- Invocation of Flame (Skullcrusher the Mountain) |
} |
-- 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 |
-- [63106] = "Corruption", -- Siphon Life |
} |
local activeAuras = {} |
local corruptSpells = {} |
local function filterButtonOnClick(self) |
local module = self.module |
local scrollFrame = module.scrollFrame |
local offset = FauxScrollFrame_GetOffset(scrollFrame) |
local id = self:GetID() |
local selection = scrollFrame.selected |
if selection then |
if selection - offset == id then |
-- clicking the selected button, clear selection |
self:UnlockHighlight() |
selection = nil |
else |
-- clear selection if visible, and set new selection |
local prevHilite = scrollFrame.buttons[selection - offset] |
if prevHilite then |
prevHilite:UnlockHighlight() |
end |
self:LockHighlight() |
selection = id + offset |
end |
else |
-- no previous selection, just set new and lock highlight |
self:LockHighlight() |
selection = id + offset |
end |
-- enable/disable "Delete" button depending on if selection exists |
if selection then |
module.delete:Enable() |
else |
module.delete:Disable() |
end |
scrollFrame.selected = selection |
end |
-- template function for mob filter buttons |
local function createFilterButton(parent) |
local btn = CreateFrame("Button", nil, parent) |
btn:SetHeight(FILTERBUTTONHEIGHT) |
btn:SetPoint("LEFT") |
btn:SetPoint("RIGHT") |
btn:SetNormalFontObject("GameFontNormal") |
btn:SetHighlightFontObject("GameFontHighlight") |
btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") |
btn:SetPushedTextOffset(0, 0) |
btn:SetScript("OnClick", filterButtonOnClick) |
return btn |
end |
local function createFilterButtons(parent, onEnter) |
local buttons = {} |
for i = 1, NUMFILTERBUTTONS do |
local btn = createFilterButton(parent) |
if i == 1 then |
btn:SetPoint("TOP") |
else |
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM") |
end |
btn:SetID(i) |
if onEnter then |
btn:SetScript("OnEnter", onEnter) |
btn:SetScript("OnLeave", GameTooltip_Hide) |
end |
btn.module = parent |
buttons[i] = btn |
end |
parent.scrollFrame.buttons = buttons |
end |
local function resetScroll(self) |
FauxScrollFrame_SetOffset(self, 0) |
self.scrollBar:SetValue(0) |
self:Update() |
end |
local function onVerticalScroll(self, offset) |
FauxScrollFrame_OnVerticalScroll(self, offset, self.buttonHeight, self.Update) |
end |
local function filterFrameOnShow(self) |
local scrollFrame = self.scrollFrame |
if scrollFrame.selected then |
local prevHilite = scrollFrame.buttons[scrollFrame.selected - FauxScrollFrame_GetOffset(scrollFrame)] |
if prevHilite then |
prevHilite:UnlockHighlight() |
end |
scrollFrame.selected = nil |
self.delete:Disable() |
end |
end |
local function addButtonOnClick(self) |
StaticPopup_Show(self.popup) |
end |
local function deleteButtonOnClick(self) |
local scrollFrame = self.scrollFrame |
local filterName = scrollFrame.filter |
local selection = scrollFrame.selected |
if selection then |
local filter = self.filters.db.global[filterName] |
local selectedEntry = filter[selection] |
tremove(filter, selection) |
local prevHighlight = scrollFrame.buttons[selection - FauxScrollFrame_GetOffset(scrollFrame)] |
if prevHighlight then |
prevHighlight:UnlockHighlight() |
end |
scrollFrame.selected = nil |
scrollFrame:Update() |
self:Disable() |
addon:Message(self.msg:format(GetSpellInfo(selectedEntry) or selectedEntry)) |
if self.func then |
self.func(selectedEntry) |
end |
end |
end |
local function createFilterFrame(name, parent, numButtons, buttonHeight) |
local frame = CreateFrame("Frame", nil, parent) |
frame:SetHeight(numButtons * buttonHeight) |
parent[name] = frame |
local scrollName = "CritlineFilters"..name.."ScrollFrame" |
local scrollFrame = CreateFrame("ScrollFrame", scrollName, frame, "FauxScrollFrameTemplate") |
scrollFrame:SetAllPoints() |
scrollFrame:SetScript("OnShow", resetScroll) |
scrollFrame:SetScript("OnVerticalScroll", onVerticalScroll) |
scrollFrame.scrollBar = _G[scrollName.."ScrollBar"] |
scrollFrame.buttons = frame.buttons |
scrollFrame.numButtons = numButtons |
scrollFrame.buttonHeight = buttonHeight |
scrollFrame.filter = name |
frame.scrollFrame = scrollFrame |
if name ~= "spell" then |
frame:SetScript("OnShow", filterFrameOnShow) |
local add = templates:CreateButton(frame) |
add:SetScript("OnClick", addButtonOnClick) |
frame.add = add |
local delete = templates:CreateButton(frame) |
delete:Disable() |
delete:SetScript("OnClick", deleteButtonOnClick) |
delete.scrollFrame = scrollFrame |
delete.filters = parent |
frame.delete = delete |
end |
return frame |
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["Ignore mob filter"], |
tooltipText = L["Enable to ignore integrated mob filter."], |
setting = "ignoreMobFilter", |
}, |
{ |
text = L["Ignore aura filter"], |
tooltipText = L["Enable to ignore integrated aura filter."], |
setting = "ignoreAuraFilter", |
}, |
{ |
text = L["Only known spells"], |
tooltipText = L["Enable to ignore spells that are not in your (or your pet's) spell book."], |
setting = "onlyKnown", |
}, |
{ |
text = L["Suppress mind control"], |
tooltipText = L["Suppress all records while mind controlled."], |
setting = "suppressMC", |
newColumn = true, |
}, |
{ |
text = L["Don't filter magic"], |
tooltipText = L["Enable to let magical damage ignore the level filter."], |
setting = "dontFilterMagic", |
}, |
} |
options.checkButtons = checkButtons |
local columnEnd = #checkButtons |
for i, v in ipairs(checkButtons) do |
local btn = templates:CreateCheckButton(filters, v) |
if i == 1 then |
btn:SetPoint("TOPLEFT", filters.title, "BOTTOMLEFT", -2, -16) |
elseif btn.newColumn then |
btn:SetPoint("TOPLEFT", filters.title, "BOTTOM", 0, -16) |
columnEnd = i - 1 |
else |
btn:SetPoint("TOP", checkButtons[i - 1], "BOTTOM", 0, -8) |
end |
btn.module = filters |
checkButtons[i] = btn |
end |
local slider = templates:CreateSlider(filters, { |
text = L["Level filter"], |
tooltipText = L["If level difference between you and the target is greater than this setting, records will not be registered."], |
minValue = -1, |
maxValue = 10, |
valueStep = 1, |
minText = OFF, |
maxText = 10, |
func = function(self) |
local value = self:GetValue() |
self.value:SetText(value == -1 and OFF or value) |
filters.profile.levelFilter = value |
end, |
}) |
slider:SetPoint("TOPLEFT", checkButtons[#checkButtons], "BOTTOMLEFT", 4, -24) |
options.slider = slider |
local filterTypes = {} |
-- spell filter frame |
local spellFilter = createFilterFrame("spell", filters, NUMSPELLBUTTONS, SPELLBUTTONHEIGHT) |
spellFilter:SetPoint("TOP", checkButtons[columnEnd], "BOTTOM", 0, -48) |
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 |
end |
local buttons = {} |
for i = 1, NUMSPELLBUTTONS do |
local btn = templates:CreateCheckButton(spellFilter) |
if i == 1 then |
btn:SetPoint("TOPLEFT") |
else |
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4) |
end |
btn:SetScript("OnClick", spellButtonOnClick) |
btn.module = filters |
buttons[i] = btn |
end |
spellFilter.scrollFrame.buttons = buttons |
end |
-- spell filter scroll frame |
local spellScrollFrame = spellFilter.scrollFrame |
-- 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"}, |
} |
local spellFilterTree = templates:CreateDropDownMenu("CritlineSpellFilterTree", spellFilter, menu) |
spellFilterTree:SetFrameWidth(120) |
spellFilterTree:SetPoint("BOTTOMRIGHT", spellFilter, "TOPRIGHT", 16, 0) |
spellFilterTree:SetSelectedValue("dmg") |
spellFilter.tree = spellFilterTree |
spellScrollFrame.tree = spellFilter.tree |
do -- mob filter frame |
local mobFilter = createFilterFrame("mobs", filters, NUMFILTERBUTTONS, FILTERBUTTONHEIGHT) |
mobFilter:SetPoint("TOP", spellFilter) |
mobFilter:SetPoint("LEFT", spellFilter) |
mobFilter:SetPoint("RIGHT", spellFilter) |
mobFilter:Hide() |
filterTypes.mobs = mobFilter |
createFilterButtons(mobFilter) |
local addTarget = templates:CreateButton(mobFilter) |
addTarget:SetSize(96, 22) |
addTarget:SetPoint("TOPLEFT", mobFilter, "BOTTOMLEFT", 0, -8) |
addTarget:SetText(L["Add target"]) |
addTarget:SetScript("OnClick", function() |
local targetName = UnitName("target") |
if targetName then |
-- we don't want to add PCs to the filter |
if UnitIsPlayer("target") then |
addon:Message(L["Cannot add players to mob filter."]) |
else |
filters:AddMob(targetName) |
end |
else |
addon:Message(L["No target selected."]) |
end |
end) |
local add = mobFilter.add |
add:SetSize(96, 22) |
add:SetPoint("TOP", mobFilter, "BOTTOM", 0, -8) |
add:SetText(L["Add by name"]) |
add.popup = "CRITLINE_ADD_MOB_BY_NAME" |
local delete = mobFilter.delete |
delete:SetSize(96, 22) |
delete:SetPoint("TOPRIGHT", mobFilter, "BOTTOMRIGHT", 0, -8) |
delete:SetText(L["Delete mob"]) |
delete.msg = L["%s removed from mob filter."] |
end |
do -- aura filter frame |
local auraFilter = createFilterFrame("auras", filters, NUMFILTERBUTTONS, FILTERBUTTONHEIGHT) |
auraFilter:SetPoint("TOP", spellFilter) |
auraFilter:SetPoint("LEFT", spellFilter) |
auraFilter:SetPoint("RIGHT", spellFilter) |
auraFilter:Hide() |
filterTypes.auras = auraFilter |
createFilterButtons(auraFilter, function(self) |
GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT") |
GameTooltip:SetHyperlink("spell:"..self.spellID) |
end) |
local add = auraFilter.add |
add:SetSize(128, 22) |
add:SetPoint("TOPLEFT", auraFilter, "BOTTOMLEFT", 0, -8) |
add:SetText(L["Add by spell ID"]) |
add.popup = "CRITLINE_ADD_AURA_BY_ID" |
local delete = auraFilter.delete |
delete:SetSize(128, 22) |
delete:SetPoint("TOPRIGHT", auraFilter, "BOTTOMRIGHT", 0, -8) |
delete:SetText(L["Delete aura"]) |
delete.msg = L["%s removed from aura filter."] |
delete.func = function(spellID) |
activeAuras[spellID] = nil |
if not filters:IsEmpowered() then |
addon:Debug("No filtered aura detected. Resuming record tracking.") |
end |
end |
end |
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", |
}, |
{ |
text = L["Mob filter"], |
value = "mobs", |
}, |
{ |
text = L["Aura filter"], |
value = "auras", |
}, |
} |
local filterType = templates:CreateDropDownMenu("CritlineFilterType", filters, menu) |
filterType:SetPoint("BOTTOMLEFT", spellFilter, "TOPLEFT", -16, 0) |
filterType:SetFrameWidth(120) |
filterType:SetSelectedValue("spell") |
filters.type = filterType |
end |
end |
StaticPopupDialogs["CRITLINE_ADD_MOB_BY_NAME"] = { |
text = L["Enter mob name:"], |
button1 = OKAY, |
button2 = CANCEL, |
hasEditBox = true, |
OnAccept = function(self) |
local name = self.editBox:GetText():trim() |
if not name:match("%S+") then |
addon:Message(L["Invalid mob name."]) |
return |
end |
filters:AddMob(name) |
end, |
EditBoxOnEnterPressed = function(self) |
local name = self:GetText():trim() |
if not name:match("%S+") then |
addon:Message(L["Invalid mob name."]) |
return |
end |
filters:AddMob(name) |
self:GetParent():Hide() |
end, |
EditBoxOnEscapePressed = function(self) |
self:GetParent():Hide() |
end, |
OnShow = function(self) |
self.editBox:SetFocus() |
end, |
whileDead = true, |
timeout = 0, |
} |
StaticPopupDialogs["CRITLINE_ADD_AURA_BY_ID"] = { |
text = L["Enter spell ID:"], |
button1 = OKAY, |
button2 = CANCEL, |
hasEditBox = true, |
OnAccept = function(self) |
local id = tonumber(self.editBox:GetText()) |
if not id then |
addon:Message(L["Invalid input. Please enter a spell ID."]) |
return |
elseif not GetSpellInfo(id) then |
addon:Message(L["Invalid spell ID. No such spell."]) |
return |
end |
filters:AddAura(id) |
end, |
EditBoxOnEnterPressed = function(self) |
local id = tonumber(self:GetText()) |
if not id then |
addon:Message(L["Invalid input. Please enter a spell ID."]) |
return |
elseif not GetSpellInfo(id) then |
addon:Message(L["Invalid spell ID. No such spell exists."]) |
return |
end |
filters:AddAura(id) |
self:GetParent():Hide() |
end, |
EditBoxOnEscapePressed = function(self) |
self:GetParent():Hide() |
end, |
OnShow = function(self) |
self.editBox:SetFocus() |
end, |
whileDead = true, |
timeout = 0, |
} |
local function updateSpellFilter(self) |
local selectedTree = self.tree:GetSelectedValue() |
local spells = addon.percharDB.profile.spells[selectedTree] |
local size = #spells |
FauxScrollFrame_Update(self, size, self.numButtons, self.buttonHeight) |
local offset = FauxScrollFrame_GetOffset(self) |
local buttons = self.buttons |
for line = 1, NUMSPELLBUTTONS do |
local button = buttons[line] |
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:Show() |
else |
button:Hide() |
end |
end |
end |
local function updateFilter(self) |
local filter = filters.db.global[self.filter] |
local size = #filter |
FauxScrollFrame_Update(self, size, self.numButtons, self.buttonHeight) |
local offset = FauxScrollFrame_GetOffset(self) |
local buttons = self.buttons |
for line = 1, self.numButtons do |
local button = buttons[line] |
local lineplusoffset = line + offset |
if lineplusoffset <= size then |
if self.selected then |
if self.selected - offset == line then |
button:LockHighlight() |
else |
button:UnlockHighlight() |
end |
end |
local entry = filter[lineplusoffset] |
button.spellID = entry |
button:SetText(type(entry) == "number" and GetSpellInfo(entry) or entry) |
button:Show() |
else |
button:Hide() |
end |
end |
end |
local defaults = { |
profile = { |
invertFilter = false, |
ignoreMobFilter = false, |
ignoreAuraFilter = false, |
onlyKnown = false, |
suppressMC = true, |
dontFilterMagic = false, |
levelFilter = -1, |
}, |
global = { |
mobs = {}, |
auras = {}, |
}, |
} |
function filters:AddonLoaded() |
self.db = addon.db:RegisterNamespace("filters", defaults) |
addon.RegisterCallback(self, "SettingsLoaded", "LoadSettings") |
addon.RegisterCallback(self, "PerCharSettingsLoaded", "UpdateSpellFilter") |
addon.RegisterCallback(self, "SpellsChanged", "UpdateSpellFilter") |
-- mix in scroll frame update functions |
self.spell.scrollFrame.Update = updateSpellFilter |
self.mobs.scrollFrame.Update = updateFilter |
self.auras.scrollFrame.Update = updateFilter |
end |
addon.RegisterCallback(filters, "AddonLoaded") |
function filters:LoadSettings() |
self.profile = self.db.profile |
for _, v in ipairs(self.options.checkButtons) do |
v:LoadSetting() |
end |
self.options.slider:SetValue(self.profile.levelFilter) |
end |
do |
-- local spellButton_OnModifiedClick = SpellButton_OnModifiedClick |
-- 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))) |
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)]] |
end |
function filters:UpdateSpellFilter() |
self.spell.scrollFrame:Update() |
end |
function filters:UpdateFilter() |
self[self.type:GetSelectedValue()].scrollFrame:Update() |
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 |
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 |
function filters:AddMob(name) |
if self:IsFilteredMob(name) then |
addon:Message(L["%s is already in mob filter."]:format(name)) |
else |
tinsert(self.db.global.mobs, name) |
self:UpdateFilter() |
addon:Message(L["%s added to mob filter."]:format(name)) |
end |
end |
function filters:AddAura(spellID) |
local spellName = GetSpellInfo(spellID) |
if self:IsFilteredAura(spellID) then |
addon:Message(L["%s is already in aura filter."]:format(spellName)) |
else |
tinsert(self.db.global.auras, spellID) |
-- after we add an aura to the filter; check if we have it |
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 |
else |
for _, v in ipairs(self.db.global.auras) do |
if v == buffID then |
activeAuras[buffID] = true |
break |
elseif v == debuffID then |
activeAuras[debuffID] = true |
break |
end |
end |
end |
end |
self:UpdateFilter() |
addon:Message(L["%s added to aura filter."]:format(spellName)) |
end |
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) |
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 |
end |
if ((corruptSpells[spellID] and corruptSpells[spellID][destGUID]) or (self:IsEmpowered() and (not isPeriodic or directHoTs[spellID]))) and not self.profile.ignoreAuraFilter then |
addon:Debug(format("Spell (%s) was cast under the influence of a filtered aura. Return.", spellName)) |
return |
end |
local targetLevel = self:GetLevelFromGUID(destGUID) |
local levelDiff = 0 |
if (targetLevel > 0) and (targetLevel < UnitLevel("player")) then |
levelDiff = (UnitLevel("player") - targetLevel) |
end |
-- ignore level adjustment if magic damage and the setting is enabled |
if not isHeal and (self.profile.levelFilter >= 0) and (self.profile.levelFilter < levelDiff) and (school == 1 or not self.profile.dontFilterMagic) then |
-- target level is too low to pass level filter |
addon:Debug(format("Target (%s) level too low (%d) and damage school is filtered. Return.", destName, targetLevel)) |
return |
end |
if self:IsFilteredMob(destName, destGUID) then |
return |
end |
return true, self:IsFilteredSpell(tree, spellName, isPeriodic), 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 |
end |
-- scan for filtered auras from the specialAuras table |
function filters:IsEmpowered() |
if next(activeAuras) or not self.inControl 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 |
addon:Debug("Mob ("..mobName..") is in integrated filter.") |
return true |
end |
end |
for _, v in ipairs(self.db.global.mobs) do |
if v:lower() == mobName:lower() then |
addon:Debug("Mob ("..mobName..") is in custom filter.") |
return true |
end |
end |
end |
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) |
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 |
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 |
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.") |
end |
--[[ |
Name: LibSharedMedia-3.0 |
Revision: $Revision: 62 $ |
Author: Elkano (elkano@gmx.de) |
Inspired By: SurfaceLib by Haste/Otravi (troeks@gmail.com) |
Website: http://www.wowace.com/projects/libsharedmedia-3-0/ |
Description: Shared handling of media data (fonts, sounds, textures, ...) between addons. |
Dependencies: LibStub, CallbackHandler-1.0 |
License: LGPL v2.1 |
]] |
local MAJOR, MINOR = "LibSharedMedia-3.0", 100001 -- increase manualy on changes |
local lib = LibStub:NewLibrary(MAJOR, MINOR) |
if not lib then return end |
local _G = getfenv(0) |
local pairs = _G.pairs |
local type = _G.type |
local band = _G.bit.band |
local table_insert = _G.table.insert |
local table_sort = _G.table.sort |
local locale = GetLocale() |
local locale_is_western |
local LOCALE_MASK = 0 |
lib.LOCALE_BIT_koKR = 1 |
lib.LOCALE_BIT_ruRU = 2 |
lib.LOCALE_BIT_zhCN = 4 |
lib.LOCALE_BIT_zhTW = 8 |
lib.LOCALE_BIT_western = 128 |
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") |
lib.callbacks = lib.callbacks or CallbackHandler:New(lib) |
lib.DefaultMedia = lib.DefaultMedia or {} |
lib.MediaList = lib.MediaList or {} |
lib.MediaTable = lib.MediaTable or {} |
lib.MediaType = lib.MediaType or {} |
lib.OverrideMedia = lib.OverrideMedia or {} |
local defaultMedia = lib.DefaultMedia |
local mediaList = lib.MediaList |
local mediaTable = lib.MediaTable |
local overrideMedia = lib.OverrideMedia |
-- create mediatype constants |
lib.MediaType.BACKGROUND = "background" -- background textures |
lib.MediaType.BORDER = "border" -- border textures |
lib.MediaType.FONT = "font" -- fonts |
lib.MediaType.STATUSBAR = "statusbar" -- statusbar textures |
lib.MediaType.SOUND = "sound" -- sound files |
-- populate lib with default Blizzard data |
-- BACKGROUND |
if not lib.MediaTable.background then lib.MediaTable.background = {} end |
lib.MediaTable.background["Blizzard Dialog Background"] = [[Interface\DialogFrame\UI-DialogBox-Background]] |
lib.MediaTable.background["Blizzard Dialog Background Dark"] = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]] |
lib.MediaTable.background["Blizzard Dialog Background Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Background]] |
lib.MediaTable.background["Blizzard Low Health"] = [[Interface\FullScreenTextures\LowHealth]] |
lib.MediaTable.background["Blizzard Marble"] = [[Interface\FrameGeneral\UI-Background-Marble]] |
lib.MediaTable.background["Blizzard Out of Control"] = [[Interface\FullScreenTextures\OutOfControl]] |
lib.MediaTable.background["Blizzard Parchment"] = [[Interface\AchievementFrame\UI-Achievement-Parchment-Horizontal]] |
lib.MediaTable.background["Blizzard Parchment 2"] = [[Interface\AchievementFrame\UI-GuildAchievement-Parchment-Horizontal]] |
lib.MediaTable.background["Blizzard Rock"] = [[Interface\FrameGeneral\UI-Background-Rock]] |
lib.MediaTable.background["Blizzard Tabard Background"] = [[Interface\TabardFrame\TabardFrameBackground]] |
lib.MediaTable.background["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Background]] |
lib.MediaTable.background["Solid"] = [[Interface\Buttons\WHITE8X8]] |
-- BORDER |
if not lib.MediaTable.border then lib.MediaTable.border = {} end |
lib.MediaTable.border["None"] = [[Interface\None]] |
lib.MediaTable.border["Blizzard Achievement Wood"] = [[Interface\AchievementFrame\UI-Achievement-WoodBorder]] |
lib.MediaTable.border["Blizzard Chat Bubble"] = [[Interface\Tooltips\ChatBubble-Backdrop]] |
lib.MediaTable.border["Blizzard Dialog"] = [[Interface\DialogFrame\UI-DialogBox-Border]] |
lib.MediaTable.border["Blizzard Dialog Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Border]] |
lib.MediaTable.border["Blizzard Party"] = [[Interface\CHARACTERFRAME\UI-Party-Border]] |
lib.MediaTable.border["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Border]] |
-- FONT |
if not lib.MediaTable.font then lib.MediaTable.font = {} end |
local SML_MT_font = lib.MediaTable.font |
if locale == "koKR" then |
LOCALE_MASK = lib.LOCALE_BIT_koKR |
-- |
SML_MT_font["êµµì ê¸ê¼´"] = [[Fonts\2002B.TTF]] |
SML_MT_font["기본 ê¸ê¼´"] = [[Fonts\2002.TTF]] |
SML_MT_font["ë°ë¯¸ì§ ê¸ê¼´"] = [[Fonts\K_Damage.TTF]] |
SML_MT_font["íì¤í¸ ê¸ê¼´"] = [[Fonts\K_Pagetext.TTF]] |
-- |
lib.DefaultMedia["font"] = "기본 ê¸ê¼´" -- someone from koKR please adjust if needed |
-- |
elseif locale == "zhCN" then |
LOCALE_MASK = lib.LOCALE_BIT_zhCN |
-- |
SML_MT_font["伤害æ°å"] = [[Fonts\ZYKai_C.ttf]] |
SML_MT_font["é»è®¤"] = [[Fonts\ZYKai_T.ttf]] |
SML_MT_font["è天"] = [[Fonts\ZYHei.ttf]] |
-- |
lib.DefaultMedia["font"] = "é»è®¤" -- someone from zhCN please adjust if needed |
-- |
elseif locale == "zhTW" then |
LOCALE_MASK = lib.LOCALE_BIT_zhTW |
-- |
SML_MT_font["æ示è¨æ¯"] = [[Fonts\bHEI00M.ttf]] |
SML_MT_font["è天"] = [[Fonts\bHEI01B.ttf]] |
SML_MT_font["å·å®³æ¸å"] = [[Fonts\bKAI00M.ttf]] |
SML_MT_font["é è¨"] = [[Fonts\bLEI00D.ttf]] |
-- |
lib.DefaultMedia["font"] = "é è¨" -- someone from zhTW please adjust if needed |
elseif locale == "ruRU" then |
LOCALE_MASK = lib.LOCALE_BIT_ruRU |
-- |
SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]] |
SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT__.TTF]] |
SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS.TTF]] |
SML_MT_font["Nimrod MT"] = [[Fonts\NIM_____.ttf]] |
SML_MT_font["Skurri"] = [[Fonts\SKURRI.TTF]] |
-- |
lib.DefaultMedia.font = "Friz Quadrata TT" |
-- |
else |
LOCALE_MASK = lib.LOCALE_BIT_western |
locale_is_western = true |
-- |
SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]] |
SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT__.TTF]] |
SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS.TTF]] |
SML_MT_font["Skurri"] = [[Fonts\SKURRI.TTF]] |
-- |
lib.DefaultMedia.font = "Friz Quadrata TT" |
-- |
end |
-- STATUSBAR |
if not lib.MediaTable.statusbar then lib.MediaTable.statusbar = {} end |
lib.MediaTable.statusbar["Blizzard"] = [[Interface\TargetingFrame\UI-StatusBar]] |
lib.MediaTable.statusbar["Blizzard Character Skills Bar"] = [[Interface\PaperDollInfoFrame\UI-Character-Skills-Bar]] |
lib.DefaultMedia.statusbar = "Blizzard" |
-- SOUND |
if not lib.MediaTable.sound then lib.MediaTable.sound = {} end |
lib.MediaTable.sound["None"] = [[Interface\Quiet.ogg]] -- Relies on the fact that PlaySound[File] doesn't error on non-existing input. |
lib.DefaultMedia.sound = "None" |
local function rebuildMediaList(mediatype) |
local mtable = mediaTable[mediatype] |
if not mtable then return end |
if not mediaList[mediatype] then mediaList[mediatype] = {} end |
local mlist = mediaList[mediatype] |
-- list can only get larger, so simply overwrite it |
local i = 0 |
for k in pairs(mtable) do |
i = i + 1 |
mlist[i] = k |
end |
table_sort(mlist) |
end |
function lib:Register(mediatype, key, data, langmask) |
if type(mediatype) ~= "string" then |
error(MAJOR..":Register(mediatype, key, data, langmask) - mediatype must be string, got "..type(mediatype)) |
end |
if type(key) ~= "string" then |
error(MAJOR..":Register(mediatype, key, data, langmask) - key must be string, got "..type(key)) |
end |
mediatype = mediatype:lower() |
if mediatype == lib.MediaType.FONT and ((langmask and band(langmask, LOCALE_MASK) == 0) or not (langmask or locale_is_western)) then return false end |
if not mediaTable[mediatype] then mediaTable[mediatype] = {} end |
local mtable = mediaTable[mediatype] |
if mtable[key] then return false end |
mtable[key] = data |
rebuildMediaList(mediatype) |
self.callbacks:Fire("LibSharedMedia_Registered", mediatype, key) |
return true |
end |
function lib:Fetch(mediatype, key, noDefault) |
local mtt = mediaTable[mediatype] |
local overridekey = overrideMedia[mediatype] |
local result = mtt and ((overridekey and mtt[overridekey] or mtt[key]) or (not noDefault and defaultMedia[mediatype] and mtt[defaultMedia[mediatype]])) or nil |
return result |
end |
function lib:IsValid(mediatype, key) |
return mediaTable[mediatype] and (not key or mediaTable[mediatype][key]) and true or false |
end |
function lib:HashTable(mediatype) |
return mediaTable[mediatype] |
end |
function lib:List(mediatype) |
if not mediaTable[mediatype] then |
return nil |
end |
if not mediaList[mediatype] then |
rebuildMediaList(mediatype) |
end |
return mediaList[mediatype] |
end |
function lib:GetGlobal(mediatype) |
return overrideMedia[mediatype] |
end |
function lib:SetGlobal(mediatype, key) |
if not mediaTable[mediatype] then |
return false |
end |
overrideMedia[mediatype] = (key and mediaTable[mediatype][key]) and key or nil |
self.callbacks:Fire("LibSharedMedia_SetGlobal", mediatype, overrideMedia[mediatype]) |
return true |
end |
function lib:GetDefault(mediatype) |
return defaultMedia[mediatype] |
end |
function lib:SetDefault(mediatype, key) |
if mediaTable[mediatype] and mediaTable[mediatype][key] and not defaultMedia[mediatype] then |
defaultMedia[mediatype] = key |
return true |
else |
return false |
end |
end |
--- **AceDB-3.0** manages the SavedVariables of your addon. |
-- It offers profile management, smart defaults and namespaces for modules.\\ |
-- Data can be saved in different data-types, depending on its intended usage. |
-- The most common data-type is the `profile` type, which allows the user to choose |
-- the active profile, and manage the profiles of all of his characters.\\ |
-- The following data types are available: |
-- * **char** Character-specific data. Every character has its own database. |
-- * **realm** Realm-specific data. All of the players characters on the same realm share this database. |
-- * **class** Class-specific data. All of the players characters of the same class share this database. |
-- * **race** Race-specific data. All of the players characters of the same race share this database. |
-- * **faction** Faction-specific data. All of the players characters of the same faction share this database. |
-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. |
-- * **global** Global Data. All characters on the same account share this database. |
-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. |
-- |
-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions |
-- of the DBObjectLib listed here. \\ |
-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note |
-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that, |
-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases. |
-- |
-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]]. |
-- |
-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs. |
-- |
-- @usage |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample") |
-- |
-- -- declare defaults to be used in the DB |
-- local defaults = { |
-- profile = { |
-- setting = true, |
-- } |
-- } |
-- |
-- function MyAddon:OnInitialize() |
-- -- Assuming the .toc says ## SavedVariables: MyAddonDB |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) |
-- end |
-- @class file |
-- @name AceDB-3.0.lua |
-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $ |
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21 |
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) |
if not AceDB then return end -- No upgrade needed |
-- Lua APIs |
local type, pairs, next, error = type, pairs, next, error |
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget |
-- WoW APIs |
local _G = _G |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: LibStub |
AceDB.db_registry = AceDB.db_registry or {} |
AceDB.frame = AceDB.frame or CreateFrame("Frame") |
local CallbackHandler |
local CallbackDummy = { Fire = function() end } |
local DBObjectLib = {} |
--[[------------------------------------------------------------------------- |
AceDB Utility Functions |
---------------------------------------------------------------------------]] |
-- Simple shallow copy for copying defaults |
local function copyTable(src, dest) |
if type(dest) ~= "table" then dest = {} end |
if type(src) == "table" then |
for k,v in pairs(src) do |
if type(v) == "table" then |
-- try to index the key first so that the metatable creates the defaults, if set, and use that table |
v = copyTable(v, dest[k]) |
end |
dest[k] = v |
end |
end |
return dest |
end |
-- Called to add defaults to a section of the database |
-- |
-- When a ["*"] default section is indexed with a new key, a table is returned |
-- and set in the host table. These tables must be cleaned up by removeDefaults |
-- in order to ensure we don't write empty default tables. |
local function copyDefaults(dest, src) |
-- this happens if some value in the SV overwrites our default value with a non-table |
--if type(dest) ~= "table" then return end |
for k, v in pairs(src) do |
if k == "*" or k == "**" then |
if type(v) == "table" then |
-- This is a metatable used for table defaults |
local mt = { |
-- This handles the lookup and creation of new subtables |
__index = function(t,k) |
if k == nil then return nil end |
local tbl = {} |
copyDefaults(tbl, v) |
rawset(t, k, tbl) |
return tbl |
end, |
} |
setmetatable(dest, mt) |
-- handle already existing tables in the SV |
for dk, dv in pairs(dest) do |
if not rawget(src, dk) and type(dv) == "table" then |
copyDefaults(dv, v) |
end |
end |
else |
-- Values are not tables, so this is just a simple return |
local mt = {__index = function(t,k) return k~=nil and v or nil end} |
setmetatable(dest, mt) |
end |
elseif type(v) == "table" then |
if not rawget(dest, k) then rawset(dest, k, {}) end |
if type(dest[k]) == "table" then |
copyDefaults(dest[k], v) |
if src['**'] then |
copyDefaults(dest[k], src['**']) |
end |
end |
else |
if rawget(dest, k) == nil then |
rawset(dest, k, v) |
end |
end |
end |
end |
-- Called to remove all defaults in the default table from the database |
local function removeDefaults(db, defaults, blocker) |
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them |
setmetatable(db, nil) |
-- loop through the defaults and remove their content |
for k,v in pairs(defaults) do |
if k == "*" or k == "**" then |
if type(v) == "table" then |
-- Loop through all the actual k,v pairs and remove |
for key, value in pairs(db) do |
if type(value) == "table" then |
-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables |
if defaults[key] == nil and (not blocker or blocker[key] == nil) then |
removeDefaults(value, v) |
-- if the table is empty afterwards, remove it |
if next(value) == nil then |
db[key] = nil |
end |
-- if it was specified, only strip ** content, but block values which were set in the key table |
elseif k == "**" then |
removeDefaults(value, v, defaults[key]) |
end |
end |
end |
elseif k == "*" then |
-- check for non-table default |
for key, value in pairs(db) do |
if defaults[key] == nil and v == value then |
db[key] = nil |
end |
end |
end |
elseif type(v) == "table" and type(db[k]) == "table" then |
-- if a blocker was set, dive into it, to allow multi-level defaults |
removeDefaults(db[k], v, blocker and blocker[k]) |
if next(db[k]) == nil then |
db[k] = nil |
end |
else |
-- check if the current value matches the default, and that its not blocked by another defaults table |
if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then |
db[k] = nil |
end |
end |
end |
end |
-- This is called when a table section is first accessed, to set up the defaults |
local function initSection(db, section, svstore, key, defaults) |
local sv = rawget(db, "sv") |
local tableCreated |
if not sv[svstore] then sv[svstore] = {} end |
if not sv[svstore][key] then |
sv[svstore][key] = {} |
tableCreated = true |
end |
local tbl = sv[svstore][key] |
if defaults then |
copyDefaults(tbl, defaults) |
end |
rawset(db, section, tbl) |
return tableCreated, tbl |
end |
-- Metatable to handle the dynamic creation of sections and copying of sections. |
local dbmt = { |
__index = function(t, section) |
local keys = rawget(t, "keys") |
local key = keys[section] |
if key then |
local defaultTbl = rawget(t, "defaults") |
local defaults = defaultTbl and defaultTbl[section] |
if section == "profile" then |
local new = initSection(t, section, "profiles", key, defaults) |
if new then |
-- Callback: OnNewProfile, database, newProfileKey |
t.callbacks:Fire("OnNewProfile", t, key) |
end |
elseif section == "profiles" then |
local sv = rawget(t, "sv") |
if not sv.profiles then sv.profiles = {} end |
rawset(t, "profiles", sv.profiles) |
elseif section == "global" then |
local sv = rawget(t, "sv") |
if not sv.global then sv.global = {} end |
if defaults then |
copyDefaults(sv.global, defaults) |
end |
rawset(t, section, sv.global) |
else |
initSection(t, section, section, key, defaults) |
end |
end |
return rawget(t, section) |
end |
} |
local function validateDefaults(defaults, keyTbl, offset) |
if not defaults then return end |
offset = offset or 0 |
for k in pairs(defaults) do |
if not keyTbl[k] or k == "profiles" then |
error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset) |
end |
end |
end |
local preserve_keys = { |
["callbacks"] = true, |
["RegisterCallback"] = true, |
["UnregisterCallback"] = true, |
["UnregisterAllCallbacks"] = true, |
["children"] = true, |
} |
local realmKey = GetRealmName() |
local charKey = UnitName("player") .. " - " .. realmKey |
local _, classKey = UnitClass("player") |
local _, raceKey = UnitRace("player") |
local factionKey = UnitFactionGroup("player") |
local factionrealmKey = factionKey .. " - " .. realmKey |
-- Actual database initialization function |
local function initdb(sv, defaults, defaultProfile, olddb, parent) |
-- Generate the database keys for each section |
-- map "true" to our "Default" profile |
if defaultProfile == true then defaultProfile = "Default" end |
local profileKey |
if not parent then |
-- Make a container for profile keys |
if not sv.profileKeys then sv.profileKeys = {} end |
-- Try to get the profile selected from the char db |
profileKey = sv.profileKeys[charKey] or defaultProfile or charKey |
-- save the selected profile for later |
sv.profileKeys[charKey] = profileKey |
else |
-- Use the profile of the parents DB |
profileKey = parent.keys.profile or defaultProfile or charKey |
-- clear the profileKeys in the DB, namespaces don't need to store them |
sv.profileKeys = nil |
end |
-- This table contains keys that enable the dynamic creation |
-- of each section of the table. The 'global' and 'profiles' |
-- have a key of true, since they are handled in a special case |
local keyTbl= { |
["char"] = charKey, |
["realm"] = realmKey, |
["class"] = classKey, |
["race"] = raceKey, |
["faction"] = factionKey, |
["factionrealm"] = factionrealmKey, |
["profile"] = profileKey, |
["global"] = true, |
["profiles"] = true, |
} |
validateDefaults(defaults, keyTbl, 1) |
-- This allows us to use this function to reset an entire database |
-- Clear out the old database |
if olddb then |
for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end |
end |
-- Give this database the metatable so it initializes dynamically |
local db = setmetatable(olddb or {}, dbmt) |
if not rawget(db, "callbacks") then |
-- try to load CallbackHandler-1.0 if it loaded after our library |
if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end |
db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy |
end |
-- Copy methods locally into the database object, to avoid hitting |
-- the metatable when calling methods |
if not parent then |
for name, func in pairs(DBObjectLib) do |
db[name] = func |
end |
else |
-- hack this one in |
db.RegisterDefaults = DBObjectLib.RegisterDefaults |
db.ResetProfile = DBObjectLib.ResetProfile |
end |
-- Set some properties in the database object |
db.profiles = sv.profiles |
db.keys = keyTbl |
db.sv = sv |
--db.sv_name = name |
db.defaults = defaults |
db.parent = parent |
-- store the DB in the registry |
AceDB.db_registry[db] = true |
return db |
end |
-- handle PLAYER_LOGOUT |
-- strip all defaults from all databases |
-- and cleans up empty sections |
local function logoutHandler(frame, event) |
if event == "PLAYER_LOGOUT" then |
for db in pairs(AceDB.db_registry) do |
db.callbacks:Fire("OnDatabaseShutdown", db) |
db:RegisterDefaults(nil) |
-- cleanup sections that are empty without defaults |
local sv = rawget(db, "sv") |
for section in pairs(db.keys) do |
if rawget(sv, section) then |
-- global is special, all other sections have sub-entrys |
-- also don't delete empty profiles on main dbs, only on namespaces |
if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then |
for key in pairs(sv[section]) do |
if not next(sv[section][key]) then |
sv[section][key] = nil |
end |
end |
end |
if not next(sv[section]) then |
sv[section] = nil |
end |
end |
end |
end |
end |
end |
AceDB.frame:RegisterEvent("PLAYER_LOGOUT") |
AceDB.frame:SetScript("OnEvent", logoutHandler) |
--[[------------------------------------------------------------------------- |
AceDB Object Method Definitions |
---------------------------------------------------------------------------]] |
--- Sets the defaults table for the given database object by clearing any |
-- that are currently set, and then setting the new defaults. |
-- @param defaults A table of defaults for this database |
function DBObjectLib:RegisterDefaults(defaults) |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) |
end |
validateDefaults(defaults, self.keys) |
-- Remove any currently set defaults |
if self.defaults then |
for section,key in pairs(self.keys) do |
if self.defaults[section] and rawget(self, section) then |
removeDefaults(self[section], self.defaults[section]) |
end |
end |
end |
-- Set the DBObject.defaults table |
self.defaults = defaults |
-- Copy in any defaults, only touching those sections already created |
if defaults then |
for section,key in pairs(self.keys) do |
if defaults[section] and rawget(self, section) then |
copyDefaults(self[section], defaults[section]) |
end |
end |
end |
end |
--- Changes the profile of the database and all of it's namespaces to the |
-- supplied named profile |
-- @param name The name of the profile to set as the current profile |
function DBObjectLib:SetProfile(name) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) |
end |
-- changing to the same profile, dont do anything |
if name == self.keys.profile then return end |
local oldProfile = self.profile |
local defaults = self.defaults and self.defaults.profile |
-- Callback: OnProfileShutdown, database |
self.callbacks:Fire("OnProfileShutdown", self) |
if oldProfile and defaults then |
-- Remove the defaults from the old profile |
removeDefaults(oldProfile, defaults) |
end |
self.profile = nil |
self.keys["profile"] = name |
-- if the storage exists, save the new profile |
-- this won't exist on namespaces. |
if self.sv.profileKeys then |
self.sv.profileKeys[charKey] = name |
end |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.SetProfile(db, name) |
end |
end |
-- Callback: OnProfileChanged, database, newProfileKey |
self.callbacks:Fire("OnProfileChanged", self, name) |
end |
--- Returns a table with the names of the existing profiles in the database. |
-- You can optionally supply a table to re-use for this purpose. |
-- @param tbl A table to store the profile names in (optional) |
function DBObjectLib:GetProfiles(tbl) |
if tbl and type(tbl) ~= "table" then |
error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) |
end |
-- Clear the container table |
if tbl then |
for k,v in pairs(tbl) do tbl[k] = nil end |
else |
tbl = {} |
end |
local curProfile = self.keys.profile |
local i = 0 |
for profileKey in pairs(self.profiles) do |
i = i + 1 |
tbl[i] = profileKey |
if curProfile and profileKey == curProfile then curProfile = nil end |
end |
-- Add the current profile, if it hasn't been created yet |
if curProfile then |
i = i + 1 |
tbl[i] = curProfile |
end |
return tbl, i |
end |
--- Returns the current profile name used by the database |
function DBObjectLib:GetCurrentProfile() |
return self.keys.profile |
end |
--- Deletes a named profile. This profile must not be the active profile. |
-- @param name The name of the profile to be deleted |
-- @param silent If true, do not raise an error when the profile does not exist |
function DBObjectLib:DeleteProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) |
end |
if self.keys.profile == name then |
error("Cannot delete the active profile in an AceDBObject.", 2) |
end |
if not rawget(self.profiles, name) and not silent then |
error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) |
end |
self.profiles[name] = nil |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.DeleteProfile(db, name, true) |
end |
end |
-- Callback: OnProfileDeleted, database, profileKey |
self.callbacks:Fire("OnProfileDeleted", self, name) |
end |
--- Copies a named profile into the current profile, overwriting any conflicting |
-- settings. |
-- @param name The name of the profile to be copied into the current profile |
-- @param silent If true, do not raise an error when the profile does not exist |
function DBObjectLib:CopyProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) |
end |
if name == self.keys.profile then |
error("Cannot have the same source and destination profiles.", 2) |
end |
if not rawget(self.profiles, name) and not silent then |
error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) |
end |
-- Reset the profile before copying |
DBObjectLib.ResetProfile(self, nil, true) |
local profile = self.profile |
local source = self.profiles[name] |
copyTable(source, profile) |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.CopyProfile(db, name, true) |
end |
end |
-- Callback: OnProfileCopied, database, sourceProfileKey |
self.callbacks:Fire("OnProfileCopied", self, name) |
end |
--- Resets the current profile to the default values (if specified). |
-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object |
-- @param noCallbacks if set to true, won't fire the OnProfileReset callback |
function DBObjectLib:ResetProfile(noChildren, noCallbacks) |
local profile = self.profile |
for k,v in pairs(profile) do |
profile[k] = nil |
end |
local defaults = self.defaults and self.defaults.profile |
if defaults then |
copyDefaults(profile, defaults) |
end |
-- populate to child namespaces |
if self.children and not noChildren then |
for _, db in pairs(self.children) do |
DBObjectLib.ResetProfile(db, nil, noCallbacks) |
end |
end |
-- Callback: OnProfileReset, database |
if not noCallbacks then |
self.callbacks:Fire("OnProfileReset", self) |
end |
end |
--- Resets the entire database, using the string defaultProfile as the new default |
-- profile. |
-- @param defaultProfile The profile name to use as the default |
function DBObjectLib:ResetDB(defaultProfile) |
if defaultProfile and type(defaultProfile) ~= "string" then |
error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) |
end |
local sv = self.sv |
for k,v in pairs(sv) do |
sv[k] = nil |
end |
local parent = self.parent |
initdb(sv, self.defaults, defaultProfile, self) |
-- fix the child namespaces |
if self.children then |
if not sv.namespaces then sv.namespaces = {} end |
for name, db in pairs(self.children) do |
if not sv.namespaces[name] then sv.namespaces[name] = {} end |
initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self) |
end |
end |
-- Callback: OnDatabaseReset, database |
self.callbacks:Fire("OnDatabaseReset", self) |
-- Callback: OnProfileChanged, database, profileKey |
self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"]) |
return self |
end |
--- Creates a new database namespace, directly tied to the database. This |
-- is a full scale database in it's own rights other than the fact that |
-- it cannot control its profile individually |
-- @param name The name of the new namespace |
-- @param defaults A table of values to use as defaults |
function DBObjectLib:RegisterNamespace(name, defaults) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) |
end |
if self.children and self.children[name] then |
error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) |
end |
local sv = self.sv |
if not sv.namespaces then sv.namespaces = {} end |
if not sv.namespaces[name] then |
sv.namespaces[name] = {} |
end |
local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self) |
if not self.children then self.children = {} end |
self.children[name] = newDB |
return newDB |
end |
--- Returns an already existing namespace from the database object. |
-- @param name The name of the new namespace |
-- @param silent if true, the addon is optional, silently return nil if its not found |
-- @usage |
-- local namespace = self.db:GetNamespace('namespace') |
-- @return the namespace object if found |
function DBObjectLib:GetNamespace(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) |
end |
if not silent and not (self.children and self.children[name]) then |
error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) |
end |
if not self.children then self.children = {} end |
return self.children[name] |
end |
--[[------------------------------------------------------------------------- |
AceDB Exposed Methods |
---------------------------------------------------------------------------]] |
--- Creates a new database object that can be used to handle database settings and profiles. |
-- By default, an empty DB is created, using a character specific profile. |
-- |
-- You can override the default profile used by passing any profile name as the third argument, |
-- or by passing //true// as the third argument to use a globally shared profile called "Default". |
-- |
-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char" |
-- will use a profile named "char", and not a character-specific profile. |
-- @param tbl The name of variable, or table to use for the database |
-- @param defaults A table of database defaults |
-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default. |
-- You can also pass //true// to use a shared global profile called "Default". |
-- @usage |
-- -- Create an empty DB using a character-specific default profile. |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB") |
-- @usage |
-- -- Create a DB using defaults and using a shared default profile |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) |
function AceDB:New(tbl, defaults, defaultProfile) |
if type(tbl) == "string" then |
local name = tbl |
tbl = _G[name] |
if not tbl then |
tbl = {} |
_G[name] = tbl |
end |
end |
if type(tbl) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) |
end |
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) |
end |
return initdb(tbl, defaults, defaultProfile) |
end |
-- upgrade existing databases |
for db in pairs(AceDB.db_registry) do |
if not db.parent then |
for name,func in pairs(DBObjectLib) do |
db[name] = func |
end |
else |
db.RegisterDefaults = DBObjectLib.RegisterDefaults |
db.ResetProfile = DBObjectLib.ResetProfile |
end |
end |
assert(LibStub, "LibDataBroker-1.1 requires LibStub") |
assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") |
local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) |
if not lib then return end |
oldminor = oldminor or 0 |
lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) |
lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} |
local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks |
if oldminor < 2 then |
lib.domt = { |
__metatable = "access denied", |
__index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, |
} |
end |
if oldminor < 3 then |
lib.domt.__newindex = function(self, key, value) |
if not attributestorage[self] then attributestorage[self] = {} end |
if attributestorage[self][key] == value then return end |
attributestorage[self][key] = value |
local name = namestorage[self] |
if not name then return end |
callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) |
end |
end |
if oldminor < 2 then |
function lib:NewDataObject(name, dataobj) |
if self.proxystorage[name] then return end |
if dataobj then |
assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") |
self.attributestorage[dataobj] = {} |
for i,v in pairs(dataobj) do |
self.attributestorage[dataobj][i] = v |
dataobj[i] = nil |
end |
end |
dataobj = setmetatable(dataobj or {}, self.domt) |
self.proxystorage[name], self.namestorage[dataobj] = dataobj, name |
self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) |
return dataobj |
end |
end |
if oldminor < 1 then |
function lib:DataObjectIterator() |
return pairs(self.proxystorage) |
end |
function lib:GetDataObjectByName(dataobjectname) |
return self.proxystorage[dataobjectname] |
end |
function lib:GetNameByDataObject(dataobject) |
return self.namestorage[dataobject] |
end |
end |
if oldminor < 4 then |
local next = pairs(attributestorage) |
function lib:pairs(dataobject_or_name) |
local t = type(dataobject_or_name) |
assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") |
local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name |
assert(attributestorage[dataobj], "Data object not found") |
return next, attributestorage[dataobj], nil |
end |
local ipairs_iter = ipairs(attributestorage) |
function lib:ipairs(dataobject_or_name) |
local t = type(dataobject_or_name) |
assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") |
local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name |
assert(attributestorage[dataobj], "Data object not found") |
return ipairs_iter, attributestorage[dataobj], 0 |
end |
end |
--[[ |
LibDualSpec-1.0 - Adds dual spec support to individual AceDB-3.0 databases |
Copyright (C) 2009 Adirelle |
All rights reserved. |
Redistribution and use in source and binary forms, with or without |
modification, are permitted provided that the following conditions are met: |
* Redistributions of source code must retain the above copyright notice, |
this list of conditions and the following disclaimer. |
* Redistributions in binary form must reproduce the above copyright notice, |
this list of conditions and the following disclaimer in the documentation |
and/or other materials provided with the distribution. |
* Redistribution of a stand alone version is strictly prohibited without |
prior written authorization from the LibDualSpec project manager. |
* Neither the name of the LibDualSpec authors nor the names of its contributors |
may be used to endorse or promote products derived from this software without |
specific prior written permission. |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
--]] |
local MAJOR, MINOR = "LibDualSpec-1.0", 4 |
assert(LibStub, MAJOR.." requires LibStub") |
local lib = LibStub:NewLibrary(MAJOR, MINOR) |
if not lib then return end |
-- ---------------------------------------------------------------------------- |
-- Library data |
-- ---------------------------------------------------------------------------- |
lib.talentGroup = lib.talentGroup or GetActiveTalentGroup() |
lib.eventFrame = lib.eventFrame or CreateFrame("Frame") |
lib.registry = lib.registry or {} |
lib.options = lib.options or {} |
lib.mixin = lib.mixin or {} |
-- ---------------------------------------------------------------------------- |
-- Locals |
-- ---------------------------------------------------------------------------- |
local registry = lib.registry |
local options = lib.options |
local mixin = lib.mixin |
-- "Externals" |
local AceDB3 = LibStub('AceDB-3.0', true) |
local AceDBOptions3 = LibStub('AceDBOptions-3.0', true) |
-- ---------------------------------------------------------------------------- |
-- Localization |
-- ---------------------------------------------------------------------------- |
local L_DUALSPEC_DESC, L_ENABLED, L_ENABLED_DESC, L_DUAL_PROFILE, L_DUAL_PROFILE_DESC |
do |
L_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." |
L_ENABLED = 'Enable dual profile' |
L_ENABLED_DESC = 'Check this box to automatically swap profiles on talent switch.' |
L_DUAL_PROFILE = 'Dual profile' |
L_DUAL_PROFILE_DESC = 'Select the profile to swap with on talent switch.' |
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." |
elseif locale == "deDE" then |
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_ENABLED = "Aktiviere Duale Profile" |
L_ENABLED_DESC = "Aktiviere diese Option, um beim Talentwechsel automatisch ".. |
"zwischen den Profilen zu wechseln." |
elseif locale == "zhCN" then |
L_DUAL_PROFILE = "åéé ç½®æ件" |
L_DUAL_PROFILE_DESC = "éæ©è½¬æ¢å¤©èµæ¶æè¦ä½¿ç¨çé ç½®æ件" |
L_DUALSPEC_DESC = "å¯æ¶ï¼ä½ å¯ä»¥ä¸ºä½ çå天èµè®¾å®å¦ä¸ç»é ç½®æ件ï¼ä½ çåéé ç½®æ件å°å¨ä½ 转æ¢å¤©èµæ¶èªå¨ä¸ç®å使ç¨é ç½®æ件交æ¢ã" |
L_ENABLED = "å¼å¯åéé ç½®æ件" |
L_ENABLED_DESC = "å¾é以便转æ¢å¤©èµæ¶èªå¨äº¤æ¢é ç½®æ件ã" |
elseif locale == "zhTW" then |
L_DUALSPEC_DESC = "åç¨æï¼ä½ å¯ä»¥çºä½ çé天賦è¨å®å¦ä¸çµè¨å®æªãä½ çéè¨å®æªå°å¨ä½ è½æ天賦æèªåèç®å使ç¨è¨å®æªäº¤æã" |
L_ENABLED = "åç¨éè¨å®æª" |
L_ENABLED_DESC = "å¾é¸ä»¥å¨è½æ天賦æèªå交æè¨å®æª" |
L_DUAL_PROFILE = "éè¨å®æª" |
L_DUAL_PROFILE_DESC = "é¸æè½æ天賦å¾æè¦ä½¿ç¨çè¨å®æª" |
end |
end |
-- ---------------------------------------------------------------------------- |
-- Mixin |
-- ---------------------------------------------------------------------------- |
--- Get dual spec feature status. |
-- @return (boolean) true is dual spec feature enabled. |
-- @name enhancedDB:IsDualSpecEnabled |
function mixin:IsDualSpecEnabled() |
return registry[self].db.char.enabled |
end |
--- Enable/disabled dual spec feature. |
-- @param enabled (boolean) true to enable dual spec feature, false to disable it. |
-- @name enhancedDB:SetDualSpecEnabled |
function mixin:SetDualSpecEnabled(enabled) |
local db = registry[self].db |
if enabled and not db.char.talentGroup then |
db.char.talentGroup = lib.talentGroup |
db.char.profile = self:GetCurrentProfile() |
db.char.enabled = true |
else |
db.char.enabled = enabled |
self:CheckDualSpecState() |
end |
end |
--- Get the alternate profile name. |
-- Defaults to the current profile. |
-- @return (string) Alternate profile name. |
-- @name enhancedDB:GetDualSpecProfile |
function mixin:GetDualSpecProfile() |
return registry[self].db.char.profile or self:GetCurrentProfile() |
end |
--- Set the alternate profile name. |
-- No validation are done to ensure the profile is valid. |
-- @param profileName (string) the profile name to use. |
-- @name enhancedDB:SetDualSpecProfile |
function mixin:SetDualSpecProfile(profileName) |
registry[self].db.char.profile = profileName |
end |
--- Check if a profile swap should occur. |
-- Do nothing if the dual spec feature is disabled. In the other |
-- case, if the internally stored talent spec is different from the |
-- actual active talent spec, the database swaps to the alternate profile. |
-- There is normally no reason to call this method directly as LibDualSpec |
-- takes care of calling it at appropriate times. |
-- @name enhancedDB:CheckDualSpecState |
function mixin:CheckDualSpecState() |
local db = registry[self].db |
if 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 |
self:SetProfile(newProfile) |
db.char.profile = currentProfile |
end |
end |
end |
-- ---------------------------------------------------------------------------- |
-- AceDB-3.0 support |
-- ---------------------------------------------------------------------------- |
local function EmbedMixin(target) |
for k,v in pairs(mixin) do |
rawset(target, k, v) |
end |
end |
-- Upgrade existing mixins |
for target in pairs(registry) do |
EmbedMixin(target) |
end |
--- Embed dual spec feature into an existing AceDB-3.0 database. |
-- LibDualSpec specific methods are added to the instance. |
-- @name LibDualSpec:EnhanceDatabase |
-- @param target (table) the AceDB-3.0 instance. |
-- @param name (string) a user-friendly name of the database (best bet is the addon name). |
function lib:EnhanceDatabase(target, name) |
AceDB3 = AceDB3 or LibStub('AceDB-3.0', true) |
if type(target) ~= "table" then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): target should be a table.", 2) |
elseif type(name) ~= "string" then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): name should be a string.", 2) |
elseif not AceDB3 or not AceDB3.db_registry[target] then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): target should be an AceDB-3.0 database.", 2) |
elseif target.parent then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): cannot enhance a namespace.", 2) |
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() |
end |
-- ---------------------------------------------------------------------------- |
-- AceDBOptions-3.0 support |
-- ---------------------------------------------------------------------------- |
local function NoDualSpec() |
return GetNumTalentGroups() == 1 |
end |
options.dualSpecDesc = { |
name = L_DUALSPEC_DESC, |
type = 'description', |
order = 40.1, |
hidden = NoDualSpec, |
} |
options.enabled = { |
name = L_ENABLED, |
desc = L_ENABLED_DESC, |
type = 'toggle', |
order = 40.2, |
get = function(info) return info.handler.db:IsDualSpecEnabled() end, |
set = function(info, value) info.handler.db:SetDualSpecEnabled(value) end, |
hidden = NoDualSpec, |
} |
options.dualProfile = { |
name = L_DUAL_PROFILE, |
desc = L_DUAL_PROFILE_DESC, |
type = 'select', |
order = 40.3, |
get = function(info) return info.handler.db:GetDualSpecProfile() end, |
set = function(info, value) info.handler.db:SetDualSpecProfile(value) end, |
values = "ListProfiles", |
arg = "common", |
hidden = NoDualSpec, |
disabled = function(info) return not info.handler.db:IsDualSpecEnabled() end, |
} |
--- Embed dual spec options into an existing AceDBOptions-3.0 option table. |
-- @name LibDualSpec:EnhanceOptions |
-- @param optionTable (table) The option table returned by AceDBOptions-3.0. |
-- @param target (table) The AceDB-3.0 the options operate on. |
function lib:EnhanceOptions(optionTable, target) |
AceDBOptions3 = AceDBOptions3 or LibStub('AceDBOptions-3.0', true) |
if type(optionTable) ~= "table" then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable should be a table.", 2) |
elseif type(target) ~= "table" then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): target should be a table.", 2) |
elseif not (AceDBOptions3 and AceDBOptions3.optionTables[target]) then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable is not an AceDBOptions-3.0 table.", 2) |
elseif optionTable.handler.db ~= target then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable must be the option table of target.", 2) |
elseif not registry[target] then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): EnhanceDatabase should be called before EnhanceOptions(optionTable, target).", 2) |
elseif optionTable.plugins and optionTable.plugins[MAJOR] then |
return |
end |
if not optionTable.plugins then |
optionTable.plugins = {} |
end |
optionTable.plugins[MAJOR] = options |
end |
-- ---------------------------------------------------------------------------- |
-- Inspection |
-- ---------------------------------------------------------------------------- |
local function iterator(registry, key) |
local data |
key, data = next(registry, key) |
if key then |
return key, data.name |
end |
end |
--- Iterate through enhanced AceDB3.0 instances. |
-- The iterator returns (instance, name) pairs where instance and name are the |
-- arguments that were provided to lib:EnhanceDatabase. |
-- @name LibDualSpec:IterateDatabases |
-- @return Values to be used in a for .. in .. do statement. |
function lib:IterateDatabases() |
return iterator, lib.registry |
end |
-- ---------------------------------------------------------------------------- |
-- Switching logic |
-- ---------------------------------------------------------------------------- |
lib.eventFrame:RegisterEvent('PLAYER_TALENT_UPDATE') |
lib.eventFrame:SetScript('OnEvent', function() |
local newTalentGroup = GetActiveTalentGroup() |
if lib.talentGroup ~= newTalentGroup then |
lib.talentGroup = newTalentGroup |
for target in pairs(registry) do |
target:CheckDualSpecState() |
end |
end |
end) |
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info |
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke |
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! |
local LibStub = _G[LIBSTUB_MAJOR] |
if not LibStub or LibStub.minor < LIBSTUB_MINOR then |
LibStub = LibStub or {libs = {}, minors = {} } |
_G[LIBSTUB_MAJOR] = LibStub |
LibStub.minor = LIBSTUB_MINOR |
function LibStub:NewLibrary(major, minor) |
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") |
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") |
local oldminor = self.minors[major] |
if oldminor and oldminor >= minor then return nil end |
self.minors[major], self.libs[major] = minor, self.libs[major] or {} |
return self.libs[major], oldminor |
end |
function LibStub:GetLibrary(major, silent) |
if not self.libs[major] and not silent then |
error(("Cannot find a library instance of %q."):format(tostring(major)), 2) |
end |
return self.libs[major], self.minors[major] |
end |
function LibStub:IterateLibraries() return pairs(self.libs) end |
setmetatable(LibStub, { __call = LibStub.GetLibrary }) |
end |
--- **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 |
local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceLocale then return end -- no upgrade needed |
-- Lua APIs |
local assert, tostring, error = assert, tostring, error |
local setmetatable, rawset, rawget = setmetatable, rawset, rawget |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: GAME_LOCALE, geterrorhandler |
local gameLocale = GetLocale() |
if gameLocale == "enGB" then |
gameLocale = "enUS" |
end |
AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref |
AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName" |
-- This metatable is used on all tables returned from GetLocale |
local readmeta = { |
__index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key |
rawset(self, key, key) -- only need to see the warning once, really |
geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'") |
return key |
end |
} |
-- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys |
local readmetasilent = { |
__index = function(self, key) -- requesting totally unknown entries: return key |
rawset(self, key, key) -- only need to invoke this function once |
return key |
end |
} |
-- Remember the locale table being registered right now (it gets set by :NewLocale()) |
-- NOTE: Do never try to register 2 locale tables at once and mix their definition. |
local registering |
-- local assert false function |
local assertfalse = function() assert(false) end |
-- This metatable proxy is used when registering nondefault locales |
local writeproxy = setmetatable({}, { |
__newindex = function(self, key, value) |
rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string |
end, |
__index = assertfalse |
}) |
-- This metatable proxy is used when registering the default locale. |
-- It refuses to overwrite existing values |
-- Reason 1: Allows loading locales in any order |
-- Reason 2: If 2 modules have the same string, but only the first one to be |
-- loaded has a translation for the current locale, the translation |
-- doesn't get overwritten. |
-- |
local writedefaultproxy = setmetatable({}, { |
__newindex = function(self, key, value) |
if not rawget(registering, key) then |
rawset(registering, key, value == true and key or value) |
end |
end, |
__index = assertfalse |
}) |
--- Register a new locale (or extend an existing one) for the specified application. |
-- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players |
-- game locale. |
-- @paramsig application, locale[, isDefault[, silent]] |
-- @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. |
-- @usage |
-- -- enUS.lua |
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true) |
-- L["string1"] = true |
-- |
-- -- deDE.lua |
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE") |
-- if not L then return end |
-- L["string1"] = "Zeichenkette1" |
-- @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 |
end |
local app = AceLocale.apps[application] |
if not app then |
app = setmetatable({}, silent and readmetasilent or readmeta) |
AceLocale.apps[application] = app |
AceLocale.appnames[app] = application |
end |
registering = app -- remember globally for writeproxy and writedefaultproxy |
if isDefault then |
return writedefaultproxy |
end |
return writeproxy |
end |
--- Returns localizations for the current locale (or default locale if translations are missing). |
-- Errors if nothing is registered (spank developer, not just a missing translation) |
-- @param application Unique name of addon / module |
-- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional) |
-- @return The locale table for the current language. |
function AceLocale:GetLocale(application, silent) |
if not silent and not AceLocale.apps[application] then |
error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2) |
end |
return AceLocale.apps[application] |
end |
--[[ $Id: CallbackHandler-1.0.lua 965 2010-08-09 00:47:52Z mikk $ ]] |
local MAJOR, MINOR = "CallbackHandler-1.0", 6 |
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) |
if not CallbackHandler then return end -- No upgrade needed |
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} |
-- Lua APIs |
local tconcat = table.concat |
local assert, error, loadstring = assert, error, loadstring |
local setmetatable, rawset, rawget = setmetatable, rawset, rawget |
local next, select, pairs, type, tostring = next, select, pairs, type, tostring |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: geterrorhandler |
local xpcall = xpcall |
local function errorhandler(err) |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local next, xpcall, eh = ... |
local method, ARGS |
local function call() method(ARGS) end |
local function dispatch(handlers, ...) |
local index |
index, method = next(handlers) |
if not method then return end |
local OLD_ARGS = ARGS |
ARGS = ... |
repeat |
xpcall(call, eh) |
index, method = next(handlers, index) |
until not method |
ARGS = OLD_ARGS |
end |
return dispatch |
]] |
local ARGS, OLD_ARGS = {}, {} |
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end |
code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, {__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end}) |
-------------------------------------------------------------------------- |
-- CallbackHandler:New |
-- |
-- target - target object to embed public APIs in |
-- RegisterName - name of the callback registration API, default "RegisterCallback" |
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" |
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. |
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) |
-- TODO: Remove this after beta has gone out |
assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") |
RegisterName = RegisterName or "RegisterCallback" |
UnregisterName = UnregisterName or "UnregisterCallback" |
if UnregisterAllName==nil then -- false is used to indicate "don't want this method" |
UnregisterAllName = "UnregisterAllCallbacks" |
end |
-- we declare all objects and exported APIs inside this closure to quickly gain access |
-- to e.g. function names, the "target" parameter, etc |
-- Create the registry object |
local events = setmetatable({}, meta) |
local registry = { recurse=0, events=events } |
-- registry:Fire() - fires the given event/message into the registry |
function registry:Fire(eventname, ...) |
if not rawget(events, eventname) or not next(events[eventname]) then return end |
local oldrecurse = registry.recurse |
registry.recurse = oldrecurse + 1 |
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) |
registry.recurse = oldrecurse |
if registry.insertQueue and oldrecurse==0 then |
-- Something in one of our callbacks wanted to register more callbacks; they got queued |
for eventname,callbacks in pairs(registry.insertQueue) do |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
for self,func in pairs(callbacks) do |
events[eventname][self] = func |
-- fire OnUsed callback? |
if first and registry.OnUsed then |
registry.OnUsed(registry, target, eventname) |
first = nil |
end |
end |
end |
registry.insertQueue = nil |
end |
end |
-- Registration of a callback, handles: |
-- self["method"], leads to self["method"](self, ...) |
-- self with function ref, leads to functionref(...) |
-- "addonId" (instead of self) with function ref, leads to functionref(...) |
-- all with an optional arg, which, if present, gets passed as first argument (after self if present) |
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) |
if type(eventname) ~= "string" then |
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) |
end |
method = method or eventname |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
if type(method) ~= "string" and type(method) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) |
end |
local regfunc |
if type(method) == "string" then |
-- self["method"] calling style |
if type(self) ~= "table" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) |
elseif self==target then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) |
elseif type(self[method]) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) self[method](self,arg,...) end |
else |
regfunc = function(...) self[method](self,...) end |
end |
else |
-- function ref with self=object or self="addonId" or self=thread |
if type(self)~="table" and type(self)~="string" and type(self)~="thread" then |
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) method(arg,...) end |
else |
regfunc = method |
end |
end |
if events[eventname][self] or registry.recurse<1 then |
-- if registry.recurse<1 then |
-- we're overwriting an existing entry, or not currently recursing. just set it. |
events[eventname][self] = regfunc |
-- fire OnUsed callback? |
if registry.OnUsed and first then |
registry.OnUsed(registry, target, eventname) |
end |
else |
-- we're currently processing a callback in this registry, so delay the registration of this new entry! |
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency |
registry.insertQueue = registry.insertQueue or setmetatable({},meta) |
registry.insertQueue[eventname][self] = regfunc |
end |
end |
-- Unregister a callback |
target[UnregisterName] = function(self, eventname) |
if not self or self==target then |
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) |
end |
if type(eventname) ~= "string" then |
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) |
end |
if rawget(events, eventname) and events[eventname][self] then |
events[eventname][self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(events[eventname]) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then |
registry.insertQueue[eventname][self] = nil |
end |
end |
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds |
if UnregisterAllName then |
target[UnregisterAllName] = function(...) |
if select("#",...)<1 then |
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) |
end |
if select("#",...)==1 and ...==target then |
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) |
end |
for i=1,select("#",...) do |
local self = select(i,...) |
if registry.insertQueue then |
for eventname, callbacks in pairs(registry.insertQueue) do |
if callbacks[self] then |
callbacks[self] = nil |
end |
end |
end |
for eventname, callbacks in pairs(events) do |
if callbacks[self] then |
callbacks[self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(callbacks) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
end |
end |
end |
end |
return registry |
end |
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it |
-- try to upgrade old implicit embeds since the system is selfcontained and |
-- relies on closures to work. |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local function onUpdate(self) |
local xpos, ypos = GetCursorPosition() |
local xmin, ymin = Minimap:GetLeft(), Minimap:GetBottom() |
xpos = xmin - xpos / Minimap:GetEffectiveScale() + 70 |
ypos = ypos / Minimap:GetEffectiveScale() - ymin - 70 |
self.profile.pos = atan2(ypos, xpos) |
self:Move() |
end |
local minimap = CreateFrame("Button", nil, Minimap) |
minimap:SetToplevel(true) |
minimap:SetMovable(true) |
minimap:RegisterForClicks("LeftButtonUp", "RightButtonUp") |
minimap:RegisterForDrag("LeftButton") |
minimap:SetPoint("TOPLEFT", -15, 0) |
minimap:SetSize(32, 32) |
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 |
elseif button == "RightButton" then |
addon:OpenConfig() |
end |
end) |
minimap:SetScript("OnEnter", function(self) |
GameTooltip:SetOwner(self, "ANCHOR_LEFT") |
GameTooltip:AddLine("Critline") |
if addon.display then |
GameTooltip:AddLine(L["Left-click to toggle summary frame"], HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) |
end |
GameTooltip:AddLine(L["Right-click to open options"], HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) |
if not self.profile.locked then |
GameTooltip:AddLine(L["Drag to move"], HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) |
end |
GameTooltip:Show() |
end) |
minimap:SetScript("OnLeave", GameTooltip_Hide) |
minimap:SetScript("OnDragStart", function(self) self:SetScript("OnUpdate", onUpdate) end) |
minimap:SetScript("OnDragStop", function(self) self:SetScript("OnUpdate", nil) end) |
minimap:SetScript("OnHide", function(self) self:SetScript("OnUpdate", nil) end) |
local icon = minimap:CreateTexture(nil, "BORDER") |
icon:SetTexture(addon.icons.dmg) |
icon:SetSize(20, 20) |
icon:SetPoint("TOPLEFT", 6, -6) |
local border = minimap:CreateTexture(nil, "OVERLAY") |
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") |
border:SetSize(54, 54) |
border:SetPoint("TOPLEFT") |
local config = templates:CreateConfigFrame(L["Minimap button"], addonName, true) |
local options = { |
{ |
text = L["Show"], |
tooltipText = L["Show minimap button."], |
setting = "show", |
func = function(self) |
if self:GetChecked() then |
minimap:Show() |
else |
minimap:Hide() |
end |
end, |
}, |
{ |
text = L["Locked"], |
tooltipText = L["Lock minimap button."], |
setting = "locked", |
func = function(self) |
minimap:RegisterForDrag(not self:GetChecked() and "LeftButton") |
end, |
}, |
} |
for i, v in ipairs(options) do |
local btn = templates:CreateCheckButton(config, v) |
if i == 1 then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOMLEFT", -2, -16) |
else |
btn:SetPoint("TOP", options[i - 1], "BOTTOM", 0, -8) |
end |
btn.module = minimap |
options[i] = btn |
end |
local defaults = { |
profile = { |
show = true, |
locked = false, |
pos = 0, |
} |
} |
function minimap:AddonLoaded() |
self.db = addon.db:RegisterNamespace("minimap", defaults) |
addon.RegisterCallback(self, "SettingsLoaded", "LoadSettings") |
end |
addon.RegisterCallback(minimap, "AddonLoaded") |
function minimap:LoadSettings() |
self.profile = self.db.profile |
for _, btn in ipairs(options) do |
btn:LoadSetting() |
end |
self:Move() |
end |
function minimap:Move() |
local angle = self.profile.pos |
self:SetPoint("TOPLEFT", (52 - 80 * cos(angle)), (80 * sin(angle) - 52)) |
end |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local LSM = LibStub("LibSharedMedia-3.0") |
local templates = addon.templates |
local splash = CreateFrame("MessageFrame", nil, UIParent) |
splash:SetMovable(true) |
splash:RegisterForDrag("LeftButton") |
splash:SetSize(512, 96) |
splash:SetScript("OnMouseUp", function(self, button) |
if button == "RightButton" then |
if self.profile.enabled then |
addon.RegisterCallback(splash, "NewRecord") |
end |
self:SetFrameStrata("MEDIUM") |
self:EnableMouse(false) |
self:SetFading(true) |
self:Clear() |
end |
end) |
splash:EnableMouse(false) |
splash:SetScript("OnDragStart", splash.StartMoving) |
splash:SetScript("OnDragStop", function(self) |
self:StopMovingOrSizing() |
local pos = self.profile.pos |
pos.point, pos.x, pos.y = select(3, self:GetPoint()) |
end) |
do -- create the options frame |
local config = templates:CreateConfigFrame(L["Splash frame"], addonName, true) |
local options = {} |
splash.options = options |
local checkButtons = { |
{ |
text = L["Enabled"], |
tooltipText = L["Shows the new record on the middle of the screen."], |
setting = "enabled", |
func = function(self) |
if self:GetChecked() then |
if not splash:IsMouseEnabled() then |
addon.RegisterCallback(splash, "NewRecord") |
end |
else |
addon.UnregisterCallback(splash, "NewRecord") |
end |
end, |
}, |
{ |
text = L["Show old record"], |
tooltipText = L["Display previous record along with \"New record\" messages."], |
setting = "oldRecord", |
}, |
{ |
text = L["Use combat text splash"], |
tooltipText = L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."], |
setting = "sct", |
}, |
} |
options.checkButtons = checkButtons |
for i, v in ipairs(checkButtons) do |
local btn = templates:CreateCheckButton(config, v) |
if i == 1 then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOMLEFT", -2, -16) |
else |
btn:SetPoint("TOP", checkButtons[i - 1], "BOTTOM", 0, -8) |
end |
btn.module = splash |
checkButtons[i] = btn |
end |
options.colorButtons = {} |
-- splash frame spell name color |
local spellColor = templates:CreateColorButton(config) |
spellColor:SetPoint("TOP", checkButtons[#checkButtons], "BOTTOM", 0, -13) |
spellColor:SetText(L["Spell color"]) |
spellColor.tooltipText = L["Sets the color for the spell text in the splash frame."] |
spellColor.setting = "spell" |
options.colorButtons[1] = spellColor |
-- splash frame amount color |
local amountColor = templates:CreateColorButton(config) |
amountColor:SetPoint("TOP", spellColor, "BOTTOM", 0, -18) |
amountColor:SetText(L["Amount color"]) |
amountColor.tooltipText = L["Sets the color for the amount text in the splash frame."] |
amountColor.setting = "amount" |
options.colorButtons[2] = amountColor |
local sliders = { |
{ |
text = L["Scale"], |
tooltipText = L["Sets the scale of the splash frame."], |
minValue = 0.5, |
maxValue = 1, |
valueStep = 0.05, |
minText = "50%", |
maxText = "100%", |
func = function(self) |
local value = self:GetValue() |
self.value:SetFormattedText("%.0f%%", value * 100) |
local os = splash:GetScale() |
splash:SetScale(value) |
local point, relativeTo, relativePoint, xOff, yOff = splash:GetPoint() |
splash:SetPoint(point, relativeTo, relativePoint, (xOff*os/value), (yOff*os/value)) |
splash.profile.scale = value |
end, |
}, |
{ |
text = L["Duration"], |
tooltipText = L["Sets the time (in seconds) the splash frame is visible before fading out."], |
minValue = 0, |
maxValue = 5, |
valueStep = 0.5, |
func = function(self) |
local value = self:GetValue() |
self.value:SetText(value) |
splash:SetTimeVisible(value) |
splash.profile.duration = value |
end, |
}, |
} |
options.sliders = sliders |
for i, v in ipairs(sliders) do |
local slider = templates:CreateSlider(config, v) |
if i == 1 then |
slider:SetPoint("TOPLEFT", amountColor, "BOTTOMLEFT", 4, -24) |
else |
slider:SetPoint("TOP", sliders[i - 1], "BOTTOM", 0, -32) |
end |
sliders[i] = slider |
end |
local moveSplash = templates:CreateButton(config) |
moveSplash:SetPoint("TOP", sliders[2], "BOTTOM", 0, -24) |
moveSplash:SetSize(148, 22) |
moveSplash:SetText(L["Move splash screen"]) |
moveSplash:SetScript("OnClick", function() |
-- don't want to be interrupted by new records |
addon.UnregisterCallback(splash, "NewRecord") |
splash:SetFrameStrata("FULLSCREEN") |
splash:EnableMouse(true) |
splash:SetFading(false) |
splash:Clear() |
local colors = splash.profile.colors |
splash:AddMessage(L["Critline splash frame unlocked"], colors.spell) |
splash:AddMessage(L["Drag to move"], colors.amount) |
splash:AddMessage(L["Right-click to lock"], colors.amount) |
end) |
options.button = moveSplash |
local function onClick(self) |
self.owner:SetSelectedValue(self.value) |
local font = splash.profile.font |
font.name = self.value |
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags) |
end |
local function initialize(self) |
for _, v in ipairs(LSM:List("font")) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v |
info.func = onClick |
info.owner = self |
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", |
}, |
} |
local fontFlags = templates:CreateDropDownMenu("CritlineSplashFontFlags", config, menu) |
fontFlags:SetFrameWidth(120) |
fontFlags:SetPoint("TOP", font, "BOTTOM", 0, -16) |
fontFlags.label:SetText(L["Font outline"]) |
options.fontFlags = fontFlags |
local fontSize = templates:CreateSlider(config, { |
text = L["Font size"], |
tooltipText = L["Sets the font size of the splash frame."], |
minValue = 8, |
maxValue = 30, |
valueStep = 1, |
func = function(self) |
local value = self:GetValue() |
self.value:SetText(value) |
local font = splash.profile.font |
font.size = value |
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags) |
end, |
}) |
fontSize:SetPoint("TOP", fontFlags, "BOTTOM", 0, -24) |
options.fontSize = fontSize |
end |
local defaults = { |
profile = { |
enabled = true, |
oldRecord = false, |
sct = false, |
scale = 1, |
duration = 2, |
font = { |
name = "Skurri", |
size = 30, |
flags = "OUTLINE", |
}, |
colors = { |
spell = {r = 1, g = 1, b = 0}, |
amount = {r = 1, g = 1, b = 1}, |
}, |
pos = { |
point = "CENTER" |
}, |
} |
} |
function splash:AddonLoaded() |
self.db = addon.db:RegisterNamespace("splash", defaults) |
addon.RegisterCallback(self, "SettingsLoaded", "LoadSettings") |
end |
addon.RegisterCallback(splash, "AddonLoaded") |
function splash:LoadSettings() |
self.profile = self.db.profile |
local options = self.options |
for _, btn in ipairs(options.checkButtons) do |
btn:LoadSetting() |
end |
local colors = self.profile.colors |
for _, btn in ipairs(options.colorButtons) do |
local color = colors[btn.setting] |
btn.swatch:SetVertexColor(color.r, color.g, color.b) |
btn.color = color |
end |
local font = self.profile.font |
self:SetFont(LSM:Fetch("font", font.name), font.size, font.flags) |
options.font:SetSelectedValue(font.name) |
options.fontFlags:SetSelectedValue(font.flags) |
options.fontSize:SetValue(font.size) |
local pos = self.profile.pos |
self:ClearAllPoints() |
self:SetPoint(pos.point, pos.x, pos.y) |
local sliders = options.sliders |
sliders[1]:SetValue(self.profile.scale) |
sliders[2]:SetValue(self.profile.duration) |
end |
local addMessage = splash.AddMessage |
function splash:AddMessage(msg, color, ...) |
addMessage(self, msg, color.r, color.g, color.b, ...) |
end |
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) |
end |
local colors = splash.profile.colors |
local spellColor = colors.spell |
local amountColor = colors.amount |
if splash.profile.sct then |
-- check if any custom SCT addon is loaded and use it accordingly |
if MikSBT then |
if crit then |
MikSBT.DisplayMessage(L["Critical!"], nil, true, 255, 0, 0) |
end |
MikSBT.DisplayMessage(spell, nil, true, spellColor.r * 255, spellColor.g * 255, spellColor.b * 255) |
MikSBT.DisplayMessage(amount, nil, true, amountColor.r * 255, amountColor.g * 255, amountColor.b * 255) |
elseif SCT then |
if crit then |
SCT:DisplayMessage(L["Critical!"], red255) |
end |
SCT:DisplayMessage(spell, spellColor) |
SCT:DisplayMessage(amount, amountColor) |
elseif Parrot then |
local Parrot = Parrot:GetModule("Display") |
Parrot:ShowMessage(amount, nil, true, amountColor.r, amountColor.g, amountColor.b) |
Parrot:ShowMessage(spell, nil, true, spellColor.r, spellColor.g, spellColor.b) |
if crit then |
Parrot:ShowMessage(L["Critical!"], nil, true, 1, 0, 0) |
end |
elseif SHOW_COMBAT_TEXT == "1" then |
CombatText_AddMessage(amount, CombatText_StandardScroll, amountColor.r, amountColor.g, amountColor.b) |
CombatText_AddMessage(spell, CombatText_StandardScroll, spellColor.r, spellColor.g, spellColor.b) |
if crit then |
CombatText_AddMessage(L["Critical!"], CombatText_StandardScroll, 1, 0, 0) |
end |
end |
else |
self:Clear() |
if crit then |
self:AddMessage(L["Critical!"], red1) |
end |
self:AddMessage(spell, spellColor) |
self:AddMessage(amount, amountColor) |
end |
end |
## Interface: 40000 |
## Title: Critline |
## Version: 3.1.3 |
## Notes: Saves your normal and critical records and flashes a message if you break the record. |
## Author: L'ombra |
## SavedVariables: CritlineDB |
## SavedVariablesPerCharacter: CritlinePerCharDB |
## OptionalDeps: Ace3, LibSharedMedia-3.0 |
libs\LibStub.lua |
libs\CallbackHandler-1.0.lua |
libs\AceDB-3.0.lua |
libs\AceDBOptions-3.0.lua |
libs\AceLocale-3.0.lua |
libs\LibDataBroker-1.1.lua |
libs\LibDualSpec-1.0.lua |
libs\LibSharedMedia-3.0.lua |
locales\enUS.lua |
locales\deDE.lua |
locales\esES.lua |
locales\ruRU.lua |
locales\zhTW.lua |
templates.lua |
core.lua |
filters.lua |
splash.lua |
display.lua |
minimap.lua |
announce.lua |
reset.lua |
Broker.lua |
profiles.lua |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = {} |
addon.templates = templates |
do -- config frame |
local function createTitle(frame) |
local title = frame:CreateFontString(nil, nil, "GameFontNormalLarge") |
title:SetPoint("TOPLEFT", 16, -16) |
title:SetPoint("RIGHT", -16, 0) |
title:SetJustifyH("LEFT") |
title:SetJustifyV("TOP") |
title:SetText(frame.name) |
frame.title = title |
end |
local function createDesc(frame) |
local desc = frame:CreateFontString(nil, nil, "GameFontHighlightSmall") |
desc:SetHeight(32) |
desc:SetPoint("TOPLEFT", frame.title, "BOTTOMLEFT", 0, -8) |
desc:SetPoint("RIGHT", -32, 0) |
desc:SetJustifyH("LEFT") |
desc:SetJustifyV("TOP") |
desc:SetNonSpaceWrap(true) |
frame.desc = desc |
end |
function templates:CreateConfigFrame(name, parent, addTitle, addDesc) |
local frame = CreateFrame("Frame") |
frame.name = name |
frame.parent = parent |
if addTitle then |
createTitle(frame) |
if addDesc then |
createDesc(frame) |
end |
end |
InterfaceOptions_AddCategory(frame) |
return frame |
end |
end |
do -- click button |
local textures = {"l", "r", "m"} |
local function setTexture(self, texture) |
for _, v in ipairs(textures) do |
self[v]:SetTexture(texture) |
end |
end |
local function onMouseDown(self) |
if self:IsEnabled() == 1 then |
setTexture(self, "Interface\\Buttons\\UI-Panel-Button-Down") |
end |
end |
local function onMouseUp(self) |
if self:IsEnabled() == 1 then |
setTexture(self, "Interface\\Buttons\\UI-Panel-Button-Up") |
end |
end |
local function onDisable(self) |
setTexture(self, "Interface\\Buttons\\UI-Panel-Button-Disabled") |
end |
local function onEnable(self) |
setTexture(self, "Interface\\Buttons\\UI-Panel-Button-Up") |
end |
function templates:CreateButton(parent) |
local btn = CreateFrame("Button", nil, parent) |
btn:SetNormalFontObject("GameFontNormal") |
btn:SetHighlightFontObject("GameFontHighlight") |
btn:SetDisabledFontObject("GameFontDisable") |
btn:SetScript("OnMouseDown", onMouseDown) |
btn:SetScript("OnMouseUp", onMouseUp) |
btn:SetScript("OnDisable", onDisable) |
btn:SetScript("OnEnable", onEnable) |
local highlight = btn:CreateTexture(nil, nil, "UIPanelButtonHighlightTexture") |
btn:SetHighlightTexture(highlight) |
local l = btn:CreateTexture(nil, "BACKGROUND") |
l:SetTexture("Interface\\Buttons\\UI-Panel-Button-Up") |
l:SetTexCoord(0, 0.09375, 0, 0.6875) |
l:SetWidth(12) |
l:SetPoint("TOPLEFT") |
l:SetPoint("BOTTOMLEFT") |
btn.l = l |
local r = btn:CreateTexture(nil, "BACKGROUND") |
r:SetTexture("Interface\\Buttons\\UI-Panel-Button-Up") |
r:SetTexCoord(0.53125, 0.625, 0, 0.6875) |
r:SetWidth(12) |
r:SetPoint("TOPRIGHT") |
r:SetPoint("BOTTOMRIGHT") |
btn.r = r |
local m = btn:CreateTexture(nil, "BACKGROUND") |
m:SetTexture("Interface\\Buttons\\UI-Panel-Button-Up") |
m:SetTexCoord(0.09375, 0.53125, 0, 0.6875) |
m:SetPoint("TOPLEFT", l, "TOPRIGHT") |
m:SetPoint("BOTTOMRIGHT", r, "BOTTOMLEFT") |
btn.m = m |
return btn |
end |
end |
do -- check button |
local function onClick(self) |
local checked = self:GetChecked() |
if checked then |
PlaySound("igMainMenuOptionCheckBoxOn") |
else |
PlaySound("igMainMenuOptionCheckBoxOff") |
end |
self.module[self.db].profile[self.setting] = checked and true or false |
if self.func then |
self:func() |
end |
addon:Debug(self.setting..(checked and " on" or " off")) |
end |
local function loadSetting(self) |
self:SetChecked(self.module[self.db].profile[self.setting]) |
if self.func then |
self:func() |
end |
end |
function templates:CreateCheckButton(parent, data) |
local btn = CreateFrame("CheckButton", nil, parent, "OptionsBaseCheckButtonTemplate") |
btn:SetPushedTextOffset(0, 0) |
btn:SetScript("OnClick", onClick) |
btn.LoadSetting = loadSetting |
local text = btn:CreateFontString(nil, nil, "GameFontHighlight") |
text:SetPoint("LEFT", btn, "RIGHT", 0, 1) |
btn:SetFontString(text) |
if data then |
btn:SetText(data.text) |
data.text = nil |
data.db = data.perchar and "percharDB" or "db" |
data.perchar = nil |
for k, v in pairs(data) do |
btn[k] = v |
end |
end |
return btn |
end |
end |
do -- slider template |
local backdrop = { |
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} |
} |
local function onEnter(self) |
if self:IsEnabled() then |
if self.tooltipText then |
GameTooltip:SetOwner(self, self.tooltipOwnerPoint or "ANCHOR_RIGHT") |
GameTooltip:SetText(self.tooltipText, nil, nil, nil, nil, true) |
end |
end |
end |
function templates:CreateSlider(parent, data) |
local slider = CreateFrame("Slider", nil, parent) |
slider:EnableMouse(true) |
slider:SetSize(144, 17) |
slider:SetOrientation("HORIZONTAL") |
slider:SetHitRectInsets(0, 0, -10, -10) |
slider:SetBackdrop(backdrop) |
slider:SetScript("OnEnter", onEnter) |
slider:SetScript("OnLeave", GameTooltip_Hide) |
local text = slider:CreateFontString(nil, nil, "GameFontNormal") |
text:SetPoint("BOTTOM", slider, "TOP") |
slider.text = text |
local min = slider:CreateFontString(nil, nil, "GameFontHighlightSmall") |
min:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", -4, 3) |
slider.min = min |
local max = slider:CreateFontString(nil, nil, "GameFontHighlightSmall") |
max:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", 4, 3) |
slider.max = max |
if data then |
slider:SetMinMaxValues(data.minValue, data.maxValue) |
slider:SetValueStep(data.valueStep) |
slider:SetScript("OnValueChanged", data.func) |
text:SetText(data.text) |
min:SetText(data.minText or data.minValue) |
max:SetText(data.maxText or data.maxValue) |
slider.tooltipText = data.tooltipText |
end |
-- font string for current value |
local value = slider:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall") |
value:SetPoint("CENTER", 0, -15) |
slider.value = value |
local thumb = slider:CreateTexture() |
thumb:SetTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal") |
thumb:SetSize(32, 32) |
slider:SetThumbTexture(thumb) |
return slider |
end |
end |
do -- swatch button template |
local function setColor(self) |
local r, g, b = ColorPickerFrame:GetColorRGB() |
self.swatch:SetVertexColor(r, g, b) |
local color = self.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 |
color.r = r |
color.g = g |
color.b = b |
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 |
OpenColorPicker(info) |
end |
local function onEnter(self) |
self.bg:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) |
if self.tooltipText then |
GameTooltip:SetOwner(self, "ANCHOR_RIGHT") |
GameTooltip:SetText(self.tooltipText, nil, nil, nil, nil, true) |
end |
end |
local function onLeave(self) |
self.bg:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) |
GameTooltip:Hide() |
end |
function templates:CreateColorButton(parent) |
local btn = CreateFrame("Button", nil, parent) |
btn:SetSize(16, 16) |
btn:SetPushedTextOffset(0, 0) |
btn:SetNormalTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") |
btn.swatch = btn:GetNormalTexture() |
local bg = btn:CreateTexture(nil, "BACKGROUND") |
bg:SetTexture(1.0, 1.0, 1.0) |
bg:SetSize(14, 14) |
bg:SetPoint("CENTER") |
btn.bg = bg |
local text = btn:CreateFontString(nil, nil, "GameFontHighlight") |
text:SetPoint("LEFT", btn, "RIGHT", 5, 1) |
text:SetJustifyH("LEFT") |
btn:SetFontString(text) |
btn:SetScript("OnClick", onClick) |
btn:SetScript("OnEnter", onEnter) |
btn:SetScript("OnLeave", onLeave) |
return btn |
end |
end |
do -- editbox |
function templates:CreateEditBox(parent) |
local editbox = CreateFrame("EditBox", nil, parent) |
editbox:SetHeight(20) |
editbox:SetFontObject("ChatFontNormal") |
editbox:SetTextInsets(5, 0, 0, 0) |
local left = editbox:CreateTexture("BACKGROUND") |
left:SetTexture("Interface\\Common\\Common-Input-Border") |
left:SetTexCoord(0, 0.0625, 0, 0.625) |
left:SetWidth(8) |
left:SetPoint("TOPLEFT") |
left:SetPoint("BOTTOMLEFT") |
local right = editbox:CreateTexture("BACKGROUND") |
right:SetTexture("Interface\\Common\\Common-Input-Border") |
right:SetTexCoord(0.9375, 1, 0, 0.625) |
right:SetWidth(8) |
right:SetPoint("TOPRIGHT") |
right:SetPoint("BOTTOMRIGHT") |
local mid = editbox:CreateTexture("BACKGROUND") |
mid:SetTexture("Interface\\Common\\Common-Input-Border") |
mid:SetTexCoord(0.0625, 0.9375, 0, 0.625) |
mid:SetPoint("TOPLEFT", left, "TOPRIGHT") |
mid:SetPoint("BOTTOMRIGHT", right, "BOTTOMLEFT") |
return editbox |
end |
end |
do -- dropdown menu frame |
local function setSelectedValue(self, value) |
UIDropDownMenu_SetSelectedValue(self, value) |
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() |
else |
self:Enable() |
end |
end |
local function initialize(self) |
local onClick = self.menu.onClick |
for _, v in ipairs(self.menu) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v.text |
info.value = v.value |
info.func = onClick or v.func |
info.owner = self |
info.fontObject = v.fontObject |
UIDropDownMenu_AddButton(info) |
end |
end |
function templates:CreateDropDownMenu(name, parent, menu, initFunc, valueLookup) |
local frame = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate") |
frame.SetFrameWidth = UIDropDownMenu_SetWidth |
frame.SetSelectedValue = setSelectedValue |
frame.GetSelectedValue = getSelectedValue |
frame.Enable = UIDropDownMenu_EnableDropDown |
frame.Disable = UIDropDownMenu_DisableDropDown |
frame.SetDisabled = setDisabled |
if menu then |
for _, v in ipairs(menu) do |
menu[v.value] = v.text |
end |
end |
frame.menu = menu or valueLookup |
frame.initialize = initFunc or initialize |
local label = frame:CreateFontString(name.."Label", "BACKGROUND", "GameFontNormalSmall") |
label:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 16, 3) |
frame.label = label |
return frame |
end |
end |
do -- used in Reset and Announce |
local MAXSPELLBUTTONS = 14 |
local ITEMHEIGHT = 22 |
local spells = {} |
local function update(self) |
local selectedTree = self.tree:GetSelectedValue() |
wipe(spells) |
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) |
local offset = FauxScrollFrame_GetOffset(self.scrollFrame) |
for line = 1, MAXSPELLBUTTONS do |
local button = self.buttons[line] |
local lineplusoffset = line + offset |
if lineplusoffset <= size then |
local data = spells[lineplusoffset] |
button.spell = data.spellName |
button.isPeriodic = data.isPeriodic |
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() |
else |
button:Hide() |
end |
end |
end |
-- this is used for creating the scroll frames for the Reset and Announce frames |
function templates:CreateList(name, title) |
local frame = templates:CreateConfigFrame(title, addonName) |
frame.selectedSpells = {} |
frame.Update = update |
local function update() |
frame:Update() |
end |
addon.RegisterCallback(frame, "PerCharSettingsLoaded", "Update") |
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:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, ITEMHEIGHT, update) end) |
frame.scrollFrame = scrollFrame |
-- 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 |
end |
if next(selectedSpells) then |
frame.button:Enable() |
else |
frame.button:Disable() |
end |
end |
-- create list of check buttons |
local buttons = {} |
for i = 1, MAXSPELLBUTTONS do |
local btn = CreateFrame("CheckButton", nil, frame, "OptionsBaseCheckButtonTemplate") |
if i == 1 then |
btn:SetPoint("TOPLEFT", scrollFrame) |
else |
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4) |
end |
btn:SetPushedTextOffset(0, 0) |
btn:SetScript("OnClick", onClick) |
-- 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) |
-- font string for record amounts |
local record = btn:CreateFontString(nil, nil, "GameFontHighlight") |
record:SetPoint("CENTER", text) |
record:SetPoint("RIGHT", scrollFrame) |
record:SetJustifyH("RIGHT") |
btn.record = record |
buttons[i] = btn |
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 |
end |
-- 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 |
local L = LibStub("AceLocale-3.0"):NewLocale("Critline", "enUS", true) |
if not L then return end |
L["Add by name"] = true |
L["Add by spell ID"] = true |
L["Add target"] = 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["By crit record"] = true |
L["By normal record"] = true |
L["Cannot add players to mob filter."] = true |
L["Chat output"] = true |
L["Check to enable damage events to be recorded."] = true |
L["Check to enable healing events to be recorded."] = true |
L["Check to enable pet damage events to be recorded."] = true |
L["Crit"] = true |
L["critical "] = true |
L["Critical!"] = true |
L["Critline splash frame unlocked"] = true |
L["damage"] = true |
L["Damage"] = true |
L["Delete aura"] = true |
L["Delete mob"] = true |
L["Detailed tooltip"] = true |
L["Disable to ignore records where the target is an NPC."] = true |
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 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["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["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 |
L["Lock summary frame."] = true |
L["Locked"] = true |
L["Minimap button"] = true |
L["Mob filter"] = true |
L["Move splash screen"] = true |
L["New %s record!"] = true |
L["New %s%s record - %d"] = true |
L["None"] = true |
L["No records"] = true |
L["Normal"] = true |
L["No target selected."] = true |
L["Only known spells"] = true |
L["pet"] = true |
L["Pet"] = true |
L["Plays a sound on a new record."] = true |
L["Play sound"] = 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["Reset"] = true |
L["Reset all"] = true |
L["Reset all %s records."] = true |
L["Reset %s (%s) records."] = true |
L["Right-click to lock"] = true |
L["Right-click to open options"] = true |
L["%s added to aura filter."] = true |
L["%s added to mob filter."] = true |
L["Saves a screenshot on a new record."] = true |
L["Scale"] = true |
L["Screenshot"] = true |
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 scale of the splash frame."] = true |
L["Sets the scale of the summary frame."] = true |
L["Sets the time (in seconds) the splash frame is visible before fading out."] = true |
L["Show"] = true |
L["Show minimap button."] = true |
L["Show icons"] = true |
L["Show old record"] = true |
L["Show summary frame."] = true |
L["Shows the new record on the middle of the screen."] = true |
L["%s is already in aura filter."] = true |
L["%s is already in mob filter."] = true |
L["Spell color"] = true |
L["Spell filter"] = true |
L["Splash frame"] = true |
L["%s removed from aura filter."] = true |
L["%s removed from mob filter."] = true |
L["Summary frame scale"] = true |
L["Suppress all records while mind controlled."] = true |
L["Suppress mind control"] = true |
L["Thick"] = true |
L["Tooltip sorting:"] = true |
L["Use combat text splash"] = true |
L["Use detailed format in the summary tooltip."] = true |
local L = LibStub("AceLocale-3.0"):NewLocale("Critline", "zhTW") |
if not L then return end |
-- Traditional Chinese localisation by wowuicn |
L["Add by name"] = "æåå" |
-- L["Add by spell ID"] = "" |
L["Add target"] = "æ°å¢ç®æ¨" |
L["Advanced options"] = "ä¸è¬è¨å®" |
L["Alphabetically"] = "æåæ¯" |
L["Amount color"] = "æ¸é¡é¡è²" |
-- L["Aura filter"] = "" |
L["Basic options"] = "åºç¤é¸é " |
L["By crit record"] = "ææ´æè¨é" |
L["By normal record"] = "ææ®éè¨é" |
L["Cannot add players to mob filter."] = "ä¸è½æ·»å ç©å®¶å°æªç©é濾å¨" |
L["Chat output"] = "è天æ¡è¼¸åº" |
L["Check to enable damage events to be recorded."] = "é¸ä¸ä¾éåè¦è¨éçå·å®³äºä»¶." |
L["Check to enable healing events to be recorded."] = "é¸ä¸ä¾éåè¦è¨éçæ²»çäºä»¶." |
L["Check to enable pet damage events to be recorded."] = "é¸ä¸ä¾éåè¦è¨éç寵ç©å·å®³äºä»¶." |
L["Crit"] = "æ´æ" |
L["Critical!"] = "æ´æï¼" |
L["Critline splash frame unlocked"] = "Critline å´æ¿ºæææ¡é«å·²è§£é" |
-- L["Delete aura"] = "" |
L["Delete mob"] = "åªé¤æªç©" |
L["Detailed summary"] = "詳細çè¨æ¯" |
L["Disable to ignore records where the target is an NPC."] = "ç¦ç¨ä¾å¿½ç¥ç®æ¨æ¯ä¸åNPCçè¨é." |
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"] = "編輯æ示è¨æ¯æ ¼å¼" |
-- L["Enable to ignore additional/loss of damage or healing due to vulnerability, resistance, absorption or blocking."] = "" |
L["Enable to ignore integrated aura filter."] = "éåä¾å¿½ç¥å®æ´çå ç°é濾å¨." |
L["Enable to ignore integrated mob filter."] = "éåä¾å¿½ç¥å®æ´çæªç©é濾å¨" |
L["Enable to include rather than exclude selected spells in the spell filter."] = "éåä¾å æ¬èä¸æ¯æé¤å¨æ³è¡é濾å¨ä¸çå·²é¸ä¸æ³è¡." |
L["Enable to let magical damage ignore the level adjustment."] = "éåä¾è®éæ³å·å®³å¿½ç¥çç´èª¿æ´." |
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = "éåä¾çº\"æ°çè¨é\"使ç¨æ»¾åæ°ææåè¨æ¯ä¾ä»£æ¿é è¨çå´æ¿ºæ¡é«." |
-- 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"] = "忽ç¥æªç©é濾å¨" |
-- L["Ignore modifiers"] = " |
-- L["Invalid channel. Please enter a valid channel name or ID."] = " |
-- L["Invalid input. Please enter a spell ID."] = " |
L["Invalid mob name."] = "ç¡æçæªç©åå." |
L["Invalid player name."] = "ç¡æçç©å®¶åå." |
-- L["Invalid spell ID. No such spell exists."] = "" |
L["Invert spell filter"] = "åè½æ³è¡é濾å¨" |
L["Left-click to toggle summary frame"] = "å·¦éµé»æ éå/ééæ¦è¦æ¡é«" |
-- L["Level filter"] = "" |
-- L["Lock minimap button."] = "" |
-- L["Lock summary frame."] = "" |
-- L["Locked"] = "" |
-- L["Minimap button"] = "å°å°åæé" |
L["Mob filter"] = "æªç©é濾å¨" |
L["Move splash screen"] = "移åå´æ¿ºææ" |
L["New %s record!"] = "æ°ç %s è¨é!" |
L["No records"] = "æ²æè¨é" |
L["Normal"] = "æ®é" |
L["No target selected."] = "æ²æé¸æç®æ¨." |
L["Plays a sound on a new record."] = "ç¶æä¸åæ°çè¨éæææ¾é³æ." |
L["Play sound"] = "ææ¾é³æ" |
L["Prints new record notifications to the chat frame."] = "æå°æ°çè¨éæéå°è天æ¡." |
L["Record damage"] = "è¨éå·å®³" |
L["Record healing"] = "è¨éæ²»ç" |
L["Record pet damage"] = "è¨é寵ç©å·å®³" |
L["Record PvE"] = "è¨é PvE" |
L["Record PvP"] = "è¨é PvP" |
L["Reset all records for this tree"] = "éç½®ææè¨é" |
L["Right-click to lock"] = "å³éµé»æ éå®" |
L["Right-click to open options"] = "å³éµé»æ æéé¸é " |
-- L["%s added to aura filter."] = "" |
L["%s added to mob filter."] = "%s 已添å å°æªç©é濾å¨." |
L["Saves a screenshot on a new record."] = "ç¶æä¸åæ°çè¨éæä¿åæªå±." |
L["Saves your high normal and critical damage records and flashes a message if you break the record."] = "ä¿åä½ çæ®éææ´æå·å®³çæé«è¨é并ç¶ä½ æç ´éåè¨éæéåè¨æ¯." |
-- L["Scale"] = "" |
L["Screenshot"] = "æªå±" |
L["Sets the number of seconds you wish to display the splash frame."] = "è¨å®ä½ æ³è¦å¨å´æ¿ºæ¡é«ä¸é¡¯ç¤ºçè¨éæ¸é." |
L["Sets the scale of the splash frame."] = "è¨å®å´æ¿ºæ¡é«ç縮æ¾å¼." |
L["Sets the scale of the summary frame."] = "è¨å®æ¦è¦æ¡é«ç縮æ¾å¼." |
L["Set the color for the amount text in the splash frame."] = "è¨å®å¨å´æ¿ºæ¡é«ä¸æ¸åæåçé¡è²." |
L["Set the color for the spell text in the splash frame."] = "è¨å®å¨å´æ¿ºæ¡é«ä¸æ³è¡æåçé¡è²." |
-- L["Show"] = "" |
L["Show minimap button."] = "å¨å°å°åä¸é¡¯ç¤ºæé." |
L["Show damage"] = "顯示å·å®³" |
L["Show damage in summary frame."] = "å¨æ¦è¦æ¡é«ä¸é¡¯ç¤ºå·å®³" |
L["Show healing"] = "顯示治ç" |
L["Show healing in summary frame."] = "å¨æ¦è¦æ¡é«ä¸é¡¯ç¤ºæ²»ç" |
L["Show old record"] = "顯示èçè¨é" |
L["Show pet damage"] = "顯示寵ç©å·å®³" |
L["Show pet damage in summary frame."] = "å¨æ¦è¦æ¡é«ä¸é¡¯ç¤ºå¯µç©å·å®³" |
-- L["Show summary frame."] = "" |
L["Shows the new record on the middle of the screen."] = "å¨å±å¹ä¸é顯示æ°çè¨é." |
-- L["%s is already in aura filter."] = "" |
L["%s is already in mob filter."] = "%s å·²å¨æªç©é濾å¨." |
L["Sort summary spells:"] = "åé¡æ¦è¦æ³è¡:" |
L["Spell color"] = "æ³è¡é¡è²" |
L["Spell filter"] = "æ³è¡é濾å¨" |
L["Splash frame"] = "å´æ¿ºæ¡é«" |
-- L["%s removed from aura filter."] = "" |
L["%s removed from mob filter."] = "%s å·²å¾æªç©é濾å¨ä¸ç§»é¤." |
L["Summary frame scale"] = "æ¦è¦æ¡é«ç¸®æ¾" |
L["Suppress all records while mind controlled."] = "ç¶è¢«ç²¾ç¥æ§å¶æåç· ææè¨é." |
L["Suppress mind control"] = "åç· ç²¾ç¥æ§å¶" |
L["Use combat text splash"] = "使ç¨æ°ææåå´æ¿º" |
L["Use detailed format in the Critline summary tooltip."] = "å¨ Critline æ¦è¦æ示è¨æ¯ä¸ä½¿ç¨è©³ç´°æ ¼å¼." |
local L = LibStub("AceLocale-3.0"):NewLocale("Critline", "ruRU") |
if not L then return end |
-- Russian localisation by unw1s3, juline and getaddoncom |
L["Add by name"] = "ÐобавиÑÑ Ð¿Ð¾ имени" |
L["Add by spell ID"] = "Ðобавлен по ÐРзаклинаниÑ" |
L["Add target"] = "ÐобавиÑÑ ÑелÑ" |
L["Alphabetically"] = "РалÑавиÑном поÑÑдке" |
L["Amount color"] = "Ð¦Ð²ÐµÑ ÑÑона" |
L["Announce"] = "СообÑение" |
L["Are you sure you want to reset all %s records?"] = "ÐÑ ÑвеÑÐµÐ½Ñ ÑÑо Ñ Ð¾ÑиÑе ÑбÑоÑиÑÑ Ð²Ñе % запиÑи?" |
L["Aura filter"] = "ФилÑÑÑ ÐÑÑ" |
L["Basic options"] = "ÐазовÑе опÑии" |
L["By crit record"] = "Ðо запиÑÑм кÑиÑов" |
L["By normal record"] = "Ðо запиÑÑм Ñ Ð¸Ñов" |
L["Cannot add players to mob filter."] = "Ðевозможно добавиÑÑ Ð¸Ð³Ñока в ÑилÑÑÑ Ð¼Ð¾Ð½ÑÑÑов" |
L["Chat output"] = "ÐÑвод в ÑаÑ" |
L["Check to enable damage events to be recorded."] = "ÐклÑÑиÑе, ÑÑÐ¾Ð±Ñ Ð·Ð°Ð¿Ð¸ÑÑваÑÑ ÑÑон." |
L["Check to enable healing events to be recorded."] = "ÐклÑÑиÑе, ÑÑÐ¾Ð±Ñ Ð·Ð°Ð¿Ð¸ÑÑваÑÑ Ð»ÐµÑение." |
L["Check to enable pet damage events to be recorded."] = "ÐклÑÑиÑе, ÑÑÐ¾Ð±Ñ Ð·Ð°Ð¿Ð¸ÑÑваÑÑ ÑÑон пиÑомÑа" |
L["Crit"] = "ÐÑиÑ" -- Needs review |
L["critical "] = "кÑÑиÑиÑеÑкий(ое)" |
L["Critical!"] = "ÐÑиÑиÑеÑкий!" |
L["Critline splash frame unlocked"] = "ÐÑплÑваÑÑее окно Critline ÑазблокиÑовано" |
L["damage"] = "ÑÑон" |
L["Damage"] = "УÑон" |
L["Delete aura"] = "УдалиÑÑ ÐÑÑÑ" |
L["Delete mob"] = "УдалиÑÑ Ð¼Ð¾Ð½ÑÑÑа" |
L["Detailed tooltip"] = "ÐодÑказка Ñ Ð´ÐµÑалÑми" |
L["Disable to ignore records where the target is an NPC."] = "ÐÑклÑÑиÑе, ÑÑÐ¾Ð±Ñ Ð¸Ð³Ð½Ð¾ÑиÑоваÑÑ Ð·Ð°Ð¿Ð¸Ñи, когда ÑÐµÐ»Ñ - ÐÐЦ" |
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"] = "ÐклÑÑено." |
L["Enable to ignore additional damage due to vulnerability."] = "ÐклÑÑиÑÑ ÑÑо Ð±Ñ Ð¸Ð³Ð½Ð¾ÑиÑоваÑÑ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸ÑелÑнÑй ÑÑон из-за ÑÑзвимоÑÑи." |
L["Enable to ignore integrated aura filter."] = "ÐклÑÑиÑÑ, ÑÑÐ¾Ð±Ñ Ð¸Ð³Ð½Ð¾ÑиÑоваÑÑ Ð²ÑÑÑоеннÑй ÑилÑÑÑ Ð°ÑÑ" |
L["Enable to ignore integrated mob filter."] = "ÐклÑÑиÑÑ, ÑÑÐ¾Ð±Ñ Ð¸Ð³Ð½Ð¾ÑиÑоваÑÑ Ð²ÑÑÑоеннÑй ÑилÑÑÑ Ð¼Ð¾Ð½ÑÑÑов" |
L["Enable to ignore spells that are not in your (or your pet's) spell book."] = "ÐклÑÑиÑÑ ÑÑо Ð±Ñ Ð¸Ð³Ð½Ð¾ÑиÑоваÑÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ ÐºÐ¾ÑоÑÑÑ Ð½ÐµÑ Ð² ваÑей книге заклинаниÑ(а Ñакже пиÑомÑа)." |
L["Enable to include rather than exclude selected spells in the spell filter."] = "ÐклÑÑиÑÑ, ÑÑÐ¾Ð±Ñ ÑÑиÑÑваÑÑ Ð²ÑбÑаннÑе Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð² ÑилÑÑÑе заклинаний, а не иÑклÑÑаÑÑ Ð¸Ñ " |
L["Enable to let magical damage ignore the level filter."] = "ÐклÑÑиÑе ÑÑо Ð±Ñ Ð¼Ð°Ð³Ð¸ÑеÑкий ÑÑон игноÑиÑовал ÑилÑÑÑ ÑÑовнÑ." |
L["Enable to show icon indicators instead of text."] = "ÐклÑÑиÑÑ ÑÑо Ð±Ñ Ð¾ÑобÑажаÑÑ Ð¸ÐºÐ¾Ð½ÐºÑ Ð¸Ð½Ð´Ð¸ÐºÐ°ÑоÑов вмеÑÑо ÑекÑÑа." |
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = "ÐклÑÑиÑÑ, ÑÑÐ¾Ð±Ñ Ð¸ÑполÑзоваÑÑ Ð¿ÑокÑÑÑиваÑÑийÑÑ ÐºÐ¾Ð¼Ð±Ð°Ñ-ÑекÑÑ Ð´Ð»Ñ ÑообÑений \"ÐовÑй ÑекоÑд\" взамен ÑÑандаÑÑÐ½Ð¾Ð¼Ñ Ð²ÑплÑваÑÑÐµÐ¼Ñ Ð¾ÐºÐ½Ñ" |
L["Enter mob name:"] = "ÐведиÑе Ð¸Ð¼Ñ Ð¼Ð¾Ð½ÑÑÑа" |
L["Enter spell ID:"] = "ÐведиÑе ÐРзаклинаниÑ:" |
L["Font"] = "ШÑиÑÑ" |
L["Font outline"] = "ÐÑÑ Ð¾Ð´ÑÑий ÑÑиÑÑ" |
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"] = "ÐгноÑиÑоваÑÑ ÑилÑÑÑ Ð¼Ð¾Ð½ÑÑÑов" |
L["Ignore vulnerability"] = "ÐгноÑиÑоваÑÑ ÑÑзвимоÑÑÑ" |
L["Invalid channel. Please enter a valid channel name or ID."] = "ÐевеÑнÑй канал. ÐожалÑйÑÑа введиÑе веÑное Ð¸Ð¼Ñ ÐºÐ°Ð½Ð°Ð»Ð° или его ÐÐ." |
L["Invalid input. Please enter a spell ID."] = "ÐевеÑнÑй ввод. ÐожалÑйÑÑа введиÑе ÐРзаклинаниÑ." |
L["Invalid mob name."] = "ÐекоÑÑекÑное Ð¸Ð¼Ñ Ð¼Ð¾Ð½ÑÑÑа" |
L["Invalid player name."] = "ÐекоÑÑекÑное Ð¸Ð¼Ñ Ð¸Ð³Ñока" |
L["Invalid spell ID. No such spell exists."] = "ÐевеÑное ÐРзаклинаниÑ. Такого Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð½Ðµ ÑÑÑеÑÑвÑеÑ." |
L["Invert spell filter"] = "ÐнвеÑÑиÑоваÑÑ ÑилÑÑÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ð¹" |
L["Left-click to toggle summary frame"] = "ÐевÑй клик Ð´Ð»Ñ Ð²ÐºÐ»/вÑкл окна ÑÑаÑиÑÑики" |
L["Level filter"] = "ФилÑÑÑ ÑÑовнÑ" |
L["Locked"] = "ÐаблокиÑовано" |
L["Lock minimap button."] = "ÐаблокиÑоваÑÑ ÐºÐ½Ð¾Ð¿ÐºÑ Ð¼Ð¸Ð½Ð¸ÐºÐ°ÑÑÑ." |
L["Lock summary frame."] = "ÐаблокиÑоваÑÑ Ð¾ÐºÐ½Ð¾ ÑÑаÑиÑÑики." |
L["Minimap button"] = "Ðнопка мини-каÑÑÑ" |
L["Mob filter"] = "ФилÑÑÑ Ð¼Ð¾Ð½ÑÑÑа" |
L["Move splash screen"] = "ÐеÑедвинÑÑÑ Ð²ÑплÑваÑÑее окно" |
L["New %s record!"] = "ÐовÑй %s ÑекоÑд!" |
L["New %s%s record - %d"] = "ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑ %s%s - %d" |
L["None"] = "ÐеÑ" |
L["No records"] = "ÐÐµÑ Ð·Ð°Ð¿Ð¸Ñей." |
L["Normal"] = "ÐоÑмалÑнÑй" |
L["No target selected."] = "ÐÐµÑ Ñели" |
L["Only known spells"] = "ТолÑко извеÑÑнÑе заклинаниÑ" |
L["pet"] = "пиÑомеÑ" |
L["Pet"] = "ÐиÑомеÑ" |
L["Plays a sound on a new record."] = "ÐоÑпÑоизвеÑÑи Ð¼ÐµÐ»Ð¾Ð´Ð¸Ñ Ð¿Ñи новом ÑекоÑде." |
L["Play sound"] = "ÐоÑпÑоизвеÑÑи мелодиÑ" |
L["Prints new record notifications to the chat frame."] = "ÐоказÑваÑÑ Ñведомление о новом ÑекоÑде в окно ÑаÑа" |
L["Record damage"] = "ÐапиÑÑ ÑÑона" |
L["Record healing"] = "ÐапиÑÑ Ð»ÐµÑениÑ" |
L["Record pet damage"] = "ÐапиÑÑ ÑÑона пиÑомÑа" |
L["Record PvE"] = "ÐапиÑÑ PVE" |
L["Record PvP"] = "ÐапиÑÑ PVP" |
L["Reset"] = "СбÑоÑиÑÑ" |
L["Reset all"] = "СбÑоÑиÑÑ Ð²Ñе" |
L["Reset all %s records."] = "СбÑоÑиÑÑ Ð²Ñе %s запиÑи." |
L["Reset %s (%s) records."] = "СбÑоÑено %s (%s) запиÑей." |
L["Right-click to lock"] = "ÐÑавÑй клик Ð´Ð»Ñ ÑазблокиÑовки" |
L["Right-click to open options"] = "ÐÑавÑй клик ÑÑо Ð±Ñ Ð¾ÑкÑÑÑÑ Ð½Ð°ÑÑÑойки" |
L["%s added to aura filter."] = "% добавлен в ÑилÑÑÑ Ð°ÑÑ" |
L["%s added to mob filter."] = "%s добавлено в ÑилÑÑÑ Ð¼Ð¾Ð½ÑÑÑов" |
L["Saves a screenshot on a new record."] = "ÐелаÑÑ ÑкÑинÑÐ¾Ñ Ð¿Ñи каждом новом ÑекоÑде" |
L["Scale"] = "РазмеÑ" |
L["Screenshot"] = "СкÑинÑоÑ" |
L["Sets the color for the amount text in the splash frame."] = "УÑÑÐ°Ð½Ð°Ð²Ð»Ð¸Ð²Ð°ÐµÑ ÑÐ²ÐµÑ Ð´Ð»Ñ ÑекÑÑа в окне вÑпÑÑек." |
L["Sets the color for the spell text in the splash frame."] = "УÑÑÐ°Ð½Ð°Ð²Ð»Ð¸Ð²Ð°ÐµÑ ÑÐ²ÐµÑ Ð´Ð»Ñ ÑекÑÑа заклинаний в окне вÑпÑÑек." |
L["Sets the font size of the splash frame."] = "УÑÑÐ°Ð½Ð°Ð²Ð»Ð¸Ð²Ð°ÐµÑ ÑÐ°Ð·Ð¼ÐµÑ ÑекÑÑа Ð´Ð»Ñ Ð¾ÐºÐ½Ð° вÑпÑÑек." |
L["Sets the scale of the splash frame."] = "УÑÑановиÑÑ Ð¼Ð°ÑÑÑаб вÑплÑваÑÑего окна" |
L["Sets the scale of the summary frame."] = "УÑÑановиÑÑ Ð¼Ð°ÑÑÑаб окна Ñводки" |
L["Sets the time (in seconds) the splash frame is visible before fading out."] = "УÑÑÐ°Ð½Ð°Ð²Ð»Ð¸Ð²Ð°ÐµÑ Ð²ÑÐµÐ¼Ñ Ð²ÑпÑÑек (в ÑекÑÐ½Ð´Ð°Ñ ) пока окно видно до иÑÑезновениÑ." |
L["Show"] = "ÐоказаÑÑ" |
L["Show icons"] = "ÐоказаÑÑ Ð¸ÐºÐ¾Ð½ÐºÐ¸" |
L["Show minimap button."] = "ÐоказаÑÑ ÐºÐ½Ð¾Ð¿ÐºÑ Ð¼Ð¸Ð½Ð¸ÐºÐ°ÑÑÑ" |
L["Show old record"] = "ÐоказÑваÑÑ ÑÑаÑÑе ÑекоÑдÑ" |
L["Shows the new record on the middle of the screen."] = "ÐоказÑваÑÑ Ð½Ð¾Ð²Ñе ÑекоÑÐ´Ñ Ð² ÑенÑÑе ÑкÑана" |
L["Show summary frame."] = "ÐоказаÑÑ Ð¾ÐºÐ½Ð¾ ÑÑаÑиÑÑики" |
L["%s is already in aura filter."] = "% Ñже в ÑилÑÑÑе аÑÑ" |
L["%s is already in mob filter."] = "%s Ñже в ÑилÑÑÑе монÑÑÑов" |
L["Spell color"] = "Ð¦Ð²ÐµÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ" |
L["Spell filter"] = "ФилÑÑÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ð¹" |
L["Splash frame"] = "ÐÑплÑваÑÑее окно" |
L["%s removed from aura filter."] = "% Ñдален из ÑилÑÑÑа аÑÑ" |
L["%s removed from mob filter."] = "%s Ñдалено из ÑилÑÑÑа монÑÑÑов" |
L["Summary frame scale"] = "ÐаÑÑÑаб окна ÑÑаÑиÑÑики" |
L["Suppress all records while mind controlled."] = "Ðе запиÑÑваÑÑ, когда Ð²Ñ Ð¿Ð¾Ð´ дейÑÑвием \"ÐонÑÑÐ¾Ð»Ñ Ð½Ð°Ð´ ÑазÑмом\"" |
L["Suppress mind control"] = "СкÑÑваÑÑ ÐÐ" |
L["Thick"] = "Тонкий" |
L["Tooltip sorting:"] = "СоÑÑиÑовка подÑказок:" |
L["Use combat text splash"] = "ÐÑполÑзоваÑÑ ÑÑÑÐµÐºÑ ÑекÑÑа боÑ" |
L["Use detailed format in the summary tooltip."] = "ÐÑполÑзоваÑÑ Ð´ÐµÑализиÑованÑй ÑоÑÐ¼Ð°Ñ ÑÑаÑиÑÑики подÑказки" |
local L = LibStub("AceLocale-3.0"):NewLocale("Critline", "esES") |
if not L then return end |
-- Spanish translation by Vladixlaus |
L["Add by name"] = "Agregar por nombre" |
L["Add target"] = "Agregar objectivo" |
L["Advanced options"] = "Opciones avanzadas" |
L["Alphabetically"] = "Alfabeticamente" |
L["Amount color"] = "Color del daño" |
L["Basic options"] = "Opciones básicas" |
L["By crit record"] = "por marca" |
L["By normal record"] = "normal" |
L["Cannot add players to mob filter."] = "No puedes agregar jugadores." |
L["Chat output"] = "Salida en el Chat" |
L["Check to enable damage events to be recorded."] = "Grabar el daño" |
L["Check to enable healing events to be recorded."] = "Grabar la curaciones." |
L["Check to enable pet damage events to be recorded."] = "Grabar daño de la mascota." |
L["Crit"] = "Crítico" |
L["Critical!"] = "Crítico!" |
L["Critline splash frame unlocked"] = "Desbloquear el marco de mensaje" |
L["Delete mob"] = "Borrar mob" |
L["Detailed summary"] = "Sumario detallado" |
L["Disable to ignore records where the target is an NPC."] = "Inhabilitar: Ignorar las marcas cuando el objectivo no es un jugador." |
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." |
L["Enable to ignore integrated mob filter."] = "Habilita para ingnorar todas las entradas en el filtro de mobs." |
-- L["Enable to include rather than exclude selected spells in the spell filter."] = "" |
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" |
L["Invalid mob name."] = "Nombre invalido." |
L["Invalid player name."] = "Nombre inválido." |
L["Invert spell filter"] = "Invertir filtro de habilidades" |
L["Left-click to open options\nRight-click to hide button"] = "Click-Izq para opciones\nClick-Dcho para ocultar botón" |
L["Left-click to toggle summary frame\nRight-click to open options\nDrag to move"] = "Click-Izq para ver sumario\nClick-Dcho para opciones\nArrastrar para mover" |
L["Level filter"] = "Ajuste de nivel" |
L["Minimap button"] = "Botón del minimapa" |
L["Mob filter"] = "Filtro de mob" |
L["Move splash screen"] = "Mover cuadro de mensaje" |
L["New %s record!"] = "Nueva marca de: %s!" |
L["No records"] = "No hay marcas aún" |
L["Normal"] = "Normal" |
L["No target selected."] = "Seleccione un objectivo primero." |
L["Plays a sound on a new record."] = "Tocar sonido cuando halla una nueva marca." |
L["Play sound"] = "Tocar sonido" |
L["Prints new record notifications to the chat frame."] = "Mostrar notificcaciones en el chat." |
L["Record damage"] = "Grabar daño" |
L["Record healing"] = "Grabar curaciones" |
L["Record pet damage"] = "Grabar daño de mascota" |
L["Record PvE"] = "Grabar PvE" -- Needs review |
L["Record PvP"] = "Grabar JcJ" |
L["Reset all records for this tree"] = "Borrar los records de este tipo" |
L["Right-click to lock"] = "Click-Dcho para bloquear" |
L["%s added to mob filter."] = "%s agregado al filtro de mobs." |
L["Saves a screenshot on a new record."] = "Guardar una foto cuando halla una nueva marca." |
L["Saves your high normal and critical damage records and flashes a message if you break the record."] = "Guardar tus marcas normales y críticas; y mostar un mesaje cuando repes tus marcas." |
L["Screenshot"] = "Foto" |
L["Sets the number of seconds you wish to display the splash frame."] = "Timpo del mensaje en la pantalla." |
L["Sets the scale of the splash frame."] = "Escala del marco de mensaje." |
L["Sets the scale of the summary frame."] = "Escala del sumario." |
L["Set the color for the amount text in the splash frame."] = "Color del daño." |
L["Set the color for the spell text in the splash frame."] = "Color de la habulidad." |
L["Show button on minimap."] = "Mostar boton en el minimapa." |
L["Show damage"] = "Mostrar daño" |
L["Show damage in summary frame."] = "Mostrar daño en el sumario." |
L["Show healing"] = "Mostar curaciones" |
L["Show healing in summary frame."] = "Mostrar curaciones en el sumario." |
L["Show old record"] = "Mostra marcas antigüas" |
L["Show pet damage"] = "Mostrar daño de mascota" |
L["Show pet damage in summary frame."] = "Mostrar daño de mascota en el sumario." |
L["Shows the new record on the middle of the screen."] = "Mostar la nueva marca en el medio de la pantalla." |
L["%s is already in mob filter."] = "%s ya esta en el filtro de mobs." |
L["Sort summary spells:"] = "Organizar sumario por:" |
L["Spell color"] = "Color de la habilidad" |
L["Spell filter"] = "Filtro de habilidad" |
L["Splash frame"] = "Marco de mensaje" |
L["Splash frame scale"] = "Escala del marco de mensajes" |
L["Splash frame timer"] = "Tiempo del mensaje" |
L["%s removed from mob filter."] = "Quitar %s del filtro de mobs." |
L["Summary frame scale"] = "Escala del sumario" |
L["Suppress all records while mind controlled."] = "Ignorar las marcas mientra se usa Control mental." |
L["Suppress mind control"] = "Ignorar Control mental" |
L["Use combat text splash"] = "Usar el marco de texto de combate" |
L["Use detailed format in the Critline summary tooltip."] = "Mostrar formato detallado en el tooltip del sumario." |
local L = LibStub("AceLocale-3.0"):NewLocale("Critline", "deDE") |
if not L then return end |
-- German translation by Destoxillo |
L["Add by name"] = "Nach Namen hinzufügen" |
L["Add by spell ID"] = "Nach Zauber-ID hinzufügen" -- Needs review |
L["Add target"] = "Ziel hinzufügen" |
L["Alphabetically"] = "Alphabetisch" |
L["Amount color"] = "Farbe des Betrages" |
L["Announce"] = "Ansagen" -- Needs review |
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["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." |
L["Chat output"] = "Ausgabe im Chat" |
L["Check to enable damage events to be recorded."] = "Haken setzen um Schaden aufzuzeichnen." |
L["Check to enable healing events to be recorded."] = "Haken setzen um Heilung aufzuzeichnen." |
L["Check to enable pet damage events to be recorded."] = "Haken setzen um den Begleiterschaden aufzuzeichnen." |
L["Crit"] = "Crit" -- Needs review |
L["critical "] = "kritisch " -- Needs review |
L["Critical!"] = "Kritisch!" |
L["Critline splash frame unlocked"] = "Critline 'Splash Anzeige' freigestellt" |
L["damage"] = "Schaden" -- Needs review |
L["Damage"] = "Schaden" |
L["Delete aura"] = "Aura löschen" -- Needs review |
L["Delete mob"] = "Mob löschen" |
L["Detailed tooltip"] = "Detailierter Tooltip" -- Needs review |
L["Disable to ignore records where the target is an NPC."] = "Deaktivieren um Rekorde zu ignorieren bei denen das Ziel ein NPC ist." |
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 |
L["Enable to ignore additional damage due to vulnerability."] = "Aktivieren um zusätzlichen Schaden durch Verwundbarkeit zu ignorieren." -- Needs review |
L["Enable to ignore integrated aura filter."] = "Aktivieren um integrierten Aurenfilter zu ignorieren." |
L["Enable to ignore integrated mob filter."] = "Aktivieren um integrierten Mobfilter zu ignorieren." |
L["Enable to ignore spells that are not in your (or your pet's) spell book."] = "Aktivieren um Zauber zu ignorieren, die nicht in deinem (oder deinem Pet seinem) Zauberbuch sind." -- Needs review |
L["Enable to include rather than exclude selected spells in the spell filter."] = "Aktivieren um ausgewählte Zauber in den Zauberfilter einzufügen anstatt sie auszuschließen." |
L["Enable to let magical damage ignore the level filter."] = "Aktivieren um magischem Schaden erlauben, den Levelfilter zu ignorieren." -- Needs review |
L["Enable to show icon indicators instead of text."] = "Aktivieren um Icons als Indikatoren anzuzeigen statt Text." -- Needs review |
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = "Aktivieren um scrollenden Kampftext für die \"Neuer Rekord\" Nachricht zu verwenden anstatt der standartmäßigen Splash Anzeige." |
L["Enter mob name:"] = "Mobname eingeben:" |
L["Enter spell ID:"] = "Zauber-ID eingeben:" -- Needs review |
L["Font"] = "Schrift" -- Needs review |
L["Font outline"] = "Schriftumrandung" -- Needs review |
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" |
L["Ignore vulnerability"] = "Verwundbarkeit ignorieren" -- Needs review |
L["Invalid channel. Please enter a valid channel name or ID."] = "Ungültiger Channel. Bitte gib einen gültigen Channel Name oder ID ein." -- Needs review |
L["Invalid input. Please enter a spell ID."] = "Ungültige Eingabe. Bitte gib eine Zauber-ID ein." -- Needs review |
L["Invalid mob name."] = "Ungültiger Mobname." |
L["Invalid player name."] = "Ungültiger Spielername." |
L["Invalid spell ID. No such spell exists."] = "Ungültige Zauber-ID. Kein solcher Zauber existiert." -- Needs review |
L["Invert spell filter"] = "Zauberfilter umkehren" |
L["Left-click to toggle summary frame"] = "Linksklicken um das Zusammenfassungs-Fenster umzuschalten" -- Needs review |
L["Level filter"] = "Levelfilter" -- Needs review |
L["Locked"] = "Gesperrt" -- Needs review |
L["Lock minimap button."] = "Minimap Button sperren" -- Needs review |
L["Lock summary frame."] = "Zusammenfassungs-Fenster sperren." -- Needs review |
L["Minimap button"] = "Minimap Knopf" |
L["Mob filter"] = "Mobfilter" |
L["Move splash screen"] = "'Splash Anzeige' bewegen" |
L["New %s record!"] = "Neuer %s Rekord!" |
L["New %s%s record - %d"] = "Neuer %s%s Rekord - %d" -- Needs review |
L["None"] = "Kein" -- Needs review |
L["No records"] = "Keine Rekorde" |
L["Normal"] = "Normal" |
L["No target selected."] = "Kein Ziel ausgewählt." |
L["Only known spells"] = "Nur bekannte Zauber" -- Needs review |
L["pet"] = "Tier" -- Needs review |
L["Pet"] = "Tier" -- Needs review |
L["Plays a sound on a new record."] = "Spielt einen Sound bei einem neuen Rekord ab." |
L["Play sound"] = "Sound abspielen" |
L["Prints new record notifications to the chat frame."] = "Gibt neue Rekord-Mitteilungen im Chat-Fenster aus." |
L["Record damage"] = "Schaden aufzeichnen" |
L["Record healing"] = "Heilung aufzeichnen" |
L["Record pet damage"] = "Begleiterschaden aufzeichnen" |
L["Record PvE"] = "PvE aufzeichnen" |
L["Record PvP"] = "PvP aufzeichnen" |
L["Reset"] = "Zurücksetzen" -- Needs review |
L["Reset all"] = "Alles zurücksetzen" -- Needs review |
L["Reset all %s records."] = "Alle %s Rekorde zurücksetzen." -- Needs review |
L["Reset %s (%s) records."] = "Rekord %s (%s) zurücksetzen." -- Needs review |
L["Right-click to lock"] = "Rechtsklick zum fixieren" |
L["Right-click to open options"] = "Rechtsklicken um die Optionen zu öffnen" -- Needs review |
L["%s added to aura filter."] = "%s zum Aurafilter hinzugefügt." -- Needs review |
L["%s added to mob filter."] = "%s zum Mobfilter hinzugefügt." |
L["Saves a screenshot on a new record."] = "Speichert einen Screenshot bei einem neuen Rekord." |
L["Scale"] = "Skalierung" -- Needs review |
L["Screenshot"] = "Screenshot" |
L["Sets the color for the amount text in the splash frame."] = "Setzt die Farbe für die Anzeige des Wertes im Splash-Fenster." -- Needs review |
L["Sets the color for the spell text in the splash frame."] = "Setzt die Farbe für den Zaubertext im Splash-Fenster." -- Needs review |
L["Sets the font size of the splash frame."] = "Setzt die Schriftgrösse im Splash-Fenster." -- Needs review |
L["Sets the scale of the splash frame."] = "Legt die Skalierung der 'Splash Anzeige' fest." |
L["Sets the scale of the summary frame."] = "Legt die Skalierung der Zusammenfassung fest." |
L["Sets the time (in seconds) the splash frame is visible before fading out."] = "Setzt die Zeit (in Sekunden) bis das Splash-Fenster ausgeblendet wird." -- Needs review |
L["Show"] = "Zeige" -- Needs review |
L["Show icons"] = "Zeige Icons" -- Needs review |
L["Show minimap button."] = "Zeige Minimap Button." -- Needs review |
L["Show old record"] = "Alten Rekord anzeigen" |
L["Shows the new record on the middle of the screen."] = "Zeigt den neuen Rekord in der Mitte des Bildschirms." |
L["Show summary frame."] = "Zeige Zusammenfassungs-Fenster." -- Needs review |
L["%s is already in aura filter."] = "%s ist schon im Aurafilter." -- Needs review |
L["%s is already in mob filter."] = "%s ist schon im Mobfilter." |
L["Spell color"] = "Zauberfarbe" |
L["Spell filter"] = "Zauberfilter" |
L["Splash frame"] = "'Splash Anzeige'" |
L["%s removed from aura filter."] = "%s vom Aurafilter entfernt." -- Needs review |
L["%s removed from mob filter."] = "%s vom Mobfilter entfernt." |
L["Summary frame scale"] = "Zusammenfassung Skalierung" |
L["Suppress all records while mind controlled."] = "Unterdrücke alle Rekorde unter Gedankenkontrolle." |
L["Suppress mind control"] = "Unterdrücke Gedankenkontrolle" |
L["Thick"] = "Stark" -- Needs review |
L["Tooltip sorting:"] = "Tooltip Sortierung:" -- Needs review |
L["Use combat text splash"] = "Benutze 'Kampftext Splash'" |
L["Use detailed format in the summary tooltip."] = "Benutze detailiertes Format im Zusammenfassungs-Tooltip." -- Needs review |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local treeNames = addon.treeNames |
local module = templates:CreateList("CritlineReset", L["Reset"]) |
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) |
-- "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, |
} |
end |
function module:ResetRecords(resetAll) |
local selectedSpells = self.selectedSpells |
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])) |
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 |
end |
wipe(selectedSpells) |
self:Update() |
self.button:Disable() |
addon:UpdateSpells(tree) |
end |
local addonName, addon = ... |
local templates = addon.templates |
local L = { |
profiles = "Profiles", |
current = "Current profile:", |
default = "Default", |
reset = "Reset profile", |
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", |
choose = "Existing profiles", |
copy_desc = "Copy the settings from one existing profile into the currently active profile.", |
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_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.", |
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["reset"] = "Profil zur\195\188cksetzen" |
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["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_confirm"] = "Willst du das ausgew\195\164hlte Profil wirklich l\195\182schen?" |
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["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["reset"] = "R\195\169initialiser le profil" |
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["choose"] = "Profils existants" |
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_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["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"] = "íë¡í" |
-- 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"] = "ì ë§ë¡ ì íí íë¡íì ìì 를 ìíìëê¹?" |
elseif LOCALE == "esES" or LOCALE == "esMX" then |
L["profiles"] = "Perfiles" |
--L["current"] = "Current Profile:" |
L["default"] = "Por defecto" |
L["reset"] = "Reiniciar Perfil" |
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["choose"] = "Perfiles existentes" |
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_confirm"] = "¿Estas seguro que quieres borrar el perfil seleccionado?" |
elseif LOCALE == "zhTW" then |
L["profiles"] = "è¨å®æª" |
--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["enabled"] = "åç¨éè¨å®æª" |
L["enabled_desc"] = "å¾é¸ä»¥å¨è½æ天賦æèªå交æè¨å®æª" |
L["dual_profile"] = "éè¨å®æª" |
elseif LOCALE == "zhCN" then |
L["profiles"] = "é ç½®æ件" |
--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["enabled"] = "å¼å¯åéé ç½®æ件" |
L["enabled_desc"] = "å¾é以便转æ¢å¤©èµæ¶èªå¨äº¤æ¢é ç½®æ件ã" |
L["dual_profile"] = "åéé ç½®æ件" |
elseif LOCALE == "ruRU" then |
L["profiles"] = "ÐÑоÑили" |
--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"] = "ÐÑ ÑвеÑенÑ, ÑÑо Ð²Ñ Ñ Ð¾ÑиÑе ÑдалиÑÑ Ð²ÑбÑаннÑй пÑоÑилÑ?" |
end |
local defaultProfiles = {} |
local function profileSort(a, b) |
return a.value < b.value |
end |
local tempProfiles = {} |
local function getProfiles(db, common, nocurrent) |
local profiles = {} |
-- copy existing profiles into the table |
local currentProfile = db:GetCurrentProfile() |
for _, v in ipairs(db:GetProfiles(tempProfiles)) do |
if not (nocurrent and v == currentProfile) then |
profiles[v] = v |
end |
end |
-- add our default profiles to choose from ( or rename existing profiles) |
for k, v in pairs(defaultProfiles) do |
if (common or profiles[k]) and not (nocurrent and k == currentProfile) then |
profiles[k] = v |
end |
end |
local sortProfiles = {} |
local n = 1 |
for k, v in pairs(profiles) do |
sortProfiles[n] = {text = v, value = k} |
n = n + 1 |
end |
sort(sortProfiles, profileSort) |
return sortProfiles |
end |
local function createFontString(parent) |
local text = parent:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall") |
-- text:SetHeight(32) |
text:SetPoint("LEFT", parent.title) |
text:SetPoint("RIGHT", -32, 0) |
text:SetJustifyH("LEFT") |
text:SetJustifyV("TOP") |
return text |
end |
local function profilesLoaded(self) |
local db = addon[self.db] |
self.db = db |
for k, object in pairs(self.objects) do |
object.db = db |
self[k] = object |
end |
db.RegisterCallback(self, "OnProfileChanged") |
db.RegisterCallback(self, "OnNewProfile") |
db.RegisterCallback(self, "OnProfileDeleted") |
local keys = db.keys |
defaultProfiles["Default"] = L.default |
defaultProfiles[keys.char] = keys.char |
defaultProfiles[keys.realm] = keys.realm |
defaultProfiles[keys.class] = UnitClass("player") |
self.currProfile:SetFormattedText("Current profile: %s%s%s", NORMAL_FONT_COLOR_CODE, db:GetCurrentProfile(), FONT_COLOR_CODE_CLOSE) |
self.choose:SetSelectedValue(db:GetCurrentProfile()) |
self.dualProfile:SetSelectedValue(db:GetDualSpecProfile()) |
local isDualSpecEnabled = db:IsDualSpecEnabled() |
self.dualEnabled:SetChecked(isDualSpecEnabled) |
self.dualProfile:SetDisabled(not isDualSpecEnabled) |
self:CheckProfiles() |
end |
local function onProfileChanged(self, event, db, profile) |
self.currProfile:SetFormattedText("Current profile: %s%s%s", NORMAL_FONT_COLOR_CODE, profile, FONT_COLOR_CODE_CLOSE) |
self.choose:SetSelectedValue(profile) |
self.dualProfile:SetSelectedValue(db:GetDualSpecProfile()) |
self:CheckProfiles() |
end |
local function onNewProfile(self, event, db, profile) |
self:CheckProfiles() |
end |
local function onProfileDeleted(self, event, db, profile) |
self:CheckProfiles() |
end |
local function checkProfiles(self) |
local hasNoProfiles = self:HasNoProfiles() |
self.copy:SetDisabled(hasNoProfiles) |
self.delete:SetDisabled(hasNoProfiles) |
end |
local function hasNoProfiles(self) |
return next(getProfiles(self.db, nil, true)) == nil |
end |
local function initializeDropdown(self) |
for _, v in ipairs(getProfiles(self.db, self.common, self.nocurrent)) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v.text |
info.value = v.value |
info.func = self.func |
info.owner = self |
UIDropDownMenu_AddButton(info) |
end |
end |
local function newProfileOnEnterPressed(self) |
self.db:SetProfile(self:GetText()) |
self:SetText("") |
self:ClearFocus() |
end |
local function chooseProfileOnClick(self) |
self.owner.db:SetProfile(self.value) |
end |
local function enableDualProfileOnClick(self) |
local checked = self:GetChecked() |
self.db:SetDualSpecEnabled(checked) |
self.dualProfile:SetDisabled(not checked) |
end |
local function dualProfileOnClick(self) |
self.owner.db:SetDualSpecProfile(self.value) |
UIDropDownMenu_SetSelectedValue(self.owner, self.value) |
end |
local function copyProfileOnClick(self) |
self.owner.db:CopyProfile(self.value) |
end |
local function deleteProfileOnClick(self) |
UIDropDownMenu_SetSelectedValue(self.owner, self.value) |
StaticPopup_Show("CRITLINE_DELETE_PROFILE", nil, nil, {db = self.owner.db, obj = self.owner}) |
end |
local function createProfileUI(name, db) |
local frame = templates:CreateConfigFrame(name, addonName, true, true) |
frame.db = db |
frame.ProfilesLoaded = profilesLoaded |
frame.OnProfileChanged = onProfileChanged |
frame.OnNewProfile = onNewProfile |
frame.OnProfileDeleted = onProfileDeleted |
addon.RegisterCallback(frame, "AddonLoaded", "ProfilesLoaded") |
frame.CheckProfiles = checkProfiles |
frame.HasNoProfiles = hasNoProfiles |
local objects = {} |
frame.objects = objects |
local reset = addon.templates:CreateButton(frame) |
reset:SetSize(160, 22) |
reset:SetPoint("TOPLEFT", frame.desc, "BOTTOMLEFT") |
reset:SetScript("OnClick", function(self) self.db:ResetProfile() end) |
reset:SetText(L.reset) |
objects.reset = reset |
local currProfile = frame:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall") |
currProfile:SetPoint("LEFT", reset, "RIGHT") |
currProfile:SetJustifyH("LEFT") |
currProfile:SetJustifyV("CENTER") |
objects.currProfile = currProfile |
local chooseDesc = createFontString(frame) |
chooseDesc:SetHeight(32) |
chooseDesc:SetPoint("TOP", reset, "BOTTOM", 0, -8) |
-- chooseDesc:SetWordWrap(true) |
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) |
newProfile:SetScript("OnEnterPressed", newProfileOnEnterPressed) |
objects.newProfile = newProfile |
local label = newProfile:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") |
label:SetPoint("BOTTOMLEFT", newProfile, "TOPLEFT", 0, -2) |
label:SetPoint("BOTTOMRIGHT", newProfile, "TOPRIGHT", 0, -2) |
label:SetJustifyH("LEFT") |
label:SetHeight(18) |
label:SetText(L.new) |
local choose = templates:CreateDropDownMenu("CritlineDBChooseProfile"..name, frame, nil, initializeDropdown, defaultProfiles) |
choose:SetFrameWidth(144) |
choose:SetPoint("LEFT", newProfile, "RIGHT", 0, -2) |
choose.label:SetText(L.choose) |
choose.func = chooseProfileOnClick |
choose.common = true |
objects.choose = choose |
do |
local dualDesc = createFontString(frame) |
dualDesc:SetHeight(32) |
dualDesc:SetPoint("TOP", newProfile, "BOTTOM", 0, -8) |
-- dualDesc:SetWordWrap(true) |
dualDesc:SetText(L.dualspec_desc) |
local enabled = CreateFrame("CheckButton", nil, frame, "OptionsBaseCheckButtonTemplate") |
enabled:SetPoint("TOPLEFT", dualDesc, "BOTTOMLEFT", 0, -16) |
enabled:SetPushedTextOffset(0, 0) |
enabled:SetScript("OnClick", enableDualProfileOnClick) |
enabled.tooltipText = L.enable_desc |
local text = enabled:CreateFontString(nil, nil, "GameFontHighlight") |
text:SetPoint("LEFT", enabled, "RIGHT", 0, 1) |
text:SetText(L.enabled) |
objects.dualEnabled = enabled |
local dualProfile = templates:CreateDropDownMenu("CritlineDBDualProfile"..name, frame, nil, initializeDropdown, defaultProfiles) |
dualProfile:SetFrameWidth(144) |
dualProfile:SetPoint("LEFT", choose) |
dualProfile:SetPoint("TOP", enabled) |
dualProfile.label:SetText(L.dual_profile) |
dualProfile.func = dualProfileOnClick |
dualProfile.common = true |
objects.dualProfile = dualProfile |
enabled.dualProfile = dualProfile |
end |
local copyDesc = createFontString(frame) |
copyDesc:SetHeight(32) |
copyDesc:SetPoint("TOP", objects.dualEnabled, "BOTTOM", 0, -8) |
copyDesc:SetWordWrap(true) |
copyDesc:SetText(L.copy_desc) |
local copy = templates:CreateDropDownMenu("CritlineDBCopyProfile"..name, frame, nil, initializeDropdown, defaultProfiles) |
copy:SetFrameWidth(144) |
copy:SetPoint("TOPLEFT", copyDesc, "BOTTOMLEFT", -16, -8) |
copy.label:SetText(L.copy) |
copy.func = copyProfileOnClick |
copy.nocurrent = true |
objects.copy = copy |
local deleteDesc = createFontString(frame) |
deleteDesc:SetHeight(32) |
deleteDesc:SetPoint("TOP", copy, "BOTTOM", 0, -8) |
deleteDesc:SetWordWrap(true) |
deleteDesc:SetText(L.delete_desc) |
local delete = templates:CreateDropDownMenu("CritlineDBDeleteProfile"..name, frame, nil, initializeDropdown, defaultProfiles) |
delete:SetFrameWidth(144) |
delete:SetPoint("TOPLEFT", deleteDesc, "BOTTOMLEFT", -16, -8) |
delete.label:SetText(L.delete) |
delete.func = deleteProfileOnClick |
delete.nocurrent = true |
objects.delete = delete |
return frame |
end |
StaticPopupDialogs["CRITLINE_DELETE_PROFILE"] = { |
text = L.delete_confirm, |
button1 = ACCEPT, |
button2 = CANCEL, |
OnAccept = function(self, data) |
local delete = data.obj |
self.data.db:DeleteProfile(delete:GetSelectedValue()) |
delete:SetSelectedValue(nil) |
end, |
OnCancel = function(self, data) |
data.obj:SetSelectedValue(nil) |
end, |
whileDead = true, |
timeout = 0, |
} |
local profiles = createProfileUI("Profiles", "db") |
profiles.desc:SetText("This profile controls all settings that are not related to individual trees or their records.") |
local spellProfiles = createProfileUI("Spell profiles", "percharDB") |
spellProfiles.desc:SetText("This profile stores individual tree settings, including which trees will be registered, and spell records.") |
local addonName, addon = ... |
local LDB = LibStub("LibDataBroker-1.1") |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local feeds = { |
dmg = L["Damage"], |
heal = L["Healing"], |
pet = L["Pet"], |
} |
for k, v in pairs(feeds) do |
feeds[k] = LDB:NewDataObject("Critline "..addon.treeNames[k], { |
type = "data source", |
label = v, |
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) |
else |
addon:OpenConfig() |
end |
end, |
OnTooltipShow = function() |
addon:ShowTooltip(k) |
end |
}) |
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 |
end |
end |
end |
addon.RegisterCallback(feeds, "PerCharSettingsLoaded", updateRecords) |
addon.RegisterCallback(feeds, "RecordsChanged", updateRecords) |
addon.RegisterCallback(feeds, "SpellsChanged", updateRecords) |
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, |
Invocation of Flame, Red Mist, Ragezone and Frothing Rage to aura filter. |
3.1.2 |
- Clicking the Announce button rather than pressing enter should now properly announce. |
- Added support for LibSharedMedia for selecting splash font. |
- Disabled adding spells to filter from the spell book to avoid tainting. |
- Removed leftover debugging for when moving the display frame. |
3.1.1 |
- Fixed adding spells to filter from the spell book. |
- Fixed display moving on login when using a scale other than 100%. |
3.1.0 |
- Updated for 4.0. |
- Disabled adding auras to the aura filter by clicking for now. |
3.0.2 |
- Updated German localisation. |
- Added Russian localisation. |
- The announce spell list will now properly be refreshed after resetting records. |
- Updated Ace3 libraries. (fixes occasional error upon deleting profiles if you didn't already have updated libraries) |
- Fixed tooltip sorting option. |
3.0.1 |
- DoT/HoT suffix is now properly hidden in the tooltip when no direct entry of the same spell exists. |
- Fixed some errors related to dual spec. |
- Fixed adding mobs to filter. |
- Added hack for direct spells being treated as periodic, and thus getting filtered incorrectly. (so far only the Divine Storm heal) |
- Fixed error that could, under certain circumstances, occur when you gained or lost a (de)buff. |
3.0.0 |
* Rewrote a lot of code. Modules are now actually independent of each other, and can be removed as desired. |
* Options are now split up more, in their respective module's category. |
* Now using AceDB-3.0 for saved variables management. See addon description for more info. |
* Also implemented LibDualSpec. You can have profiles automatically change upon respeccing. |
* Unfortunately, the first time you login on a character that has data from a previous version, all general settings will be reset. |
* It is recommended that you delete your old Critline folder, as the folder structure has changed significantly. |
- Added aura filter UI. You can now add custom auras by providing spell ID or shift-clicking a buff or debuff with the UI open. |
- Improved aura filter. Will now try to filter spells that were cast with a special aura, but ticks after you lost it. |
- Removed the ability to specify custom tooltip formats. Was just a lot of trouble for little to no gain. |
- The trees in the standalone frame can no longer be explicitly set, and are instead tied to whether they are being registered. |
- Added an option to to show a text label instead of an icon in the display frame. |
- Added option to only register spells from the spell book. (eg no items, procs, vehicles etc) |
- The font style of the splash frame can now be changed. |
- Fixed rare bug where spells would not be properly alphabetically sorted. |
- Added option to disregard of additional damage due to vulnerability. |
- Added support for Ebon Gargoyle attacks. |
- Water Elemental attacks will now be registered when using the Glyph of Eternal Water. |
- Added Essence of the Blood Queen (Blood Queen Lana'thel) to aura filter. |
- Added Storm Power from 25 man Hodir to aura filter. |
2.3.1 |
- Added Phantom Hallucination to mob filter. |
- Added Gastric Bloat (Festergut) to aura filter. |
- Scaling the summary frame or the splash frame will no longer relocate them. |
- Filtered auras are now tracked more accurately. (spell ID is available where it previously was not) |
- You can now reset the standalone display's position with '/cl reset'. |
2.3.0 |
- Added Spanish localisation by Vladixlaus. |
- Added Aerial Command Unit, Fjola Lightbane, Eydis Darkbane and Icehowl to mob filter. |
- Reset scroll frame should no longer misbehave after deleting filtered records. |
- Modified some text colors in the detailed summary view to make it more readable. |
- Removed some legacy code. Data from versions <2.1.0 will no longer be converted. |
- Shift clicking the Broker plugin will now announce the highest record in that tree. |
- ToC bump for 3.3. |
2.2.2 |
- Added Parrot SCT support. |
- You can now choose to print record notifications to the chat frame. |
2.2.1 |
- 'Show old record' setting will now stick between sessions. |
2.2.0 |
- Updated for patch 3.2. |
- Added German localisation by Destoxillo. |
- Changed spell ID for Burning Adrenaline, again. (should've worked the first time...) |
- Added option to display previous record with "New record" messages. |
2.1.2 |
- Should no longer register unwanted quest pet records when you have your regular pet summoned. |
- Added Overwhelming Power (hard mode Assembly of Iron, normal and heroic) to aura filter. |
2.1.1 |
- Fixed scaling settings not being remembered between sessions. |
2.1.0 |
- Spells with a direct and a periodic effect will now be stored separately. |
- As a result of the above, database structure has changed slightly. |
- Spells that has no crit record will now only display its normal record in the summary. |
- Periodic spells will have DoT/HoT appended to its name in the summary tooltip only if the non periodic effect is visible, as well. |
- Now using combat log unit flags for new possibilities! |
- Record PvP no longer needs to be enabled to track healing done to players. |
- Removed "Ignore self damage" option. Damage done to any friendly unit is no longer tracked. |
- Healing done to hostile units is no longer tracked. |
- Healing now ignores the level filter. |
- DAMAGE_SHIELD type effects (Thorns, Retribution Aura etc) are no longer tracked. |
- Hopefully fixed custom tooltip formatting. |
- Added Fury of the Storm, Speed of Invention, Fortitude of Frost and Resilience of Nature from the Yogg-Saron encounter to aura filter. |
- Added Shadow Crash (General Vezax) to aura filter. (untested) |
- Added options to ignore integrated mob and aura filters respectively. (off by default) |
- Record sorting dropdown menu should no longer be empty on login. |
- Added Metanoia (Valkyrion Aspirant) to aura filter. |
- Improved pet tracking. Should now only register your class pets. |
- Merged the invert options for each tree into one. |
- Lots of small performance and memory improvements. |
2.0.2 |
- Added Potent Pheromones (Freya) to aura filter. |
- Added Unstable Sun Beam (Elder Brightleaf) to aura filter. |
- Added Death Knight Understudy to mob filter. |
- Added Storm Power (Hodir) to aura filter. |
2.0.1 |
- Fixed combat log event handler. Records should now be recorded again. |
- Fixed slash command. |
2.0.0 |
* Renamed simply Critline with new version numbering. (hopefully for the last time!) |
- Mobs can now be added by name, in addition to by target. (case insensitive) |
- Added Heart of the Deconstructor to mob filter. |
- Added Rune of Power (Assembly of Iron) to aura filter. |
- Actually register UNIT_ENTERED/EXITED_VEHICLE events... |
r16 |
- All XML code rewritten in Lua. End user shouldn't notice any significant difference. |
- Changed database formats for better readability. Records and filters are kept, but other settings are reset. |
- Tooltip format for detailed summary can now be customised to your liking. See advanced options for details. |
- Main GUI is now draggable by right mouse button at the text area. Left clicking the icon will open options, right click hides the button. |
- Added sorting option for summary tooltip. (alphabetically/crit/normal) |
- Changed default detailed tooltip format. |
- Added Might of Mograine (death knight story line) to aura filter. |
- Various cosmetic changes. |
r15 |
- Fixed an error caused by an unintentionally added database entry. Broken databases will be repaired. |
- Removed some deprecated database entries that will never be used. (date and count) |
r14 |
- Records in the tooltip is now sorted by crit amount > normal amount > spell name. |
- Added an option to use scrolling combat text instead of the default splash frame. Currently supports Mik SBT and SCT in addition to Blizzard's. |
- 'Move splash frame' is now a regular button rather than a check button. |
- Now using another (hopefully correct) spell ID for Burning Adrenaline. |
r13 |
- Toggling standalone display via the minimap button is now permanent. |
- Dragging the minimap button should now function properly. |
- Added a single letter indicator for the Broker feeds. |
r12 |
- Added option to ignore self inflicted damage. (off by default) |
- Fixed critical strike text error that occured on certain locales. |
- Added Blessing of the Crusade (Icecrown quest) to aura filter. |
r11 |
- Fixed 'Move splash frame' option. |
- Added Iron's Bane (Storm Peaks quest) to aura filter. |
r10 |
- Implemented LibDataBroker feed, which replaces Titan and FuBar plugins. FuBar users need install Broker2FuBar. |
- Fixed standalone display scaling not being saved between sessions. |
- Fixed the "Play sound" option. |
- Attacks when in a vehicle should no longer be recorded in the pet section. |
r9 |
- FuBar plugins should now properly use the new icons. |
- An attempt at using mob IDs instead of names for the default mob filter. No need for localisations anymore. |
- Announce and reset check buttons will now reset when leaving the respective view. |
- Added Aura of Anger and Aura of Desire (Reliquary of Souls) to aura filter. |
- Added Shade of Akama to mob filter. |
- Added Energy Infusion and Energy Feedback (Vexallus) to aura filter. |
- Mob filter list should now work properly when scrollable. |
- Code cleanup. |
r8 |
- Fixed FuBar error. |
- Fixed minimap icon. |
- Removed more legacy code. |
r7 |
- Moved the options to Blizzard's interface panel and redesigned it slightly. |
- Non existant entries now won't be added to the tooltip when using detailed view. |
- Added Malygos' Power Spark and Potent Fungus (Amanitar) to aura filters. |
- Removed some legacy code. |
- Added options to record PvE/PvP and removed the old "PvP only" option. |
- Added option to let magical damage ignore level adjustment. |
- New (non custom made) icons. |
r6 |
- Implemented (so far very simple) special (de)buff filtering. |
- Magical damage will yet again take level adjustment into consideration. |
r5 |
- Fixed occasional error upon zoning. |
r4 |
- Fixed level adjustment filter issues. |
- You can now set level adjustment to 0. |
- Default filtering is now in place for mobs that mostly receives extra damage. |
- Addition and removal of filtered mobs is now notified in the chat frame. |
r3 |
- Splash frame will now be cleared before every record splash. |
- Mob filter list should no longer error when scrolled. |
- Polished some code. |
r2 |
- Hopefully fixed FuBar plugins. |
- Splash screen spell text colour is now correctly yellow by default. |
- Fixed splash screen text colour picker. |
r1 |
- Updated for 3.0. |
- Level adjustment should now work properly. |
- Level adjustment will now only apply to physical damage. |
- When displaying all three types, the display frames should now not appear on top of each other on login. |
- Splash screen should now behave correctly when using inverted filters. |
- Buttons in the Reset records scroll pane now should not become unchecked when you scroll it. |
- Self healing spells on your pet will no longer be recorded. (Prayer of Mending, Lifebloom etc) |
- Fixed the Titan Panel plugins' right click menu. |
- Cleaned up some code. |
v5.0 |
- Renamed "Titan [Critline]" to "Critline" |
- Redesigned from the ground up to be a stand-alone mod, that supports Titan Panel. |
- Split out the Summary to show Damage, Healing and Pet on separate summary screens. |
- Added the option for Detailed or Simple summary display. |
- Code is now broken down into function sections. makes maintenance and feature development much easier. |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local width, height |
local trees = { |
dmg = L["Damage"], |
heal = L["Healing"], |
pet = L["Pet"], |
} |
local function onDragStart(self) |
self.owner:StartMoving() |
end |
local function onDragStop(self) |
local owner = self.owner |
owner:StopMovingOrSizing() |
local pos = owner.profile.pos |
pos.point, pos.x, pos.y = select(3, owner:GetPoint()) |
end |
local function onEnter(self) |
GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT") |
addon:ShowTooltip(self.tree) |
if not self.owner.profile.locked then |
GameTooltip:AddLine(" ") |
GameTooltip:AddLine(L["Drag to move"], GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) |
end |
GameTooltip:Show() |
end |
local function createDisplay(parent) |
local frame = CreateFrame("Frame", nil, parent) |
frame:EnableMouse(true) |
frame:RegisterForDrag("LeftButton") |
frame:SetPoint("LEFT", 4, 0) |
frame:SetPoint("RIGHT", -4, 0) |
frame:SetScript("OnDragStart", onDragStart) |
frame:SetScript("OnDragStop", onDragStop) |
frame:SetScript("OnEnter", onEnter) |
frame:SetScript("OnLeave", GameTooltip_Hide) |
frame.owner = parent |
local text = frame:CreateFontString(nil, nil, "GameFontHighlightSmall") |
text:SetPoint("CENTER", frame, "RIGHT", -50, 0) |
frame.text = text |
local icon = frame:CreateTexture(nil, "OVERLAY") |
icon:SetSize(20, 20) |
icon:SetPoint("LEFT", 2, 0) |
frame.icon = icon |
local label = frame:CreateFontString(nil, nil, "GameFontHighlightSmall") |
label:SetPoint("LEFT", 4, 0) |
frame.label = label |
return frame |
end |
local display = CreateFrame("Frame", nil, UIParent) |
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} |
}) |
display:SetBackdropColor(0, 0, 0, 0.8) |
display:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) |
display.trees = {} |
for k, treeName in pairs(trees) do |
local frame = createDisplay(display) |
frame.icon:SetTexture(addon.icons[k]) |
frame.label:SetText(treeName..":") |
frame.tree = k |
display.trees[k] = frame |
end |
display.trees.dmg:SetPoint("TOP", 0, -4) |
local config = templates:CreateConfigFrame("Display", addonName, true) |
local function displayFunc(self) |
self.module:UpdateLayout(self.setting, self:GetChecked()) |
end |
local options = { |
db = {}, |
{ |
text = L["Show"], |
tooltipText = L["Show summary frame."], |
setting = "show", |
func = function(self) |
if self:GetChecked() then |
display:Show() |
else |
display:Hide() |
end |
end, |
}, |
{ |
text = L["Locked"], |
tooltipText = L["Lock summary frame."], |
setting = "locked", |
func = function(self) |
local btn = not self:GetChecked() and "LeftButton" |
for _, tree in pairs(display.trees) do |
tree:RegisterForDrag(btn) |
end |
end, |
}, |
{ |
text = L["Show icons"], |
tooltipText = L["Enable to show icon indicators instead of text."], |
setting = "icons", |
func = function(self) |
local checked = self:GetChecked() |
width = checked and 128 or 152 |
height = checked and 22 or 16 |
for _, tree in pairs(display.trees) do |
if checked then |
tree.icon:Show() |
tree.label:Hide() |
else |
tree.icon:Hide() |
tree.label:Show() |
end |
tree:SetHeight(height) |
end |
display:UpdateLayout() |
end, |
}, |
} |
for i, v in ipairs(options) do |
local btn = templates:CreateCheckButton(config, v) |
if i == 1 then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOMLEFT", -2, -16) |
else |
btn:SetPoint("TOP", options[i - 1], "BOTTOM", 0, -8) |
end |
btn.module = display |
local btns = options[btn.db] |
btns[#btns + 1] = btn |
options[i] = btn |
end |
local slider = templates:CreateSlider(config, { |
text = L["Scale"], |
tooltipText = L["Sets the scale of the summary frame."], |
minValue = 0.5, |
maxValue = 1.5, |
valueStep = 0.05, |
minText = "50%", |
maxText = "150%", |
func = function(self) |
local value = self:GetValue() |
self.value:SetFormattedText("%.0f%%", value * 100) |
local os = display:GetScale() |
display:SetScale(value) |
local point, relativeTo, relativePoint, xOffset, yOffset = display:GetPoint() |
display:SetPoint(point, relativeTo, relativePoint, (xOffset * os / value), (yOffset * os / value)) |
display.profile.scale = value |
end, |
}) |
slider:SetPoint("TOPLEFT", options[#options], "BOTTOMLEFT", 4, -24) |
local defaults = { |
profile = { |
show = true, |
locked = false, |
icons = true, |
scale = 1, |
pos = { |
point = "CENTER", |
}, |
} |
} |
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") |
end |
addon.RegisterCallback(display, "AddonLoaded") |
function display:SettingsLoaded() |
self.profile = self.db.profile |
self:UpdateRecords() |
for _, btn in ipairs(options.db) do |
btn:LoadSetting() |
end |
-- restore stored position |
local pos = self.profile.pos |
self:ClearAllPoints() |
self:SetPoint(pos.point, pos.x, pos.y) |
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) |
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 |
end |
end |
end |
function display:UpdateTree(tree) |
if addon.percharDB.profile[tree] then |
self.trees[tree]:Show() |
else |
self.trees[tree]:Hide() |
end |
self:UpdateLayout() |
end |
-- rearrange display buttons when any of them is shown or hidden |
function display:UpdateLayout() |
local trees = self.trees |
local dmg = trees.dmg |
local heal = trees.heal |
local pet = trees.pet |
if heal:IsShown() then |
if dmg:IsShown() then |
heal:SetPoint("TOP", dmg, "BOTTOM") |
else |
heal:SetPoint("TOP", 0, -4) |
end |
end |
if pet:IsShown() then |
if heal:IsShown() then |
pet:SetPoint("TOP", heal, "BOTTOM") |
elseif dmg:IsShown() then |
pet:SetPoint("TOP", dmg, "BOTTOM") |
else |
pet:SetPoint("TOP", 0, -4) |
end |
end |
local n = 0 |
if dmg:IsShown() then |
n = n + 1 |
end |
if heal:IsShown() then |
n = n + 1 |
end |
if pet:IsShown() then |
n = n + 1 |
end |
self:SetSize(width, n * height + 8) |
-- hide the entire frame if it turns out none of the individual frames are shown |
if n == 0 then |
self:Hide() |
elseif self.profile.show then |
self:Show() |
end |
end |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local module = templates:CreateList("CritlineAnnounce", L["Announce"]) |
do |
local button = module.button |
button:SetScript("OnClick", function() |
PlaySound("gsTitleOptionOK") |
module:AnnounceRecords() |
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 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 |
end |
addon.RegisterCallback(module, "SpellsChanged", "Update") |
function module:AnnounceRecords() |
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 |
if target == "" then |
addon:Message(L["Invalid player name."]) |
return |
end |
elseif channel == "CHANNEL" then |
target = GetChannelName(target) |
if target == 0 then |
addon:Message(L["Invalid channel. Please enter a valid channel name or ID."]) |
return |
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 |
end |