/tags
local addonName, addon = ... |
local templates = addon.templates |
local L = { |
default = "Default", |
-- intro = "You can change the active database profile, so you can have different settings for every character.", |
-- reset_desc = "Reset the current profile back to its default values, in case your configuration is broken, or you simply want to start over.", |
reset = "Reset profile", |
-- reset_sub = "Reset the current profile to the default", |
choose_desc = "You can either create a new profile by entering a name in the editbox, or choose one of the already existing profiles.", |
new = "New", |
-- new_sub = "Create a new empty profile.", |
choose = "Existing profiles", |
-- choose_sub = "Select one of your currently available profiles.", |
copy_desc = "Copy the settings from one existing profile into the currently active profile.", |
copy = "Copy From", |
delete_desc = "Delete existing and unused profiles from the database to save space, and cleanup the SavedVariables file.", |
delete = "Delete a profile", |
-- delete_sub = "Deletes a profile from the database.", |
delete_confirm = "Are you sure you want to delete the selected profile?", |
profiles = "Profiles", |
-- profiles_sub = "Manage Profiles", |
current = "Current profile:", |
dualspec_desc = "When enabled, this feature allow you to select a different profile for each talent spec. The dual profile will be swapped with the current profile each time you switch from a talent spec to the other.", |
dual_profile = "Dual profile", |
enabled = "Enable dual profile", |
enabled_desc = "Check this box to automatically swap profiles on talent switch.", |
} |
local LOCALE = GetLocale() |
if LOCALE == "deDE" then |
L["default"] = "Standard" |
-- L["intro"] = "Hier kannst du das aktive Datenbankprofile \195\164ndern, damit du verschiedene Einstellungen f\195\188r jeden Charakter erstellen kannst, wodurch eine sehr flexible Konfiguration m\195\182glich wird." |
-- L["reset_desc"] = "Setzt das momentane Profil auf Standardwerte zur\195\188ck, f\195\188r den Fall das mit der Konfiguration etwas schief lief oder weil du einfach neu starten willst." |
L["reset"] = "Profil zur\195\188cksetzen" |
-- L["reset_sub"] = "Das aktuelle Profil auf Standard zur\195\188cksetzen." |
L["choose_desc"] = "Du kannst ein neues Profil erstellen, indem du einen neuen Namen in der Eingabebox 'Neu' eingibst, oder w\195\164hle eines der vorhandenen Profile aus." |
L["new"] = "Neu" |
-- L["new_sub"] = "Ein neues Profil erstellen." |
L["choose"] = "Vorhandene Profile" |
-- L["choose_sub"] = "W\195\164hlt ein bereits vorhandenes Profil aus." |
L["copy_desc"] = "Kopiere die Einstellungen von einem vorhandenen Profil in das aktive Profil." |
L["copy"] = "Kopieren von..." |
L["delete_desc"] = "L\195\182sche vorhandene oder unbenutzte Profile aus der Datenbank um Platz zu sparen und um die SavedVariables Datei 'sauber' zu halten." |
L["delete"] = "Profil l\195\182schen" |
-- L["delete_sub"] = "L\195\182scht ein Profil aus der Datenbank." |
L["delete_confirm"] = "Willst du das ausgew\195\164hlte Profil wirklich l\195\182schen?" |
L["profiles"] = "Profile" |
-- L["profiles_sub"] = "Profile verwalten" |
--L["current"] = "Current Profile:" |
L["dualspec_desc"] = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel der dualen Talentspezialisierung das Profil. Das duale Profil wird beim Wechsel automatisch mit dem derzeit aktiven Profil getauscht." |
L["dual_profile"] = "Duales Profil" |
L["enabled"] = "Aktiviere Duale Profile" |
L["enabled_desc"] = "Aktiviere diese Option, um beim Talentwechsel automatisch zwischen den Profilen zu wechseln." |
elseif LOCALE == "frFR" then |
L["default"] = "D\195\169faut" |
-- L["intro"] = "Vous pouvez changer le profil actuel afin d'avoir des param\195\168tres diff\195\169rents pour chaque personnage, permettant ainsi d'avoir une configuration tr\195\168s flexible." |
-- L["reset_desc"] = "R\195\169initialise le profil actuel au cas o\195\185 votre configuration est corrompue ou si vous voulez tout simplement faire table rase." |
L["reset"] = "R\195\169initialiser le profil" |
-- L["reset_sub"] = "R\195\169initialise le profil actuel avec les param\195\168tres par d\195\169faut." |
L["choose_desc"] = "Vous pouvez cr\195\169er un nouveau profil en entrant un nouveau nom dans la bo\195\174te de saisie, ou en choississant un des profils d\195\169j\195\160 existants." |
L["new"] = "Nouveau" |
-- L["new_sub"] = "Cr\195\169\195\169e un nouveau profil vierge." |
L["choose"] = "Profils existants" |
-- L["choose_sub"] = "Permet de choisir un des profils d\195\169j\195\160 disponibles." |
L["copy_desc"] = "Copie les param\195\168tres d'un profil d\195\169j\195\160 existant dans le profil actuellement actif." |
L["copy"] = "Copier \195\160 partir de" |
L["delete_desc"] = "Supprime les profils existants inutilis\195\169s de la base de donn\195\169es afin de gagner de la place et de nettoyer le fichier SavedVariables." |
L["delete"] = "Supprimer un profil" |
-- L["delete_sub"] = "Supprime un profil de la base de donn\195\169es." |
L["delete_confirm"] = "Etes-vous s\195\187r de vouloir supprimer le profil s\195\169lectionn\195\169 ?" |
L["profiles"] = "Profils" |
-- L["profiles_sub"] = "Gestion des profils" |
L["dualspec_desc"] = "Lorsqu'elle est activée, cette fonctionnalité vous permet de choisir un profil différent pour chaque spécialisation de talents. Le second profil sera échangé avec le profil courant chaque fois que vous passerez d'une spécialisation à l'autre." |
L["dual_profile"] = 'Second profil' |
L["enabled"] = 'Activez le second profil' |
L["enabled_desc"] = "Cochez cette case pour échanger automatiquement les profils lors d'un changement de spécialisation." |
elseif LOCALE == "koKR" then |
L["default"] = "기본ê°" |
-- L["intro"] = "모ë ìºë¦í°ì ë¤ìí ì¤ì ê³¼ ì¬ì©ì¤ì¸ ë°ì´í°ë² ì´ì¤ íë¡í, ì´ëê²ì´ëì§ ë§¤ì° ë¤ë£¨ê¸° ì½ê² ë°ê¿ì ììµëë¤." |
-- L["reset_desc"] = "ë¨ìí ë¤ì ìë¡ê² 구ì±ì ìíë ê²½ì°, íì¬ íë¡íì 기본ê°ì¼ë¡ ì´ê¸°í í©ëë¤." |
L["reset"] = "íë¡í ì´ê¸°í" |
-- L["reset_sub"] = "íì¬ì íë¡íì 기본ê°ì¼ë¡ ì´ê¸°í í©ëë¤" |
L["choose_desc"] = "ìë¡ì´ ì´ë¦ì ì ë ¥íê±°ë, ì´ë¯¸ ìë íë¡íì¤ íë를 ì ííì¬ ìë¡ì´ íë¡íì ë§ë¤ ì ììµëë¤." |
L["new"] = "ìë¡ì´ íë¡í" |
-- L["new_sub"] = "ìë¡ì´ íë¡íì ë§ëëë¤." |
L["choose"] = "íë¡í ì í" |
-- L["choose_sub"] = "ë¹ì ì´ íì¬ ì´ì©í ì ìë íë¡íì ì íí©ëë¤." |
L["copy_desc"] = "íì¬ ì¬ì©ì¤ì¸ íë¡íì, ì íí íë¡íì ì¤ì ì ë³µì¬í©ëë¤." |
L["copy"] = "ë³µì¬" |
L["delete_desc"] = "ë°ì´í°ë² ì´ì¤ì ì¬ì©ì¤ì´ê±°ë ì ì¥ë íë¡íì¼ ìì ë¡ SavedVariables íì¼ì ì 리ì ê³µê° ì ì½ì´ ë©ëë¤." |
L["delete"] = "íë¡í ìì " |
-- L["delete_sub"] = "ë°ì´í°ë² ì´ì¤ì íë¡íì ìì í©ëë¤." |
L["delete_confirm"] = "ì ë§ë¡ ì íí íë¡íì ìì 를 ìíìëê¹?" |
L["profiles"] = "íë¡í" |
L["profiles_sub"] = "íë¡í ì¤ì " |
-- L["current"] = "Current Profile:" |
L["dualspec_desc"] = "ê°ë¥íë©´ ì¬ì©í©ëë¤. ì´ì¤ í¹ì±ì ìíì¬ ë¤ë¥¸ íë¡íì ì íí ì ìê² íëë¤. ì´ì¤ íë¡íì íì¬ íë¡íê³¼ ë²ê°ìì í¹ì±ì´ ë³ê²½ë ë ê°ì´ ì ì©ë©ëë¤." |
L["dual_profile"] = "ì´ì¤ íë¡í" |
L["enabled"] = "ì´ì¤ íë¡í ì¬ì©" |
L["enabled_desc"] = "í¹ì±ì´ ë³ê²½ ë ë ìëì¼ë¡ íë¡íì ë³ê²½íëë¡ ì íí©ëë¤." |
elseif LOCALE == "esES" or LOCALE == "esMX" then |
L["default"] = "Por defecto" |
-- L["intro"] = "Puedes cambiar el perfil activo de tal manera que cada personaje tenga diferentes configuraciones." |
-- L["reset_desc"] = "Reinicia el perfil actual a los valores por defectos, en caso de que se haya estropeado la configuración o quieras volver a empezar de nuevo." |
L["reset"] = "Reiniciar Perfil" |
-- L["reset_sub"] = "Reinicar el perfil actual al de por defecto" |
L["choose_desc"] = "Puedes crear un nuevo perfil introduciendo un nombre en el recuadro o puedes seleccionar un perfil de los ya existentes." |
L["new"] = "Nuevo" |
-- L["new_sub"] = "Crear un nuevo perfil vacio." |
L["choose"] = "Perfiles existentes" |
-- L["choose_sub"] = "Selecciona uno de los perfiles disponibles." |
L["copy_desc"] = "Copia los ajustes de un perfil existente al perfil actual." |
L["copy"] = "Copiar de" |
L["delete_desc"] = "Borra los perfiles existentes y sin uso de la base de datos para ganar espacio y limpiar el archivo SavedVariables." |
L["delete"] = "Borrar un Perfil" |
-- L["delete_sub"] = "Borra un perfil de la base de datos." |
L["delete_confirm"] = "¿Estas seguro que quieres borrar el perfil seleccionado?" |
L["profiles"] = "Perfiles" |
-- L["profiles_sub"] = "Manejar Perfiles" |
--L["current"] = "Current Profile:" |
elseif LOCALE == "zhTW" then |
L["default"] = "é è¨" |
-- L["intro"] = "ä½ å¯ä»¥é¸æä¸åæ´»åçè³æè¨å®æªï¼éæ¨£ä½ çæ¯åè§è²å°±å¯ä»¥ææä¸åçè¨å®å¼ï¼å¯ä»¥çµ¦ä½ çæ件è¨å®å¸¶ä¾æ¥µå¤§çéæ´»æ§ã" |
-- L["reset_desc"] = "å°ç¶åçè¨å®æªæ¢å¾©å°å®çé è¨å¼ï¼ç¨æ¼ä½ çè¨å®æªæå£ï¼æè ä½ åªæ¯æ³éä¾çæ æ³ã" |
L["reset"] = "éç½®è¨å®æª" |
-- L["reset_sub"] = "å°ç¶åçè¨å®æªæ¢å¾©çºé è¨å¼" |
L["choose_desc"] = "ä½ å¯ä»¥ééå¨ææ¬æ¡å §è¼¸å ¥ä¸ååååµç«ä¸åæ°çè¨å®æªï¼ä¹å¯ä»¥é¸æä¸åå·²ç¶åå¨çè¨å®æªã" |
L["new"] = "æ°å»º" |
-- L["new_sub"] = "æ°å»ºä¸å空çè¨å®æªã" |
L["choose"] = "ç¾æçè¨å®æª" |
-- L["choose_sub"] = "å¾ç¶åå¯ç¨çè¨å®æªè£é¢é¸æä¸åã" |
L["copy_desc"] = "å¾ç¶åæåå·²ä¿åçè¨å®æªè¤è£½å°ç¶åæ£ä½¿ç¨çè¨å®æªã" |
L["copy"] = "è¤è£½èª" |
L["delete_desc"] = "å¾è³æ庫è£åªé¤ä¸å使ç¨çè¨å®æªï¼ä»¥ç¯ç空éï¼ä¸¦ä¸æ¸ çSavedVariablesæªã" |
L["delete"] = "åªé¤ä¸åè¨å®æª" |
-- L["delete_sub"] = "å¾è³æ庫è£åªé¤ä¸åè¨å®æªã" |
L["delete_confirm"] = "ä½ ç¢ºå®è¦åªé¤æé¸æçè¨å®æªåï¼" |
L["profiles"] = "è¨å®æª" |
-- L["profiles_sub"] = "管çè¨å®æª" |
--L["current"] = "Current Profile:" |
elseif LOCALE == "zhCN" then |
L["default"] = "é»è®¤" |
-- L["intro"] = "ä½ å¯ä»¥éæ©ä¸ä¸ªæ´»å¨çæ°æ®é ç½®æ件ï¼è¿æ ·ä½ çæ¯ä¸ªè§è²å°±å¯ä»¥æ¥æä¸åç设置å¼ï¼å¯ä»¥ç»ä½ çæ件é 置带æ¥æ大ççµæ´»æ§ã" |
-- L["reset_desc"] = "å°å½åçé ç½®æ件æ¢å¤å°å®çé»è®¤å¼ï¼ç¨äºä½ çé ç½®æ件æåï¼æè ä½ åªæ¯æ³éæ¥çæ åµã" |
L["reset"] = "éç½®é ç½®æ件" |
-- L["reset_sub"] = "å°å½åçé ç½®æ件æ¢å¤ä¸ºé»è®¤å¼" |
L["choose_desc"] = "ä½ å¯ä»¥éè¿å¨ææ¬æ¡å è¾å ¥ä¸ä¸ªåååç«ä¸ä¸ªæ°çé ç½®æ件ï¼ä¹å¯ä»¥éæ©ä¸ä¸ªå·²ç»åå¨çé ç½®æ件ã" |
L["new"] = "æ°å»º" |
-- L["new_sub"] = "æ°å»ºä¸ä¸ªç©ºçé ç½®æ件ã" |
L["choose"] = "ç°æçé ç½®æ件" |
-- L["choose_sub"] = "ä»å½åå¯ç¨çé ç½®æ件éé¢éæ©ä¸ä¸ªã" |
L["copy_desc"] = "ä»å½åæ个已ä¿åçé ç½®æ件å¤å¶å°å½åæ£ä½¿ç¨çé ç½®æ件ã" |
L["copy"] = "å¤å¶èª" |
L["delete_desc"] = "ä»æ°æ®åºéå é¤ä¸å使ç¨çé ç½®æ件ï¼ä»¥èç空é´ï¼å¹¶ä¸æ¸ çSavedVariablesæ件ã" |
L["delete"] = "å é¤ä¸ä¸ªé ç½®æ件" |
-- L["delete_sub"] = "ä»æ°æ®åºéå é¤ä¸ä¸ªé ç½®æ件ã" |
L["delete_confirm"] = "ä½ ç¡®å®è¦å é¤æéæ©çé ç½®æ件ä¹ï¼" |
L["profiles"] = "é ç½®æ件" |
-- L["profiles_sub"] = "管çé ç½®æ件" |
--L["current"] = "Current Profile:" |
L["dualspec_desc"] = "å¯æ¶ï¼ä½ å¯ä»¥ä¸ºä½ çå天èµè®¾å®å¦ä¸ç»é ç½®æ件ï¼ä½ çåéé ç½®æ件å°å¨ä½ 转æ¢å¤©èµæ¶èªå¨ä¸ç®å使ç¨é ç½®æ件交æ¢ã" |
L["dual_profile"] = "åéé ç½®æ件" |
L["enabled"] = "å¼å¯åéé ç½®æ件" |
L["enabled_desc"] = "å¾é以便转æ¢å¤©èµæ¶èªå¨äº¤æ¢é ç½®æ件ã" |
elseif LOCALE == "ruRU" then |
L["default"] = "Ðо ÑмолÑаниÑ" |
-- L["intro"] = "ÐзменÑÑ Ð°ÐºÑивнÑй пÑоÑилÑ, Ð²Ñ Ð¼Ð¾Ð¶ÐµÑе задаÑÑ ÑазлиÑнÑе наÑÑÑойки модиÑикаÑий Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ пеÑÑонажа." |
-- L["reset_desc"] = "ÐÑли ваÑа конÑигÑÑаÑии иÑпоÑÑена или еÑли Ð²Ñ Ñ Ð¾ÑиÑе наÑÑÑоиÑÑ Ð²ÑÑ Ð·Ð°Ð½Ð¾Ð²Ð¾ - ÑбÑоÑÑÑе ÑекÑÑий пÑоÑÐ¸Ð»Ñ Ð½Ð° ÑÑандаÑÑнÑе знаÑениÑ." |
L["reset"] = "СбÑÐ¾Ñ Ð¿ÑоÑилÑ" |
-- L["reset_sub"] = "СбÑÐ¾Ñ ÑекÑÑего пÑоÑÐ¸Ð»Ñ Ð½Ð° ÑÑандаÑÑнÑй" |
L["choose_desc"] = "ÐÑ Ð¼Ð¾Ð¶ÐµÑе ÑоздаÑÑ Ð½Ð¾Ð²Ñй пÑоÑилÑ, Ð²Ð²ÐµÐ´Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ в поле ввода, или вÑбÑаÑÑ Ð¾Ð´Ð¸Ð½ из Ñже ÑÑÑеÑÑвÑÑÑÐ¸Ñ Ð¿ÑоÑилей." |
L["new"] = "ÐовÑй" |
-- L["new_sub"] = "СоздаÑÑ Ð½Ð¾Ð²Ñй ÑиÑÑÑй пÑоÑилÑ" |
L["choose"] = "СÑÑеÑÑвÑÑÑие пÑоÑили" |
-- L["choose_sub"] = "ÐÑÐ±Ð¾Ñ Ð¾Ð´Ð¸Ð½Ð¾Ð³Ð¾ из Ñже доÑÑÑпнÑÑ Ð¿ÑоÑилей" |
L["copy_desc"] = "СкопиÑоваÑÑ Ð½Ð°ÑÑÑойки из вÑбÑанного пÑоÑÐ¸Ð»Ñ Ð² акÑивнÑй." |
L["copy"] = "СкопиÑоваÑÑ Ð¸Ð·" |
L["delete_desc"] = "УдалиÑÑ ÑÑÑеÑÑвÑÑÑий и неиÑполÑзÑемÑй пÑоÑÐ¸Ð»Ñ Ð¸Ð· ÐÐ Ð´Ð»Ñ ÑÐ¾Ñ ÑÐ°Ð½ÐµÐ½Ð¸Ñ Ð¼ÐµÑÑа, и оÑиÑÑиÑÑ SavedVariables Ñайл." |
L["delete"] = "УдалиÑÑ Ð¿ÑоÑилÑ" |
-- L["delete_sub"] = "Удаление пÑоÑÐ¸Ð»Ñ Ð¸Ð· ÐÐ" |
L["delete_confirm"] = "ÐÑ ÑвеÑенÑ, ÑÑо Ð²Ñ Ñ Ð¾ÑиÑе ÑдалиÑÑ Ð²ÑбÑаннÑй пÑоÑилÑ?" |
L["profiles"] = "ÐÑоÑили" |
-- L["profiles_sub"] = "УпÑавление пÑоÑилÑми" |
--L["current"] = "Current Profile:" |
L["dualspec_desc"] = "Ðвойной пÑоÑÐ¸Ð»Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»ÑÐµÑ Ð²Ð°Ð¼ вÑбÑаÑÑ ÑазлиÑнÑе пÑоÑили Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑаÑкладки ÑаланÑов. ÐÑоÑили бÑдÑÑ Ð¿ÐµÑеклÑÑаÑÑÑÑ ÐºÐ°Ð¶Ð´Ñй Ñаз, когда Ð²Ñ Ð¿ÐµÑеклÑÑаеÑе ÑаÑÐºÐ»Ð°Ð´ÐºÑ ÑаланÑов." |
L["dual_profile"] = "ÐÑоÑой пÑоÑилÑ" |
L["enabled"] = "ÐклÑÑиÑÑ Ð´Ð²Ð¾Ð¹Ð½Ð¾Ð¹ пÑоÑилÑ" |
L["enabled_desc"] = "ÐклÑÑиÑе ÑÑÑ Ð¾Ð¿ÑÐ¸Ñ Ð´Ð»Ñ Ð°Ð²ÑомаÑиÑеÑкого пеÑеклÑÑÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñ Ð¿ÑоÑилÑми пÑи пеÑеклÑÑении ÑаÑкладки ÑаланÑов." |
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: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, defaultProfiles) |
choose:SetFrameWidth(144) |
choose:SetPoint("LEFT", newProfile, "RIGHT", 0, -2) |
choose.label:SetText(L.choose) |
choose.initialize = initializeDropdown |
choose.func = chooseProfileOnClick |
choose.common = true |
objects.choose = choose |
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, defaultProfiles) |
dualProfile:SetFrameWidth(144) |
dualProfile:SetPoint("LEFT", choose) |
dualProfile:SetPoint("TOP", enabled) |
dualProfile.label:SetText(L.dual_profile) |
dualProfile.initialize = initializeDropdown |
dualProfile.func = dualProfileOnClick |
dualProfile.common = true |
objects.dualProfile = dualProfile |
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, defaultProfiles) |
copy:SetFrameWidth(144) |
copy:SetPoint("TOPLEFT", copyDesc, "BOTTOMLEFT", -16, -8) |
copy.label:SetText(L.copy) |
copy.initialize = initializeDropdown |
copy.func = copyProfileOnClick |
copy.nocurrent = true |
objects.copy = copy |
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, defaultProfiles) |
delete:SetFrameWidth(144) |
delete:SetPoint("TOPLEFT", deleteDesc, "BOTTOMLEFT", -16, -8) |
delete.label:SetText(L.delete) |
delete.initialize = initializeDropdown |
delete.func = deleteProfileOnClick |
delete.nocurrent = true |
objects.delete = delete |
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"], |
} |
local msgFormat = format("%s: %%s - %s: %%s", L["Normal"], L["Crit"]) |
for k, v in pairs(feeds) do |
feeds[k] = LDB:NewDataObject("Critline "..addon.treeNames[k], { |
type = "data source", |
label = v, |
icon = addon.icons[k], |
OnClick = function() |
if IsShiftKeyDown() then |
local normalRecord, critRecord = addon:GetHighest(k) |
local normalSpell, critSpell |
local spells = addon:GetSpellArray(k) |
for i = 1, #spells do |
local v = spells[i] |
local normal = v.normal |
if normal and normal.amount == normalRecord then |
normalSpell = v.spellName |
end |
local crit = v.crit |
if crit and crit.amount == critRecord then |
critSpell = v.spellName |
end |
if (normalSpell or not normalRecord) and (critSpell or not normalRecord) then |
break |
end |
end |
local normal = normalSpell and format("%s (%s)", addon:ShortenNumber(normalRecord), normalSpell) or L["n/a"] |
local crit = critSpell and format("%s (%s)", addon:ShortenNumber(critRecord), critSpell) or L["n/a"] |
ChatFrame_OpenChat(format(msgFormat, normal, crit)) |
else |
addon:OpenConfig() |
end |
end, |
OnTooltipShow = function() |
addon:ShowTooltip(k) |
end |
}) |
end |
local function updateRecords(event, tree) |
if tree then |
local normal, crit = addon:GetHighest(tree) |
feeds[tree].text = format("%s/%s", addon:ShortenNumber(normal), addon:ShortenNumber(crit)) |
else |
for k in pairs(feeds) do |
updateRecords(event, k) |
end |
end |
end |
local function onTreeStateChanged(event, tree, enabled) |
if enabled then |
updateRecords(event, tree) |
else |
feeds[tree].text = L["n/a"] |
end |
end |
local function addonLoaded() |
addon.RegisterCallback(feeds, "OnNewTopRecord", updateRecords) |
addon.RegisterCallback(feeds, "OnTreeStateChanged", onTreeStateChanged) |
end |
addon.RegisterCallback(feeds, "AddonLoaded", addonLoaded) |
4.0.3 |
- Updated for WoW 4.1. |
- Added Sweeping Strikes and Aimed Shot! exceptions. |
- Removed pre-4.1 compability code. |
4.0.2 |
- Hunter traps will now properly display tooltip records. |
- "Include old record" moved from splash module to core. Applies to both chat frame and splash messages. |
- Second attempt at including old record in chat frame output. |
- Made critical record message in the chat frame slightly more distinguishable from a non crit. |
- Do not show pet auto attack records in the regular auto attack tooltip. |
4.0.1 |
- Records printed to the chat frame can now be shortened, and can once again include the previous record. |
- Added Aerial Command Unit back to mob filter. Not eligible for aura tracking. |
- Splash frame records can now be shortened when no previous record is included in the message. |
- Do not try to load AceDBOptions since we're not using it. |
- You can now specify the sound effect played on new records. (LibSharedMedia) |
- Pet auto attack records are now displayed on the pet attack action tooltip. |
- Added Hellscream's Warsong, Strength of Wrynn, Singed and Chemical Cloud to aura filters. |
4.0.0 |
Core: |
* Rewrote database to store spells by ID rather than name. This is more flexible and opens up a few possibilities. |
* Most spells will be carried over to the new database, but others (mostly procs and item effects) will be lost. |
- Absorb effects are now stored as healing records. Uses the remaining absorb amount when you apply shields. |
- Added option to use shorter number format for records. (eg 13 k instead of 13000) |
- Added option to display records in its spell's tooltip. |
- Spells that have different versions, but identical effects, (such as "Pyroblast!" and "Ravage!") are now treated as one and the same. |
- Added compability for patch 4.1. |
- Certain guardian type pets should once again have their records properly registered. |
- Periodic damage is now annotated by an asterisk (*) or the word "tick" rather than DoT/HoT. |
- Removed some legacy code. |
- Some code and performance optimisation. |
Filter: |
- Implemented basic filtering based on target auras. For now you have to be in combat log range when an aura is applied or removed for the addon to register it. See filters.lua for details. |
- Implemented a basic aura tracker (mostly for debugging purposes). Type '/critline aura' to show or hide it. |
- Removed option to invert spell filter. From now on unchecked spells are filtered, and new spells are not filtered by default. |
- Added option to automatically filter newly registered spells. (disabled by default) |
- Spell filter now shows spell tooltip and associated records on mouseover. |
- Spells with no records are no longer kept in the database for the purpose of saving its filter flag. |
- Added Drakeadon Mongrel and Exposed Head of Magmaw to mob filter. |
- Added Power Generator, Engulfing Magic, Lightning Charge and Blessing of the Sun (heroic) to aura filter. |
Display: |
- The different panels' background color can now be changed as desired. |
- The frame can now be scaled up to 200%. (from 150%) |
- The frame's alpha can now be changed. |
- It should no longer be possible to force show the frame when no trees are enabled, resulting in a weird appearance. |
- Reset and announce spell lists had their appearance redesigned. Click to directly to your thing, instead of checking and then clicking. |
- New records achieved in the last fight can now be undone in the reset module. Eligible spells will have a different button. |
- Spell profiles should now display the correct used profiles at all times. |
- Updated libraries. |
3.1.3 |
- Added Karsh Steelbender, Ragnaros (Mount Hyjal) and Debilitated Apexar to mob filter. |
- Added Tidal Surge, Pyrogenics, Blessing of the Sun, Empowering Twilight, |
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 = 128, 22 |
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 backdrop = { |
bgFile = [[Interface\ChatFrame\ChatFrameBackground]], |
insets = {left = -1, right = -1, top = -1, bottom = -1}, |
} |
local function createDisplay(parent) |
local frame = CreateFrame("Frame", nil, parent) |
frame:SetFrameStrata("LOW") |
frame:EnableMouse(true) |
frame:RegisterForDrag("LeftButton") |
frame:SetPoint("LEFT", 4, 0) |
frame:SetPoint("RIGHT", -4, 0) |
frame:SetBackdrop(backdrop) |
frame:SetScript("OnDragStart", onDragStart) |
frame:SetScript("OnDragStop", onDragStop) |
frame:SetScript("OnEnter", onEnter) |
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({ |
edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], |
edgeSize = 12, |
}) |
display:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) |
display.trees = {} |
Critline.SlashCmdHandlers["reset"] = function() |
display:ClearAllPoints() |
display:SetPoint("CENTER") |
end |
for k, treeName in pairs(trees) do |
local frame = createDisplay(display) |
frame.icon:SetTexture(addon.icons[k]) |
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 options = { |
db = {}, |
{ |
text = L["Show"], |
tooltipText = L["Show summary frame."], |
setting = "show", |
func = function(self) |
display:UpdateLayout() |
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 sliders = {} |
sliders[1] = templates:CreateSlider(config, { |
text = L["Scale"], |
tooltipText = L["Sets the scale of the display."], |
minValue = 0.5, |
maxValue = 2, |
valueStep = 0.05, |
minText = "50%", |
maxText = "200%", |
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, |
}) |
sliders[1]:SetPoint("TOPLEFT", options[#options], "BOTTOMLEFT", 4, -24) |
sliders[2] = templates:CreateSlider(config, { |
text = L["Alpha"], |
tooltipText = L["Sets the opacity of the display."], |
minValue = 0, |
maxValue = 1, |
valueStep = 0.05, |
minText = "0%", |
maxText = "100%", |
func = function(self) |
local value = self:GetValue() |
self.value:SetFormattedText("%.0f%%", value * 100) |
display:SetAlpha(value) |
display.profile.alpha = value |
end, |
}) |
sliders[2]:SetPoint("TOP", sliders[1], "BOTTOM", 0, -32) |
local function swatchFunc(self, r, g, b) |
display.trees[self.setting]:SetBackdropColor(r, g, b) |
end |
local colorButtons = {} |
for i, v in ipairs({"dmg", "heal", "pet"}) do |
local btn = templates:CreateColorButton(config) |
if i == 1 then |
btn:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -21) |
else |
btn:SetPoint("TOP", colorButtons[i - 1], "BOTTOM", 0, -18) |
end |
btn:SetText(trees[v]) |
btn.setting = v |
btn.func = swatchFunc |
btn.opacityFunc = opacityFunc |
colorButtons[i] = btn |
end |
local defaults = { |
profile = { |
show = true, |
locked = false, |
icons = true, |
scale = 1, |
alpha = 1, |
colors = { |
dmg = {r = 0, g = 0, b = 0}, |
heal = {r = 0, g = 0, b = 0}, |
pet = {r = 0, g = 0, b = 0}, |
}, |
pos = { |
point = "CENTER", |
}, |
} |
} |
function display:AddonLoaded() |
self.db = addon.db:RegisterNamespace("display", defaults) |
addon.RegisterCallback(self, "SettingsLoaded") |
addon.RegisterCallback(self, "OnNewTopRecord", "UpdateRecords") |
end |
addon.RegisterCallback(display, "AddonLoaded") |
function display:SettingsLoaded() |
self.profile = self.db.profile |
for _, btn in ipairs(options.db) do |
btn:LoadSetting() |
end |
local colors = self.profile.colors |
for _, btn in ipairs(colorButtons) do |
local color = colors[btn.setting] |
local r, g, b = color.r, color.g, color.b |
btn:func(r, g, b) |
btn.swatch:SetVertexColor(r, g, b) |
btn.color = color |
end |
-- restore stored position |
local pos = self.profile.pos |
self:ClearAllPoints() |
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) |
sliders[1]:SetValue(scale) |
sliders[2]:SetValue(self.profile.alpha) |
end |
function display:UpdateRecords(event, tree) |
if tree then |
local normal, crit = addon:GetHighest(tree) |
self.trees[tree].text:SetFormattedText("%8s / %-8s", addon:ShortenNumber(normal), addon:ShortenNumber(crit)) |
else |
for k in pairs(trees) do |
self:UpdateRecords(nil, k) |
end |
end |
end |
function display:UpdateTree(tree) |
if addon.percharDB.profile[tree] then |
self.trees[tree]:Show() |
else |
self.trees[tree]:Hide() |
end |
self:UpdateLayout() |
end |
function display:Toggle() |
local show = not self.profile.show |
self.profile.show = show |
options[1]:SetChecked(show) |
self:UpdateLayout() |
end |
-- rearrange display buttons when any of them is shown or hidden |
function display:UpdateLayout() |
local trees = self.trees |
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", 0, -2) |
else |
heal:SetPoint("TOP", 0, -4) |
end |
end |
if pet:IsShown() then |
if heal:IsShown() then |
pet:SetPoint("TOP", heal, "BOTTOM", 0, -2) |
elseif dmg:IsShown() then |
pet:SetPoint("TOP", dmg, "BOTTOM", 0, -2) |
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 + 2) + 6) |
-- hide the entire frame if it turns out none of the individual frames are shown |
if n == 0 or not self.profile.show then |
self:Hide() |
else |
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"], "Announce") |
addon.RegisterCallback(module, "SpellsChanged", "Update") |
local function onClick(self) |
self.owner:SetSelectedValue(self.value) |
local target = module.target |
if self.value == "WHISPER" or self.value == "CHANNEL" then |
target:Show() |
target:SetFocus() |
else |
target:Hide() |
end |
end |
local channels = { |
"SAY", |
"GUILD", |
"PARTY", |
"RAID", |
"WHISPER", |
"CHANNEL", |
} |
local channel = templates:CreateDropDownMenu("CritlineAnnounceChannel", module, nil, _G) |
channel:SetFrameWidth(120) |
channel:SetPoint("TOPLEFT", module.tree, "BOTTOMLEFT") |
channel:SetSelectedValue("SAY") |
channel.initialize = function(self) |
for i, v in ipairs(channels) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = _G[v] |
info.value = v |
info.func = onClick |
info.owner = self |
UIDropDownMenu_AddButton(info) |
end |
end |
module.channel = channel |
local target = templates:CreateEditBox(module) |
target:SetWidth(144) |
target:SetPoint("LEFT", channel, "RIGHT", 0, 2) |
target:SetScript("OnEnterPressed", target.ClearFocus) |
target:SetScript("OnEscapePressed", target.ClearFocus) |
target:Hide() |
module.target = target |
local announceFormat = format("%%s - %s: %%s %s: %%s", L["Normal"], L["Crit"]) |
function module:Announce(data) |
local channel = self.channel:GetSelectedValue() |
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 normal = data.normal and addon:ShortenNumber(data.normal.amount) |
local crit = data.crit and addon:ShortenNumber(data.crit.amount) |
local text = format(announceFormat, addon:GetFullSpellName(data.spellID, data.periodic, true), (normal or L["n/a"]), (crit or L["n/a"])) |
SendChatMessage(text, channel, nil, target) |
self.target:SetText("") |
end |
local addonName, Critline = ... |
_G.Critline = Critline |
-- local addon = { } |
-- local mt_func = { __index = function() return function() end end } |
-- local empty_tbl = { } |
-- local mt = { __index = function() return setmetatable(empty_tbl, mt_func) end } |
-- setmetatable(addon, mt) |
-- print(addon.module.method()) |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local LSM = LibStub("LibSharedMedia-3.0") |
local templates = Critline.templates |
local playerClass = select(2, UnitClass("player")) |
local debugging |
-- auto attack spell |
local AUTOATK_ID = 6603 |
local AUTOATK = GetSpellInfo(AUTOATK_ID) |
-- 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) |
} |
-- spells that are essentially the same, but has different IDs, we'll register them under the same ID |
local similarSpells = { |
[26654] = 12723, -- Sweeping Strikes (Whirlwind) |
[27285] = 27243, -- Seed of Corruption (direct) |
[33778] = 33763, -- Lifebloom (direct) |
[44461] = 44457, -- Living Bomb (direct) |
[47960] = 47897, -- Shadowflame (tick) |
[81170] = 6785, -- Ravage (Stampede) |
[82928] = 19434, -- Aimed Shot (Master Marksman) |
[83853] = 11129, -- Combustion (tick) |
[88148] = 2120, -- Flamestrike (Improved Flamestrike) |
[92315] = 11366, -- Pyroblast (Hot Streak) |
} |
-- spells whose actual effect is the result of a different spell, eg seals, damage shields, used for displaying relevant records in spell tooltips |
local indirectSpells = { |
[324] = 26364, -- Lightning Shield |
[724] = 7001, -- Lightwell |
[740] = 44203, -- Tranquility |
[772] = 94009, -- Rend |
[974] = 379, -- Earth Shield |
[1329] = 5374, -- Mutilate |
[1535] = 8349, -- Fire Nova |
[1949] = 5857, -- Hellfire |
[5938] = 5940, -- Shiv |
[8024] = 10444, -- Flametongue Weapon |
[8033] = 8034, -- Frostbrand Weapon |
[8232] = 25504, -- Windfury Weapon |
[12328] = 12723, -- Sweeping Strikes |
[13795] = 13797, -- Immolation Trap |
[13813] = 13812, -- Explosive Trap |
[16857] = 60089, -- Faerie Fire (Feral) |
[16914] = 42231, -- Hurricane |
[17364] = 32175, -- Stormstrike |
[20154] = 25742, -- Seal of Righteousness |
[20164] = 20170, -- Seal of Justice |
[20165] = 20167, -- Seal of Insight |
[20473] = 25912, -- Holy Shock |
[22842] = 22845, -- Frenzied Regeneration |
[26573] = 81297, -- Consecration |
[31801] = 42463, -- Seal of Truth |
[31850] = 66235, -- Ardent Defender |
[33076] = 33110, -- Prayer of Mending |
[43265] = 52212, -- Death and Decay |
[47540] = 47666, -- Penance |
[47541] = 47632, -- Death Coil (death knight) |
[47788] = 48153, -- Guardian Spirit |
[48045] = 49821, -- Mind Sear |
[51730] = 51945, -- Earthliving |
[61882] = 77478, -- Earthquake |
[64843] = 64844, -- Divine Hymn |
[73920] = 73921, -- Healing Rain |
[82327] = 86452, -- Holy Radiance |
[82939] = 13812, -- Explosive Trap (trap launcher) |
[82945] = 13797, -- Immolation Trap (trap launcher) |
[88685] = 88686, -- Holy Word: Sanctuary |
} |
-- those that has both a damage and a healing component has their healing spell listed here |
local indirectHeals = { |
[15237] = 23455, -- Holy Nova |
[20473] = 25914, -- Holy Shock |
[47540] = 47750, -- Penance |
[47541] = 47633, -- Death Coil (death knight) |
[49998] = 45470, -- Death Strike |
[53385] = 54172, -- Divine Storm |
} |
-- cache of spell ID -> spell name |
local spellNameCache = { |
-- add form name to hybrid druid abilities, so the user can tell which is cat and which is bear |
[33878] = format("%s (%s)", GetSpellInfo(33878)), -- Mangle (Bear Form) |
[33876] = format("%s (%s)", GetSpellInfo(33876)), -- Mangle (Cat Form) |
[779] = format("%s (%s)", GetSpellInfo(779)), -- Swipe (Bear Form) |
[62078] = format("%s (%s)", GetSpellInfo(62078)), -- Swipe (Cat Form) |
} |
-- cache of spell textures |
local spellTextureCache = { |
-- use a static icon for auto attack (otherwise uses your weapon's icon) |
[AUTOATK_ID] = [[Interface\Icons\INV_Sword_04]], |
-- "fix" some other misleading icons |
[20253] = GetSpellTexture(20252), -- Intercept |
[26364] = GetSpellTexture(324), -- use Lightning Shield icon for Lightning Shield damage |
[66235] = GetSpellTexture(31850), -- Ardent Defender icon for Ardent Defender heal |
} |
local swingDamage = function(amount, _, school, resisted, _, _, critical) |
return AUTOATK_ID, AUTOATK, amount, resisted, critical, school |
end |
local spellDamage = function(spellID, spellName, _, amount, _, school, resisted, _, _, critical) |
return spellID, spellName, amount, resisted, critical, school |
end |
local healing = function(spellID, spellName, _, amount, _, _, critical) |
return spellID, spellName, amount, 0, critical, 0 |
end |
local absorb = function(spellID, spellName, _, _, amount) |
return spellID, spellName, amount, 0, critical, 0 |
end |
local combatEvents = { |
SWING_DAMAGE = swingDamage, |
RANGE_DAMAGE = spellDamage, |
SPELL_DAMAGE = spellDamage, |
SPELL_PERIODIC_DAMAGE = spellDamage, |
SPELL_HEAL = healing, |
SPELL_PERIODIC_HEAL = healing, |
SPELL_AURA_APPLIED = absorb, |
SPELL_AURA_REFRESH = absorb, |
} |
-- alpha: sort by name |
local alpha = function(a, b) |
if a == b then return end |
if a.spellName == b.spellName then |
if a.spellID == b.spellID then |
-- sort DoT entries after non DoT |
return a.periodic < b.periodic |
else |
return a.spellID < b.spellID |
end |
else |
return a.spellName < b.spellName |
end |
end |
-- normal: sort by normal > crit > name |
local normal = function(a, b) |
if a == b then return end |
local normalA, normalB = (a.normal and a.normal.amount or 0), (b.normal and b.normal.amount or 0) |
if normalA == normalB then |
-- equal normal amounts, sort by crit amount instead |
local critA, critB = (a.crit and a.crit.amount or 0), (b.crit and b.crit.amount or 0) |
if critA == critB then |
-- equal crit amounts too, sort by name instead |
return alpha(a, b) |
else |
return critA > critB |
end |
else |
return normalA > normalB |
end |
end |
-- crit: sort by crit > normal > name |
local crit = function(a, b) |
if a == b then return end |
local critA, critB = (a.crit and a.crit.amount or 0), (b.crit and b.crit.amount or 0) |
if critA == critB then |
return normal(a, b) |
else |
return critA > critB |
end |
end |
local recordSorters = { |
alpha = alpha, |
normal = normal, |
crit = crit, |
} |
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 = {}} |
-- indicates whether a given tree will need to have its tooltip updated before next use |
local doTooltipUpdate = {} |
-- overall record for each tree |
local topRecords = { |
dmg = {normal = 0, crit = 0}, |
heal = {normal = 0, crit = 0}, |
pet = {normal = 0, crit = 0}, |
} |
-- sortable spell tables |
local spellArrays = {dmg = {}, heal = {}, pet = {}} |
LSM:Register("sound", "Level up", [[Sound\Interface\LevelUp.ogg]]) |
-- tooltip for level scanning |
local tooltip = CreateFrame("GameTooltip", "CritlineTooltip", nil, "GameTooltipTemplate") |
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("PLAYER_TALENT_UPDATE") |
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) |
callbacks:Fire("OnTreeStateChanged", self.setting, self:GetChecked()) |
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["Ignore vulnerability"], |
tooltipText = L["Enable to ignore additional damage due to vulnerability."], |
setting = "ignoreVulnerability", |
}, |
{ |
text = L["Chat output"], |
tooltipText = L["Prints new record notifications to the chat frame."], |
setting = "chatOutput", |
newColumn = true, |
}, |
{ |
text = L["Play sound"], |
tooltipText = L["Plays a sound on a new record."], |
setting = "playSound", |
func = function(self) options.sound:SetDisabled(not self:GetChecked()) end, |
}, |
{ |
text = L["Screenshot"], |
tooltipText = L["Saves a screenshot on a new record."], |
setting = "screenshot", |
gap = 48, |
}, |
{ |
text = L["Include old record"], |
tooltipText = L["Includes previous record along with \"New record\" messages."], |
setting = "oldRecord", |
}, |
{ |
text = L["Shorten records"], |
tooltipText = L["Use shorter format for records numbers."], |
setting = "shortFormat", |
func = function(self) callbacks:Fire("OnNewTopRecord") Critline:UpdateTooltips() end, |
gap = 16, |
}, |
{ |
text = L["Records in spell tooltips"], |
tooltipText = L["Include (unfiltered) records in spell tooltips."], |
setting = "spellTooltips", |
}, |
{ |
text = L["Detailed tooltip"], |
tooltipText = L["Use detailed format in the summary tooltip."], |
setting = "detailedTooltip", |
func = function(self) Critline:UpdateTooltips() end, |
}, |
} |
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 |
options[v.setting] = btn |
checkButtons[i] = btn |
end |
local function onClick(self) |
self.owner:SetSelectedValue(self.value) |
Critline.db.profile.sound = self.value |
PlaySoundFile(LSM:Fetch("sound", self.value)) |
end |
local sound = templates:CreateDropDownMenu("CritlineSoundEffect", config) |
sound:SetFrameWidth(160) |
sound:SetPoint("TOPLEFT", options.playSound, "BOTTOMLEFT", -15, -8) |
sound.initialize = function(self) |
for _, v in ipairs(LSM:List("sound")) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v |
info.func = onClick |
info.owner = self |
UIDropDownMenu_AddButton(info) |
end |
end |
options.sound = sound |
-- summary sort dropdown |
local menu = { |
{ |
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(160) |
sorting:SetPoint("TOPLEFT", checkButtons[#checkButtons], "BOTTOMLEFT", -15, -24) |
sorting.label:SetText(L["Tooltip sorting:"]) |
sorting.onClick = function(self) |
self.owner:SetSelectedValue(self.value) |
Critline.db.profile.tooltipSort = self.value |
Critline:UpdateTooltips() |
end |
options.tooltipSort = sorting |
end |
Critline.SlashCmdHandlers = { |
debug = function() Critline:ToggleDebug() end, |
} |
SlashCmdList.CRITLINE = function(msg) |
msg = msg:trim():lower() |
local slashCmdHandler = Critline.SlashCmdHandlers[msg] |
if slashCmdHandler then |
slashCmdHandler() |
else |
Critline:OpenConfig() |
end |
end |
SLASH_CRITLINE1 = "/critline" |
SLASH_CRITLINE2 = "/cl" |
local defaults = { |
profile = { |
PvE = true, |
PvP = true, |
ignoreVulnerability = true, |
chatOutput = false, |
playSound = true, |
sound = "Level up", |
screenshot = false, |
oldRecord = false, |
shortFormat = false, |
spellTooltips = false, |
detailedTooltip = false, |
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") |
self:LoadSettings() |
self:LoadPerCharSettings() |
self.ADDON_LOADED = nil |
end |
end |
-- import native spells to new database format (4.0) |
function Critline:PLAYER_TALENT_UPDATE() |
if GetMajorTalentTreeBonuses(1) then |
self:UnregisterEvent("PLAYER_TALENT_UPDATE") |
self.PLAYER_TALENT_UPDATE = nil |
else |
return |
end |
local tooltip = CreateFrame("GameTooltip", "CritlineImportScanTooltip", nil, "GameTooltipTemplate") |
local function getID(query) |
local link = GetSpellLink(query) |
if link then |
return tonumber(link:match("spell:(%d+)")) |
end |
for tab = 1, 3 do |
local id = GetMajorTalentTreeBonuses(tab) |
if GetSpellInfo(id) == query then |
return id |
end |
for i = 1, GetNumTalents(tab) do |
local name, _, _, _, _, _, _, _, _, isExceptional = GetTalentInfo(tab, i) |
if name == query and isExceptional then |
tooltip:SetOwner(UIParent) |
tooltip:SetTalent(tab, i) |
return select(3, tooltip:GetSpell()) |
end |
end |
end |
end |
for k, profile in pairs(self.percharDB.profiles) do |
if profile.spells then |
for k, tree in pairs(profile.spells) do |
local spells = {} |
for i, spell in pairs(tree) do |
if not spell.spellName then |
return |
end |
local id = getID(spell.spellName) |
id = (tree == heal and indirectHeals[id]) or indirectSpells[id] or id |
if id and (spell.normal or spell.crit) then |
spells[id] = spells[id] or {} |
spells[id][spell.isPeriodic and 2 or 1] = spell |
spell.spellName = nil |
spell.isPeriodic = nil |
end |
end |
profile.spells[k] = spells |
end |
end |
end |
tooltip:Hide() |
-- invert filter flag on all spells if inverted filter was enabled |
if self.filters then |
if self.filters.db.profile.invertFilter then |
for k, profile in pairs(self.percharDB.profiles) do |
if profile.spells then |
for k, tree in pairs(profile.spells) do |
for i, spell in pairs(tree) do |
for i, spell in pairs(spell) do |
spell.filtered = not spell.filtered |
end |
end |
end |
end |
end |
end |
for k, profile in pairs(self.filters.db.profiles) do |
profile.invertFilter = nil |
end |
end |
self:LoadPerCharSettings() |
end |
function Critline:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, ...) |
-- we seem to get events with standard arguments equal to nil, so they need to be ignored |
if not (timestamp and eventType) then |
self:Debug("nil errors on start") |
return |
end |
-- if 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 |
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(7, 10), 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 isPeriodic |
local periodic = 1 |
local isHeal = eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" or eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" |
-- we don't care about healing done by the pet |
if isHeal and isPet then |
self:Debug("Pet healing. Return.") |
return |
end |
if eventType == "SPELL_PERIODIC_DAMAGE" or eventType == "SPELL_PERIODIC_HEAL" then |
isPeriodic = true |
periodic = 2 |
end |
-- get the relevants arguments |
local spellID, spellName, amount, resisted, critical, school = combatEvents[eventType](...) |
local similarSpell = similarSpells[spellID] |
if similarSpell then |
spellID = similarSpell |
spellName = self:GetSpellName(similarSpell) |
end |
-- return if the event has no amount (non-absorbing aura applied) |
if not amount then |
return |
end |
if amount <= 0 then |
self:Debug(format("Amount <= 0. (%s) Return.", self:GetFullSpellName(spellID, periodic))) |
return |
end |
local tree = "dmg" |
if isPet then |
tree = "pet" |
elseif isHeal then |
tree = "heal" |
end |
local targetLevel = self:GetLevelFromGUID(destGUID) |
local passed, isFiltered |
if self.filters then |
passed, isFiltered = self.filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school, targetLevel) |
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 |
-- 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, spellID, periodic) |
local arrayData |
-- create spell database entries as required |
if not data then |
self:Debug(format("Creating data for %s (%s)", self:GetFullSpellName(spellID, periodic), tree)) |
data, arrayData = self:AddSpell(tree, spellID, periodic, spellName, isFiltered) |
self:UpdateSpells(tree) |
end |
if not data[hitType] then |
data[hitType] = {amount = 0} |
(arrayData or self:GetSpellArrayEntry(tree, spellID, periodic))[hitType] = data[hitType] |
end |
data = data[hitType] |
-- if new amount is larger than the stored amount we'll want to store it |
if amount > data.amount then |
self:NewRecord(tree, spellID, periodic, amount, critical, data, isFiltered) |
if not isFiltered then |
-- update the highest record if needed |
local topRecords = topRecords[tree] |
if amount > topRecords[hitType] then |
topRecords[hitType] = amount |
callbacks:Fire("OnNewTopRecord", tree) |
end |
end |
data.amount = amount |
data.target = destName |
data.targetLevel = targetLevel |
data.isPvPTarget = isPlayer |
self:UpdateRecords(tree, isFiltered) |
end |
end |
function Critline:GetLevelFromGUID(destGUID) |
tooltip:SetOwner(UIParent, "ANCHOR_NONE") |
tooltip:SetHyperlink("unit:"..destGUID) |
local level = -1 |
for i = 1, tooltip:NumLines() do |
local text = _G["CritlineTooltipTextLeft"..i]:GetText() |
if text then |
if text:match(LEVEL) then -- our destGUID has the word Level in it. |
level = text:match("(%d+)") -- find the level |
if level then -- if we found the level, break from the for loop |
level = tonumber(level) |
else |
-- well, the word Level is in this tooltip, but we could not find the level |
-- either the destGUID is at least 10 levels higher than us, or we just couldn't find it. |
level = -1 |
end |
end |
end |
end |
return level |
end |
function Critline:Message(msg) |
if msg then |
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00Critline:|r "..msg) |
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.sound:SetSelectedValue(self.db.profile.sound) |
options.tooltipSort:SetSelectedValue(self.db.profile.tooltipSort) |
end |
function Critline:LoadPerCharSettings() |
for tree in pairs(treeNames) do |
wipe(spellArrays[tree]) |
for spellID, spell in pairs(self.percharDB.profile.spells[tree]) do |
for i, v in pairs(spell) do |
if type(v) ~= "table" or v.spellName then return end -- avoid error in pre 4.0 DB |
spellArrays[tree][#spellArrays[tree] + 1] = { |
spellID = spellID, |
spellName = self:GetSpellName(spellID), |
filtered = v.filtered, |
periodic = i, |
normal = v.normal, |
crit = v.crit, |
} |
end |
end |
end |
callbacks:Fire("PerCharSettingsLoaded") |
self:UpdateTopRecords() |
self:UpdateTooltips() |
for _, btn in ipairs(self.options.checkButtons.percharDB) do |
btn:LoadSetting() |
end |
end |
function Critline:NewRecord(tree, spellID, periodic, amount, critical, prevRecord, isFiltered) |
callbacks:Fire("NewRecord", tree, spellID, periodic, amount, critical, prevRecord, isFiltered) |
if isFiltered then |
return |
end |
amount = self:ShortenNumber(amount) |
if self.db.profile.oldRecord and prevRecord.amount > 0 then |
amount = format("%s (%s)", amount, self:ShortenNumber(prevRecord.amount)) |
end |
if self.db.profile.chatOutput then |
self:Message(format(L["New %s%s record - %s"], critical and "|cffff0000"..L["critical "].."|r" or "", self:GetFullSpellName(spellID, periodic, true), amount)) |
end |
if self.db.profile.playSound then |
PlaySoundFile(LSM:Fetch("sound", self.db.profile.sound)) |
end |
if self.db.profile.screenshot then |
TakeScreenshot() |
end |
end |
local FIRST_NUMBER_CAP = FIRST_NUMBER_CAP:lower() |
function Critline:ShortenNumber(amount) |
if tonumber(amount) and self.db.profile.shortFormat then |
if amount >= 1e7 then |
amount = (floor(amount / 1e5) / 10)..SECOND_NUMBER_CAP |
elseif amount >= 1e6 then |
amount = (floor(amount / 1e4) / 100)..SECOND_NUMBER_CAP |
elseif amount >= 1e4 then |
amount = (floor(amount / 100) / 10)..FIRST_NUMBER_CAP |
end |
end |
return amount |
end |
function Critline:GetSpellArrayEntry(tree, spellID, periodic) |
for i, v in ipairs(spellArrays[tree]) do |
if v.spellID == spellID and v.periodic == periodic then |
return v |
end |
end |
end |
-- local previousTree |
-- local previousSort |
function Critline:GetSpellArray(tree, useProfileSort) |
local array = spellArrays[tree] |
local sortMethod = useProfileSort and self.db.profile.tooltipSort or "alpha" |
-- no need to sort if it's already sorted the way we want it |
-- if sortMethod ~= previousSort or tree ~= previousTree then |
sort(array, recordSorters[sortMethod]) |
-- previousTree = tree |
-- previousSort = sortMethod |
-- end |
return array |
end |
-- return spell table from database, given tree, spell name and isPeriodic value |
function Critline:GetSpellInfo(tree, spellID, periodic) |
local spell = self.percharDB.profile.spells[tree][spellID] |
return spell and spell[periodic] |
end |
function Critline:GetSpellName(spellID) |
local spellName = spellNameCache[spellID] or GetSpellInfo(spellID) |
spellNameCache[spellID] = spellName |
return spellName |
end |
function Critline:GetSpellTexture(spellID) |
local spellTexture = spellTextureCache[spellID] or GetSpellTexture(spellID) |
spellTextureCache[spellID] = spellTexture |
return spellTexture |
end |
function Critline:GetFullSpellName(spellID, periodic, verbose) |
local spellName = self:GetSpellName(spellID) |
if periodic == 2 then |
spellName = format("%s (%s)", spellName, verbose and L["tick"] or "*") |
end |
return spellName |
end |
function Critline:GetFullTargetName(spell) |
local suffix = "" |
if spell.isPvPTarget then |
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:UpdateTopRecords(tree) |
if not tree then |
for tree in pairs(topRecords) do |
self:UpdateTopRecords(tree) |
end |
return |
end |
local normalRecord, critRecord = 0, 0 |
for spellID, spell in pairs(self.percharDB.profile.spells[tree]) do |
for i, v in pairs(spell) do |
if type(v) ~= "table" then return end -- avoid error in pre 4.0 DB |
if not (self.filters and v.filtered) then |
local normal = v.normal |
if normal then |
normalRecord = max(normal.amount, normalRecord) |
end |
local crit = v.crit |
if crit then |
critRecord = max(crit.amount, critRecord) |
end |
end |
end |
end |
local topRecords = topRecords[tree] |
topRecords.normal = normalRecord |
topRecords.crit = critRecord |
callbacks:Fire("OnNewTopRecord", tree) |
end |
-- retrieves the top, non filtered record amounts and spell names for a given tree |
function Critline:GetHighest(tree) |
local topRecords = topRecords[tree] |
return topRecords.normal, topRecords.crit |
end |
function Critline:AddSpell(tree, spellID, periodic, spellName, filtered) |
local spells = self.percharDB.profile.spells[tree] |
local spell = spells[spellID] or {} |
spells[spellID] = spell |
spell[periodic] = {filtered = filtered} |
local spellArray = spellArrays[tree] |
local arrayData = { |
spellID = spellID, |
spellName = spellName, |
filtered = filtered, |
periodic = periodic, |
} |
spellArray[#spellArray + 1] = arrayData |
return spell[periodic], arrayData |
end |
function Critline:DeleteSpell(tree, spellID, periodic) |
do |
local tree = self.percharDB.profile.spells[tree] |
local spell = tree[spellID] |
spell[periodic] = nil |
-- remove this entire spell entry if neither direct nor tick entries remain |
if not spell[3 - periodic] then |
tree[spellID] = nil |
end |
end |
for i, v in ipairs(spellArrays[tree]) do |
if v.spellID == spellID and v.periodic == periodic then |
tremove(spellArrays[tree], i) |
self:Message(format(L["Reset %s (%s) records."], self:GetFullSpellName(v.spellID, v.periodic), treeNames[tree])) |
break |
end |
end |
self:UpdateTopRecords(tree) |
end |
-- this "fires" when spells are added to/removed from the database |
function Critline:UpdateSpells(tree) |
if tree then |
doTooltipUpdate[tree] = true |
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 |
doTooltipUpdate[tree] = true |
callbacks:Fire("RecordsChanged", tree, isFiltered) |
else |
for k in pairs(tooltips) do |
self:UpdateRecords(k, isFiltered) |
end |
end |
end |
function Critline:UpdateTooltips() |
for k in pairs(tooltips) do |
doTooltipUpdate[k] = true |
end |
end |
local LETHAL_LEVEL = "??" |
local leftFormat = "|cffc0c0c0%s:|r %s" |
local leftFormatIndent = leftFormat |
local rightFormat = format("%s%%s|r (%%s)", HIGHLIGHT_FONT_COLOR_CODE) |
local recordFormat = format("%s%%s|r", GREEN_FONT_COLOR_CODE) |
local r, g, b = HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b |
function Critline:ShowTooltip(tree) |
if doTooltipUpdate[tree] then |
self:UpdateTooltip(tree) |
end |
local r, g, b = r, g, b |
local rR, gR, bR |
GameTooltip:AddLine("Critline "..treeNames[tree], r, g, b) |
if not self.db.profile.detailedTooltip then |
-- advanced tooltip uses different text color |
rR, gR, bR = r, g, b |
r, g, b = nil |
end |
local tooltip = tooltips[tree] |
for i = 1, #tooltips[tree] do |
local v = tooltip[i] |
-- v is either an array containing the left and right tooltip strings, or a single string |
if type(v) == "table" then |
local left, right = unpack(v) |
GameTooltip:AddDoubleLine(left, right, r, g, b, rR, gR, bR) |
else |
GameTooltip:AddLine(v) |
end |
end |
GameTooltip:Show() |
end |
function Critline:UpdateTooltip(tree) |
local tooltip = tooltips[tree] |
wipe(tooltip) |
local normalRecord, critRecord = self:GetHighest(tree) |
local n = 1 |
for _, v in ipairs(self:GetSpellArray(tree, true)) do |
if not (self.filters and self:GetSpellInfo(tree, v.spellID, v.periodic).filtered) then |
local spellName = self:GetFullSpellName(v.spellID, v.periodic) |
-- if this is a DoT/HoT, and a direct entry exists, add the proper suffix |
-- if v.periodic == 2 and not (self.filters and self.filters:IsFilteredSpell(tree, v.spellID, 1)) then |
-- spellName = self:GetFullSpellName(v.spellID, 2) |
-- end |
if self.db.profile.detailedTooltip then |
tooltip[n] = spellName |
n = n + 1 |
tooltip[n] = {self:GetTooltipLine(v, "normal", tree)} |
n = n + 1 |
tooltip[n] = {self:GetTooltipLine(v, "crit", tree)} |
else |
local normalAmount, critAmount = 0, 0 |
-- color the top score amount green |
local normal = v.normal |
if normal then |
normalAmount = self:ShortenNumber(normal.amount) |
normalAmount = normal.amount == normalRecord and GREEN_FONT_COLOR_CODE..normalAmount..FONT_COLOR_CODE_CLOSE or normalAmount |
end |
local crit = v.crit |
if crit then |
critAmount = self:ShortenNumber(crit.amount) |
critAmount = crit.amount == critRecord and GREEN_FONT_COLOR_CODE..critAmount..FONT_COLOR_CODE_CLOSE or critAmount |
end |
tooltip[n] = {spellName, crit and format("%s / %s", normalAmount, critAmount) or normalAmount} |
end |
n = n + 1 |
end |
end |
if #tooltip == 0 then |
tooltip[1] = L["No records"] |
end |
doTooltipUpdate[tree] = nil |
end |
local hitTypes = { |
normal = L["Normal"], |
crit = L["Crit"], |
} |
function Critline:GetTooltipLine(data, hitType, tree) |
local leftFormat = tree and " "..leftFormat or leftFormat |
data = data and data[hitType] |
if data then |
local amount = self:ShortenNumber(data.amount) |
if tree and data.amount == topRecords[tree][hitType] then |
amount = format(recordFormat, amount) |
end |
local level = data.targetLevel |
level = level > 0 and level or LETHAL_LEVEL |
return format(leftFormat, hitTypes[hitType], amount), format(rightFormat, self:GetFullTargetName(data), level), r, g, b |
end |
end |
function Critline:AddTooltipLine(data, tree) |
GameTooltip:AddDoubleLine(self:GetTooltipLine(data, "normal", tree)) |
GameTooltip:AddDoubleLine(self:GetTooltipLine(data, "crit", tree)) |
end |
local funcset = {} |
for k in pairs(treeNames)do |
funcset[k] = function(spellID) |
local spell = Critline.percharDB.profile.spells[k][spellID] |
if not spell then |
return |
end |
local direct = spell[1] |
local tick = spell[2] |
if Critline.filters then |
direct = direct and not direct.filtered and direct |
tick = tick and not tick.filtered and tick |
end |
return direct, tick |
end |
end |
local function addLine(header, nonTick, tick) |
if header then |
GameTooltip:AddLine(header) |
end |
Critline:AddTooltipLine(nonTick) |
if tick and nonTick then |
GameTooltip:AddLine(" ") |
GameTooltip:AddLine(L["Tick"]) |
end |
Critline:AddTooltipLine(tick) |
end |
GameTooltip:HookScript("OnTooltipSetSpell", function(self) |
if self.Critline or not Critline.db.profile.spellTooltips then |
return |
end |
local spellName, rank, spellID = self:GetSpell() |
local indirectSpell = indirectSpells[spellID] |
local indirectHeal = indirectHeals[spellID] |
local dmg1, dmg2 = funcset.dmg(indirectSpell or spellID) |
local dmg = dmg1 or dmg2 |
local heal1, heal2 = funcset.heal(indirectHeal or indirectSpell or spellID) |
local heal = heal1 or heal2 |
-- ignore pet auto attack records here, since that's handled by another function |
local pet1, pet2 = spellID ~= AUTOATK_ID and funcset.pet(indirectSpell or spellID) |
local pet = pet1 or pet2 |
if dmg or heal or pet then |
self:AddLine(" ") |
end |
if dmg then |
addLine((heal or pet) and L["Damage"], dmg1, dmg2) |
end |
if heal then |
if dmg then |
GameTooltip:AddLine(" ") |
end |
addLine((dmg or pet) and L["Healing"], heal1, heal2) |
end |
if pet then |
if dmg or heal then |
GameTooltip:AddLine(" ") |
end |
addLine((dmg or heal) and L["Pet"], pet1, pet2) |
end |
end) |
GameTooltip:HookScript("OnTooltipCleared", function(self) |
self.Critline = nil |
end) |
hooksecurefunc(GameTooltip, "SetPetAction", function(self, action) |
if not Critline.db.profile.spellTooltips then |
return |
end |
if GetPetActionInfo(action) == "PET_ACTION_ATTACK" then |
addLine(" ", (funcset.pet(AUTOATK_ID))) |
self:Show() |
end |
end) |
local addonName, addon = ... |
local L = LibStub("AceLocale-3.0"):GetLocale(addonName) |
local templates = addon.templates |
local IsSpellKnown = IsSpellKnown |
local UnitAura = UnitAura |
local UnitName = UnitName |
local UnitGUID = UnitGUID |
local CombatLog_Object_IsA = CombatLog_Object_IsA |
local band = bit.band |
local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME |
local COMBATLOG_OBJECT_REACTION_FRIENDLY = COMBATLOG_OBJECT_REACTION_FRIENDLY |
-- 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 |
[38567] = true, -- Phantom Hallucination |
[42347] = true, -- Exposed Head of Magmaw (Point of Vulnerability [79011]) ? |
[42803] = true, -- Drakeadon Mongrel (Brood Power: Red/Green/Black/Blue/Bronze [80368+80369+80370+80371+80372]) |
[46083] = true, -- Drakeadon Mongrel (Brood Power: Red/Green/Black/Blue/Bronze [80368+80369+80370+80371+80372]) |
[46273] = true, -- Debilitated Apexar |
[48270] = true, -- Exposed Head of Magmaw |
} |
-- 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 - 25 man) |
[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 - 10 man) |
[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) |
[73822] = true, -- Hellscream's Warsong (Icecrown Citadel) |
[73828] = true, -- Strength of Wrynn (Icecrown Citadel) |
[76133] = true, -- Tidal Surge (Neptulon) |
[76155] = true, -- Tidal Surge (Neptulon) |
[76159] = true, -- Pyrogenics (Sun-Touched Spriteling) |
[76355] = true, -- Blessing of the Sun (Rajh) |
[76693] = true, -- Empowering Twilight (Crimsonborne Warlord) |
[79624] = true, -- Power Generator (Arcanotron) ? |
[81096] = true, -- Red Mist (Red Mist) |
[86622] = true, -- Engulfing Magic (Theralion) ? |
[86872] = true, -- Frothing Rage (Thundermar Ale) |
[89879] = true, -- Blessing of the Sun (Rajh - heroic) |
[90933] = true, -- Ragezone (Defias Blood Wizard) |
[91871] = true, -- Lightning Charge (Siamat) |
[93777] = true, -- Invocation of Flame (Skullcrusher the Mountain) |
[95639] = true, -- Engulfing Magic (Theralion) ? |
[95640] = true, -- Engulfing Magic (Theralion) ? |
[95641] = true, -- Engulfing Magic (Theralion) ? |
} |
-- these are auras that increases the target's damage or healing received |
local targetAuras = { |
[64436] = true, -- Magnetic Core (Aerial Command Unit) ? |
[65280] = true, -- Singed (Hodir) |
[66758] = true, -- Staggered Daze (Icehowl) ? |
[75664] = true, -- Shadow Gale (Erudax) ? |
[75846] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ? |
[76015] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ? |
[76232] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ? |
[77717] = true, -- Vertigo (Atramedes) |
[80164] = true, -- Chemical Cloud (Toxitron) |
[87683] = true, -- Dragon's Vengeance (Halfus Wyrmbreaker) |
[87904] = true, -- Feedback (Al'Akir) |
[90933] = true, -- Ragezone (Defias Blood Wizard) ? |
[91086] = true, -- Shadow Gale (Erudax - heroic) |
[92390] = true, -- Vertigo (Atramedes) ? |
[92910] = true, -- Debilitating Slime (Maloriak) ? |
[93567] = true, -- Superheated Quicksilver Armor (Karsh Steelbender) ? |
[95723] = true, -- Storm's Fury (Ragnaros - Mount Hyjal) ? |
} |
-- these heals are treated as periodic, but has no aura associated with them, or is associated to an aura with a different name, need to add exceptions for them to filter properly |
local directHoTs = { |
[54172] = true, -- Divine Storm |
-- [63106] = "Corruption", -- Siphon Life |
} |
local activeAuras = {} |
local corruptSpells = {} |
local corruptTargets = {} |
local playerAuras = { |
session = {}, |
instance = {}, |
lastFight = {}, |
} |
local enemyAuras = { |
session = {}, |
instance = {}, |
lastFight = {}, |
} |
-- name of current instance |
local currentInstance = L["n/a"] |
-- amount of buttons in the spell, mob and aura filter scroll lists |
local NUMSPELLBUTTONS = 8 |
local SPELLBUTTONHEIGHT = 22 |
local NUMFILTERBUTTONS = 10 |
local FILTERBUTTONHEIGHT = 16 |
local filters = templates:CreateConfigFrame(FILTERS, addonName, true) |
filters:SetScript("OnEvent", function(self, event, ...) |
return self[event] and self[event](self, ...) |
end) |
addon.filters = filters |
local function filterButtonOnClick(self) |
local module = self.module |
local scrollFrame = module.scrollFrame |
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 = 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 |
frame.delete = delete |
end |
return frame |
end |
do |
local options = {} |
filters.options = options |
local checkButtons = { |
{ |
text = L["Filter new spells"], |
tooltipText = L["Enable to filter out new spell entries by default."], |
setting = "filterNew", |
}, |
{ |
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 checked = self:GetChecked() == 1 |
PlaySound(checked and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff") |
filters:FilterSpell(not checked, filters.spell.tree:GetSelectedValue(), self.data) |
end |
local function spellButtonOnEnter(self) |
-- prevent records being added twice |
GameTooltip.Critline = true |
GameTooltip:SetOwner(self, "ANCHOR_LEFT") |
GameTooltip:SetSpellByID(self.data.spellID) |
GameTooltip:AddLine(" ") |
addon:AddTooltipLine(self.data) |
GameTooltip:Show() |
end |
local buttons = {} |
for i = 1, NUMSPELLBUTTONS do |
local btn = templates:CreateCheckButton(spellFilter) |
if i == 1 then |
btn:SetPoint("TOPLEFT") |
else |
btn:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, 4) |
end |
btn:SetScript("OnClick", spellButtonOnClick) |
btn:SetScript("OnEnter", spellButtonOnEnter) |
buttons[i] = btn |
end |
spellFilter.scrollFrame.buttons = buttons |
end |
-- spell filter scroll frame |
local spellScrollFrame = spellFilter.scrollFrame |
-- spell filter tree dropdown |
local menu = { |
{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") |
spellFilterTree.onClick = function(self) |
self.owner:SetSelectedValue(self.value) |
FauxScrollFrame_SetOffset(spellScrollFrame, 0) |
spellScrollFrame.scrollBar:SetValue(0) |
spellScrollFrame:Update() |
end |
spellFilter.tree = spellFilterTree |
spellScrollFrame.tree = spellFilter.tree |
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 addAura = templates:CreateButton(auraFilter) |
-- addAura:SetSize(48, 22) |
-- addAura:SetPoint("TOP", auraFilter, "BOTTOM") |
-- addAura:SetText("Add") |
-- addAura:SetScript("OnClick", function() if auraList:IsShown() then auraList:Hide() else auraList:Show() end end) |
local delete = auraFilter.delete |
delete:SetSize(128, 22) |
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 = { |
{ |
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") |
filterType.onClick = function(self) |
self.owner:SetSelectedValue(self.value) |
for k, v in pairs(filterTypes) do |
if k == self.value then |
v:Show() |
else |
v:Hide() |
end |
end |
end |
filters.type = filterType |
end |
end |
do |
local auraList = CreateFrame("Frame", nil, UIParent) |
auraList:SetFrameStrata("DIALOG") |
auraList:EnableMouse(true) |
auraList:SetSize(320, 360) |
auraList:SetPoint("CENTER") |
auraList:SetBackdrop({ |
bgFile = [[Interface\ChatFrame\ChatFrameBackground]], |
edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], |
edgeSize = 16, |
insets = {left = 4, right = 4, top = 4, bottom = 4}, |
}) |
auraList:SetBackdropColor(0, 0, 0) |
auraList:SetBackdropBorderColor(0.5, 0.5, 0.5) |
auraList:Hide() |
local closeButton = CreateFrame("Button", nil, auraList, "UIPanelCloseButton") |
closeButton:SetPoint("TOPRIGHT") |
Critline.SlashCmdHandlers["aura"] = function() auraList:Show() end |
local currentFilter = playerAuras.session |
local function auraSort(a, b) |
return currentFilter[a].spellName < currentFilter[b].spellName |
end |
local function sourceSort(a, b) |
a, b = currentFilter[a], currentFilter[b] |
if a.source == b.source then |
return a.spellName < b.spellName |
else |
return a.source < b.source |
end |
end |
local auraFilters = { |
BUFF = true, |
DEBUFF = true, |
targetAffiliation = playerAuras, |
sourceType = "npc", |
sort = auraSort, |
} |
local function onClick(self, text) |
self.owner:SetSelectedValue(self.value) |
self.owner:SetText(text) |
currentFilter = auraFilters.targetAffiliation[self.value] |
CritlineAuraListScrollFrame:Update() |
end |
local menuList = { |
{ |
text = L["Current fight"], |
value = "lastFight", |
}, |
{ |
text = L["Current instance (%s)"], |
value = "instance", |
}, |
{ |
text = L["Current session"], |
value = "session", |
}, |
} |
local auraListFilter = templates:CreateDropDownMenu("CritlineAuraListFilter", auraList) |
auraListFilter:SetPoint("TOP", 0, -16) |
auraListFilter:SetFrameWidth(220) |
auraListFilter:JustifyText("LEFT") |
auraListFilter:SetSelectedValue("session") |
auraListFilter:SetText(L["Current session"]) |
auraListFilter.initialize = function(self) |
for i, v in ipairs(menuList) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = format(v.text, currentInstance) |
info.value = v.value |
info.func = onClick |
info.owner = self |
info.arg1 = info.text |
UIDropDownMenu_AddButton(info) |
end |
end |
local auraListAuraType = templates:CreateDropDownMenu("CritlineAuraListAuraType", auraList) |
auraListAuraType:SetPoint("TOPLEFT", auraListFilter, "BOTTOMLEFT") |
auraListAuraType:SetFrameWidth(96) |
auraListAuraType:JustifyText("LEFT") |
auraListAuraType:SetText(L["Aura type"]) |
do |
local function onClick(self) |
auraFilters[self.value] = self.checked |
CritlineAuraListScrollFrame:Update() |
end |
local menuList = { |
{ |
text = L["Buffs"], |
value = "BUFF", |
}, |
{ |
text = L["Debuffs"], |
value = "DEBUFF", |
}, |
} |
auraListAuraType.initialize = function(self) |
for i, v in ipairs(menuList) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v.text |
info.value = v.value |
info.func = onClick |
info.checked = auraFilters[v.value] |
info.isNotRadio = true |
info.keepShownOnClick = true |
UIDropDownMenu_AddButton(info) |
end |
end |
end |
local auraListFilters = templates:CreateDropDownMenu("CritlineAuraListFilters", auraList) |
auraListFilters:SetPoint("TOPRIGHT", auraListFilter, "BOTTOMRIGHT") |
auraListFilters:SetFrameWidth(96) |
auraListFilters:JustifyText("LEFT") |
auraListFilters:SetText(FILTERS) |
do |
local function onClick(self, key) |
auraFilters[key] = self.value |
self.owner:Refresh() |
self.owner:SetText(FILTERS) |
currentFilter = auraFilters.targetAffiliation[auraListFilter:GetSelectedValue()] |
CritlineAuraListScrollFrame:Update() |
end |
local function checked(self) |
return auraFilters[self.arg1] == self.value |
end |
local menuList = { |
{ |
text = L["Show auras cast on me"], |
value = playerAuras, |
arg1 = "targetAffiliation", |
}, |
{ |
text = L["Show auras cast on hostile NPCs"], |
value = enemyAuras, |
arg1 = "targetAffiliation", |
}, |
{ |
text = L["Show auras cast by NPCs"], |
value = "npc", |
arg1 = "sourceType", |
}, |
{ |
text = L["Show auras cast by players"], |
value = "pvp", |
arg1 = "sourceType", |
}, |
{ |
text = L["Sort by aura name"], |
value = auraSort, |
arg1 = "sort", |
}, |
{ |
text = L["Sort by source name"], |
value = sourceSort, |
arg1 = "sort", |
}, |
} |
auraListFilters.initialize = function(self) |
for i, v in ipairs(menuList) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v.text |
info.value = v.value |
info.func = onClick |
info.checked = checked |
info.owner = self |
info.keepShownOnClick = true |
info.arg1 = v.arg1 |
UIDropDownMenu_AddButton(info) |
end |
end |
end |
local search = templates:CreateEditBox(auraList) |
search:SetPoint("TOPLEFT", auraListAuraType, "BOTTOMLEFT", 18, -8) |
search:SetPoint("TOPRIGHT", auraListFilters, "BOTTOMRIGHT", -18, -8) |
search:SetWidth(192) |
search:SetScript("OnTextChanged", function() CritlineAuraListScrollFrame:Update() end) |
search:SetScript("OnEscapePressed", search.ClearFocus) |
local label = search:CreateFontString(nil, nil, "GameFontNormalSmall") |
label:SetPoint("BOTTOMLEFT", search, "TOPLEFT") |
label:SetText(L["Text filter"]) |
local NUM_BUTTONS = 6 |
local BUTTON_HEIGHT = 36 |
local auraListScrollFrame = CreateFrame("ScrollFrame", "CritlineAuraListScrollFrame", auraList, "FauxScrollFrameTemplate") |
auraListScrollFrame:SetHeight(NUM_BUTTONS * BUTTON_HEIGHT) |
auraListScrollFrame:SetPoint("TOP", search, "BOTTOM", 0, -8) |
auraListScrollFrame:SetPoint("LEFT", 32, 0) |
auraListScrollFrame:SetPoint("RIGHT", -32, 0) |
auraListScrollFrame:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, BUTTON_HEIGHT, self.Update) end) |
local sortedAuras = {} |
function auraListScrollFrame:Update() |
if not auraList:IsShown() then |
self.doUpdate = true |
return |
end |
self.doUpdate = nil |
wipe(sortedAuras) |
local n = 0 |
local search = search:GetText():lower() |
for spellID, v in pairs(currentFilter) do |
if auraFilters[v.type] and v.sourceType == auraFilters.sourceType and (v.spellName:lower():find(search, nil, true) or v.sourceName:lower():find(search, nil, true)) then |
n = n + 1 |
sortedAuras[n] = spellID |
end |
end |
sort(sortedAuras, auraFilters.sort) |
FauxScrollFrame_Update(self, n, NUM_BUTTONS, BUTTON_HEIGHT) |
local offset = FauxScrollFrame_GetOffset(self) |
local buttons = self.buttons |
for line = 1, NUM_BUTTONS do |
local button = buttons[line] |
local lineplusoffset = line + offset |
if lineplusoffset <= n then |
local spellID = sortedAuras[lineplusoffset] |
button:SetFormattedText("%s (%d)", currentFilter[spellID].spellName, spellID) |
button.source:SetText(currentFilter[spellID].source) |
button.icon:SetTexture(addon:GetSpellTexture(spellID)) |
button.spellID = spellID |
-- local disabled = filters:IsFilteredAura(spellID) |
-- button.icon:SetDesaturated(disabled) |
-- button.text:SetFontObject(disabled and "GameFontDisable" or "GameFontNormal") |
button:Show() |
else |
button:Hide() |
end |
end |
end |
auraList:SetScript("OnShow", function(self) |
if auraListScrollFrame.doUpdate then |
auraListScrollFrame:Update() |
end |
end) |
local auraListButtons = {} |
auraListScrollFrame.buttons = auraListButtons |
-- local function onClick(self) |
-- local disabled = filters:IsFilteredAura(self.spellID) |
-- if disabled then |
-- if specialAuras[self.spellID] then |
-- addon:Message("Cannot delete integrated auras.") |
-- return |
-- else |
-- local t = filters.db.global.auras |
-- for i = 1, #t do |
-- if t[i] == self.spellID then |
-- tremove(t, i) |
-- addon:Message(format("Removed aura (%s) from filter.", GetSpellInfo(self.spellID))) |
-- break |
-- end |
-- end |
-- end |
-- else |
-- filters:AddAura(self.spellID) |
-- end |
-- disabled = not disabled |
-- self.icon:SetDesaturated(disabled) |
-- self.text:SetFontObject(disabled and "GameFontDisable" or "GameFontNormal") |
-- end |
local function onEnter(self) |
GameTooltip:SetOwner(self, "ANCHOR_LEFT") |
GameTooltip:SetSpellByID(self.spellID) |
GameTooltip:AddLine(" ") |
GameTooltip:AddLine(format(L["Spell ID: |cffffffff%d|r"], self.spellID)) |
GameTooltip:Show() |
end |
for i = 1, NUM_BUTTONS do |
local btn = CreateFrame("Button", nil, auraList) |
btn:SetHeight(BUTTON_HEIGHT) |
if i == 1 then |
btn:SetPoint("TOP", auraListScrollFrame) |
else |
btn:SetPoint("TOP", auraListButtons[i - 1], "BOTTOM") |
end |
btn:SetPoint("LEFT", auraListScrollFrame) |
btn:SetPoint("RIGHT", auraListScrollFrame) |
btn:SetPushedTextOffset(0, 0) |
-- btn:SetScript("OnClick", onClick) |
btn:SetScript("OnEnter", onEnter) |
btn:SetScript("OnLeave", GameTooltip_Hide) |
if i % 2 == 0 then |
local bg = btn:CreateTexture(nil, "BACKGROUND") |
bg:SetAllPoints() |
bg:SetTexture(1, 1, 1, 0.1) |
end |
local icon = btn:CreateTexture() |
icon:SetSize(32, 32) |
icon:SetPoint("LEFT") |
btn.icon = icon |
local text = btn:CreateFontString(nil, nil, "GameFontNormal") |
text:SetPoint("TOPLEFT", icon, "TOPRIGHT", 4, -4) |
text:SetPoint("RIGHT") |
text:SetJustifyH("LEFT") |
btn:SetFontString(text) |
btn.text = text |
local source = btn:CreateFontString(nil, nil, "GameFontHighlightSmall") |
source:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 4, 4) |
source:SetPoint("RIGHT") |
source:SetJustifyH("LEFT") |
btn.source = source |
auraListButtons[i] = btn |
end |
end |
StaticPopupDialogs["CRITLINE_ADD_MOB_BY_NAME"] = { |
text = L["Enter mob name:"], |
button1 = OKAY, |
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:GetSpellArray(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.data = data |
button:SetText(addon:GetFullSpellName(data.spellID, data.periodic)) |
button:SetChecked(not 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 = { |
filterNew = false, |
onlyKnown = false, |
ignoreMobFilter = false, |
ignoreAuraFilter = 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") |
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") |
self:RegisterEvent("PLAYER_REGEN_DISABLED") |
self:RegisterEvent("PLAYER_ENTERING_WORLD") |
self:RegisterEvent("PLAYER_LOGIN") |
self:RegisterEvent("UNIT_NAME_UPDATE") |
self:RegisterEvent("PLAYER_CONTROL_LOST") |
self:RegisterEvent("PLAYER_CONTROL_GAINED") |
-- mix in scroll frame update functions |
self.spell.scrollFrame.Update = updateSpellFilter |
self.mobs.scrollFrame.Update = updateFilter |
self.auras.scrollFrame.Update = updateFilter |
end |
addon.RegisterCallback(filters, "AddonLoaded") |
function filters:COMBAT_LOG_EVENT_UNFILTERED(timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, spellID, spellName, spellSchool, auraType) |
if eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" then |
if targetAuras[spellID] then |
corruptTargets[destGUID] = corruptTargets[destGUID] or {} |
corruptTargets[destGUID][spellID] = true |
addon:Debug(format("Target (%s) gained filtered aura. (%s) Ignore received damage.", destName, spellID)) |
end |
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) then |
self:RegisterAura(playerAuras, sourceName, sourceGUID, spellID, spellName, auraType) |
if self:IsFilteredAura(spellID) then |
-- if we gain any aura in the filter we can just stop tracking records |
if not (self:IsEmpowered() or self.profile.ignoreAuraFilter) then |
addon:Debug(format("Filtered aura gained. (%s) Disabling combat log tracking.", spellName)) |
end |
activeAuras[spellID] = true |
end |
else |
if CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then |
corruptSpells[spellID] = corruptSpells[spellID] or {} |
corruptSpells[spellID][destGUID] = self:IsEmpowered() or self:IsVulnerableTarget(destGUID) |
end |
-- only non friendly NPC units |
local unitType = band(destGUID:sub(1, 5), 0x007) |
if (unitType ~= 0 and unitType ~= 4) and (band(destFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == 0) then |
self:RegisterAura(enemyAuras, sourceName, sourceGUID, spellID, spellName, auraType) |
end |
end |
elseif (eventType == "SPELL_AURA_REMOVED" or eventType == "SPELL_AURA_BROKEN" or eventType == "SPELL_AURA_BROKEN_SPELL" or eventType == "SPELL_AURA_STOLEN") then |
if targetAuras[spellID] then |
corruptTargets[destGUID] = corruptTargets[destGUID] or {} |
corruptTargets[destGUID][spellID] = nil |
addon:Debug(format("Filtered aura (%s) faded from %s.", spellName, destName)) |
end |
if CombatLog_Object_IsA(destFlags, COMBATLOG_FILTER_ME) then |
if self:IsFilteredAura(spellID) then |
addon:Debug(format("Filtered aura (%s) faded from player.", spellName)) |
-- if we lost a special aura we have to check if any other filtered auras remain |
activeAuras[spellID] = nil |
if not filters:IsEmpowered() then |
addon:Debug("No filtered aura detected. Resuming record tracking.") |
end |
-- elseif CombatLog_Object_IsA(sourceFlags, COMBATLOG_FILTER_ME) then |
-- corruptSpells[spellID] = corruptSpells[spellID] or {} |
-- corruptSpells[spellID][destGUID] = nil |
end |
-- else |
end |
end |
end |
-- reset current fight auras upon entering combat |
function filters:PLAYER_REGEN_DISABLED() |
wipe(playerAuras.lastFight) |
wipe(enemyAuras.lastFight) |
CritlineAuraListScrollFrame:Update() |
end |
function filters:PLAYER_ENTERING_WORLD() |
-- wipe instance buff data when entering a new instance |
local instanceName = GetInstanceInfo() |
if IsInInstance() and instanceName ~= currentInstance then |
wipe(playerAuras.instance) |
wipe(enemyAuras.instance) |
currentInstance = instanceName |
if CritlineAuraListFilter:GetSelectedValue() == "instance" then |
CritlineAuraListFilter:SetText(format(L["Current instance (%s)"], currentInstance)) |
end |
CritlineAuraListScrollFrame:Update() |
end |
end |
function filters:PLAYER_LOGIN() |
self:ScanAuras() |
end |
function filters:UNIT_NAME_UPDATE() |
self:ScanAuras() |
self:UnregisterEvent("UNIT_NAME_UPDATE") |
end |
function filters:PLAYER_CONTROL_LOST() |
self.inControl = false |
addon:Debug("Lost control. Disabling combat log tracking.") |
end |
function filters:PLAYER_CONTROL_GAINED() |
self.inControl = true |
addon:Debug("Regained control. Resuming combat log tracking.") |
end |
function filters:LoadSettings() |
self.profile = self.db.profile |
for i, v in ipairs(self.options.checkButtons) do |
v:LoadSetting() |
end |
self.options.slider:SetValue(self.profile.levelFilter) |
end |
local auraTypes = { |
BUFF = "HELPFUL", |
DEBUFF = "HARMFUL", |
} |
function filters:ScanAuras() |
local auras = {} |
for auraType, filter in pairs(auraTypes) do |
for i = 1, 40 do |
local spellName, _, _, _, _, _, _, source, _, _, spellID = UnitAura("player", i, filter) |
if not spellID then break end |
auras[spellID] = true |
if specialAuras[spellID] then |
activeAuras[spellID] = true |
end |
self:RegisterAura(playerAuras, source and UnitName(source), source and UnitGUID(source), spellID, spellName, auraType) |
end |
end |
CritlineAuraListScrollFrame:Update() |
if next(auras) then |
self:UnregisterEvent("UNIT_NAME_UPDATE") |
end |
for i, v in ipairs(self.db.global.auras) do |
activeAuras[v] = auras[v] |
end |
if next(activeAuras) then |
addon:Debug("Filtered aura detected. Disabling combat log tracking.") |
end |
self.inControl = HasFullControl() |
if not self.inControl then |
addon:Debug("Lost control. Disabling combat log tracking.") |
end |
end |
function filters:UpdateSpellFilter() |
self.spell.scrollFrame:Update() |
end |
function filters:UpdateFilter() |
self[self.type:GetSelectedValue()].scrollFrame:Update() |
end |
function filters:FilterSpell(filter, tree, data) |
data.filtered = filter |
addon:GetSpellInfo(tree, data.spellID, data.periodic).filtered = filter |
addon:UpdateTopRecords(tree) |
addon:UpdateRecords(tree) |
end |
-- adds a mob to the mob filter |
function filters:AddMob(name) |
if self:IsFilteredMob(name) then |
addon:Message(L["%s is already in mob filter."]:format(name)) |
else |
tinsert(self.db.global.mobs, name) |
self:UpdateFilter() |
addon:Message(L["%s added to mob filter."]:format(name)) |
end |
end |
-- adds an aura to the aura filter |
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 |
-- check if a spell passes the filter settings |
function filters:SpellPassesFilters(tree, spellName, spellID, isPeriodic, destGUID, destName, school, targetLevel) |
if spellID and not IsSpellKnown(spellID, tree == "pet") and self.profile.onlyKnown then |
addon:Debug(format("%s is not in your%s spell book. Return.", spellName, tree == "pet" and " pet's" or "")) |
return |
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 |
if self:IsVulnerableTarget(destGUID) and not self.profile.ignoreAuraFilter then |
addon:Debug("Target is vulnerable. Return.") |
return |
end |
local levelDiff = 0 |
if (targetLevel > 0) and (targetLevel < UnitLevel("player")) then |
levelDiff = (UnitLevel("player") - targetLevel) |
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 |
local filteredMob = self:IsFilteredMob(destName, destGUID) |
if filteredMob then |
addon:Debug(format("Target (%s) is in %s target filter.", mobName, filteredMob)) |
return |
end |
return true, self:IsFilteredSpell(tree, spellID, isPeriodic and 2 or 1), targetLevel |
end |
-- check if a spell will be filtered out |
function filters:IsFilteredSpell(tree, spellID, periodic) |
local spell = addon:GetSpellInfo(tree, spellID, periodic) |
return (not spell and self.db.profile.filterNew) or (spell and spell.filtered) |
end |
-- scan for filtered auras from the specialAuras table |
function filters:IsEmpowered() |
if next(activeAuras) or not self.inControl then |
return true |
end |
end |
-- checks if a target is affected by any vulnerability auras |
function filters:IsVulnerableTarget(guid) |
local corruptTarget = corruptTargets[guid] |
if corruptTarget and next(corruptTarget) then |
return true |
end |
end |
function filters:IsFilteredMob(mobName, guid) |
-- GUID is provided if the function was called from the combat event handler |
if guid and not self.profile.ignoreMobFilter and specialMobs[tonumber(guid:sub(7, 10), 16)] then |
return "default" |
end |
for _, v in ipairs(self.db.global.mobs) do |
if v:lower() == mobName:lower() then |
return "custom" |
end |
end |
end |
function filters:IsFilteredAura(spellID) |
if specialAuras[spellID] then |
return true |
end |
for _, v in ipairs(self.db.global.auras) do |
if v == spellID then |
return true |
end |
end |
end |
function filters:RegisterAura(auraTable, sourceName, sourceGUID, spellID, spellName, auraType) |
local session = auraTable.session |
if session[spellID] or IsSpellKnown(spellID) or not sourceName then |
return |
end |
local source = L["n/a"] |
local sourceType |
local unitType = bit.band(sourceGUID:sub(1, 5), 0x007) |
if unitType == 0 or unitType == 4 then |
-- this is a player or a player's permanent pet |
source = PVP |
sourceType = "pvp" |
else |
source = tonumber(sourceGUID:sub(7, 10), 16) |
sourceType = "npc" |
end |
local aura = { |
source = format("%s (%s)", sourceName, source), |
sourceName = sourceName, |
spellName = spellName, |
sourceType = sourceType, |
type = auraType, |
} |
auraTable.lastFight[spellID] = aura |
if IsInInstance() then |
auraTable.instance[spellID] = aura |
end |
session[spellID] = aura |
CritlineAuraListScrollFrame:Update() |
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-2011 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", 9 |
assert(LibStub, MAJOR.." requires LibStub") |
local lib = LibStub:NewLibrary(MAJOR, MINOR) |
if not lib then return end |
-- ---------------------------------------------------------------------------- |
-- Library data |
-- ---------------------------------------------------------------------------- |
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_DUAL_PROFILE = "Second profil" |
L_DUAL_PROFILE_DESC = "Sélectionnez le profil à échanger avec le profil courant lors du changement de spécialisation." |
L_ENABLED = "Activez le second profil" |
L_ENABLED_DESC = "Cochez cette case pour échanger automatiquement les profils lors d'un changement de spécialisation." |
elseif locale == "deDE" then |
L_DUALSPEC_DESC = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel der dualen Talentspezialisierung das Profil. Das duale Profil wird beim Wechsel automatisch mit dem derzeit aktiven Profil getauscht." |
L_DUAL_PROFILE = "Duales Profil" |
L_DUAL_PROFILE_DESC = "Wähle das Profil, das beim Wechsel der Talente aktiviert wird." |
L_ENABLED = "Aktiviere Duale Profile" |
L_ENABLED_DESC = "Aktiviere diese Option, um beim Talentwechsel automatisch zwischen den Profilen zu wechseln." |
elseif locale == "koKR" then |
L_DUALSPEC_DESC = "ê°ë¥íë©´ ì¬ì©í©ëë¤. ì´ì¤ í¹ì±ì ìíì¬ ë¤ë¥¸ íë¡íì ì íí ì ìê² íëë¤. ì´ì¤ íë¡íì íì¬ íë¡íê³¼ ë²ê°ìì í¹ì±ì´ ë³ê²½ë ë ê°ì´ ì ì©ë©ëë¤." |
L_DUAL_PROFILE = "ì´ì¤ íë¡í" |
L_DUAL_PROFILE_DESC = "í¹ì±ì´ ë°ë ë íë¡íì ì íí©ëë¤." |
L_ENABLED = "ì´ì¤ íë¡í ì¬ì©" |
L_ENABLED_DESC = "í¹ì±ì´ ë³ê²½ ë ë ìëì¼ë¡ íë¡íì ë³ê²½íëë¡ ì íí©ëë¤." |
elseif locale == "ruRU" then |
L_DUALSPEC_DESC = "Ðвойной пÑоÑÐ¸Ð»Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»ÑÐµÑ Ð²Ð°Ð¼ вÑбÑаÑÑ ÑазлиÑнÑе пÑоÑили Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑаÑкладки ÑаланÑов. ÐÑоÑили бÑдÑÑ Ð¿ÐµÑеклÑÑаÑÑÑÑ ÐºÐ°Ð¶Ð´Ñй Ñаз, когда Ð²Ñ Ð¿ÐµÑеклÑÑаеÑе ÑаÑÐºÐ»Ð°Ð´ÐºÑ ÑаланÑов." |
L_DUAL_PROFILE = "ÐÑоÑой пÑоÑилÑ" |
L_DUAL_PROFILE_DESC = "ÐÑбеÑиÑе пÑоÑилÑ, коÑоÑÑй Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ акÑивиÑоваÑÑ Ð¿Ñи пеÑеклÑÑениии ÑаланÑов." |
L_ENABLED = "ÐклÑÑиÑÑ Ð´Ð²Ð¾Ð¹Ð½Ð¾Ð¹ пÑоÑилÑ" |
L_ENABLED_DESC = "ÐклÑÑиÑе ÑÑÑ Ð¾Ð¿ÑÐ¸Ñ Ð´Ð»Ñ Ð°Ð²ÑомаÑиÑеÑкого пеÑеклÑÑÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñ Ð¿ÑоÑилÑми пÑи пеÑеклÑÑении ÑаÑкладки ÑаланÑов." |
elseif locale == "zhCN" then |
L_DUALSPEC_DESC = "å¯æ¶ï¼ä½ å¯ä»¥ä¸ºä½ çå天èµè®¾å®å¦ä¸ç»é ç½®æ件ï¼ä½ çåéé ç½®æ件å°å¨ä½ 转æ¢å¤©èµæ¶èªå¨ä¸ç®å使ç¨é ç½®æ件交æ¢ã" |
L_DUAL_PROFILE = "åéé ç½®æ件" |
L_DUAL_PROFILE_DESC = "éæ©è½¬æ¢å¤©èµæ¶æè¦ä½¿ç¨çé ç½®æ件" |
L_ENABLED = "å¼å¯åéé ç½®æ件" |
L_ENABLED_DESC = "å¾é以便转æ¢å¤©èµæ¶èªå¨äº¤æ¢é ç½®æ件ã" |
elseif locale == "zhTW" then |
L_DUALSPEC_DESC = "åç¨æï¼ä½ å¯ä»¥çºä½ çé天賦è¨å®å¦ä¸çµè¨å®æªãä½ çéè¨å®æªå°å¨ä½ è½æ天賦æèªåèç®å使ç¨è¨å®æªäº¤æã" |
L_DUAL_PROFILE = "éè¨å®æª" |
L_DUAL_PROFILE_DESC = "é¸æè½æ天賦å¾æè¦ä½¿ç¨çè¨å®æª" |
L_ENABLED = "åç¨éè¨å®æª" |
L_ENABLED_DESC = "å¾é¸ä»¥å¨è½æ天賦æèªå交æè¨å®æª" |
elseif locale == "esES" then |
L_DUALSPEC_DESC = "Si está activa, esta caracterÃstica te permite seleccionar un perfil distinto para cada configuración de talentos. El perfil secundario será intercambiado por el activo cada vez que cambies de una configuración de talentos a otra." |
L_DUAL_PROFILE = "Perfil secundario" |
L_DUAL_PROFILE_DESC = "Elige el perfil secundario que se usará cuando cambies de talentos." |
L_ENABLED = "Activar perfil secundario" |
L_ENABLED_DESC = "Activa esta casilla para alternar automáticamente entre prefiles cuando cambies de talentos." |
end |
end |
-- ---------------------------------------------------------------------------- |
-- 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 lib.talentsLoaded and db.char.enabled and db.char.talentGroup ~= lib.talentGroup then |
local currentProfile = self:GetCurrentProfile() |
local newProfile = db.char.profile |
db.char.talentGroup = lib.talentGroup |
if newProfile ~= currentProfile then |
db.char.profile = currentProfile |
self:SetProfile(newProfile) |
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 |
-- Actually enhance the database |
-- This is used on first initialization and everytime the database is reset using :ResetDB |
function lib:_EnhanceDatabase(event, target) |
registry[target].db = target:GetNamespace(MAJOR, true) or target:RegisterNamespace(MAJOR) |
EmbedMixin(target) |
target:CheckDualSpecState() |
end |
--- Embed dual spec feature into an existing AceDB-3.0 database. |
-- LibDualSpec specific methods are added to the instance. |
-- @name LibDualSpec:EnhanceDatabase |
-- @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 |
registry[target] = { name = name } |
lib:_EnhanceDatabase("EnhanceDatabase", target) |
target.RegisterCallback(lib, "OnDatabaseReset", "_EnhanceDatabase") |
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() |
lib.talentsLoaded = true |
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 1005 2011-01-29 14:19:43Z mikk $ |
local MAJOR,MINOR = "AceLocale-3.0", 5 |
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. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used). |
-- @usage |
-- -- enUS.lua |
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true) |
-- 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) |
-- GAME_LOCALE allows translators to test translations of addons without having that wow client installed |
local gameLocale = GAME_LOCALE or gameLocale |
local app = AceLocale.apps[application] |
if silent and app then |
geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered") |
end |
if not app then |
if silent=="raw" then |
app = {} |
else |
app = setmetatable({}, silent and readmetasilent or readmeta) |
end |
AceLocale.apps[application] = app |
AceLocale.appnames[app] = application |
end |
if locale ~= gameLocale and not isDefault then |
return -- nop, we don't need these translations |
end |
registering = app -- remember globally for writeproxy and writedefaultproxy |
if isDefault then |
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. |
--[[ |
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 |
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 |
display:Toggle() |
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["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 font = templates:CreateDropDownMenu("CritlineSplashFont", config) |
font:SetFrameWidth(120) |
font:SetPoint("TOPLEFT", config.title, "BOTTOM", 0, -28) |
font.label:SetText(L["Font"]) |
font.initialize = function(self) |
for _, v in ipairs(LSM:List("font")) do |
local info = UIDropDownMenu_CreateInfo() |
info.text = v |
info.func = onClick |
info.owner = self |
UIDropDownMenu_AddButton(info) |
end |
end |
options.font = font |
local menu = { |
{text = L["None"], value = ""}, |
{text = L["Normal"], value = "OUTLINE"}, |
{text = L["Thick"], value = "THICKOUTLINE"}, |
} |
local fontFlags = templates:CreateDropDownMenu("CritlineSplashFontFlags", config, menu) |
fontFlags:SetFrameWidth(120) |
fontFlags:SetPoint("TOP", font, "BOTTOM", 0, -16) |
fontFlags.label:SetText(L["Font outline"]) |
fontFlags.onClick = function(self) |
self.owner:SetSelectedValue(self.value) |
local font = splash.profile.font |
font.flags = self.value |
splash:SetFont(LSM:Fetch("font", font.name), font.size, font.flags) |
end |
options.fontFlags = fontFlags |
local fontSize = templates:CreateSlider(config, { |
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, |
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, tree, spellID, periodic, amount, crit, prevRecord, isFiltered) |
if isFiltered then |
return |
end |
spell = format(L["New %s record!"], addon:GetFullSpellName(spellID, periodic, true)) |
amount = addon:ShortenNumber(amount) |
if addon.db.profile.oldRecord and prevRecord.amount > 0 then |
amount = format("%s (%s)", amount, addon:ShortenNumber(prevRecord.amount)) |
end |
local colors = self.profile.colors |
local spellColor = colors.spell |
local amountColor = colors.amount |
if self.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: 40100 |
## Title: Critline |
## Version: 4.0.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\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 ColorPickerFrame = ColorPickerFrame |
local function swatchFunc() |
local button = ColorPickerFrame.extraInfo |
local r, g, b = ColorPickerFrame:GetColorRGB() |
button.swatch:SetVertexColor(r, g, b) |
if button.func then button:func(r, g, b) end |
local color = button.color |
color.r = r |
color.g = g |
color.b = b |
end |
local function cancelFunc(prev) |
local button = ColorPickerFrame.extraInfo |
local r, g, b, a = prev.r, prev.g, prev.b, prev.opacity |
button.swatch:SetVertexColor(r, g, b) |
if button.func then button:func(r, g, b) end |
local color = button.color |
color.r = r |
color.g = g |
color.b = b |
end |
-- local function opacityFunc() |
-- local button = ColorPickerFrame.extraInfo |
-- local alpha = 1.0 - OpacitySliderFrame:GetValue() |
-- if button.opacityFunc then button:opacityFunc(alpha) end |
-- end |
local function onClick(self) |
local info = UIDropDownMenu_CreateInfo() |
local color = self.color |
info.r, info.g, info.b = color.r, color.g, color.b |
info.swatchFunc = swatchFunc |
-- info.hasOpacity = self.hasOpacity |
-- info.opacityFunc = opacityFunc |
-- info.opacity = color.a |
info.cancelFunc = cancelFunc |
info.extraInfo = self |
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:SetAutoFocus(false) |
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 setDisabled(self, disable) |
if disable then |
self:Disable() |
else |
self:Enable() |
end |
end |
local function initialize(self) |
local onClick = self.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, valueLookup) |
local frame = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate") |
frame.SetFrameWidth = UIDropDownMenu_SetWidth |
frame.SetSelectedValue = setSelectedValue |
frame.GetSelectedValue = UIDropDownMenu_GetSelectedValue |
frame.Refresh = UIDropDownMenu_Refresh |
frame.SetText = UIDropDownMenu_SetText |
frame.Enable = UIDropDownMenu_EnableDropDown |
frame.Disable = UIDropDownMenu_DisableDropDown |
frame.SetDisabled = setDisabled |
frame.JustifyText = UIDropDownMenu_JustifyText |
if menu then |
for _, v in ipairs(menu) do |
menu[v.value] = v.text |
end |
end |
frame.menu = menu or valueLookup |
frame.initialize = 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 = 8 |
local ITEMHEIGHT = 36 |
local function update(self) |
local selectedTree = self.tree:GetSelectedValue() |
local spells = addon:GetSpellArray(selectedTree) |
local size = #spells |
FauxScrollFrame_Update(self.scrollFrame, size, MAXSPELLBUTTONS, ITEMHEIGHT) |
local offset = FauxScrollFrame_GetOffset(self.scrollFrame) |
for line = 1, MAXSPELLBUTTONS do |
local item = self.buttons[line] |
local lineplusoffset = line + offset |
if lineplusoffset <= size then |
local data = spells[lineplusoffset] |
item.data = data |
item.button.data = data |
local normal = data.normal and data.normal.amount |
local crit = data.crit and data.crit.amount |
item.icon:SetTexture(addon:GetSpellTexture(data.spellID)) |
item.spell:SetText(addon:GetFullSpellName(data.spellID, data.periodic)) |
item.target:SetFormattedText("%s / %s", data.normal and data.normal.target or "n/a", data.crit and data.crit.target or "n/a") |
item.record:SetFormattedText("%s\n%s", addon:ShortenNumber(normal or 0), addon:ShortenNumber(crit or 0)) |
if self.history then |
local prevRecord = self.history[selectedTree][data.spellID] |
if prevRecord and prevRecord[data.periodic] then |
item.button.bg:Hide() |
item.button.texture:Hide() |
item.button.action = "Undo" |
else |
item.button.bg:Show() |
item.button.texture:Show() |
item.button.action = "Reset" |
end |
end |
item:Show() |
else |
item:Hide() |
end |
end |
end |
local function onMouseDown(self) |
self:SetPoint("RIGHT", -7, -1) |
end |
local function onMouseUp(self) |
self:SetPoint("RIGHT", -8, 0) |
end |
-- this is used for creating the scroll frames for the Reset and Announce frames |
function templates:CreateList(name, title, action) |
local frame = templates:CreateConfigFrame(title, addonName) |
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:SetHeight(MAXSPELLBUTTONS * ITEMHEIGHT + 4) |
scrollFrame:SetPoint("TOPLEFT", 32, -24) |
scrollFrame:SetPoint("TOPRIGHT", -32, -24) |
scrollFrame:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, ITEMHEIGHT, update) end) |
frame.scrollFrame = scrollFrame |
local function onClick(self) |
frame[self.action](frame, self.data) |
end |
local function onEnter(self) |
GameTooltip.Critline = true |
GameTooltip:SetOwner(self, "ANCHOR_LEFT") |
GameTooltip:SetSpellByID(self.data.spellID) |
GameTooltip:AddLine(" ") |
addon:AddTooltipLine(self.data) |
if frame.history then |
local prevRecord = frame.history[frame.tree.selectedValue][self.data.spellID] |
prevRecord = prevRecord and prevRecord[self.data.periodic] |
if prevRecord then |
GameTooltip:AddLine(" ") |
GameTooltip:AddLine(L["Previous record:"]) |
addon:AddTooltipLine(prevRecord) |
end |
end |
GameTooltip:Show() |
end |
-- create list of check buttons |
local buttons = {} |
for i = 1, MAXSPELLBUTTONS do |
local item = CreateFrame("Frame", nil, frame) |
item:SetHeight(ITEMHEIGHT) |
if i == 1 then |
item:SetPoint("TOPLEFT", scrollFrame) |
else |
item:SetPoint("TOP", buttons[i - 1], "BOTTOM") |
end |
item:SetPoint("RIGHT", scrollFrame) |
item:SetScript("OnEnter", onEnter) |
item:SetScript("OnLeave", GameTooltip_Hide) |
local icon = item:CreateTexture() |
icon:SetSize(32, 32) |
icon:SetPoint("LEFT") |
item.icon = icon |
local spell = item:CreateFontString(nil, nil, "GameFontNormal") |
spell:SetPoint("TOPLEFT", icon, "TOPRIGHT", 4, -4) |
spell:SetJustifyH("LEFT") |
item.spell = spell |
local target = item:CreateFontString(nil, nil, "GameFontHighlightSmall") |
target:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 4, 4) |
target:SetPoint("RIGHT", -64, 0) |
target:SetJustifyH("LEFT") |
item.target = target |
local button = CreateFrame("Button", nil, item) |
button:SetSize(24, 24) |
button:SetPoint("RIGHT", -8, 0) |
button:SetScript("OnClick", onClick) |
button:SetScript("OnMouseDown", onMouseDown) |
button:SetScript("OnMouseUp", onMouseUp) |
button.action = action |
item.button = button |
local border = button:CreateTexture(nil, "BORDER") |
border:SetAllPoints() |
border:SetTexture([[Interface\PaperDollInfoFrame\UI-GearManager-Undo]]) |
local bg = button:CreateTexture() |
bg:SetSize(20, 20) |
bg:SetPoint("CENTER") |
bg:SetTexture(0, 0, 0) |
button.bg = bg |
local texture = button:CreateTexture() |
texture:SetPoint("CENTER") |
button.texture = texture |
if action == "Announce" then |
texture:SetSize(16, 16) |
texture:SetTexture([[Interface\Buttons\UI-GuildButton-MOTD-Up]]) |
else |
texture:SetSize(22, 22) |
texture:SetTexture([[Interface\RaidFrame\ReadyCheck-NotReady]]) |
end |
-- font string for record amounts |
local record = item:CreateFontString(nil, nil, "GameFontHighlightSmall") |
record:SetPoint("TOPRIGHT", -36, 0) |
record:SetPoint("BOTTOMRIGHT", -36, 0) |
record:SetJustifyH("RIGHT") |
record:SetSpacing(2) |
item.record = record |
buttons[i] = item |
end |
frame.buttons = buttons |
local menu = { |
{text = L["Damage"], value = "dmg"}, |
{text = L["Healing"], value = "heal"}, |
{text = L["Pet"], value = "pet"}, |
} |
local tree = templates:CreateDropDownMenu(name.."Tree", frame, menu) |
tree:SetPoint("TOPLEFT", scrollFrame, "BOTTOMLEFT", -16, -4) |
tree:SetFrameWidth(120) |
tree:SetSelectedValue("dmg") |
tree.onClick = function(self) |
self.owner:SetSelectedValue(self.value) |
StaticPopup_Hide("CRITLINE_RESET_ALL") |
FauxScrollFrame_SetOffset(scrollFrame, 0) |
_G[scrollFrame:GetName().."ScrollBar"]:SetValue(0) |
frame:Update() |
end |
frame.tree = tree |
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 from spell book"] = true |
L["Add target"] = true |
L["Alpha"] = true |
L["Alphabetically"] = true |
L["Amount color"] = true |
L["Announce"] = true |
L["Are you sure you want to reset all %s records?"] = true |
L["Aura filter"] = true |
L["Aura type"] = true |
L["Buffs"] = 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["Current fight"] = true |
L["Current instance (%s)"] = true |
L["Current session"] = true |
L["damage"] = true |
L["Damage"] = true |
L["Debuffs"] = true |
L["Delete aura"] = true |
L["Delete mob"] = true |
L["Detailed tooltip"] = true |
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["Don't filter magic"] = true |
L["Duration"] = true |
L["Drag to move"] = true |
L["Enable to filter out new spell entries by default."] = true |
L["Enable to ignore additional damage due to vulnerability."] = true |
L["Enable to ignore integrated aura filter."] = true |
L["Enable to ignore integrated mob filter."] = true |
L["Enable to ignore spells that are not in your (or your pet's) spell book."] = true |
L["Enable to let magical damage ignore the level filter."] = true |
L["Enable to show icon indicators instead of text."] = true |
L["Enable to use scrolling combat text for \"New record\" messages instead of the default splash frame."] = true |
L["Enabled"] = true |
L["Enter mob name:"] = true |
L["Enter spell ID:"] = true |
L["Filter new spells"] = true |
L["Font"] = true |
L["Font outline"] = true |
L["Font size"] = true |
L["healing"] = true |
L["Healing"] = true |
L["If level difference between you and the target is greater than this setting, records will not be registered."] = true |
L["Ignore aura filter"] = true |
L["Ignore mob filter"] = true |
L["Ignore vulnerability"] = true |
L["Include old record"] = true |
L["Include (unfiltered) records in spell tooltips."] = true |
L["Includes previous record along with \"New record\" messages."] = 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["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 - %s"] = true |
L["n/a"] = 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["Previous record:"] = true |
L["Prints new record notifications to the chat frame."] = true |
L["Record damage"] = true |
L["Record healing"] = true |
L["Record pet damage"] = true |
L["Record PvE"] = true |
L["Record PvP"] = true |
L["Records in spell tooltips"] = true |
L["Reset"] = true |
L["Reset all"] = true |
L["Reset all %s records."] = true |
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 opacity of the display."] = true |
L["Sets the scale of the splash frame."] = true |
L["Sets the scale of the display."] = true |
L["Sets the time (in seconds) the splash frame is visible before fading out."] = true |
L["Shorten records"] = true |
L["Show"] = true |
L["Show auras cast by NPCs"] = true |
L["Show auras cast by players"] = true |
L["Show auras cast on hostile NPCs"] = true |
L["Show auras cast on me"] = true |
L["Show minimap button."] = true |
L["Show icons"] = true |
L["Show 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["Spell ID: |cffffffff%d|r"] = true |
L["Splash frame"] = true |
L["%s removed from aura filter."] = true |
L["%s removed from mob filter."] = true |
L["Sort by aura name"] = true |
L["Sort by source name"] = true |
L["Summary frame scale"] = true |
L["Suppress all records while mind controlled."] = true |
L["Suppress mind control"] = true |
L["Text filter"] = true |
L["Thick"] = true |
L["tick"] = true |
L["Tick"] = true |
L["Tooltip sorting:"] = true |
L["Use combat text splash"] = true |
L["Use detailed format in the summary tooltip."] = true |
L["Use shorter format for records numbers."] = 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["Includes previous record along with \"New record\" messages."] = "å¨\"æ°çè¨é\"è¨æ¯ä¸èµ·é¡¯ç¤ºåä¸æ¢è¨é." |
L["Don't filter magic"] = "ä¸é濾éæ³" |
-- 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["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["Include 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["Includes previous record along with \"New record\" messages."] = "ÐоказÑваÑÑ Ð¿ÑедÑдÑÑие ÑекоÑÐ´Ñ Ð²Ð¼ÐµÑÑе Ñ ÑообÑениÑми \"ÐовÑй ÑекоÑд\"" |
L["Don't filter magic"] = "Ðе ÑилÑÑÑоваÑÑ Ð¼Ð°Ð³Ð¸Ñ" |
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["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 - %s"] = "ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑ %s%s - %s" |
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["Include 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["Includes previous record along with \"New record\" messages."] = "Mostrar marca anterior junto con la nueva marca." |
L["Don't filter magic"] = "No filtrar magia" |
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["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["Include 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["Buffs"] = "Stärkungszauber" |
L["By crit record"] = "Nach kritischen Rekorden" |
L["By normal record"] = "Nach normalen Rekorden" |
L["Cannot add players to mob filter."] = "Es können keine Spieler zum Mob Filter hinzugefügt werden." |
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["Debuffs"] = "Schwächungszauber" |
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["Includes 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["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["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 - %s"] = "Neuer %s%s Rekord - %s" -- 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["Include 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 |
-- history for undoing recent (last fight) records |
local history = {dmg = {}, heal = {}, pet = {}} |
local module = templates:CreateList("CritlineReset", L["Reset"]) |
module:RegisterEvent("PLAYER_REGEN_DISABLED") |
module:SetScript("OnEvent", function(self) |
-- previous records are wiped upon entering combat |
self:ClearHistory() |
end) |
module.history = history |
-- reset/announce button |
local button = templates:CreateButton(module) |
button:SetPoint("TOPRIGHT", module.scrollFrame, "BOTTOMRIGHT", 0, -7) |
button:SetSize(100, 22) |
button:SetText(L["Reset all"]) |
button:SetScript("OnClick", function(self) |
PlaySound("gsTitleOptionOK") |
StaticPopup_Show("CRITLINE_RESET_ALL", addon.treeNames[module.tree:GetSelectedValue()]) |
end) |
-- "edit tooltip format" popup |
StaticPopupDialogs["CRITLINE_RESET_ALL"] = { |
text = L["Are you sure you want to reset all %s records?"], |
button1 = OKAY, |
button2 = CANCEL, |
OnAccept = function(self) |
module:ResetAll() |
end, |
whileDead = true, |
timeout = 0, |
} |
function module:Reset(data) |
local tree = self.tree.selectedValue |
addon:DeleteSpell(tree, data.spellID, data.periodic) |
addon:UpdateRecords(tree) |
end |
function module:Undo(data) |
local tree = self.tree.selectedValue |
local history = history[tree][data.spellID] |
local spell = addon:GetSpellInfo(tree, data.spellID, data.periodic) |
for k, v in pairs(history[data.periodic]) do |
local hitType = spell[k] |
local amount, target = hitType.amount, hitType.target |
for k, v in pairs(v) do |
hitType[k] = v |
end |
addon:Message(format("Reverted %s (%d, %s) record on %s.", data.spellName, amount, tree, target)) |
end |
history[data.periodic] = nil |
addon:UpdateTopRecords(tree) |
addon:UpdateRecords(tree) |
end |
function module:ResetAll() |
local tree = self.tree:GetSelectedValue() |
local spells = addon.percharDB.profile.spells[tree] |
wipe(spells) |
wipe(addon:GetSpellArray(tree)) |
addon:Message(format(L["Reset all %s records."], treeNames[tree])) |
self:Update() |
addon:UpdateTopRecords(tree) |
addon:UpdateSpells(tree) |
end |
-- stores previous record for the undo feature |
function module:NewRecord(event, tree, spellID, periodic, amount, crit, prevRecord) |
-- do not store previous record if it was 0 |
if prevRecord.amount == 0 then |
return |
end |
history[tree][spellID] = history[tree][spellID] or {} |
local hitType = crit and "crit" or "normal" |
local spell = history[tree][spellID] |
spell[periodic] = spell[periodic] or {} |
-- do not store previous records gained in current fight |
if spell[periodic][hitType] then |
return |
else |
spell[periodic][hitType] = {} |
end |
for k, v in pairs(prevRecord) do |
spell[periodic][hitType][k] = v |
end |
addon:Debug(format("Storing previous record for %s = %d (%s, %s, %s)", addon:GetSpellName(spellID), prevRecord.amount, tree, periodic == 2 and "periodic" or "direct", hitType)) |
end |
addon.RegisterCallback(module, "NewRecord") |
function module:ClearHistory() |
for k, tree in pairs(history) do |
wipe(tree) |
end |
self:Update() |
end |
addon.RegisterCallback(module, "PerCharSettingsLoaded", "ClearHistory") |