/
## Interface: 30000 |
## Title: |cFFFFB366Ara|r - Broker - XP |
## Version: r1 |
## Notes: A data broker plugin that displays an ASCII XP bar. |
## Dependencies: |
## SavedVariablesPerCharacter: |
## LoadManagers: AddonLoader |
## X-LoadOn-Always: delayed |
libs\LibStub.lua |
libs\CallbackHandler-1.0.lua |
libs\LibDataBroker-1.1.lua |
Ara_Broker_XP.lua |
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", 3) |
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 |
-- 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 |
--[[ $Id: CallbackHandler-1.0.lua 60697 2008-02-09 16:51:20Z nevcairiel $ ]] |
local MAJOR, MINOR = "CallbackHandler-1.0", 3 |
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} |
local type = type |
local pcall = pcall |
local pairs = pairs |
local assert = assert |
local concat = table.concat |
local loadstring = loadstring |
local next = next |
local select = select |
local type = type |
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", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(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" |
if type(self)~="table" and type(self)~="string" then |
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) method(arg,...) end |
else |
regfunc = method |
end |
end |
if events[eventname][self] or registry.recurse<1 then |
-- if registry.recurse<1 then |
-- we're overwriting an existing entry, or not currently recursing. just set it. |
events[eventname][self] = regfunc |
-- fire OnUsed callback? |
if registry.OnUsed and first then |
registry.OnUsed(registry, target, eventname) |
end |
else |
-- we're currently processing a callback in this registry, so delay the registration of this new entry! |
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency |
registry.insertQueue = registry.insertQueue or setmetatable({},meta) |
registry.insertQueue[eventname][self] = regfunc |
end |
end |
-- Unregister a callback |
target[UnregisterName] = function(self, eventname) |
if not self or self==target then |
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) |
end |
if type(eventname) ~= "string" then |
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) |
end |
if rawget(events, eventname) and events[eventname][self] then |
events[eventname][self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(events[eventname]) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then |
registry.insertQueue[eventname][self] = nil |
end |
end |
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds |
if UnregisterAllName then |
target[UnregisterAllName] = function(...) |
if select("#",...)<1 then |
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) |
end |
if select("#",...)==1 and ...==target then |
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) |
end |
for i=1,select("#",...) do |
local self = select(i,...) |
if registry.insertQueue then |
for eventname, callbacks in pairs(registry.insertQueue) do |
if callbacks[self] then |
callbacks[self] = nil |
end |
end |
end |
for eventname, callbacks in pairs(events) do |
if callbacks[self] then |
callbacks[self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(callbacks) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
end |
end |
end |
end |
return registry |
end |
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it |
-- try to upgrade old implicit embeds since the system is selfcontained and |
-- relies on closures to work. |
local dd_item = {} -- supprime la génération de garbage liée aux dropdown menus |
local dd_value -- = { buff_name=nil } |
-- fonction a passer dans le param "SecureActionButton.menu" |
function MSB_ShowMenu(button) |
dd_value = button.value -- passage indirecte de valeur à l'initialisation du menu |
MSB_dropdown = global["MSB_DD"] or CreateFrame("Frame", "MSB_DD", UIParent, "UIDropDownMenuTemplate") |
UIDropDownMenu_Initialize(MSB_dropdown, MSB_CreateMenuItems, "MENU") |
UIDropDownMenu_SetAnchor(3-this:GetParent():GetWidth(),0,MSB_dropdown,"TOPRIGHT",this:GetParent():GetName(),"TOPRIGHT") |
UIDropDownMenu_Refresh(this); |
ToggleDropDownMenu(1, nil, MSB_dropdown) --, MSB_top_frame, 0, nb_entry*20+20 ) |
end |
-- scope = menu (pas de hidden self en arg) |
function MSB_CreateMenuItems(level) |
if( not level ) then return end |
local items = MSB:GetClassItems(buffs[dd_value].group) |
for i,v in ipairs(items) do |
local item_quantity = GetItemCount(v) |
if( item_quantity > 0 ) then |
dd_item.text = string.format( "%s [|cff60C060%d|r]", v, item_quantity ) |
dd_item.func = MSB_MenuItems_OnClick |
dd_item.arg1 = v |
dd_item.owner = this:GetParent() |
UIDropDownMenu_AddButton(dd_item) |
end |
end |
end |
-- scope = menu items |
function MSB_MenuItems_OnClick() |
-- DEFAULT_CHAT_FRAME:AddMessage("testing menu: "..(this.arg1 or "nil")) |
end |
local function SetTranslations(...) |
local L = {} |
for i=1, select("#",...), 2 do |
local k,v = select(i,...) |
L[k] = v |
end |
_G.ABGF_TC = L |
end |
local l = GetLocale() |
if l == "enUS" then |
SetTranslations( "Death Knight", "DEATHKNIGHT" ) |
elseif l == "deDE" then |
SetTranslations( "Hexenmeister", "WARLOCK", "Krieger", "WARRIOR", "J\195\164ger", "HUNTER", "Magier", "MAGE", "Priester", "PRIEST", "Druide", "DRUID", "Paladin", "PALADIN", "Schamane", "SHAMAN", "Schurke", "ROGUE", "Todesritter", "DEATHKNIGHT", "Hexenmeisterin", "WARLOCK", "Kriegerin", "WARRIOR", "J\195\164gerin", "HUNTER", "Magierin", "MAGE", "Priesterin", "PRIEST", "Druidin", "DRUID", "Paladin", "PALADIN", "Schamanin", "SHAMAN", "Schurkin", "ROGUE", "Todesritter", "DEATHKNIGHT" ) |
elseif l == "frFR" then |
SetTranslations( "Démoniste", "WARLOCK", "Guerrier", "WARRIOR", "Chasseur", "HUNTER", "Mage", "MAGE", "Prêtre", "PRIEST", "Druide", "DRUID", "Paladin", "PALADIN", "Chaman", "SHAMAN", "Voleur", "ROGUE", "Chevalier de la mort", "DEATHKNIGHT", "Guerrière", "WARRIOR", "Chasseresse", "HUNTER", "Prêtresse", "PRIEST", "Druidesse", "DRUID", "Chamane", "SHAMAN", "Voleuse", "ROGUE" ) |
elseif l == "zhCN" then |
SetTranslations( "æ¯å£«", "WARLOCK", "æ士", "WARRIOR", "ç人", "HUNTER", "æ³å¸", "MAGE", "ç§å¸", "PRIEST", "å¾·é²ä¼", "DRUID", "å£éªå£«", "PALADIN", "è¨æ»¡ç¥å¸", "SHAMAN", "æ½è¡è ", "ROGUE", "æ»äº¡éªå£«", "DEATHKNIGHT", "æ¯å£«", "WARLOCK", "æ士", "WARRIOR", "ç人", "HUNTER", "æ³å¸", "MAGE", "ç§å¸", "PRIEST", "å¾·é²ä¼", "DRUID", "å£éªå£«", "PALADIN", "è¨æ»¡ç¥å¸", "SHAMAN", "æ½è¡è ", "ROGUE", "æ»äº¡éªå£«", "DEATHKNIGHT" ) |
elseif l == "zhTW" then |
SetTranslations( "è¡å£«", "WARLOCK", "æ°å£«", "WARRIOR", "çµäºº", "HUNTER", "æ³å¸«", "MAGE", "ç§å¸«", "PRIEST", "å¾·é¯ä¼", "DRUID", "èé¨å£«", "PALADIN", "è©æ»¿", "SHAMAN", "çè³", "ROGUE", "æ»äº¡é¨å£«", "DEATHKNIGHT", "è¡å£«", "WARLOCK", "æ°å£«", "WARRIOR", "çµäºº", "HUNTER", "æ³å¸«", "MAGE", "ç§å¸«", "PRIEST", "å¾·é¯ä¼", "DRUID", "èé¨å£«", "PALADIN", "è©æ»¿", "SHAMAN", "çè³", "ROGUE", "æ»äº¡é¨å£«", "DEATHKNIGHT" ) |
elseif l == "koKR" then |
SetTranslations( "íë§ë²ì¬", "WARLOCK", "ì ì¬", "WARRIOR", "ì¬ë¥ê¾¼", "HUNTER", "ë§ë²ì¬", "MAGE", "ì¬ì ", "PRIEST", "ë루ì´ë", "DRUID", "ì±ê¸°ì¬", "PALADIN", "주ì ì¬", "SHAMAN", "ëì ", "ROGUE", "죽ìì 기ì¬", "DEATHKNIGHT", "íë§ë²ì¬", "WARLOCK", "ì ì¬", "WARRIOR", "ì¬ë¥ê¾¼", "HUNTER", "ë§ë²ì¬", "MAGE", "ì¬ì ", "PRIEST", "ë루ì´ë", "DRUID", "ì±ê¸°ì¬", "PALADIN", "주ì ì¬", "SHAMAN", "ëì ", "ROGUE", "죽ìì 기ì¬", "DEATHKNIGHT" ) |
elseif l == "esES" then |
SetTranslations( "Brujo", "WARLOCK", "Guerrero", "WARRIOR", "Cazador", "HUNTER", "Mago", "MAGE", "Sacerdote", "PRIEST", "Druida", "DRUID", "Palad\195\173n", "PALADIN", "Cham\195\161n", "SHAMAN", "P\195\173caro", "ROGUE", "Bruja", "WARLOCK", "Guerrera", "WARRIOR", "Cazadora", "HUNTER", "Maga", "MAGE", "Sacerdotisa", "PRIEST", "Druida", "DRUID", "Palad\195\173n", "PALADIN", "Cham\195\161n", "SHAMAN", "P\195\173cara", "ROGUE" ) |
elseif l == "ruRU" then |
SetTranslations( "ЧеÑнокнижник", "WARLOCK", "Ðоин", "WARRIOR", "ÐÑ Ð¾Ñник", "HUNTER", "Ðаг", "MAGE", "ÐÑеÑ", "PRIEST", "ÐÑÑид", "DRUID", "Ðаладин", "PALADIN", "Шаман", "SHAMAN", "Разбойник", "ROGUE", "Ð ÑÑаÑÑ ÑмеÑÑи", "DEATHKNIGHT", "ЧеÑнокнижниÑа", "WARLOCK", "ÐÑ Ð¾ÑниÑа", "HUNTER", "ÐÑиÑа", "PRIEST", "Шаманка", "SHAMAN", "РазбойниÑа", "ROGUE" ) |
end |
-- TODO: scroll large guild members |
local BUTTON_HEIGHT, ICON_SIZE, GAP, TEXT_OFFSET = |
15, 13, 10, 5 |
local f = CreateFrame( "Frame", "AraBrokerGuildFriends", UIParent ) |
local t = CreateFrame"Frame" |
local dontShow, block, horde, config, isGuild, tip, tipShown = true |
local defaultConfig = { showGuildNotes = true, showGuildName = true, sortType = "class", sortDESC = false, fontSize=12 } |
local guildEntries, friendEntries = {}, {} |
local format, strfind, strupper, GetGuildRosterInfo, GetFriendInfo, RAID_CLASS_COLORS, CLASS_BUTTONS = |
format, strfind, strupper, GetGuildRosterInfo, GetFriendInfo, RAID_CLASS_COLORS, CLASS_BUTTONS |
local L = setmetatable( _G.ABGF_TC or {}, { __index = function(t,k) return strupper(k) end } ) |
local orgReloadUI = ReloadUI |
ReloadUI = function(...) |
config.reloading = true |
orgReloadUI(...) |
end |
local tables = {} |
local new = function( ... ) |
local table = tremove(tables) or {} |
for i=1, select("#",...) do table[i] = select(i,...) end |
return table |
end |
local del = function( table ) |
tables[ #tables + 1 ] = wipe(table) |
end |
local friendOnline, friendOffline = gsub(ERR_FRIEND_ONLINE_SS,"\124Hplayer:%%s\124h%[%%s%]\124h",""), gsub(ERR_FRIEND_OFFLINE_S,"%%s","") |
function f:CHAT_MSG_SYSTEM( event, msg ) |
if strfind( msg, friendOnline ) or strfind( msg, friendOffline ) then ShowFriends() end |
end |
function f:FRIENDLIST_UPDATE() |
for k,v in next,friendEntries do del(v) friendEntries[k]=nil end |
local total, online = GetNumFriends(), 0 |
for i = 1, total do |
local name, level, class, zone, connected, status, note = GetFriendInfo(i) |
if connected then |
if online == 0 then |
friendEntries[1] = new( nil, "Name", "", "Lv", "Zone", "Notes" ) |
end |
online = online + 1 |
friendEntries[online+1] = new( name, status ~= "" and format("%s %s", tostring(status), tostring(name)) or name, class, level, zone or "Unknown", note and format("[%s]",note) or "-" ) |
end |
end |
f.FriendsBlock.text = format(config.hideTotalFriends and " %d" or " %d/%d", online, total) |
if not isGuild and block then f:FriendsOnEnter(block) end |
end |
function f:GUILD_ROSTER_UPDATE() |
dontShow = not(isGuild and block) |
f.GuildOnEnter(block) |
end |
function f:PLAYER_GUILD_UPDATE(event, unit) |
if unit and unit ~= "player" then return end |
return IsInGuild() and GuildRoster() |
end |
local hordeZones = "Orgrimmar,Undercity,Thunder Bluff,Silvermoon City,Durotar,Tirisfal Glades,The Barrens,Silverpine Forest,Mulgore,The Sepulcher,Eversong Woods,Ghostlands," |
local allianceZones = "Ironforge,Stormwind City,Darnassus,Azuremyst Isle,Bloodmyst Isle,Darkshore,Dun Morogh,Loch Modan,Wetlands,Elwynn Forest,Redridge Mountains,Westfall,Teldrassil,Duskwood,Darshire,Thelsamar,Deeprun Tram,The Exodar,Auberdine,Menethil Harbor,Theramore Isle" |
local function GetZoneColor(zone) |
if strfind( hordeZones , zone.."," ) then if horde then return 0,1,0 else return 1,0,0 end end |
if strfind( allianceZones, zone.."," ) then if horde then return 1,0,0 else return 0,1,0 end end |
return 1,1,0 |
end |
local function ShowBlockHints() |
local showBelow = select(2, f:GetCenter()) > UIParent:GetHeight()/2 |
tip:SetOwner(f, "ANCHOR_NONE") |
tip:SetPoint(showBelow and "TOP" or "BOTTOM", f, showBelow and "BOTTOM" or "TOP") |
tip:AddLine"Hints [|cffffffffBlock|r]" |
tip:AddLine("|cffff8020Click|r to open panel.", .2,1,.2) |
tip:AddLine("|cffff8020Ctrl+Click|r to toggle hints.", .2,1,.2) |
tip:AddLine("|cffff8020Shift+RightClick|r to toggle total number.", .2,1,.2) |
if isGuild then |
tip:AddLine("|cffff8020Shift+Click|r to toggle guild name.", .2,1,.2) |
tip:AddLine("|cffff8020RightClick|r to toggle note column.", .2,1,.2) |
else |
tip:AddLine("|cffff8020EverythingElse|r to add a friend.", .2,1,.2) |
end |
tip:Show() |
end |
local function ShowHints(btn) |
if not config.hideHints and btn and btn.unit then |
local showBelow = select(2, f:GetCenter()) > UIParent:GetHeight()/2 |
tip:SetOwner(f, "ANCHOR_NONE") |
tip:SetPoint(showBelow and "TOP" or "BOTTOM", f, showBelow and "BOTTOM" or "TOP") |
tip:AddLine"Hints" |
tip:AddLine("|cffff8020Click|r to whisper.", .2,1,.2) |
tip:AddLine("|cffff8020Alt+Click|r to invite.", .2,1,.2) |
tip:AddLine("|cffff8020Shift+Click|r to query guild.", .2, 1, .2) |
if not isGuild or CanEditPublicNote() then tip:AddLine("|cffff8020Ctrl+Click|r to edit note.", .2, 1, .2) end |
if isGuild then |
if CanEditOfficerNote() then tip:AddLine("|cffff8020Ctrl+RightClick|r to edit officer note.", .2, 1, .2) end |
tip:AddLine("|cffff8020RightClick|r to sort closest column.", .2, 1, .2) |
else |
tip:AddLine("|cffff8020MiddleClick|r to remove friend.", .2, 1, .2) |
end |
tip:Show() |
tipShown = true |
end |
end |
local highlight = f:CreateTexture() |
highlight:SetTexture"Interface\\QuestFrame\\UI-QuestTitleHighlight" |
highlight:SetBlendMode"ADD" |
highlight:SetAlpha(0) |
local function Menu_OnEnter(b) |
if b.index then |
highlight:SetAllPoints(b) |
if b.index > 1 then |
highlight:SetAlpha(1) |
ShowHints(b) |
end |
end |
end |
local function Menu_OnLeave(b) |
highlight:ClearAllPoints() |
tip:Hide() |
tipShown = false |
if b.index and b.index > 1 then highlight:SetAlpha(0) end |
if not MouseIsOver(f) then block = nil f:Hide() end |
end |
local function CreateFS( index, parent, justify, anchor ) |
local fs = parent:CreateFontString( nil, "OVERLAY", "SystemFont_Shadow_Med1" ) |
fs:SetJustifyH( justify ) |
fs:SetTextColor( 1, index == 1 and .8 or 1, 0 ) |
if anchor then fs:SetPoint( "LEFT", anchor, "RIGHT", GAP, 0 ) end |
return fs |
end |
local buttons = setmetatable( { }, { __index = function( table, key ) |
local button = CreateFrame( "Button", nil, f ) |
table[key] = button |
button.index = key |
button:SetNormalFontObject(GameFontNormal) |
button:RegisterForClicks"AnyUp" |
button:SetHeight( BUTTON_HEIGHT ) |
button:SetScript( "OnEnter", Menu_OnEnter ) |
button:SetScript( "OnLeave", Menu_OnLeave ) |
button.fontName = CreateFS( key, button, "LEFT" ) |
if key == 0 then |
button.fontName:SetJustifyV"TOP" |
else |
button.icon = button:CreateTexture() |
button.icon:SetWidth( ICON_SIZE ) button.icon:SetHeight( ICON_SIZE ) |
button.icon:SetPoint( "LEFT", button, "LEFT" ) |
button.fontName:SetPoint( "LEFT", button.icon, "RIGHT", TEXT_OFFSET, 0 ) |
button.fontLevel = CreateFS( key, button, "CENTER", button.fontName ) |
button.fontZone = CreateFS( key, button, "CENTER", button.fontLevel ) |
button.fontNotes = CreateFS( key, button, "CENTER", button.fontZone ) |
button.fontRank = CreateFS( key, button, "RIGHT", button.fontNotes ) |
end |
return button |
end } ) |
local function SetButtonData( index, name, class, level, zone, notes, rank, inGroup, isGrouped ) |
local button = buttons[index] |
button:Show() |
button.fontName:SetText( name or "" ) |
if class then |
if index > 1 then |
class = isGuild and class or L[class] |
local color = RAID_CLASS_COLORS[class] |
if color then |
button.fontName:SetTextColor( color.r, color.g, color.b ) |
if inGroup then |
button.icon:SetTexture(isGrouped and "Interface\\Buttons\\UI-CheckBox-Check" or "") |
button.icon:SetTexCoord(.15,.85,.15,.85) |
else |
local classIcon = CLASS_BUTTONS[class] |
button.icon:SetTexture"Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes" |
local offset, left, right, bottom, top = 0.025, unpack( classIcon ) |
button.icon:SetTexCoord( left+offset, right-offset, bottom+offset, top-offset ) |
end |
else |
button.fontName:SetTextColor( 1,1,0 ) |
button.icon:SetTexture"" |
end |
color = GetDifficultyColor(level) |
button.fontLevel:SetTextColor( color.r, color.g, color.b ) |
button.fontZone:SetTextColor( GetZoneColor(zone or "#") ) |
end |
button.fontLevel:SetText( level or "" ) |
button.fontZone:SetText( zone or "" ) |
button.fontNotes:SetText( notes or "" ) |
button.fontRank:SetText( rank or "" ) |
return button, button.fontName:GetStringWidth(), button.fontLevel:GetStringWidth(), button.fontZone:GetStringWidth(), button.fontNotes:GetStringWidth(), button.fontRank:GetStringWidth() |
end |
return button, button.fontName:GetStringWidth() |
end |
local function EditMOTD() |
f:Hide() |
block = nil |
GuildMOTDEditButton:Click() |
end |
local cols, colspace = { "class", "name", "level", "zone", "note", "rank" }, { ICON_SIZE } |
local function OnGuildmateClick( self, button ) |
if not( self and self.unit ) then return end |
if isGuild and button ~= "LeftButton" and not IsModifierKeyDown() then -- sort columns |
local prevDist = GetCursorPosition() / self:GetEffectiveScale() - self:GetLeft() |
local colpos, dist = colspace[1] - prevDist |
for i=2, #colspace do |
colpos = colpos + colspace[i] + (i==2 and TEXT_OFFSET or GAP) |
dist = abs( colpos - colspace[i]*.5 ) |
if dist > prevDist then dist, prevDist = i-1, -1 break else prevDist = dist end |
end |
colpos = prevDist < 0 and dist or #colspace |
colpos = not config.showGuildNotes and colpos == 5 and "rank" or cols[colpos] |
if config.sortType == colpos then |
config.sortDESC = not config.sortDESC |
else |
config.sortType, config.sortDESC = colpos, false |
end |
SortGuildRoster( colpos ) |
elseif button == "MiddleButton" and not isGuild then |
RemoveFriend( self.unit ) -- ADDED (TEST) |
elseif IsAltKeyDown() then |
InviteUnit( self.unit ) |
elseif IsControlKeyDown() then |
if not isGuild then |
FriendsFrame.NotesID = self.index - 1 |
StaticPopup_Show( "SET_FRIENDNOTE", self.unit ) |
elseif button == "LeftButton" and CanEditPublicNote() or button ~= "LeftButton" and CanEditOfficerNote() then |
SetGuildRosterSelection( guildEntries[self.index][8] ) |
StaticPopup_Show( button == "LeftButton" and "SET_GUILDPLAYERNOTE" or "SET_GUILDOFFICERNOTE" ) |
end |
else |
SetItemRef("player:"..self.unit, "|Hplayer:"..self.unit.."|h["..self.unit.."]|h", "LeftButton") |
end |
end |
local function ShowTablet( self, _isGuild, entries ) |
f:Show() |
isGuild = _isGuild |
block = block or self |
local hasEntries = #entries > 0 |
if not hasEntries then |
entries[1] = new( nil, isGuild and ERR_GUILD_PLAYER_NOT_IN_GUILD or "No friends online.", "" ) |
end |
local offsetEntries, button = isGuild and hasEntries and 1 or 0 |
local nameC, levelC, zoneC, notesC, rankC = 0, 0, 0, 0, 0 |
local nameW, levelW, zoneW, notesW, rankW |
local hideNotes = isGuild and not config.showGuildNotes |
local extraHeight, motd = 0 |
if isGuild and hasEntries then |
motd, extraHeight = SetButtonData( 0, "|cffffffffMOTD:|r "..GetGuildRosterMOTD() ) |
if CanEditMOTD() then motd:SetScript( "OnClick", EditMOTD ) end |
motd.fontName:ClearAllPoints() |
end |
local isGrouped = GetNumRaidMembers()>0 and UnitInRaid or GetNumPartyMembers()>0 and UnitInParty or nil |
for i, entry in ipairs(entries) do |
button, nameW, levelW, zoneW, notesW, rankW = SetButtonData( i, entry[2], entry[3], entry[4], entry[5], entry[6], entry[7], isGrouped, isGrouped and entry[1] and isGrouped(entry[1]), isGrouped ) --MOD |
button.unit = entry[1] |
button:SetScript( "OnClick", OnGuildmateClick ) |
if nameW > nameC then nameC = nameW end |
if levelW and levelW>0 then |
if levelW > levelC then levelC = levelW end |
if zoneW > zoneC then zoneC = zoneW end |
if notesW > notesC then notesC = notesW end |
if rankW > rankC then rankC = rankW end |
if hideNotes then button.fontNotes:Hide() else button.fontNotes:Show() end |
button.fontRank:SetPoint( "LEFT", hideNotes and button.fontZone or button.fontNotes, "RIGHT", GAP, 0 ) |
end |
end |
if hideNotes then notesC = -GAP end |
local maxWidth = ICON_SIZE + TEXT_OFFSET + nameC + levelC + zoneC + notesC + rankC + GAP * (isGuild and 4 or 3) |
extraHeight = ceil( extraHeight / maxWidth ) |
if isGuild and hasEntries then |
motd = buttons[0] |
motd:SetWidth( maxWidth ) |
motd:SetPoint( "TOPLEFT", f, "TOPLEFT", GAP, -GAP ) |
motd:SetHeight( (extraHeight+1) * BUTTON_HEIGHT ) |
motd.fontName:SetAllPoints(motd) |
buttons[1]:SetPoint( "TOPLEFT", motd, "BOTTOMLEFT" ) |
elseif rawget( buttons, 0 ) then |
buttons[0]:Hide() |
end |
for i=1, #entries do |
button = buttons[i] |
button:SetWidth( maxWidth ) |
if not isGuild or i>1 or not hasEntries then button:SetPoint( "TOPLEFT", f, "TOPLEFT", GAP, (1-i-extraHeight-offsetEntries) * BUTTON_HEIGHT - GAP ) end |
button.fontName:SetWidth(nameC) |
button.fontLevel:SetWidth(levelC) |
button.fontZone:SetWidth(zoneC) |
button.fontNotes:SetWidth(notesC) |
button.fontRank:SetWidth(rankC) |
end |
colspace[2], colspace[3], colspace[4], colspace[5], colspace[6] = nameC, levelC, zoneC, notesC, rankC |
f:SetWidth( maxWidth + GAP*2 ) |
f:SetHeight( BUTTON_HEIGHT * (#entries+offsetEntries+extraHeight) + GAP*2 ) |
Menu_OnEnter(self) |
local showBelow = select( 2, block:GetCenter() ) > ( UIParent:GetHeight() / 2 ) |
f:ClearAllPoints() |
f:SetPoint( showBelow and "TOP" or "BOTTOM", block, showBelow and "BOTTOM" or "TOP" ) |
for i=#entries+1, #buttons do buttons[i]:Hide() end |
if not (tipShown or config.hideHints) then ShowBlockHints() end |
end |
function f:FriendsOnEnter() |
if InCombatLockdown() then f:Show() return Menu_OnEnter(f) end |
return ShowTablet( self, false, friendEntries ) |
end |
function f:GuildOnEnter() |
if not dontShow and InCombatLockdown() then f:Show() return Menu_OnEnter(f) end |
for k, v in next, guildEntries do del(v) guildEntries[k]=nil end |
for i=1, GetNumGuildMembers(true) do |
local name, rank, _, level, _, zone, note, offnote, connected, status, class = GetGuildRosterInfo(i) |
if connected then |
if #guildEntries == 0 then |
guildEntries[1] = new( nil, "Name", "", "Lv", "Zone", "Notes", "Rank" ) |
end |
local notes = note ~= "" and (offnote ~= "" and |
format( "[%s] - \124cffff9944[%s]\124r", note, offnote ) or |
format( "[%s]", note )) or (offnote ~= "" and |
format( "\124cffff9944[%s]\124r", offnote ) or "-") |
guildEntries[#guildEntries+1] = new( name, status == "" and name or format( "%s %s", status, name ), class, level, zone, notes, rank, i ) |
end |
end |
f.GuildBlock.text = IsInGuild() and format(config.hideGuildTotal and" %s%d"or" %s%d/%d", config.showGuildName and GetGuildInfo"player" and GetGuildInfo"player"..": " or "", #guildEntries-1, GetNumGuildMembers(true) ) or "No Guild" |
if dontShow then dontShow = false else return ShowTablet(self, true, guildEntries) end |
end |
local ldb = LibStub("LibDataBroker-1.1") |
f.GuildBlock = ldb:NewDataObject( "|cFFFFB366Ara|r Guild", { |
type = "data source", |
text = GUILD, |
icon = "Interface\\AddOns\\Ara_Broker_Guild_Friends\\guild.tga", |
OnEnter = f.GuildOnEnter, |
OnLeave = Menu_OnLeave, |
OnClick = function(self, button) |
if button == "LeftButton" then |
if IsShiftKeyDown() and IsInGuild() then |
config.showGuildName = not config.showGuildName |
f.GuildBlock.text = format( " %s%d/%d", config.showGuildName and GetGuildInfo"player"..": " or "", #guildEntries-1, GetNumGuildMembers(true) ) |
elseif IsControlKeyDown() then |
config.hideHints = not config.hideHints |
ShowTablet( block, isGuild, isGuild and guildEntries or friendEntries ) |
else ToggleFriendsFrame(3) end |
elseif button == "RightButton" and IsShiftKeyDown() then |
config.hideGuildTotal = not config.hideGuildTotal |
f:GuildOnEnter() |
elseif not InCombatLockdown() then |
config.showGuildNotes = not config.showGuildNotes |
ShowTablet( block, true, guildEntries ) |
end |
end, |
} ) |
f.FriendsBlock = ldb:NewDataObject( "|cFFFFB366Ara|r Friends", { |
type = "data source", |
text = FRIENDS, |
icon = "Interface\\AddOns\\Ara_Broker_Guild_Friends\\friends.tga", |
OnEnter = f.FriendsOnEnter, |
OnLeave = Menu_OnLeave, |
OnClick = function(self, btn) |
if IsControlKeyDown() then |
config.hideHints = not config.hideHints |
ShowTablet( block, isGuild, isGuild and guildEntries or friendEntries ) |
elseif IsShiftKeyDown() then |
config.hideTotalFriends = not config.hideTotalFriends |
f:FRIENDLIST_UPDATE() |
elseif IsModifierKeyDown() or btn~="LeftButton" then |
StaticPopup_Show"ADD_FRIEND" |
else |
ToggleFriendsFrame(1) |
end |
end, |
} ) |
local orgGuildRoster = GuildRoster |
GuildRoster = function(...) |
t.guildTimer = 0 |
return orgGuildRoster(...) |
end |
local orgShowFriends = ShowFriends |
ShowFriends = function(...) |
t.friendTimer = 0 |
return orgShowFriends(...) |
end |
local function OnUpdate( self, elapsed ) |
t.guildTimer = t.guildTimer + elapsed |
if t.guildTimer > 15 then |
if IsInGuild() then GuildRoster() else t.guildTimer = 0 end |
end |
t.friendTimer = t.friendTimer + elapsed |
if t.friendTimer > 15 then ShowFriends() end |
end |
function f:ADDON_LOADED( event, addon ) |
if addon ~= "Ara_Broker_Guild_Friends" then return else self:UnregisterEvent(event) end |
AraBrokerGuildFriendsDB = AraBrokerGuildFriendsDB or defaultConfig |
config = AraBrokerGuildFriendsDB |
for k, v in next, defaultConfig do if config[k]==nil then config[k]=v end end |
if config.reloading then config.reloading = nil else SortGuildRoster(config.sortType) if config.sortDESC then SortGuildRoster(config.sortType) end end |
tip = _G.GameTooltip |
horde = UnitFactionGroup"player" == "Horde" |
if _G.Skinner then |
_G.Skinner:applySkin(self) |
else |
f:SetBackdrop( { bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", |
tile=true, tileSize=12, edgeSize=12, insets = { left=2, right=2, top=2, bottom=2 } } ) |
f:SetBackdropColor( 0, 0, 0, .75 ) |
end |
f.red, f.green, f.blue, f.alpha = f:GetBackdropColor() |
f:SetFrameStrata"TOOLTIP" |
f:SetClampedToScreen(true) |
f:EnableMouse(true) |
t:SetScript( "OnUpdate", OnUpdate ) |
f:SetScript( "OnEnter", Menu_OnEnter ) |
f:SetScript( "OnLeave", Menu_OnLeave ) |
f:RegisterEvent"GUILD_ROSTER_UPDATE" |
f:RegisterEvent"PLAYER_GUILD_UPDATE" |
f:RegisterEvent"FRIENDLIST_UPDATE" |
f:RegisterEvent"CHAT_MSG_SYSTEM" |
ShowFriends() t.friendTimer = 0 |
if IsInGuild() then GuildRoster() else t.guildTimer = 0 end |
f:GuildOnEnter() |
f.ADDON_LOADED = nil |
end |
f:RegisterEvent"ADDON_LOADED" |
f:SetScript( "OnEvent", function(self, event, ...) return self[event](self, event, ...) end ) |
f:SetScript( "OnShow", function(self) f:SetBackdropColor( InCombatLockdown() and f.red+.25 or f.red, f.green, f.blue, f.alpha) end ) |
## Interface: 30000 |
## Title: |cFFFFB366Ara|r - Broker - Guild & Friends |
## Version: r17 |
## Author: Ara |
## Notes: A Data Broker plugin that provides guildmates & friends informations and interactions. |
## SavedVariables: AraBrokerGuildFriendsDB |
## OptionalDeps: SharedMedia, Skinner |
## X-Credits: Tekkub (picoGuild) |
## LoadManagers: AddonLoader |
## X-LoadOn-Always: delayed |
libs\LibStub.lua |
libs\CallbackHandler-1.0.lua |
libs\LibDataBroker-1.1.lua |
LocalizedClasses.lua |
Ara_Broker_Guild_Friends.lua |
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", 3) |
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 |
-- 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 |
--[[ $Id: CallbackHandler-1.0.lua 60697 2008-02-09 16:51:20Z nevcairiel $ ]] |
local MAJOR, MINOR = "CallbackHandler-1.0", 3 |
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} |
local type = type |
local pcall = pcall |
local pairs = pairs |
local assert = assert |
local concat = table.concat |
local loadstring = loadstring |
local next = next |
local select = select |
local type = type |
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", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(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" |
if type(self)~="table" and type(self)~="string" then |
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string 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. |
--[[ TODO |
- when saving pos do a "SetUserPlaced(nil)" to avoid scaling problems at load time. |
- meta for name/icon/rank should be handled to account for proper rank. |
- dontFade/vanish attribute to zap some fade effect |
]] |
local _, playerClass = UnitClass"player" |
local spells = {} |
local mt = { __index = function(t,k) |
if k == "hasGlyph" then -- on glyph change: if spell.glyphID then spell.hasGlyph = nil end |
for i=1, GetNumGlyphSockets() do |
if select( 3, GetGlyphSocketInfo(i) ) == t.glyphID then t[k] = true return true end |
end |
t[k] = false |
return false |
elseif k == "hasTalent" then -- on talent change: if spell.talentTree then spell.hasTalent = nil end |
local rank = select( 5, GetTalentInfo( t.talentTree, t.talentIndex ) ) |
if rank > 0 then t[k], t.talentRank = true, rank return true end |
t[k] = false |
return false |
elseif k == "name" or k == "icon" or k == "rank" then |
t.name, t.rank, t.icon = GetSpellInfo( t.id ) -- UnitDebuff(unit,t.name,t.rank) |
return t[k] |
end |
end } |
local function AddSpells( ... ) |
local spell |
for i=1, select( "#", ... ) do |
local obj, id = select( i, ... ) |
if type(obj) == "table" then |
obj.id = id |
if not obj.glyphID then obj.hasGlyph = false end |
if not obj.talentTree then obj.hasTalent = false end |
spell = setmetatable( obj, mt ) |
else |
spells[obj] = spell |
end |
end |
end |
if playerClass == "WARLOCK" then |
local CURSE = 1 |
AddSpells( |
--[[ Aftermath ]] { timer = 5, dontFade=true }, 18118, |
--[[ Banish ]] { timer = 20, dontFade=true, glyphID=56242, glyphAdd=5 }, 710, |
{ timer = 30, dontFade=true, glyphID=56242, glyphAdd=5 }, 18647, |
--[[ Corruption ]] { timer = 12 }, 172, |
{ timer = 15 }, 6222, |
{ timer = 18 }, 6223, 7648, 11671, 11672, 25311, 27216, 47812, 47813, |
--[[ Curse of Agony ]] { timer = 24, group=CURSE, glyphID=56241, glyphAdd=4 }, 980, 1014, 6217, 11711, 11712, 11713, 27218, 47863, 47864, |
--[[ Curse of Doom ]] { timer = 60, group=CURSE }, 603, 30910, 47867, |
--[[ Curse of Elem. ]] { timer = 300, dr=true, group=CURSE }, 1490, 11721, 11722, 27228, 47865, |
--[[ Curse of Exhau. ]] { timer = 12, group=CURSE }, 18223, |
--[[ Curse of Reck. ]] { timer = 120, group=CURSE }, 704, 7658, 7659, 11717, 27226, 57595, |
--[[ Curse of Tongue ]] { timer = 30, dr=true, group=CURSE, pvpTimer=12 }, 1714, 11719, -- TOCHANGE |
--[[ Curse of Weak. ]] { timer = 120, group=CURSE }, 702, 1108, 6205, 7646, 11707, 11708, 27224, 30909, 50511, |
--[[ Enslave Demon ]] { timer = 300, dontFade=true }, 1098, 11725, 11726, |
--[[ Fear ]] { timer = 10, dr=true }, 5782, |
{ timer = 15, dr=true }, 6213, |
{ timer = 20, dr=true }, 6215, |
--[[ Haunt ]] { timer = 12 }, 48181, 59161, 59163, 59164, |
--[[ Howl of Terror ]] { timer = 6, dr=true }, 5484, |
{ timer = 8, dr=true }, 17928, |
--[[ Immolate ]] { timer = 15 }, 348, 707, 1094, 2941, 11665, 11667, 11668, 25309, 27215, 47810, 47811, |
--[[ Shadow Embrace ]] { timer = 12, dontFade=true }, 32390, 32391, 32392, 32393, 32394, |
--[[*Shadowburn ]] { timer = 5, dontFade=true }, 29341, |
--[[ Shadowfury ]] { timer = 3, dontFade=true, dr=true }, 30283, 30413, 30414, 47846, 47847, |
--[[ Siphon Life ]] { timer = 30 }, 18265, 18879, 18880, 18881, 27264, 30911, 47861, 47862, |
--[[ Succubus: Seduce]] { timer = 15, dr=true, glyphID=56250, glyphAdd=3, talentTree=2, talentIndex=7, talentMul=0.1 }, 6358, |
--[[ Unstable Affli. ]] { timer = 15 }, 30108, 30404, 30405, 47841, 47843 |
) |
elseif playerClass == "DRUID" then |
AddSpells( |
--[[ Animal Soothe ]] { timer = 15, dontFade=true }, 2908, 8955, 9901, 26995, |
--[[ Bash ]] { timer = 2, dontFade=true, dr=true, talentTree=2, talentIndex=13, talentAdd=.5 }, 5211, |
{ timer = 3, dontFade=true, dr=true, talentTree=2, talentIndex=13, talentAdd=.5 }, 6798, |
{ timer = 4, dontFade=true, dr=true, talentTree=2, talentIndex=13, talentAdd=.5 }, 8983, |
--[[ Cyclone ]] { timer = 6, dontFade=true, dr=true }, 33786, -- Cyclone |
--[[ Demoraliz.Roar ]] { timer = 30 }, 99, 1735, 9490, 9747, 9898, 26998, |
--[[ Faerie Fire ]] { timer = 40 }, 770, 778, 9749, 9907, 26993, 48476, --[[Feral]] 16857, 17390, 17391, 17392, 27011, 48475, |
--[[ Feral Charge ]] { timer = 4, dontFade=true, dr=true }, 16979, -- immobilize effect |
--[[ Hibernate ]] { timer = 20, dr=true }, 2637, |
{ timer = 30, dr=true }, 18657, |
{ timer = 40, dr=true }, 18658, |
--[[ Insect Swarm ]] { timer = 12, talentTree=1, talentIndex=8, talentAdd=2 }, 5570, 24974, 24975, 24976, 24977, 27013, 48468, |
--[[ Lacerate ]] { timer = 15 }, 33745, 48567, 48568, |
--[[ Mangle ]] { timer = 12, glyphID=54813, glyphAdd=6 }, 33878, --[[Bear]] 33986, 33987, 48563, 48564, --[[Cat]] 33876, 33982, 33983, 48565, 48566, |
--[[ Moonfire ]] { timer = 9, talentTree=1, talentIndex=8, talentAdd=3 }, 8921, |
{ timer = 12, talentTree=1, talentIndex=8, talentAdd=3 }, 8924, 8925, 8926, 8927, 8928, 8929, 9833, 9834, 9835, 26987, 26988, 48462, 48463, |
--[[ Pounce (stun) ]] { timer = 4, dontFade=true, dr=true, talentTree=2, talentIndex=13, talentAdd=.5 }, 9005, 9823, 9827, 27006, 49803, -- Pounce (stun part) + talent |
--[[ Pounce (bleed) ]] { timer = 18 }, 9007, 9824, 9826, 27007, 49804, -- Pounce Bleed (dot part) |
--[[ Rake ]] { timer = 9 }, 1822, 1823, 1824, 9904, 27003, |
--[[ Rip ]] { timer = 12, glyphID=54818, glyphAdd=4 }, 1079, 9492, 9493, 9752, 9894, 9896, 27008, 49799, 49800, |
--[[ Roots ]] { timer = 12, dontFade=true, dr=true }, 339, |
{ timer = 15, dontFade=true, dr=true }, 1062, |
{ timer = 18, dontFade=true, dr=true }, 5195, |
{ timer = 21, dontFade=true, dr=true }, 5196, |
{ timer = 24, dontFade=true, dr=true }, 9852, |
{ timer = 27, dontFade=true, dr=true }, 9853, 26989, 53308, |
--[[ Starfire ]] { timer = 3, dontFade=true }, 16922 -- Starfire Stun (EFFECT) |
-- TODO: Natural Perfection, |
) |
elseif playerClass == "DEATHKNIGHT" then |
AddSpells( |
--[[ Chains of Ice ]] { timer = 10, dontFade=true, }, 47805, |
--[[ Death Grip { timer = 3 }, 49560, --]] |
--[[ Ebon Plague ]]-- { timer = 12, talentTree=3, talentIndex=4, talentAdd=3 }, , |
--[[ Hungering Cold ]] { timer = 10, dontFade=true }, 51209, |
--[[ Icy Touch ]] { timer = 12, talentTree=3, talentIndex=4, talentAdd=3 }, 55095, -- Frost Fever (Icy Touch) ]] |
--[[*Mind Freeze ]] { timer = 4, dontFade=true, SPELL_INTERRUPT=true }, 47528, -- Mind Freeze (SPELL_INTERRUPT) |
--[[ Plague Strike ]] { timer = 12, talentTree=3, talentIndex=4, talentAdd=3 }, 55078, 57601, 58840, 58844, -- Blood Plague (Plague Strike dot) |
--[[ Strangulate ]] { timer = 5, dontFade=true, dr=true }, 47476, 49913, 49914, 49915, 49916, |
--[[ Rune of Razorice ]]{ timer = 20, dontFade=true }, 51714, |
--[[ Glyph.Blood Boil ]]{ timer = 5, dontFade=true }, 58617 |
) |
elseif playerClass == "PRIEST" then |
AddSpells( |
--[[ Blackout ]] { timer = 3, dontFade=true, dr=true }, 15269, |
--[[ Devouring Plague ]]{ timer = 24 }, 2944, 19276, 19277, 19278, 19279, 19280, 25467, 48299, 48300, |
--[[ Divine Hymn ]]-- { timer = 20 }, 47951, |
--[[ Holy Fire ]] { timer = 7 }, 14914, 15262, 15263, 15264, 15265, 15266, 15267, 15261, 25384, 48134, 48135, |
--[[ Mind Control ]] { timer = 60, dr=true }, 605, |
--[[ Mind Soothe ]] { timer = 15, dontFade=true }, 453, |
--[[ Psychic Screem ]] { timer = 8, dr=true, glyphID=55676, glyphAdd=1 }, 8122, 8124, 10888, 10890, |
--[[ Shackle Undead ]] { timer = 30, dontFade=true, dr=true }, 9484, |
{ timer = 40, dontFade=true, dr=true }, 9485, |
{ timer = 50, dontFade=true, dr=true }, 10955, |
--[[ Silence ]] { timer = 5, dontFade=true, dr=true }, 15487, |
--[[ SW: Pain ]] { timer = 18 }, 589, 594, 970, 992, 2767, 10892, 10893, 10894, 25367, 25368, 48124, 48125, |
--[[ Vampiric Embrace ]]{ timer = 60, dontFade=true }, 15286, |
--[[ Vampiric Touch ]] { timer = 15 }, 34914, 34916, 34917, 48159, 48160 |
) |
elseif playerClass == "MAGE" then |
AddSpells( |
--[[ Blast Wave ]] { timer = 6, dontFade=true }, 11113, 13018, 13019, 13020, 13021, 27133, 33933, 42944, 42945, |
--[[ Cone of Cold ]] { timer = 8, dontFade=true }, 120, 8492, 10159, 10160, 10161, 27087, 42930, 42931, |
--[[ Counterspell ]] { timer = 8, dontFade=true, dr=true, SPELL_INTERRUPT=true }, 2139, |
--[[ Deep Freeze ]] { timer = 5, dontFade=true, dr=true }, 44572, |
--[[ Dragon's Breath ]] { timer = 5, dontFade=true }, 31661, 33041, 33042, 33042, 42949, 42950, |
--[[ Fingers of Frost ]]{ timer = 15, dontFade=true }, 44544, |
--[[ Fireball DoT ]] { timer = 4, glyphID=56368, glyphAdd=-30 }, 133, |
{ timer = 6, glyphID=56368, glyphAdd=-30 }, 143, 145, |
{ timer = 8, glyphID=56368, glyphAdd=-30 }, 3140, 8400, 8401, 8402, 10148, 10149, 10150, 10151, 25306, 27070, 38692, 42832, 42833, |
--[[ Flamestrike ]] { timer = 8 }, 2120, 2121, 8422, 8423, 10215, 10216, 27086, 42925, 42926, |
--[[ Frost Armor ]] { timer = 5, dontFade=true, group=1 }, 6136, |
--[[ Frost Nova ]] { timer = 8, dontFade=true, dr=true }, 122, 865, 6131, 10230, 27088, 42917, |
--[[ Frostbite ]] { timer = 5, dontFade=true, dr=true }, 12494, -- DR ? |
--[[ Frostbolt effect]] { timer = 5, dontFade=true, group=1, talentTree=3, talentIndex=7, talentAdd=1 }, 116, |
{ timer = 6, dontFade=true, group=1, talentTree=3, talentIndex=7, talentAdd=1 }, 205, 837, |
{ timer = 7, dontFade=true, group=1, talentTree=3, talentIndex=7, talentAdd=1 }, 7322, 8406, |
{ timer = 8, dontFade=true, group=1, talentTree=3, talentIndex=7, talentAdd=1 }, 8407, 8408, |
{ timer = 9, dontFade=true, group=1, talentTree=3, talentIndex=7, talentAdd=1 }, 10179, 10180, 10181, 25304, 27071, 27072, 38697, 42841, 42842, |
--[[ Impact ]] { timer = 2, dontFade=true }, 12355, |
--[[ Imp. CS ]] { timer = 0, dontFade=true, talentTree=1, talentIndex=12, talentAdd=2 }, 18469, |
--[[ Imp. Scorch ]] { timer = 30 }, 22959, |
--[[ Living Bomb ]] { timer = 12 }, 44457, 55359, 55360, |
--[[ Polymorph ]] { timer = 20, dr=true }, 118, |
{ timer = 30, dr=true }, 12824, |
{ timer = 40, dr=true }, 12825, |
{ timer = 50, dr=true }, 12826, 28271, 28272, 61025, 61305, |
--[[ Slow ]] { timer = 15, dr=true }, 31589, -- pvp timer != pve timer ? |
--[[ Winter's Chill ]] { timer = 15, dontFade=true }, 12579 |
) |
else return end |
-- CONST |
local refreshRate = 0.100 -- sec |
local BORDER_GAP = 6 |
local offsetTimer = 28 -- espacement horizontal entre timers |
local offsetHeader = 18 -- espacement vertical entre le nom de la cible et les dots |
local offsetTarget = offsetHeader + 50 -- espacement vertical des targets |
local backdrop = { |
bgFile = "Interface/Buttons/WHITE8X8", |
edgeFile = "Interface/Tooltips/UI-Tooltip-Border", |
tile = true, tileSize = 16, edgeSize = 12, |
insets = { left = 2, right = 2, top = 2, bottom = 2 } } |
-- DATA |
local frame, OnUpdate = CreateFrame( "Frame", "Ara_Dotimer_Frame" ) |
local targets = {} |
local nbTargets = 0 |
local playerGUID |
local targetGUID, targetLevel, targeted, prevTargetGUID, prevTargetLevel |
local focusGUID, focusLevel, focused |
local config |
local defaultConfig = { timersGrowRight=true, scale=1, x=330, y=400 } |
local border = { |
bgFile = "Interface/Buttons/WHITE8X8", |
edgeFile = "Interface/Tooltips/UI-Tooltip-Border", |
tile = true, tileSize = 16, edgeSize = 12, |
insets = { left = 2, right = 2, top = 2, bottom = 2 } } |
local targetBorder = CreateFrame("Frame", "Ara_Dotimer_TargetBorder", frame) |
targetBorder:SetFrameStrata"BACKGROUND" |
targetBorder:Show() |
targetBorder:SetAlpha(0) |
targetBorder:SetBackdrop( border ) |
targetBorder:SetBackdropBorderColor( .2, .2, 1, .15 ) |
targetBorder:SetBackdropColor( .2,.2,1,.15 ) |
local focusBorder = CreateFrame("Frame", "Ara_Dotimer_FocusBorder", frame) |
focusBorder:SetFrameStrata"BACKGROUND" |
focusBorder:Show() |
focusBorder:SetAlpha(0) |
focusBorder:SetBackdrop( border ) |
focusBorder:SetBackdropBorderColor( 1, .2, .2, .2 ) |
focusBorder:SetBackdropColor( 1,.2,.2,.2 ) |
-- LOCAL GLOBALS |
local bit_or, bit_and, tremove, next, floor, format, wipe, UnitDebuff, GetTime, UnitGUID, GameFontNormal, GameFontHighlightSmall, GameFontHighlight, NumberFontNormalLarge = |
bit.bor, bit.band, tremove, next, floor, format, wipe, UnitDebuff, GetTime, UnitGUID, GameFontNormal, GameFontHighlightSmall, GameFontHighlight, NumberFontNormalLarge |
-- POOLS |
local tables = {} -- table reuse |
local function AcquireTable( ... ) |
local table = tremove(tables) or {} |
for i = 1, select( "#", ... ), 2 do |
local key, value = select( i, ... ) |
table[key] = value |
end |
return table |
end |
local function ReleaseTable(table) |
tables[#tables+1] = wipe(table) |
end |
local fontstrings = {} -- fontstring reuse |
local function AcquireFS(doseCounter) |
local fs = tremove(fontstrings) |
if not fs then |
fs = frame:CreateFontString( nil, "BACKGROUND" ) |
fs:SetJustifyH("CENTER") |
fs.hidden = true |
end |
fs:SetFontObject( doseCounter and NumberFontNormalLarge or GameFontHighlightSmall ) |
return fs |
end |
local function ReleaseFS(fs) |
fs:ClearAllPoints() |
fs:Hide() |
fs.hidden = true |
fontstrings[#fontstrings+1] = fs |
end |
local headers = {} |
local function AcquireHeader() |
local header = tremove(headers) |
if not header then |
header = frame:CreateFontString( nil, "BACKGROUND" ) |
header:SetJustifyH("LEFT") |
header:SetFontObject(GameFontNormal) |
header.hidden = true |
end |
return header |
end |
local function ReleaseHeader(header) |
header:Hide() |
header.hidden = true |
headers[#headers+1] = header |
end |
local function ClearTargetHL() |
targeted.header:SetFontObject(GameFontNormal) |
targetBorder:SetAlpha(0) |
targeted.targeted = nil |
targeted = nil |
end |
local function SetTargetHL(target) |
if targeted then ClearTargetHL() end |
targeted = target |
if not target then return end |
target.header:SetFontObject(GameFontHighlight) |
targetBorder:SetAlpha(1) |
targetBorder:SetWidth( target.header:GetWidth()+BORDER_GAP*2 ) |
targetBorder:SetHeight( target.header:GetHeight() + offsetHeader + 16 + BORDER_GAP*2 ) |
targetBorder:SetPoint( "TOPLEFT", frame, config.timersGrowRight and "TOPLEFT" or "TOPRIGHT", config.timersGrowRight and -BORDER_GAP or -targetBorder:GetWidth()+BORDER_GAP, -offsetTarget*target.pos +BORDER_GAP ) |
target.targeted = true |
end |
local function ClearFocusHL() |
focusBorder:SetAlpha(0) |
focused.focused = nil |
focused = nil |
end |
local function SetFocusHL(focus) |
if focused then ClearFocusHL() end |
focused = focus |
if not focus then return end |
focusBorder:SetAlpha(1) |
focusBorder:SetWidth( focus.header:GetWidth()+BORDER_GAP*2) |
focusBorder:SetHeight( focus.header:GetHeight() + offsetHeader + 16 + BORDER_GAP*2 ) |
focusBorder:SetPoint( "TOPLEFT", frame, config.timersGrowRight and "TOPLEFT" or "TOPRIGHT", config.timersGrowRight and -BORDER_GAP or -focusBorder:GetWidth()+BORDER_GAP, -offsetTarget*focus.pos +BORDER_GAP ) |
-- focusBorder:SetWidth( focus.header:GetWidth()+BORDER_GAP*2) |
-- focusBorder:SetHeight( focus.header:GetHeight() + offsetHeader + 16 + BORDER_GAP*2 ) |
focus.focused = true |
end |
local function CleanUITarget( target, timers ) |
local removedPos, guid = target.pos, target.guid |
ReleaseHeader( target.header ) |
ReleaseTable( timers ) |
if target.targeted then ClearTargetHL() end |
if target.focused then ClearFocusHL() end |
ReleaseTable( target ) |
targets[guid] = nil |
nbTargets = nbTargets - 1 |
-- remove OnUpdate if no target |
if nbTargets == 0 then |
return frame:SetScript("OnUpdate",nil) |
end |
-- reorder ui targets |
for k,v in next, targets do |
if v.pos > removedPos then |
v.pos = v.pos - 1 |
end |
end |
end |
local function RemoveTimer( target, spell, keepTarget ) |
local timers = target.timers |
local timer = timers[spell] |
if timer then |
local removedPos = timer.pos |
ReleaseFS( timer.fs ) |
if timer.dose then ReleaseFS( timer.dose ) end |
ReleaseTable( timer ) |
timers[spell] = nil |
if target.nbTimers == 1 and not keepTarget then |
return CleanUITarget( target, timers ) |
end |
--if removedPos < nbTimers then -- reorder ui timers |
for k,v in next, timers do |
if v.pos > removedPos then |
v.pos = v.pos - 1 |
end |
end |
target.nbTimers = target.nbTimers - 1 |
end |
end |
local function AddTimer( guid, name, spell, timestamp, offset ) |
local target = targets[guid] |
if not target then |
target = AcquireTable( "guid", guid, "name", name, "timers", AcquireTable(), |
"isPlayer", strsub( guid, 3, 5 ) == "000", "pos", nbTargets, "nbTimers", 0, "header", AcquireHeader() ) |
target.level = targetGUID==guid and targetLevel or prevTargetGUID==guid and prevTargetLevel |
or focusGUID==guid and focusLevel or UnitGUID"mouseover"==guid and UnitLevel"mouseover" or 0 |
if target.level==0 then |
target.level="??" |
elseif target.level==-1 then |
target.level = "BOSS" |
end |
targets[guid] = target |
nbTargets = nbTargets + 1 |
if nbTargets == 1 then |
frame:SetScript("OnUpdate",OnUpdate) |
end |
elseif spell.group then -- curses & co |
for sp in next, target.timers do |
if sp.group == spell.group then |
RemoveTimer( target, sp, true ) |
break |
end |
end |
end |
local timers = target.timers |
local timer = timers[spell] |
if not timer then -- NEW TIMER |
timer = AcquireTable( "name", spell.name, "fs", AcquireFS() ) |
timers[spell] = timer |
target.nbTimers = target.nbTimers + 1 |
end |
local spellTimer, duration = offset or spell.timer |
if not offset then |
if spell.dr and target.isPlayer then |
local unit = targetGUID==guid and "target" or focusGUID==guid and "focus" or UnitGUID"mouseover"==guid and "mouseover" |
if unit then |
duration = select(6, UnitDebuff( unit, spell.name, spell.rank, "PLAYER,PET" ) ) |
if duration then spellTimer = duration end |
end |
end |
if not duration then |
if spell.hasGlyph then spellTimer = spellTimer + spell.glyphAdd end |
if spell.hasTalent then |
if spell.talentAdd then |
spellTimer = spellTimer + spell.talentRank * spell.talentAdd |
else spellTimer = spellTimer * ( 1 + spell.talentRank * spell.talentMul ) end |
end |
end |
end |
timer.startTime, timer.expireTime = timestamp, timestamp + spellTimer |
timer.fs:SetAlpha(1) -- ADDED fade out |
timer.fadeout = nil -- ADDED fade out |
-- sort timers ( expire: 300 -> 0 ) |
local insertedPos = timer.pos or target.nbTimers |
timer.pos = insertedPos |
for k, v in next, timers do |
if timer.expireTime > v.expireTime then |
if insertedPos > v.pos then |
timer.pos = timer.pos - 1 |
v.pos = v.pos + 1 |
end |
elseif timer.expireTime < v.expireTime then |
if insertedPos < v.pos then |
timer.pos = timer.pos + 1 |
v.pos = v.pos - 1 |
end |
end |
end |
end |
local function RemoveTarget( guid ) |
local target = targets[guid] |
if target then |
local timers = target.timers |
for k,v in next, timers do |
ReleaseFS( v.fs ) |
if v.dose then ReleaseFS( v.dose ) end |
ReleaseTable( v ) |
timers[k] = nil |
end |
return CleanUITarget( target, timers ) |
end |
end |
OnUpdate = function( self, elapsed ) |
self.timer = self.timer - elapsed |
if self.timer >= 0 then return else self.timer = refreshRate end |
local currentTime = GetTime() |
-- process each target |
for guid, target in next, targets do |
local timers = target.timers |
local targetPosChanged = target.pos ~= target.prevPos |
-- repositionne le nom de la target si necessaire |
if targetPosChanged then |
target.prevPos = target.pos |
local header = target.header |
-- affiche si caché |
if header.hidden then |
header:Show() |
header:SetFormattedText( "[%s] %s", target.level ~= 0 and tostring(target.level) or "??", target.name or "unknown" ) |
if targetGUID==guid then SetTargetHL(target) end |
if focusGUID==guid then SetFocusHL(target) end |
header.hidden = false |
end |
if config.timersGrowRight then |
header:SetPoint( "TOPLEFT", frame, "TOPLEFT", 0, -offsetTarget * target.pos ) |
else |
header:SetPoint( "TOPLEFT", frame, "TOPRIGHT", -header:GetStringWidth(), -offsetTarget * target.pos ) |
end |
if target.targeted then -- repositionne le target highlight |
targetBorder:SetPoint( "TOPLEFT", frame, config.timersGrowRight and "TOPLEFT" or "TOPRIGHT", config.timersGrowRight and -BORDER_GAP or -targetBorder:GetWidth()+BORDER_GAP, -offsetTarget*target.pos +BORDER_GAP ) |
end |
if target.focused then -- repositionne le focus highlight |
focusBorder:SetPoint( "TOPLEFT", frame, config.timersGrowRight and "TOPLEFT" or "TOPRIGHT", config.timersGrowRight and -BORDER_GAP or -focusBorder:GetWidth()+BORDER_GAP, -offsetTarget*target.pos +BORDER_GAP ) |
end |
end |
-- process each timer |
for spell, timer in next, timers do |
local timeLeft = timer.expireTime - currentTime |
if timeLeft > -15 then |
if timeLeft >= 5 then -- less granular |
timeLeft = floor( timeLeft ) |
if timer.prevTime ~= timeLeft then |
timer.prevTime = timeLeft |
local ftime = timeLeft < 60 and format( "%is", timeLeft) or format( "%i:%.2i", floor( timeLeft / 60 ), floor( timeLeft ) % 60 ) |
timer.fs:SetFormattedText( "\124T%s:25:25:0:0\124t\n\124cff%s%s\124r", spell.icon, timeLeft < 10 and "ffff20" or "20ff20", ftime ) |
end |
elseif timeLeft >= 0 then |
timer.fs:SetFormattedText( "\124T%s:25:25:0:0\124t\n\124cffff2020%.1f\124r", spell.icon, timeLeft ) |
elseif not timer.fadeout then |
if spell.dontFade then return RemoveTimer( target, spell ) end |
timer.fadeout = true |
if timer.dose then ReleaseFS( timer.dose ) timer.dose = nil end |
timer.fs:SetAlpha(.6) |
-- timer.fs:SetFormattedText( "\124T%s:25:25:0:0\124t\n ", spell.icon ) -- REM, see below |
end |
if timer.fadeout then -- ADDED timer for 0 --> -15 sec |
timer.fs:SetFormattedText( "\124T%s:25:25:0:0\124t\n\124cff800080%i\124r", spell.icon, timeLeft ) |
end |
-- repositionne le timer si necessaire, l'affiche si caché. |
if targetPosChanged or timer.prevPos ~= timer.pos then |
local fs = timer.fs |
if config.timersGrowRight then |
fs:SetPoint( "TOPLEFT", frame, "TOPLEFT", offsetTimer * (timer.pos-1), -offsetTarget * target.pos - offsetHeader ) |
else |
fs:SetPoint( "TOPLEFT", frame, "TOPRIGHT", -offsetTimer * (timer.pos), -offsetTarget * target.pos - offsetHeader ) |
end |
timer.prevPos = timer.pos |
if fs.hidden then |
fs:Show() |
fs.hidden = false |
end |
end |
else -- fixes some issues |
RemoveTimer( target, spell ) |
end |
end |
end |
end |
local combatEvents = { |
["SPELL_AURA_APPLIED"] = true, |
["SPELL_AURA_REFRESH"] = true, |
["SPELL_AURA_REMOVED"] = true, |
["SPELL_INTERRUPT"] = true, |
["SPELL_AURA_APPLIED_DOSE"] = true, |
["SPELL_AURA_REMOVED_DOSE"] = true, |
["UNIT_DIED"] = true, |
["UNIT_DESTROYED"] = true, |
} |
local function OnCombatEvent( self, event, timestamp, combatEvent, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ... ) |
if not combatEvents[combatEvent] then return end |
if srcGUID == playerGUID or srcFlags == 0x1111 --[[ pet ]] then |
-- target must be unfriendly |
if bit_and( dstFlags, COMBATLOG_OBJECT_REACTION_HOSTILE + COMBATLOG_OBJECT_REACTION_NEUTRAL ) == 0 then return end |
local spell = spells[...] |
if not spell then return end |
if combatEvent == "SPELL_AURA_APPLIED" or combatEvent == "SPELL_AURA_REFRESH" then |
if not spell.set then spell.set, spell.name, spell.rank, spell.icon = true, GetSpellInfo((...)) end |
return AddTimer( dstGUID, dstName, spell, GetTime() ) --timestamp ) |
elseif combatEvent == "SPELL_AURA_REMOVED" then |
local target = targets[dstGUID] |
if not target then return end |
local timer = target.timers[ spells[...] ] |
if not timer then return end |
if spell.dontFade then RemoveTimer(target, spells[...]) else timer.expireTime = GetTime() - 0.001 end |
elseif combatEvent == "SPELL_INTERRUPT" then -- TODO: merge with SPELL_AURA_APPLIED ? |
AddTimer( dstGUID, dstName, spell, GetTime() ) |
else--if combatEvent == "SPELL_AURA_APPLIED_DOSE" or combatEvent == "SPELL_AURA_REMOVED_DOSE" then |
local nbDose = select(5,...) |
local target = targets[dstGUID] |
if not target then return end |
local timer = target.timers[spell] |
if not timer then return end |
AddTimer( dstGUID, dstName, spell, GetTime() ) |
if not timer.dose then |
timer.dose = AcquireFS(true) |
timer.dose:SetPoint( "TOP", timer.fs, "TOP", 0, -2 ) |
timer.dose:SetTextColor( 1, 1, 1, .75 ) |
timer.dose:Show() |
end |
return timer.dose:SetText( nbDose ) |
end |
elseif combatEvent == "UNIT_DIED" or combatEvent == "UNIT_DESTROYED" then -- ou "else" tout court ? |
return RemoveTarget( dstGUID ) |
end |
end |
local function OnEvent( self, event, arg1, arg2, ... ) |
if event == "PLAYER_TARGET_CHANGED" then |
prevTargetGUID, prevTargetLevel = targetGUID, targetLevel |
targetGUID, targetLevel = UnitGUID"target", UnitLevel"target" |
if nbTargets == 0 then return end |
local target = targets[targetGUID] |
if targeted and targeted ~= target or target then return SetTargetHL(target) end |
elseif event == "PLAYER_FOCUS_CHANGED" then |
focusGUID = UnitGUID"focus" |
focusLevel = UnitLevel"focus" |
if nbTargets == 0 then return end |
local focus = targets[focusGUID] |
if focused and focused ~= focus or focus then return SetFocusHL(focus) end |
elseif event == "PLAYER_REGEN_ENABLED" then |
-- fin du combat, on vire les mobs qui ont reset |
for guid in next, targets do |
if strsub(guid,5,5) == "3" then -- mob |
RemoveTarget( guid ) |
end |
end |
elseif event == "PLAYER_DEAD" then |
-- si joueur mort, on retire toutes les targets |
for guid in next, targets do |
RemoveTarget( guid ) |
end |
elseif event == "PLAYER_TALENT_UPDATE" then |
for id, spell in next, spells do |
if spell.talentTree then spell.hasTalent = nil end |
end |
elseif event == "ADDON_LOADED" then |
if arg1 ~= "Ara_Dotimer" then return else self:UnregisterEvent(event) end |
AraDotimerDB = AraDotimerDB or defaultConfig |
config = AraDotimerDB |
for k, v in next, defaultConfig do |
if config[k] == nil then config[k] = v end |
end |
frame:SetScale( config.scale ) |
frame:SetPoint( "CENTER", UIParent, "BOTTOMLEFT", config.x, config.y ) |
elseif event == "PLAYER_LOGIN" then |
playerGUID = UnitGUID"player" |
else -- GLYPH |
for id, spell in next, spells do |
if spell.glyphID then spell.hasGlyph = nil end |
end |
end |
end |
frame:SetScript("OnEvent",OnEvent) |
frame:RegisterEvent"PLAYER_TARGET_CHANGED" |
frame:RegisterEvent"PLAYER_FOCUS_CHANGED" |
frame:RegisterEvent"PLAYER_REGEN_ENABLED" |
frame:RegisterEvent"PLAYER_DEAD" |
frame:RegisterEvent"PLAYER_TALENT_UPDATE" |
frame:RegisterEvent"GLYPH_ADDED" frame:RegisterEvent"GLYPH_REMOVED" frame:RegisterEvent"GLYPH_UPDATED" |
frame:RegisterEvent"ADDON_LOADED" |
frame.timer = refreshRate |
frame:SetWidth(140) |
frame:SetHeight(60) |
frame:SetMovable(true) |
local extraFrame = CreateFrame"Frame" |
extraFrame:SetScript("OnEvent",OnCombatEvent) |
extraFrame:RegisterEvent"COMBAT_LOG_EVENT_UNFILTERED" |
local function ToggleAnchor() |
local show = not frame:IsMouseEnabled() |
frame:SetBackdrop( show and backdrop or nil ) |
frame:EnableMouse( show ) |
if show then frame:SetBackdropColor( .3, 0, .3, .5 ) end |
end |
frame:SetClampedToScreen(true) -- ADDED |
frame:EnableMouseWheel(true) -- ADDED |
frame:SetScript("OnMouseWheel", function(self, delta) |
local scale = self:GetScale() + delta * .05 |
config.scale = scale |
self:SetScale(scale) |
end) |
SLASH_ARADOTIMER1, SLASH_ARADOTIMER2 = "/dt", "/dot" |
SlashCmdList["ARADOTIMER"] = function( param, self ) |
param = strlower(param) |
if param == "grow" then |
config.timersGrowRight = not config.timersGrowRight |
return print( "[|cffffb366Ara|r|cffffff00Dotimer|r] Timers grow direction set to |cff40ff40" .. (config.timersGrowRight and "RIGHT" or "LEFT") .. "|r." ) |
end |
ToggleAnchor() |
end |
frame:SetScript( "OnMouseDown", function(self,arg) if arg~="LeftButton" then self:StopMovingOrSizing();ToggleAnchor() else self:StartMoving() end end ) |
frame:SetScript( "OnMouseUp", function(self,arg) |
self:StopMovingOrSizing() |
config.x, config.y = self:GetCenter() |
end ) |
return IsLoggedIn() and OnEvent(frame, "PLAYER_LOGIN") or frame:RegisterEvent"PLAYER_LOGIN" |
## Interface: 30100 |
## Title: |cFFFFB366Ara|r - Dotimer |
## Version: r10 |
## Author: Ara |
## Notes: Provides a display for multiple target debuff tracking. Type "/dot" or "/dt" to move/scale the display (right-click the window after you're done positioning). |
## Dependencies: |
## SavedVariables: AraDotimerDB |
## SavedVariablesPerCharacter: |
## LoadManagers: AddonLoader |
## X-LoadOn-Always: delayed |
Ara_Dotimer.lua |
local HOUR, DAY = 3600, 82800 -- 23h |
local watchedTimers = { |
-- [28027] = { name= "Prismatic Sphere", skill = "Enchanting", }, |
-- [28028] = { name= "Void Sphere", skill = "Enchanting", }, |
[47280]= { skill= L"Jewelcrafting", name= "Brilliant Glass", CD= 20*HOUR, icon= "Interface\\Icons\\INV_Misc_Gem_Diamond_03", }, |
-- []= { skill= L"Jewelcrafting", name= "Icy Prism", CD= 20*HOUR, icon= "Interface\\Icons\\INV_Misc_Gem_Diamond_03", }, |
[26751]= { skill= L"Tailoring", name= "Primal Mooncloth", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_MoonRag_Primal", }, |
[36686]= { skill= L"Tailoring", name= "Shadowcloth", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_Felcloth_Ebon", }, |
[31373]= { skill= L"Tailoring", name= "Spellcloth", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_Spellfire", }, |
[56002]= { skill= L"Tailoring", name= "Ebonweave", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_Ebonweave", }, |
[56003]= { skill= L"Tailoring", name= "Spellweave", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_Spellweave", }, |
[56001]= { skill= L"Tailoring", name= "Moonshroud", CD= 4*DAY, icon= "Interface\\Icons\\INV_Fabric_Moonshroud", }, |
[61288]= { skill= L"Inscription", name= "Minor Glyph R.", CD= 20*HOUR, icon= "Interface\\Icons\\INV_Inscription_Tradeskill01", }, |
[61177]= { skill= L"Inscription", name= "Northrend Glyph R.", CD= 20*HOUR, icon= "Interface\\Icons\\INV_Inscription_Tradeskill01", }, |
[55208]= { skill= L"Mining", name= "Smelt Titansteel", CD= 20*HOUR, icon= "Interface\\Icons\\INV_Ingot_Titansteel_blue", }, |
[60893]= { skill= L"Alchemy", name= "Alchemy Research", CD= 7*DAY, icon= "Interface\\Icons\\INV_Potion_106", }, |
} |
local watchedIndex, watchedCast, watchedTimer |
local orgDoTradeSkill = _G.DoTradeSkill |
function DoTradeSkill(index, ...) |
watchedTimer = watchedTimers[ tonumber( strmatch( GetTradeSkillRecipeLink(index), "enchant:(%d+)" ) ) ] |
if watchedTimer then |
watchedIndex, watchedCast = index, GetTradeSkillInfo(index) |
f:RegisterEvent"UNIT_SPELLCAST_SUCCEEDED" |
f:RegisterEvent"UNIT_SPELLCAST_STOP" |
end |
return orgDoTradeSkill(index, ...) |
end |
-- alternative A : |
function f:SPELL_UPDATE_COOLDOWN() |
self:UnregisterEvent"SPELL_UPDATE_COOLDOWN" |
char[watchedTimer.spellID] = time() + ( GetTradeSkillCooldown(watchedIndex) --[[or watchedTimer.CD]] ) |
if block then Block_OnEnter(block) end |
end |
function f:UNIT_SPELLCAST_SUCCEEDED(event, unit, spell) |
if unit ~= "player" or spell ~= watchedCast then return end |
self:UnregisterEvent"UNIT_SPELLCAST_SUCCEEDED" |
self:UnregisterEvent"UNIT_SPELLCAST_STOP" |
if event ~= "UNIT_SPELLCAST_SUCCEEDED" then return end |
self:RegisterEvent"SPELL_UPDATE_COOLDOWN" -- ou bien virer cet event et ajouter directe "watchedTimer.CD" |
end |
f.UNIT_SPELLCAST_STOP = f.UNIT_SPELLCAST_SUCCEEDED |
-- alternative B : |
function f:UNIT_SPELLCAST_SUCCEEDED(event, unit, spell) |
if unit ~= "player" or spell ~= watchedCast then return end |
self:UnregisterEvent"UNIT_SPELLCAST_SUCCEEDED" |
self:UnregisterEvent"UNIT_SPELLCAST_STOP" |
if event ~= "UNIT_SPELLCAST_SUCCEEDED" then return end |
char[watchedTimer.spellID] = time() + watchedTimer.CD |
if block then Block_OnEnter(block) end |
end |
f.UNIT_SPELLCAST_STOP = f.UNIT_SPELLCAST_SUCCEEDED |