WoWInterface SVN RaidToolkit

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /trunk/RaidToolkit/libs
    from Rev 7 to Rev 9
    Reverse comparison

Rev 7 → Rev 9

LibGroupTalents-1.0/LibGroupTalents-1.0.toc New file
0,0 → 1,17
## Interface: 30300
## LoadOnDemand: 1
## Title: Lib: GroupTalents-1.0
## Notes: Library to help with querying unit talents.
## Author: Zek
## Version: $Rev: 51 $
## OptionalDeps: Ace3, LibTalentQuery-1.0
## X-Category: Library
## X-ReleaseDate: $Date$
## X-Website: http://wowace.com/wiki/LibGroupTalents-1.0
## X-License: MIT
## X-Curse-Packaged-Version: 3.3 Release 3
## X-Curse-Project-Name: LibGroupTalents-1.0
## X-Curse-Project-ID: libgrouptalents-1-0
## X-Curse-Repository-ID: wow/libgrouptalents-1-0/mainline
 
lib.xml
LibGroupTalents-1.0/Changelog-LibGroupTalents-1.0-3.3 Release 3.txt New file
0,0 → 1,13
------------------------------------------------------------------------
r56 | greltok | 2010-09-30 14:49:58 -0500 (Thu, 30 Sep 2010) | 1 line
Changed paths:
A /tags/3.3 Release 3 (from /trunk:55)
 
Tagging as 3.3 Release 3
------------------------------------------------------------------------
r55 | greltok | 2010-09-23 23:25:05 -0500 (Thu, 23 Sep 2010) | 1 line
Changed paths:
M /trunk/LibGroupTalents-1.0.lua
 
Ticket 6 - Use full name where needed.
------------------------------------------------------------------------
LibGroupTalents-1.0/lib.xml New file
0,0 → 1,3
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd">
<Script file="LibGroupTalents-1.0.lua"/>
</Ui>
LibGroupTalents-1.0/LibGroupTalents-1.0.lua New file
0,0 → 1,1727
--[[
Name: LibGroupTalents-1.0
Revision: $Rev: 55 $
Author: Zek
Documentation: http://wowace.com/wiki/LibGroupTalents-1.0
SVN: svn://svn.wowace.com/wow/libgrouptalents-1-0/mainline/trunk
Description: Talent Library abstraction layer to provide easy interface to the lower level system
Dependancies: LibStub, CallbackHandler-1.0, LibTalentQuery-1.0
License: GPL v3
 
Purpose:
LibGroupTalents-1.0 is intended to do the following basic functions usually handled at the mod level.
 
- Maintain a raid wide table of talents, automatically updated on roster changes, notifying you on talent receipts.
- Provide easy access to talent queries (spec weight, spec name, specific talent presence)
- Monitor talent changes in players, and notify of changes (respec, talent swap, update after out of sight, level up)
- Monitor player roles, and notify of changes (melee, tank, healer, caster)
- Communicate directly with itself to other users to update talents via addon channel when possible
 
Notes:
The LibTalentQuery-1.0 dependancy must be included before LibGroupTalents-1.0 in any lib.xml or mod side TOC declarations.
 
Functions:
UnitHasTalent(unit, talentName[, group])-- Returns: Points spent in talent or nil
GUIDHasTalent(guid, talentName[, group])-- As UnitHasTalent
GetUnitTalentSpec(unitid[, group]) -- Returns: Dominant Tree, spent1, spent2, spent3
GetGUIDTalentSpec(guid[, group]) -- As GetUnitTalentSpec
GetUnitTalents(unit, refresh) -- Returns: Raw talent information in form of table of 3 strings of points spent. The refresh arg will force a re-query of the unit's talents
GetGUIDTalents(guid, refresh) -- As GetUnitTalents
GetUnitRole(unit) -- Returns one of: "melee", "caster", "healer", "tank"
GetGUIDRole(guid) -- As GetUnitRole
RefreshTalentsByUnit(unit) -- Force a refresh of talents for the specific unit
RefreshTalentsByGUID(guid) -- Force a refresh of talents for the specific player GUID
GetTreeNames(class) -- Returns: The three talent tree names for that class (Note: These return values are only valid after a player of that class has been inspected)
GetTreeIcons(class) -- Returns: The three talent tree icons for that class (Note: As above)
GetTalentCount() -- Returns: Talent info got, Talent info missing
GetTalentMissingNames() -- Returns: Comma delimited list of player names we're missing talents for
GetClassTalentInfo(class, talentName) -- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
GetUnitStorageString(unit) -- Returns: An encoded data string containing talent information for the player which can be stored by mods to set in later sessions using SetStorageString()
GetGUIDStorageString(guid) -- As GetUnitStorageString
SetStorageString(talentString) -- Returns: true on success (applicable). Any second return value indicates the data was invalid and should not be kept
GetUnitGlyphs(unit[, group]) -- Returns: Up to 6 spell IDs for the currently assigned Glyphs (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
GetGUIDGlyphs(guid[, group]) -- As GetUnitGlyphs
UnitHasGlyph(unit, glyph [, group]) -- Returns: true if the player has the glyph associated with spellID or spellName (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
GUIDHasGlyph(unit, glyph [, group]) -- As UnitHasGlyph
PurgeAndRescanTalents() -- Wipe current roster of all talents and rescan from start
 
Convenience Functions (Similar to Blizzard API functions, but callable with a unit ID):
GetActiveTalentGroup(unit) -- Returns: Active talent group for unit
GetNumTalentGroups(unit) -- Returns: Number of talent groups for unit
GetNumTalentTabs(unit) -- Returns: Number of talent tabs. Here's a clue; it's going to be 3...
GetTalentTabInfo(unit, tab[, group]) -- Returns: Tree Name, Tree Icon, Points Spent, Tree Background
GetNumTalents(unit, tab) -- Returns: Number of talents for specified tree
GetTalentInfo(unit, tab, index[, group])-- Returns: Talent Name, Icon, Tier, Column, Points Spent, Max Rank (Note that preview return values are not given unless called with "player")
GetUnspentTalentPoints(unit[, group]) -- Returns: Number of un-spent talent points for the unit
 
Events:
LibGroupTalents_Update(guid, unit, newSpec, n1, n2, n3 [, oldSpec, o1, o2, o3]) -- Received updated talents. If it's a respec, or old set is know, it passes the old info also (this is not sent if new talent scan is same as previous)
LibGroupTalents_UpdateComplete(guid1, guid2[, ...]) -- Sent when there are no more pending talent reads due (passes all GUIDs that were updated since last time this event was fired)
LibGroupTalents_Add(guid, unit, name, realm) -- Unit added to talent roster (Talents not necessarily available yet, but this is the mod's chance to feed talents using SetStorageString)
LibGroupTalents_Remove(guid, name, realm) -- Unit removed from talent roster (This is your last chance to store talents if required using GetUnitStorageString)
LibGroupTalents_RoleChange(guid, unit, newrole, oldrole) -- Roles are: "melee", "caster", "healer", "tank"
LibGroupTalents_GlyphUpdate(guid, unit) -- Fired when a player's glyphs change (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
 
]]
 
local TalentQuery = LibStub("LibTalentQuery-1.0")
 
local MAJOR, MINOR = "LibGroupTalents-1.0", tonumber(("$Rev: 55 $"):match("(%d+)"))
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end
 
local ChatThrottleLib = _G.ChatThrottleLib
 
lib.roster = lib.roster or {}
lib.classTalentData = lib.classTalentData or {}
lib.batch = lib.batch or {}
lib.pendingStorageStrings = lib.pendingStorageStrings or {}
 
local function UnitFullName(unit)
local name, realm = UnitName(unit)
local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
return namerealm
end
 
local function RosterInfoFullName(info)
local name, realm = info.name, info.realm
local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
return namerealm
end
 
local specChangers = {}
for index,spellid in ipairs(_G.TALENT_ACTIVATION_SPELLS) do
specChangers[GetSpellInfo(spellid)] = index
end
 
local frame = lib.frame
if (not frame) then
frame = CreateFrame("Frame", "LibGroupTalents_Frame")
lib.frame = frame
end
frame:UnregisterAllEvents()
frame:RegisterEvent("RAID_ROSTER_UPDATE")
frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
frame:RegisterEvent("UNIT_NAME_UPDATE")
frame:RegisterEvent("PLAYER_TALENT_UPDATE")
frame:RegisterEvent("UNIT_LEVEL")
frame:RegisterEvent("UNIT_AURA") -- Always get a UNIT_AURA when a unit's UnitIsVisible() changes
frame:RegisterEvent("CHAT_MSG_ADDON")
frame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("GLYPH_ADDED")
frame:RegisterEvent("GLYPH_REMOVED")
frame:RegisterEvent("GLYPH_UPDATED")
 
frame:SetScript("OnEvent", function(self, event, ...)
return lib[event](lib, ...)
end)
 
if not lib.events then
lib.events = LibStub("CallbackHandler-1.0"):New(lib)
end
 
local next, select, pairs, type = next, select, pairs, type
local new, del, deepDel
do
local list = setmetatable({},{__mode='k'})
function new(...)
local t = next(list)
if t then
list[t] = nil
for i = 1, select('#', ...) do
t[i] = select(i, ...)
end
return t
else
return {...}
end
end
function del(t)
if (t) then
wipe(t)
t[''] = true
t[''] = nil
list[t] = true
end
end
function deepDel(t)
if (t) then
for k,v in pairs(t) do
if type(v) == "table" then
deepDel(v)
end
t[k] = nil
end
t[''] = true
t[''] = nil
list[t] = true
end
end
end
 
do
local delay = 0
frame:SetScript("OnUpdate", function(self, elapsed)
if (lib.raidRosterUpdate) then
lib.raidRosterUpdate = nil
lib:OnRaidRosterUpdate()
end
 
if (lib.refreshCheckTimer) then
lib.refreshCheckTimer = lib.refreshCheckTimer - elapsed
if (lib.refreshCheckTimer < 0) then
lib.refreshCheckTimer = nil
lib:CheckForMissingTalents()
end
end
 
if (lib.talentTimers) then
delay = delay + elapsed
if (delay > 1) then
delay = 0
local now = GetTime()
local triggers
for guid,when in pairs(lib.talentTimers) do
if (now > when) then
-- Pass to second table to process, because RefreshTimers can affect this talentTimers table
-- So it's important we're not still iterating it at the time
if (not triggers) then
triggers = new()
end
triggers[guid] = true
lib.talentTimers[guid] = nil
if (not next(lib.talentTimers)) then
lib.talentTimers = del(lib.talentTimers)
break
end
end
end
 
if (triggers) then
for guid in pairs(triggers) do
lib:RefreshTalentsByGUID(guid)
end
del(triggers)
end
end
end
 
if (not lib.talentTimers and not lib.refreshCheckTimer) then
self:Hide()
end
end)
end
frame:Show()
lib.raidRosterUpdate = true
 
-- GetGUIDTalentsRaw
local function GetGUIDTalentsRaw(guid, group)
local r = guid and lib.roster[guid]
return r and r.talents and r.talents[group or r.active], r
end
 
-- PLAYER_LOGIN
function lib:PLAYER_LOGIN()
ChatThrottleLib = _G.ChatThrottleLib
lib.PLAYER_LOGIN = nil
end
 
-- RAID_ROSTER_UPDATE
function lib:RAID_ROSTER_UPDATE()
self.raidRosterUpdate = true
frame:Show()
end
lib.PARTY_MEMBERS_CHANGED = lib.RAID_ROSTER_UPDATE
 
-- UNIT_NAME_UPDATE
function lib:UNIT_NAME_UPDATE(unit)
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
if (r) then
local needsAdd = r.name == UNKNOWN
r.name, r.realm = UnitName(unit)
if (r.realm == "") then
r.realm = nil
end
r.class = select(2, UnitClass(unit))
r.level = UnitLevel(unit)
if (not r.talents) then
if (needsAdd) then
self.events:Fire("LibGroupTalents_Add", guid, unit, r.name, r.realm)
end
 
self:CheckForMissingTalents()
end
end
end
 
-- AnyPending
local function AnyPending()
local checkUpdate
for guid,info in pairs(lib.roster) do
local namerealm = RosterInfoFullName(info)
if (UnitIsConnected(namerealm)) then
if (lib.wasOffline) then
lib.wasOffline[guid] = nil
end
if (not info.talents or info.refresh) then
return true
end
else
if (not lib.wasOffline) then
lib.wasOffline = new()
end
lib.wasOffline[guid] = true
end
end
if (lib.wasOffline and not next(lib.wasOffline)) then
lib.wasOffline = del(lib.wasOffline)
end
end
 
-- CheckForUpdateComplete
local function CheckForUpdateComplete()
-- When all pending updates are complete, send an event to notify nothing else is due
if (next(lib.batch)) then
if (not AnyPending()) then
lib.events:Fire("LibGroupTalents_UpdateComplete", unpack(lib.batch))
wipe(lib.batch)
end
end
end
 
-- UNIT_LEVEL
function lib:UNIT_LEVEL(unit)
if (UnitInParty(unit) or UnitInRaid(unit)) then
local guid = UnitGUID(unit)
local r = guid and self.roster[guid]
if (r) then
r.level = UnitLevel(unit)
self:RefreshTalentsByUnit(unit)
end
end
end
 
-- UNIT_AURA
function lib:UNIT_AURA(unit)
local guid = UnitGUID(unit)
if (not UnitIsVisible(unit) or (self.wasOffline and self.wasOffline[guid])) then
if (not self.outOfSight) then
self.outOfSight = {}
end
self.outOfSight[guid] = true
self:RefreshTalentsByGUID(guid)
end
end
 
-- OnRaidRosterUpdate
function lib:OnRaidRosterUpdate()
local instanceType = select(2, IsInInstance())
if (instanceType == "pvp" or instanceType == "arena") then
self.distribution = "BATTLEGROUND"
else
if (GetNumRaidMembers() > 0) then
self.distribution = "RAID"
elseif (GetNumPartyMembers() > 0) then
self.distribution = "PARTY"
else
self.distribution = nil
end
end
if (self.distribution) then
if (self.sentHello ~= self.distribution) then
self.sentHello = self.distribution
self:SendCommMessage("HELLO "..MINOR, nil, self.distribution)
end
else
self.sentHello = nil
self.talentThrottle = del(self.talentThrottle)
self.wasOffline = del(self.wasOffline)
self.outOfSight = del(self.outOfSight)
wipe(self.pendingStorageStrings)
end
 
-- Now check for roster changes
local subtractions = new()
local additions = new()
local changes = new()
 
if (self.roster) then
for guid,info in pairs(self.roster) do
subtractions[guid] = info.level or 0
end
end
 
for unit in self:IterateRoster() do
local guid = UnitGUID(unit)
if (guid) then
local n = self.roster[guid]
if (not n) then
n = new()
self.roster[guid] = n
end
 
n.name, n.realm = UnitName(unit)
if (n.realm == "") then
n.realm = nil -- Fix this already..
end
n.level = UnitLevel(unit)
n.class = select(2, UnitClass(unit))
n.unit = unit
 
if (subtractions[guid]) then
if (subtractions[guid] ~= n.level) then
changes[guid] = unit -- Level changed, needs a rescan
end
 
subtractions[guid] = nil
else
if (n.name ~= UNKNOWN) then
self.events:Fire("LibGroupTalents_Add", guid, unit, n.name, n.realm)
end
additions[guid] = unit
end
end
end
 
if (next(additions)) then
for guid,unit in pairs(additions) do
self:GetUnitTalents(unit)
end
end
 
if (next(changes)) then
for guid,unit in pairs(changes) do
self:GetUnitTalents(unit)
end
end
 
if (next(subtractions)) then
for guid in pairs(subtractions) do
local r = self.roster[guid]
if (r) then
self.events:Fire("LibGroupTalents_Remove", guid, r.name, r.realm)
self.roster[guid] = deepDel(r)
 
local classStorageStrings = self.pendingStorageStrings[r.class]
if (classStorageStrings) then
classStorageStrings[guid] = del(classStorageStrings[guid])
if (not next(classStorageStrings)) then
self.pendingStorageStrings[r.class] = del(self.pendingStorageStrings[r.class])
end
end
end
end
 
CheckForUpdateComplete()
end
 
del(additions)
del(subtractions)
del(changes)
 
self:CheckForMissingTalents()
end
 
-- ValidateUnit
local function ValidateUnit(r, guid)
local unit = r.unit
if (UnitGUID(unit) ~= guid) then
local name = r.name .. (r.realm and "-" or "") .. (r.realm or "")
local index = UnitInRaid(name)
if (index) then
r.unit = "raid"..index
return true
else
if (UnitGUID("player") == guid) then
r.unit = "player"
return true
 
elseif (UnitInParty(name)) then
for i = 1,4 do
if (UnitGUID("party"..i) == guid) then
r.unit = "party"..i
return true
end
end
end
end
return
end
 
return true
end
 
-- CountTree
local function CountTree(branch)
local count = 0
for i = 1,#branch do
count = count + branch:byte(i) - 48
end
return count
end
 
-- TalentWeight
local function TalentWeight(talents, class)
if (talents and #talents == 3 and class) then
local c1, c2, c3 = CountTree(talents[1]), CountTree(talents[2]), CountTree(talents[3])
 
local weight = 1
if (c2 > c1 and c2 > c3) then
weight = 2
elseif (c3 > c1 and c3 > c2) then
weight = 3
end
 
local data = lib.classTalentData[class]
if (data and data[weight]) then
return data[weight].name, c1, c2, c3
end
 
return weight, c1, c2, c3
end
return nil, 0, 0, 0
end
 
do
-- First segment: Player ID (from GUID), Name, level, class, activePage, TalentString
-- Subsequent: spec number, talentString()
 
-- crc
local function crc32(str)
local val = tonumber((select(2, GetBuildInfo()))) -- Use WoW build as CRC base
for i = 1,#str do
val = bit.band(val * 2 + str:byte(i), 0xFFFF)
end
return val
end
 
-- GetUnitStorageString
function lib:GetUnitStorageString(unit)
return self:GetGUIDStorageString(UnitGUID(unit))
end
 
-- GetGUIDStorageString
-- Make a storage string for mods to store talents.
-- Rules: 1) Your own realm only 2) Their talents are complete (nothing unspent)
function lib:GetGUIDStorageString(guid)
local r = self.roster[guid]
if (r) then
local id
local playerGUID = UnitGUID("player")
if (playerGUID:sub(1, 6) == guid:sub(1, 6)) then
-- Same realm code, so just trim it off. This is likely always true from what I've seen
id = format("%X", tonumber(guid:sub(7), 16))
else
id = guid:sub(4)
end
 
if (r.talents and r.active and not r.realm and (not r.unspent or not r.unspent[r.active])) then
if (r.level < 1) then
r.level = UnitLevel(r.name) or 0
end
local str = format("%s,%d,%s,%d,%d", id, r.level, r.class, r.active, r.numActive)
for i = 1,r.numActive do
local t = r.talents[i]
if (t) then
str = format("%s;%d,%s", str, i, table.concat(t, "-"))
end
end
return format("%s;%d", str, crc32(str))
end
end
end
 
-- SetStorageString
function lib:SetStorageString(str, comms)
local ret, retInfo
if (str) then
local parts = new(strsplit(";", str))
if (#parts >= 2) then
local strCRC = tonumber(parts[#parts])
local temp = table.concat(parts, ";", 1, #parts - 1)
if (crc32(temp) == strCRC) then
local part1 = new(strsplit(",", parts[1]))
 
while true do
local guid
local id = part1[1]
if (id:len() < 12) then
-- Trimmed GUID, we'll prefix it with our own GUID's realm code
guid = format("%s%012X", UnitGUID("player"):sub(1, 6), tonumber(id, 16))
else
guid = format("0x0%015s", id)
end
 
local r = self.roster[guid]
if (not r) then
retInfo = format("Unexpected SetStorageString for ID %s", guid)
ret = true -- Still return true, we just didn't want this string yet
break
elseif (r.name == UNKNOWN) then
retInfo = format("Premature SetStorageString for ID %s", guid)
ret = true -- Still return true, we just didn't want this string yet
break
end
if (r.talents) then
-- We've already received talents for this player
ret = true -- Still return true, we just didn't want this string because we have their talents
break
end
 
if (not self.classTalentData[r.class]) then
-- Received a storage string for a class that we've not yet been able to scan
-- the talent trees for. We store this until we have that data
local classStorageStrings = self.pendingStorageStrings[r.class]
if (not classStorageStrings) then
classStorageStrings = new()
self.pendingStorageStrings[r.class] = classStorageStrings
end
classStorageStrings[guid] = str
ret = true
break
end
 
local level = tonumber(part1[2])
local class = part1[3]
local active = tonumber(part1[4])
local numActive = tonumber(part1[5])
 
if (r.level < 1) then
r.level = UnitLevel(r.name) or 0
end
if (level ~= r.level and r.level > 1) then
-- Won't accept talents for mismatched levels (but ignore errors reading the UnitLevel early)
retInfo = "Wrong level"
break
end
if (not r.class and class) then
-- If we don't have the class, but the storage string does, we'll take it
r.class = class
end
if (class ~= r.class) then
-- Class doesn't match, probably a char delete/remake or xrealm
retInfo = format("Wrong class: expected %q, got %q", tostring(r.class), tostring(class))
break
end
 
-- Now the talent trees
local talents = new()
for i = 2,#parts - 1 do
local partN = new(strsplit(",", parts[i]))
if (#partN == 2) then
local specNumber = tonumber(partN[1])
local specTalents = new(strsplit("-", partN[2]))
 
if (specNumber and #specTalents >= 3) then
talents[specNumber] = specTalents
else
del(specTalents)
talents = del(talents)
retInfo = "Invalid talent specs in tree "..i
break
end
end
end
 
if (talents) then
r.talents = talents
r.active = active
r.numActive = numActive
if (comms ~= r.name) then
-- If comms part sends player name along with packet, then we'll skip the refresh later
-- which we'd normally do when Storage is set via app startup
r.refresh = true
else
r.refresh = nil
end
 
ValidateUnit(r, guid)
local newSpec, n1, n2, n3 = TalentWeight(r.talents[r.active], r.class)
self.events:Fire("LibGroupTalents_Update", guid, r.unit, newSpec, n1, n2, n3)
self:GetGUIDRole(guid, true)
ret = true
end
break
end
 
del(part1)
else
retInfo = "Invalid string"
end
end
del(parts)
end
 
return ret, retInfo
end
end
 
-- GetClassTalentData
-- Builds an internal table for talent name -> tree/index lookups.
function GetClassTalentData(unit)
local _, class = UnitClass(unit)
if (class) then
local data = lib.classTalentData[class]
if (not data) then
local isnotplayer = not UnitIsUnit("player", unit)
if (GetNumTalentTabs(isnotplayer) > 0) then
data = new()
 
for tab = 1, GetNumTalentTabs(isnotplayer) do
local tree = new()
local _
tree.name, tree.icon, _, tree.background = GetTalentTabInfo(tab, isnotplayer)
tinsert(data, tree)
 
tree.list = new()
for i = 1,GetNumTalents(tab, isnotplayer) do
local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer)
if (name) then
local entry = new()
entry.name = name
entry.icon = icon
entry.tier = tier
entry.column = column
entry.maxRank = maxRank
entry.index = i
entry.treeIndex = tab
tinsert(tree.list, entry)
if (not data.list) then
data.list = new()
end
data.list[name] = entry
end
end
end
 
if (next(data)) then
lib.classTalentData[class] = data
 
--for guid,r in pairs(lib.roster) do
-- if (r.class == class and r.talents) then
-- -- We picked up class talent data for a class after receiving talents for them via comms
-- -- So, we fire an Update event for any members of the class we already have so that
-- -- talents can now be interpreted correctly.
-- local spec, n1, n2, n3 = TalentWeight(r.talents[r.active], class)
-- lib.events:Fire("LibGroupTalents_Update", guid, unit, spec, n1, n2, n3)
-- end
--end
 
local classStorageStrings = lib.pendingStorageStrings[class]
if (classStorageStrings) then
local unitGUID = UnitGUID(unit)
for guid, str in pairs(classStorageStrings) do
if (guid ~= unitGUID) then
lib:SetStorageString(str)
end
end
lib.pendingStorageStrings[class] = del(lib.pendingStorageStrings[class])
end
else
deepDel(data)
end
end
end
end
end
 
-- GetTreeNames
function lib:GetTreeNames(class)
local info = self.classTalentData[class]
if (info) then
return info[1].name, info[2].name, info[3].name
end
end
 
-- GetTreeIcons
function lib:GetTreeIcons(class)
local info = self.classTalentData[class]
if (info) then
return info[1].icon, info[2].icon, info[3].icon
end
end
 
-- ReadTalentGroup
local function ReadTalentGroup(isnotplayer, group, class)
local numTabs = GetNumTalentTabs(isnotplayer)
if (numTabs and numTabs >= 3 and GetNumTalents(1, isnotplayer) > 0) then
local ctd = lib.classTalentData[class]
--[===[@debug@
assert(ctd and ctd[1] and ctd[2] and ctd[3])
assert(ctd[1].list and ctd[2].list and ctd[3].list)
--@end-debug@]===]
 
local n = new()
for tab = 1, numTabs do
local branchLength = GetNumTalents(tab, isnotplayer, nil, group)
if (branchLength ~= #ctd[tab].list) then
-- Tab tree size is not what we expected for this class
del(n)
return
end
 
local t = new()
local trim
for i = 1,branchLength do
local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer, nil, group)
tinsert(t, currentRank)
if (currentRank > 0) then
trim = i -- We strip off trailing zeros from talent strings to save storage space
end
end
 
tinsert(n, table.concat(t, nil, 1, trim or 0))
del(t)
end
 
return n
end
end
 
-- TalentQuery_Ready
function lib:TalentQuery_Ready_Outsider(e, name, realm, unit)
self:TalentQuery_Ready(e, name, realm, unit)
end
 
-- TalentQuery_Ready
function lib:TalentQuery_Ready(e, name, realm, unit)
GetClassTalentData(unit)
 
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
if (r) then
local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
local isnotplayer = not UnitIsUnit(unit, "player")
 
if (GetTalentTabInfo(1, isnotplayer)) then
local active = GetActiveTalentGroup(isnotplayer)
local numActive = GetNumTalentGroups(isnotplayer)
local listUnspent, invalid
local talents = new()
 
for group = 1,numActive do
local n = ReadTalentGroup(isnotplayer, group, r.class)
if (n and #n >= 3) then
talents[group] = n
else
invalid = true
break
end
 
local unspent = GetUnspentTalentPoints(isnotplayer, nil, group)
if (unspent and unspent > 0) then
if (not listUnspent) then
listUnspent = new()
end
listUnspent[group] = unspent
end
end
 
if (isnotplayer and (invalid or (listUnspent and listUnspent[active] or 0) > 0)) then
-- Unit didn't have all their points spent in active group, so we'll have another look in 10 seconds
-- Don't need to check when it's "player" because we get PLAYER_TALENT_UPDATE event on changes
self:TriggerRefreshTalents(guid, 10)
end
 
if (not invalid) then
if (active > numActive) then
-- May be better to discard instead? We'll see
active = 1
end
self:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
end
end
end
end
TalentQuery.RegisterCallback(lib, "TalentQuery_Ready")
TalentQuery.RegisterCallback(lib, "TalentQuery_Ready_Outsider")
 
-- GetUnitTalentSpec
function lib:GetUnitTalentSpec(unit, group)
return self:GetGUIDTalentSpec(UnitGUID(unit), group)
end
 
-- GetGUIDTalentSpec
function lib:GetGUIDTalentSpec(guid, group)
local talents, r = GetGUIDTalentsRaw(guid, group)
if (talents) then
return TalentWeight(talents, r.class)
end
end
 
-- CompareTalents
local function CompareTalents(tree1, tree2)
if ((tree1 ~= nil) ~= (tree2 ~= nil)) then
return
end
if (tree1 and tree2 and #tree1 == #tree2) then
for i = 1,#tree1 do
if (tree1[i] ~= tree2[i]) then
return
end
end
return true
end
end
 
-- OnReceiveTalents
function lib:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
local r = self.roster[guid]
if (r) then
if (active ~= r.active or numActive ~= r.numActive or not CompareTalents(talents and talents[active], r.talents and r.talents[r.active])) then
local oldTalents
if (r.talents) then
oldTalents = r.talents[r.active]
end
del(r.unspent)
 
r.talents = talents
r.active = active
r.numActive = numActive
r.unspent = listUnspent
 
local newSpec, n1, n2, n3 = TalentWeight(r.talents[active], r.class)
 
local fired
if (oldTalents) then
-- For those cases when we didn't have the alternate talents for a player for whatever reason.
-- Maybe they just picked up dual talent spec, or gated to trainer to respec.
local oldSpec, o1, o2, o3 = TalentWeight(oldTalents, r.class)
 
if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
fired = true
end
end
 
if (not fired) then
self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
end
self:GetGUIDRole(guid, true)
 
tinsert(self.batch, guid)
CheckForUpdateComplete()
 
oldTalents = del(oldTalents)
return
end
end
del(talents)
end
 
-- OnReceiveGlyphs
function lib:OnReceiveGlyphs(guid, sender, glyphs)
local r = self.roster[guid]
if (r) then
if (ValidateUnit(r, guid)) then
local oldGlyphs
if (r.glyphs) then
oldGlyphs = r.glyphs[r.active]
r.glyphs = del(r.glyphs)
end
 
r.glyphs = glyphs
 
local newGlyphs = r.glyphs and r.glyphs[r.active]
if (newGlyphs ~= oldGlyphs) then
self.events:Fire("LibGroupTalents_GlyphUpdate", guid, r.unit)
end
return
end
end
 
del(glyphs)
end
 
-- GetUnitGlyphs
function lib:GetUnitGlyphs(unit, group)
return self:GetGUIDGlyphs(UnitGUID(unit), group)
end
 
-- GetGUIDGlyphs
function lib:GetGUIDGlyphs(guid, group)
local r = self.roster[guid]
if (r) then
local g = r.glyphs and r.glyphs[group or r.active]
if (g) then
local temp = new(strsplit(",", g))
for i,str in ipairs(temp) do
temp[i] = tonumber(str)
end
local a, b, c, d, e, f = unpack(temp)
del(temp)
return a, b, c, d, e, f
end
end
end
 
-- UnitHasGlyph
function lib:UnitHasGlyph(unit, glyphID, group)
return lib:GUIDHasGlyph(UnitGUID(unit), glyphID, group)
end
 
-- GUIDHasGlyph
function lib:GUIDHasGlyph(guid, glyphID, group)
local ret
local r = self.roster[guid]
if (r) then
local g = r.glyphs and r.glyphs[group or r.active]
if (g) then
local temp = new(strsplit(",", g))
for i,str in ipairs(temp) do
local id = tonumber(str)
if (type(glyphID) == "number") then
if (glyphID == id) then
ret = true
break
end
else
if (glyphID == GetSpellInfo(id)) then
ret = true
break
end
end
end
del(temp)
end
end
return ret
end
 
-- GLYPH_ADDED
function lib:GLYPH_ADDED(index, a, b, c)
self:RefreshPlayerGlyphs()
end
 
-- GLYPH_REMOVED
function lib:GLYPH_REMOVED(index, a, b, c)
self:RefreshPlayerGlyphs()
end
 
-- GLYPH_UPDATED
function lib:GLYPH_UPDATED(index, a, b, c)
self:RefreshPlayerGlyphs()
end
 
-- RefreshPlayerGlyphs
function lib:RefreshPlayerGlyphs()
local guid = UnitGUID("player")
local r = self.roster[guid]
if (not r) then
return
end
 
local glyphs = new()
local any
for talentGroup = 1,GetNumTalentGroups() do
local list = new()
for i = 1,GetNumGlyphSockets() do
local enabled, glyphType, glyphSpell, icon = GetGlyphSocketInfo(i, talentGroup)
if (enabled and glyphType and glyphSpell) then
tinsert(list, glyphSpell)
any = true
end
end
glyphs[talentGroup] = table.concat(list, ",")
del(list)
end
 
local oldGlyphs = r.glyphs
if (any) then
r.glyphs = glyphs
else
del(glyphs)
end
 
local change = (oldGlyphs and oldGlyphs[r.active]) ~= (r.glyphs and r.glyphs[r.active])
if (change) then
self:SendMyGlyphs()
self.events:Fire("LibGroupTalents_GlyphUpdate", guid, "player")
end
 
del(oldGlyphs)
end
 
-- PLAYER_TALENT_UPDATE
function lib:PLAYER_TALENT_UPDATE()
self:TriggerRefreshTalents(UnitGUID("player"), 2)
end
 
-- UNIT_SPELLCAST_SUCCEEDED
function lib:UNIT_SPELLCAST_SUCCEEDED(unit, spell)
local newActiveGroup = specChangers[spell]
if (newActiveGroup) then
local guid = UnitGUID(unit)
local r = guid and self.roster[guid]
if (r) then
if (newActiveGroup == r.active) then
-- We obviously didn't see them switch from this set
self:GetGUIDRole(guid, true)
return
end
 
if (r.talents) then
local oldSet = r.talents[r.active]
local newSet = r.talents[newActiveGroup]
if (oldSet and newSet) then
-- We have the other talent set, so no need to refresh anything. Just compare and notify
r.active = newActiveGroup
 
local oldSpec, o1, o2, o3 = TalentWeight(oldSet, r.class)
local newSpec, n1, n2, n3 = TalentWeight(newSet, r.class)
 
if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
else
self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
end
self:GetGUIDRole(guid, true)
return
end
end
 
-- If we get this far, then someone probably gated to respec
self:RefreshTalentsByGUID(guid)
end
end
end
 
-- TriggerRefreshTalents
function lib:TriggerRefreshTalents(guid, delay)
if (not self.talentTimers) then
self.talentTimers = new()
end
if (guid) then
self.talentTimers[guid] = GetTime() + delay
frame:Show()
end
end
 
-- RefreshTalentsByUnit
function lib:RefreshTalentsByUnit(unit)
local guid = UnitGUID(unit)
if (guid) then
self:RefreshTalentsByGUID(guid)
end
end
 
-- RefreshTalentsByGUID
function lib:RefreshTalentsByGUID(guid)
local r = self.roster[guid]
if (not r) then
return
end
if (not ValidateUnit(r, guid)) then
return
end
 
if (self.talentTimers) then
self.talentTimers[guid] = nil
end
 
if (not self.talentThrottle) then
self.talentThrottle = {}
end
for guidThrottle,when in pairs(self.talentThrottle) do
if (when < GetTime() - 5) then
self.talentThrottle[guidThrottle] = nil
elseif (guid == guidThrottle) then
return
end
end
self.talentThrottle[guid] = GetTime()
 
if (self.commQueried) then
self.commQueried[guid] = nil
if (not next(self.commQueried)) then
self.commQueried = del(self.commQueried)
end
end
 
r.refresh = true
self:CheckForMissingTalents()
 
if (UnitGUID("player") == guid) then
self:SendMyTalents()
end
end
 
-- CheckForMissingTalents
function lib:CheckForMissingTalents()
local any
for guid,info in pairs(self.roster) do
local namerealm = RosterInfoFullName(info)
if (not info.talents or (not UnitIsVisible(namerealm) and UnitExists(namerealm)) or info.refresh) then
any = true
info.refresh = nil
self:GetUnitTalents(info.unit, true)
end
end
 
if (any) then
lib.refreshCheckTimer = 15
frame:Show()
end
end
 
do
local survivalOfTheFittest = GetSpellInfo(33853) -- Survival of the Fittest
local protectorOfThePack = GetSpellInfo(57873) -- Protector of the Pack
local dkBladeBarrier = GetSpellInfo(49182) -- Blade Barrier
local dkToughness = GetSpellInfo(49042) -- Toughness
local dkAnticipation = GetSpellInfo(55129) -- Anticipation
 
-- GetUnitRole
function lib:GetUnitRole(unit, reset)
local guid = UnitGUID(unit)
if (guid) then
return self:GetGUIDRole(guid, reset)
end
end
 
-- GetGUIDRole
function lib:GetGUIDRole(guid, reset)
local r = guid and self.roster[guid]
if (not r) then
return
end
if (r.role and not reset) then
return r.role
end
if (not ValidateUnit(r, guid)) then
return
end
 
local class = r.class
local role
 
local unit = r.unit
if (class == "ROGUE" or class == "HUNTER") then
role = "melee"
elseif (class == "MAGE" or class == "WARLOCK") then
role = "caster"
elseif (r.talents and r.talents[r.active]) then
if (class == "DEATHKNIGHT") then
local score = self:GUIDHasTalent(guid, dkBladeBarrier) and 1 or 0
score = score + (self:GUIDHasTalent(guid, dkToughness) and 1 or 0)
score = score + (self:GUIDHasTalent(guid, dkAnticipation) and 1 or 0)
role = score >= 2 and "tank" or "melee" -- Has 2 of the 3 tanking talents at least
 
else
local specName, t1, t2, t3 = TalentWeight(r.talents[r.active], class)
 
if (class == "PRIEST") then
role = ((t1 + t2) > t3) and "healer" or "caster"
elseif (class == "WARRIOR") then
role = ((t1 + t2) > t3) and "melee" or "tank"
else
local heavy = (t1 > t2 and t1 > t3 and 1) or (t2 > t1 and t2 > t3 and 2) or (t3 > t1 and t3 > t2 and 3) or 0
if (class == "PALADIN") then
role = heavy == 1 and "healer" or heavy == 2 and "tank" or heavy == 3 and "melee"
 
elseif (class == "DRUID") then
if (heavy == 2) then
if (self:GUIDHasTalent(guid, survivalOfTheFittest) and self:GUIDHasTalent(guid, protectorOfThePack)) then
role = "tank"
else
role = "melee"
end
else
role = heavy == 1 and "caster" or "healer"
end
 
elseif (class == "SHAMAN") then
role = heavy == 1 and "caster" or heavy == 2 and "melee" or heavy == 3 and "healer"
end
end
end
end
 
local oldrole = r.role
r.role = role
 
if (role and role ~= oldrole) then
self.events:Fire("LibGroupTalents_RoleChange", guid, unit, role, oldrole)
end
return role
end
end
 
-- GetUnitTalents
function lib:GetUnitTalents(unit, refetch)
local guid = UnitGUID(unit)
if (not guid) then
return
end
return self:GetGUIDTalents(guid, refetch)
end
 
-- CanCommQuery
local function CanCommQuery(guid)
if (not lib.commQueried or not lib.commQueried[guid]) then
if (not lib.commQueried) then
lib.commQueried = new()
end
lib.commQueried[guid] = true
return true
end
end
 
-- GetGUIDTalents
function lib:GetGUIDTalents(guid, refetch)
local r = self.roster[guid]
if (not r) then
return
end
if (not ValidateUnit(r, guid)) then
return
end
 
local unit = r.unit
local name, realm = UnitName(unit)
local activeTalents = r.talents and r.talents[r.active]
 
if (activeTalents) then
-- If someone is out of sight, we won't catch their talent swap spell cast, so we'll invalidate them here and recheck talents
if ((not UnitIsVisible(unit) and UnitIsConnected(unit)) or (self.outOfSight and self.outOfSight[guid])) then
if (not r.version) then
refetch = true
end
end
end
 
if (not activeTalents or refetch) then
if (UnitIsUnit("player", unit)) then
self:RefreshPlayerGlyphs()
self:TalentQuery_Ready(nil, name, nil, unit)
 
elseif ((UnitInRaid(unit) or UnitInParty(unit)) and UnitIsConnected(unit)) then
TalentQuery:Query(unit)
 
local namerealm = UnitFullName(unit)
if (not r.talents and not r.requested) then
-- Don't need to query on a 'refetch' because they'll send changes anyway via comms
local skipGlyphs
if (not UnitIsVisible(unit) or not CanInspect(unit)) then
if (r.version) then
if (CanCommQuery(guid)) then
-- We request talents via comms for anyone that may be out of inspect range
self:SendCommMessage("REQUESTTALENTS", namerealm)
r.requested = true
skipGlyphs = true
end
end
end
end
 
if (not r.glyphs and not skipGlyphs) then
if (r.version and r.version >= 15) then
if (CanCommQuery(guid)) then
-- They're in range to inspect, but we'll still want to ask for their glyphs
self:SendCommMessage("REQUESTGLYPHS", namerealm)
end
end
end
end
 
if (self.outOfSight) then
self.outOfSight[guid] = nil
end
end
 
return activeTalents
end
 
-- SendCommMessage
function lib:SendCommMessage(msg, target, channel)
if (msg) then
if (ChatThrottleLib) then
ChatThrottleLib:SendAddonMessage("NORMAL", MAJOR, msg, channel or "WHISPER", target)
else
SendAddonMessage(MAJOR, msg, channel or "WHISPER", target)
end
end
end
 
-- Throttle - Purposely local to here
-- Abuse prevention. Yes, who would abuse addon comms? Noone would make a macro to crash a mod user would they. Right?
-- Well, this one time, at band camp. Someone thought it was super funny to make a macro that DCd PallyPower users
local throttle
local function Throttle(sender, key)
if (not throttle) then
throttle = {}
end
local s = throttle[sender]
if (not s) then
s = {}
throttle[sender] = s
end
 
if ((s[key] or 0) < GetTime() - 4.5) then
-- Same message key only allowable once every 4.5 secs from 1 person (Respec cast time is 5 seconds)
s[key] = GetTime()
return true
end
end
 
-- CHAT_MSG_ADDON
function lib:CHAT_MSG_ADDON(prefix, msg, channel, sender)
if (prefix == MAJOR) then
if (sender == UnitName("player")) then
return
elseif (not UnitInRaid(sender) and not UnitInParty(sender)) then
return
end
 
local guid = UnitGUID(sender)
if (not guid) then
return
end
local r = self.roster[guid]
if (not r) then
return
end
 
local cmd, str = msg:match("^(%a+) *(.*)$")
if (not cmd) then
return
end
 
if (cmd == "TALENTS") then
-- Talents come in form of:
local t = r.talents
r.talents = nil -- SetStorageString won't overwrite talents usually, but we want it to here, without providing a means to do it easily with an arg from a mod
if (not self:SetStorageString(str, sender)) then
r.talents = t
else
deepDel(t)
end
 
elseif (cmd == "GLYPHS") then
local invalid
local pages = new(strsplit(";", str))
local glyphs = new()
for page,info in ipairs(pages) do
local list = new(strsplit(",", info))
local tab = tonumber(tremove(list, 1))
if (tab) then
glyphs[tab] = table.concat(list, ",")
del(list)
else
invalid = true
del(glyphs)
del(list)
break
end
end
if (not invalid) then
self:OnReceiveGlyphs(guid, sender, glyphs)
end
del(pages)
 
elseif (cmd == "REQUESTTALENTS") then
if (Throttle(sender, "REQUESTTALENTS")) then
if ((r.version or 0) < 39) then
if (lib.sentToOld and lib.sentToOld[guid]) then
return
end
if (not lib.sentToOld) then
lib.sentToOld = new()
end
lib.sentToOld[guid] = time()
end
 
self:SendMyTalents(sender)
self:SendMyGlyphs(sender)
end
 
elseif (cmd == "REQUESTGLYPHS") then
if (Throttle(sender, "REQUESTGLYPHS")) then
self:SendMyGlyphs(sender)
end
 
elseif (cmd == "HELLO") then
r.version = tonumber(str)
if (channel ~= "WHISPER") then
if (lib.sentToOld) then
lib.sentToOld[guid] = nil
end
if (UnitIsConnected(sender) and Throttle(sender, "HELLO")) then
self:SendCommMessage("HELLO "..MINOR, sender)
self:SendMyGlyphs(sender)
end
end
end
end
end
 
-- SendMy
local function SendMy(sender, str)
if (sender) then
if (UnitIsConnected(sender)) then
lib:SendCommMessage(str, sender)
end
else
for guid,info in pairs(lib.roster) do
if (info.version) then
local namerealm = RosterInfoFullName(info)
if (UnitIsConnected(namerealm)) then
lib:SendCommMessage(str, namerealm)
end
end
end
end
end
 
-- SendMyTalents
function lib:SendMyTalents(sender)
if (sender or self:UserCount() > 0) then
local str = self:GetGUIDStorageString(UnitGUID("player"))
if (str) then
SendMy(sender, "TALENTS "..str)
end
end
end
 
-- SendMyGlyphs
function lib:SendMyGlyphs(sender)
if (sender or self:UserCount() > 0) then
local r = self.roster[UnitGUID("player")]
if (r and r.glyphs) then
local str = "GLYPHS "
local i = 1
for tab,g in pairs(r.glyphs) do
local temp = format("%d,%s", tab, g)
str = str .. (i > 1 and ";" or "") .. temp
i = i + 1
end
SendMy(sender, str)
end
end
end
 
-- UserCount
function lib:UserCount()
local count = 0
for guid,info in pairs(self.roster) do
if (info.version and not UnitIsUnit("player", RosterInfoFullName(info))) then
count = count + 1
end
end
return count
end
 
-- UnitHasTalent
-- eg: lib:UnitHasTalent("player", GetSpellInfo(talentSpellID))
-- Returns: nil, or number of points spent into talent
-- If the talent group is not specified, then the active talent group is used
function lib:UnitHasTalent(unit, talentName, group)
return unit and self:GUIDHasTalent(UnitGUID(unit), talentName, group)
end
 
-- GUIDHasTalent
-- Returns: nil, or number of points spent into talent
function lib:GUIDHasTalent(guid, talentName, group)
local talents, r = GetGUIDTalentsRaw(guid, group)
if (talents and r.class) then
local data = self.classTalentData[r.class]
if (data) then
local info = data.list and data.list[talentName]
if (info) then
local str = talents[info.treeIndex]
if (str) then
local amount = (str:byte(info.index) or 48) - 48
return (amount or 0) > 0 and amount or nil
end
end
end
end
end
 
-- GetClassTalentInfo
function lib:GetClassTalentInfo(class, talentName)
-- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
local data = self.classTalentData[class]
if (data) then
local info = data.list and data.list[talentName]
if (info) then
return info.maxRank, info.icon, info.treeIndex, info.column, info.tier, info.index
end
end
end
 
-- GetActiveTalentGroup
function lib:GetActiveTalentGroup(unit)
if (UnitIsUnit(unit, "player")) then
return GetActiveTalentGroup()
else
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
return r and r.active or nil
end
end
 
-- GetNumTalentGroups
function lib:GetNumTalentGroups(unit)
if (UnitIsUnit(unit, "player")) then
return GetNumTalentGroups()
else
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
return r and r.numActive or nil
end
end
 
-- GetTalentTabInfo
function lib:GetTalentTabInfo(unit, tab, group)
if (UnitIsUnit(unit, "player")) then
return GetTalentTabInfo(tab, nil, nil, group or GetActiveTalentGroup())
else
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
if (r and r.class) then
local ctd = self.classTalentData[r.class]
if (ctd and tab >= 1 and tab <= #ctd) then
local spec, c1, c2, c3 = self:GetGUIDTalentSpec(guid, group)
return ctd[tab].name, ctd[tab].icon, tab == 1 and c1 or tab == 2 and c2 or c3, ctd[tab].background, 0
end
end
end
end
 
-- GetNumTalents
function lib:GetNumTalents(unit, tab)
if (UnitIsUnit(unit, "player")) then
return GetNumTalents(tab)
else
local _, class = UnitClass(unit)
if (class) then
local ctd = self.classTalentData[class]
if (ctd and tab >= 1 and tab <= #ctd) then
return #ctd[tab].list
end
end
end
end
 
-- GetTalentInfo
function lib:GetTalentInfo(unit, tab, index, group)
if (UnitIsUnit(unit, "player")) then
return GetTalentInfo(tab, index, nil, nil, group or GetActiveTalentGroup())
else
local _, class = UnitClass(unit)
if (class) then
local ctd = self.classTalentData[class]
if (ctd and tab >= 1 and tab <= #ctd) then
local info = ctd[tab].list[index]
if (info) then
local spent = self:UnitHasTalent(unit, info.name, group)
return info.name, info.icon, info.tier, info.column, spent or 0, info.maxRank
end
end
end
end
end
 
-- GetNumTalentTabs
function lib:GetNumTalentTabs()
return GetNumTalentTabs()
end
 
-- GetNumTalentTabs
function lib:GetUnspentTalentPoints(unit, group)
if (UnitIsUnit(unit, "player")) then
return GetUnspentTalentPoints(nil, nil, group)
else
local guid = unit and UnitGUID(unit)
local r = guid and self.roster[guid]
if (r) then
return r.unspent and r.unspent[group or r.active or 1]
end
end
end
 
-- GetTalentCount
function lib:GetTalentCount()
local count, missing = 0, 0
for guid,info in pairs(self.roster) do
if (info.talents) then
count = count + 1
else
missing = missing + 1
end
end
return count, missing
end
 
-- GetTalentMissingNames
function lib:GetTalentMissingNames()
local list = new()
for unit in self:IterateRoster() do
local guid = UnitGUID(unit)
local r = guid and self.roster[guid]
if (not r or not r.talents) then
tinsert(list, UnitFullName(unit))
end
end
local ret
if (next(list)) then
ret = table.concat(list, ",")
end
del(list)
return ret
end
 
-- PurgeAndRescanTalents
function lib:PurgeAndRescanTalents()
if (self.roster) then
wipe(self.pendingStorageStrings)
for guid,info in pairs(self.roster) do
info.talents = del(info.talents)
info.active = nil
info.numActive = nil
info.requested = nil
end
end
self:CheckForMissingTalents()
end
 
-- Roster iterator
do
local function iter(t)
local key = t.id
local ret
if (t.mode == "raid") then
if (key > t.r) then
del(t)
return nil
end
ret = "raid"..key
else
if (key > t.p) then
del(t)
return nil
end
ret = key == 0 and "player" or "party"..key
end
t.id = key + 1
return ret
end
 
-- IterateRoster
function lib:IterateRoster()
local t = new()
if (GetNumRaidMembers() > 0) then
t.mode = "raid"
t.id = 1
t.r = GetNumRaidMembers()
else
t.mode = "party"
t.id = 0
t.p = GetNumPartyMembers()
end
return iter, t
end
end
Ace3/Ace3.toc
1,5 → 1,5
## Interface: 30300
## X-Curse-Packaged-Version: Release-r960
## Interface: 40000
## X-Curse-Packaged-Version: Release-r971
## X-Curse-Project-Name: Ace3
## X-Curse-Project-ID: ace3
## X-Curse-Repository-ID: wow/ace3/mainline
Ace3/changelog.txt
1,3 → 1,9
Ace3 Release - Revision r971 (October 12th, 2010)
-------------------------------------------------
- Small fixes and adjustments for the 4.0 Content Patch.
- AceGUI-3.0: ScrollFrame: Allow for a small margin of error when determining if the scroll bar should be shown.
- AceGUI-3.0: Added new widget APIs: GetText for EditBox and DisableButton for MultiLineEditBox
 
Ace3 Release - Revision r960 (July 20th, 2010)
----------------------------------------------
- AceGUI-3.0: Label: Reset Image Size and TexCoords on Acquire (Ticket #110)
Ace3/AceConfig-3.0/AceConfig-3.0.lua
3,7 → 3,7
-- as well as associate it with a slash command.
-- @class file
-- @name AceConfig-3.0
-- @release $Id: AceConfig-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $
-- @release $Id: AceConfig-3.0.lua 969 2010-10-07 02:11:48Z shefki $
 
--[[
AceConfig-3.0
19,8 → 19,8
 
local cfgreg = LibStub("AceConfigRegistry-3.0")
local cfgcmd = LibStub("AceConfigCmd-3.0")
local cfgdlg = LibStub("AceConfigDialog-3.0")
--TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0")
--TODO: local cfgdlg = LibStub("AceConfigDialog-3.0", true)
--TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0", true)
 
-- Lua APIs
local pcall, error, type, pairs = pcall, error, type, pairs
36,7 → 36,7
-- You can supply a slash command (or a table of slash commands) to register with AceConfigCmd directly.
-- @paramsig appName, options [, slashcmd]
-- @param appName The application name for the config table.
-- @param options The option table (or a function to generate one on demand)
-- @param options The option table (or a function to generate one on demand). http://www.wowace.com/addons/ace3/pages/ace-config-3-0-options-tables/
-- @param slashcmd A slash command to register for the option table, or a table of slash commands.
-- @usage
-- local AceConfig = LibStub("AceConfig-3.0")
Ace3/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua
1,10 → 1,10
--- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables.
-- @class file
-- @name AceConfigDialog-3.0
-- @release $Id: AceConfigDialog-3.0.lua 958 2010-07-03 10:22:29Z nevcairiel $
-- @release $Id: AceConfigDialog-3.0.lua 967 2010-09-25 08:20:55Z nevcairiel $
 
local LibStub = LibStub
local MAJOR, MINOR = "AceConfigDialog-3.0", 49
local MAJOR, MINOR = "AceConfigDialog-3.0", 50
local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
 
if not AceConfigDialog then return end
322,8 → 322,8
end
local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
if OrderA == OrderB then
local NameA = (type(tempNames[a] == "string") and tempNames[a]) or ""
local NameB = (type(tempNames[b] == "string") and tempNames[b]) or ""
local NameA = (type(tempNames[a]) == "string") and tempNames[a] or ""
local NameB = (type(tempNames[b]) == "string") and tempNames[b] or ""
return NameA:upper() < NameB:upper()
end
if OrderA < 0 then
Ace3/AceTab-3.0/AceTab-3.0.xml
1,5 → 1,4
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="AceTab-3.0.lua"/>
<Script file="AceConfigTab-3.0.lua"/>
</Ui>
Ace3/CallbackHandler-1.0/CallbackHandler-1.0.lua
1,5 → 1,5
--[[ $Id: CallbackHandler-1.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ ]]
local MAJOR, MINOR = "CallbackHandler-1.0", 5
--[[ $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
147,9 → 147,9
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)
-- 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!
Ace3/AceGUI-3.0/widgets/AceGUIWidget-MultiLineEditBox.lua
1,4 → 1,4
local Type, Version = "MultiLineEditBox", 22
local Type, Version = "MultiLineEditBox", 23
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
 
15,6 → 15,27
-- GLOBALS: ACCEPT, ChatFontNormal
 
--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function Layout(self)
self:SetHeight(self.numlines * 14 + (self.disablebutton and 19 or 41) + self.labelHeight)
 
if self.labelHeight == 0 then
self.scrollBar:SetPoint("TOP", self.frame, "TOP", 0, -23)
else
self.scrollBar:SetPoint("TOP", self.label, "BOTTOM", 0, -19)
end
 
if self.disablebutton then
self.scrollBar:SetPoint("BOTTOM", self.frame, "BOTTOM", 0, 21)
self.scrollBG:SetPoint("BOTTOMLEFT", 0, 4)
else
self.scrollBar:SetPoint("BOTTOM", self.button, "TOP", 0, 18)
self.scrollBG:SetPoint("BOTTOMLEFT", self.button, "TOPLEFT")
end
end
 
--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function OnClick(self) -- Button
113,23 → 134,17
Methods
-------------------------------------------------------------------------------]]
local methods = {
["GetText"] = function(self)
return self.editBox:GetText()
end,
 
["OnAcquire"] = function(self)
self.editBox:SetText("")
self:SetDisabled(false)
self:SetWidth(200)
self:DisableButton(false)
self:SetNumLines()
self.entered = nil
self:SetMaxLetters(0)
end,
 
["OnRelease"] = function(self)
self.frame:ClearAllPoints()
self.frame:Hide()
end,
-- ["OnRelease"] = nil,
 
["SetDisabled"] = function(self, disabled)
local editBox = self.editBox
153,31 → 168,43
self.label:SetText(text)
if self.labelHeight ~= 10 then
self.labelHeight = 10
self.scrollBar:SetPoint("TOP", self.label, "BOTTOM", 0, -19)
self:SetHeight(self.frame.height + 10)
self.label:Show()
end
elseif self.labelHeight ~= 0 then
self.labelHeight = 0
self.label:Hide()
self.scrollBar:SetPoint("TOP", self.frame, "TOP", 0, -23)
self:SetHeight(self.frame.height - 10)
end
Layout(self)
end,
 
["SetNumLines"] = function(self, value)
if not value or value < 4 then
value = 4
end
self:SetHeight(value * 14 + 41 + self.labelHeight)
self.numlines = value
Layout(self)
end,
 
["SetText"] = function(self, text)
self.editBox:SetText(text)
end,
 
["GetText"] = function(self)
return self.editBox:GetText()
end,
 
["SetMaxLetters"] = function (self, num)
self.editBox:SetMaxLetters(num or 0)
end,
 
["DisableButton"] = function(self, disabled)
self.disablebutton = disabled
if disabled then
self.button:Hide()
else
self.button:Show()
end
Layout(self)
end
}
 
204,7 → 231,7
label:SetHeight(10)
 
local button = CreateFrame("Button", ("%s%dButton"):format(Type, widgetNum), frame, "UIPanelButtonTemplate2")
button:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 4)
button:SetPoint("BOTTOMLEFT", 0, 4)
button:SetHeight(22)
button:SetWidth(label:GetStringWidth() + 24)
button:SetText(ACCEPT)
267,7 → 294,9
frame = frame,
label = label,
labelHeight = 10,
numlines = 4,
scrollBar = scrollBar,
scrollBG = scrollBG,
scrollFrame = scrollFrame,
type = Type
}
276,8 → 305,7
end
button.obj, editBox.obj, scrollFrame.obj = widget, widget, widget
 
AceGUI:RegisterAsWidget(widget)
return widget
return AceGUI:RegisterAsWidget(widget)
end
 
AceGUI:RegisterWidgetType(Type, Constructor, Version)
Ace3/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua
1,7 → 1,7
--[[-----------------------------------------------------------------------------
EditBox Widget
-------------------------------------------------------------------------------]]
local Type, Version = "EditBox", 21
local Type, Version = "EditBox", 22
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
 
146,6 → 146,10
HideButton(self)
end,
 
["GetText"] = function(self, text)
return self.editbox:GetText()
end,
 
["SetLabel"] = function(self, text)
if text and text ~= "" then
self.label:SetText(text)
164,6 → 168,9
 
["DisableButton"] = function(self, disabled)
self.disablebutton = disabled
if disabled then
HideButton(self)
end
end,
 
["SetMaxLetters"] = function (self, num)
Ace3/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua
2,13 → 2,13
ScrollFrame Container
Plain container that scrolls its content and doesn't grow in height.
-------------------------------------------------------------------------------]]
local Type, Version = "ScrollFrame", 20
local Type, Version = "ScrollFrame", 21
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
 
-- Lua APIs
local pairs, assert, type = pairs, assert, type
local min, max, floor = math.min, math.max, math.floor
local min, max, floor, abs = math.min, math.max, math.floor, math.abs
 
-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent
77,10 → 77,7
local status = self.status or self.localstatus
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
 
if height > viewheight then
self.scrollbar:Hide()
else
self.scrollbar:Show()
if self.scrollBarShown then
local diff = height - viewheight
local delta = 1
if value < 0 then
97,7 → 94,9
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
local offset = status.offset or 0
local curvalue = self.scrollbar:GetValue()
if viewheight < height then
-- Give us a margin of error of 2 pixels to stop some conditions that i would blame on floating point inaccuracys
-- No-one is going to miss 2 pixels at the bottom of the frame, anyhow!
if viewheight < height + 2 then
if self.scrollBarShown then
self.scrollBarShown = nil
self.scrollbar:Hide()
Ace3/Changelog-Ace3-Release-r971.txt New file
0,0 → 1,51
------------------------------------------------------------------------
r972 | nevcairiel | 2010-10-12 18:56:37 +0000 (Tue, 12 Oct 2010) | 1 line
Changed paths:
A /tags/Release-r971 (from /trunk:971)
 
Tag r971 as release
------------------------------------------------------------------------
r971 | nevcairiel | 2010-10-12 18:55:55 +0000 (Tue, 12 Oct 2010) | 1 line
Changed paths:
M /trunk/changelog.txt
 
Update changelog
------------------------------------------------------------------------
r970 | nevcairiel | 2010-10-12 18:17:30 +0000 (Tue, 12 Oct 2010) | 1 line
Changed paths:
M /trunk/Ace3.toc
 
Bump toc
------------------------------------------------------------------------
r969 | shefki | 2010-10-07 02:11:48 +0000 (Thu, 07 Oct 2010) | 2 lines
Changed paths:
M /trunk/AceConfig-3.0/AceConfig-3.0.lua
 
Add link to documentation of the contents of the options table.
 
------------------------------------------------------------------------
r968 | nevcairiel | 2010-10-06 06:44:54 +0000 (Wed, 06 Oct 2010) | 1 line
Changed paths:
M /trunk/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua
 
AceGUI-3.0: ScrollFrame: Allow for a small margin of error when determining if the scroll bar should be shown.
------------------------------------------------------------------------
r967 | nevcairiel | 2010-09-25 08:20:55 +0000 (Sat, 25 Sep 2010) | 1 line
Changed paths:
M /trunk/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua
 
AceConfigDialog-3.0: Fix a typo that broke options sorting by name in case of same order (Ticket #172)
------------------------------------------------------------------------
r966 | nevcairiel | 2010-09-18 06:34:04 +0000 (Sat, 18 Sep 2010) | 1 line
Changed paths:
D /trunk/AceTab-3.0/AceConfigTab-3.0.lua
M /trunk/AceTab-3.0/AceTab-3.0.xml
 
Remove AceConfigTab as it was never functional and breaks tab completion on 4.0
------------------------------------------------------------------------
r965 | mikk | 2010-08-09 00:47:52 +0000 (Mon, 09 Aug 2010) | 1 line
Changed paths:
M /trunk/CallbackHandler-1.0/CallbackHandler-1.0.lua
 
Upgrade CallbackHandler to rev6 (hrmph... should this be an extern? i guess that might cause packager funz though..)
------------------------------------------------------------------------
LibTalentQuery-1.0/LibTalentQuery-1.0.lua New file
0,0 → 1,358
--[[
Name: LibTalentQuery-1.0
Revision: $Rev: 84 $
Author: Rich Martel (richmartel@gmail.com)
Documentation: http://wowace.com/wiki/LibTalentQuery-1.0
SVN: svn://svn.wowace.com/wow/libtalentquery-1-0/mainline/trunk
Description: Library to help with querying unit talents
Dependancies: LibStub, CallbackHandler-1.0
License: LGPL v2.1
 
Example Usage:
local TalentQuery = LibStub:GetLibrary("LibTalentQuery-1.0")
TalentQuery.RegisterCallback(self, "TalentQuery_Ready")
 
local raidTalents = {}
...
TalentQuery:Query(unit)
...
function MyAddon:TalentQuery_Ready(e, name, realm, unitid)
local isnotplayer = not UnitIsUnit(unitid, "player")
local spec = {}
for tab = 1, GetNumTalentTabs(isnotplayer) do
local treename, _, pointsspent = GetTalentTabInfo(tab, isnotplayer)
tinsert(spec, pointsspent)
end
raidTalents[UnitGUID(unitid)] = spec
end
]]
 
local MAJOR, MINOR = "LibTalentQuery-1.0", 90000 + tonumber(("$Rev: 84 $"):match("(%d+)"))
 
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end
 
local INSPECTDELAY = 1
local INSPECTTIMEOUT = 5
if not lib.events then
lib.events = LibStub("CallbackHandler-1.0"):New(lib)
end
 
local validateTrees
local enteredWorld = IsLoggedIn()
local frame = lib.frame
if not frame then
frame = CreateFrame("Frame", MAJOR .. "_Frame")
lib.frame = frame
end
frame:UnregisterAllEvents()
frame:RegisterEvent("INSPECT_TALENT_READY")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PLAYER_LEAVING_WORLD")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function(this, event, ...)
return lib[event](lib, ...)
end)
 
do
local lastUpdateTime = 0
frame:SetScript("OnUpdate", function(this, elapsed)
lastUpdateTime = lastUpdateTime + elapsed
if lastUpdateTime > INSPECTDELAY then
lib:CheckInspectQueue()
lastUpdateTime = 0
end
end)
frame:Hide()
end
 
local inspectQueue = lib.inspectQueue or {}
lib.inspectQueue = inspectQueue
local garbageQueue = lib.garbageQueue or {} -- Added a second queue to things. Inspects that initially fail are now
lib.garbageQueue = garbageQueue -- thrown into second queue will will be processed once main queue is empty
 
if next(inspectQueue) then
frame:Show()
end
 
local UnitIsPlayer = _G.UnitIsPlayer
local UnitName = _G.UnitName
local UnitExists = _G.UnitExists
local UnitGUID = _G.UnitGUID
local GetNumRaidMembers = _G.GetNumRaidMembers
local GetNumPartyMembers = _G.GetNumPartyMembers
local UnitIsVisible = _G.UnitIsVisible
local UnitIsConnected = _G.UnitIsConnected
local UnitCanAttack = _G.UnitCanAttack
local CanInspect = _G.CanInspect
 
local function UnitFullName(unit)
local name, realm = UnitName(unit)
local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
return namerealm
end
 
-- GuidToUnitID
local function GuidToUnitID(guid)
local prefix, min, max = "raid", 1, GetNumRaidMembers()
if max == 0 then
prefix, min, max = "party", 0, GetNumPartyMembers()
end
 
-- Prioritise getting direct units first because other players targets
-- can change between notify and event which can bugger things up
for i = min, max do
local unit = i == 0 and "player" or prefix .. i
if (UnitGUID(unit) == guid) then
return unit
end
end
 
-- This properly detects target units
if (UnitGUID("target") == guid) then
return "target"
elseif (UnitGUID("focus") == guid) then
return "focus"
elseif (UnitGUID("mouseover") == guid) then
return "mouseover"
end
 
for i = min, max + 3 do
local unit
if i == 0 then
unit = "player"
elseif i == max + 1 then
unit = "target"
elseif i == max + 2 then
unit = "focus"
elseif i == max + 3 then
unit = "mouseover"
else
unit = prefix .. i
end
if (UnitGUID(unit .. "target") == guid) then
return unit .. "target"
elseif (i <= max and UnitGUID(unit.."pettarget") == guid) then
return unit .. "pettarget"
end
end
return nil
end
 
-- Query
function lib:Query(unit)
if (UnitLevel(unit) < 10 or UnitName(unit) == UNKNOWN) then
return
end
 
self.lastQueuedInspectReceived = nil
if UnitIsUnit(unit, "player") then
self.events:Fire("TalentQuery_Ready", UnitName("player"), nil, "player")
else
if type(unit) ~= "string" then
error(("Bad argument #2 to 'Query'. Expected %q, received %q (%s)"):format("string", type(unit), tostring(unit)), 2)
elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
error(("Bad argument #2 to 'Query'. %q is not a valid player unit"):format(tostring(unit)), 2)
elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
error(("Bad argument #2 to 'Query'. %q does not require a server query before reading talents"):format("player"), 2)
else
local name = UnitFullName(unit)
if (not inspectQueue[name]) then
inspectQueue[name] = UnitGUID(unit)
garbageQueue[name] = nil
end
frame:Show()
end
end
end
 
-- CheckInspectQueue
-- Originally, it would wait until no pending NotifyInspect() were expected, and then do it's own.
-- It was also only bother looking at ready results if it had triggered the Notify for that occasion.
-- For the changes I've done, no assumption is made about which mod is performing NotifyInspect().
-- We note the name, unit, time of any inspects done whether from this queue or any other source,
-- we remove from our queue any we were expecting, and use a seperate event in case extra talent
-- info is any time wanted (opportunistic refreshes etc) - Zeksie, 20th May 2009
function lib:CheckInspectQueue()
if (_G.InspectFrame and _G.InspectFrame:IsShown()) then
return
end
 
if (not self.lastInspectTime or self.lastInspectTime < GetTime() - INSPECTTIMEOUT) then
self.lastInspectPending = 0
end
 
if (self.lastInspectPending > 0 or not enteredWorld) then
return
end
 
if (self.lastQueuedInspectReceived and self.lastQueuedInspectReceived < GetTime() - 60) then
-- No queued results received for a minute, so purge the queue as invalid and move on with our lives
self.lastQueuedInspectReceived = nil
inspectQueue = {}
lib.inspectQueue = inspectQueue
garbageQueue = {}
lib.garbageQueue = garbageQueue
frame:Hide()
return
end
 
for name,guid in pairs(inspectQueue) do
local unit = GuidToUnitID(guid)
if (not unit) then
inspectQueue[name] = nil
else
if (UnitIsVisible(unit) and UnitIsConnected(unit) and not UnitCanAttack("player", unit) and not UnitCanAttack(unit, "player") and CanInspect(unit) and UnitClass(unit)) then
NotifyInspect(unit)
break
else
garbageQueue[name] = guid -- Not available, throw into secondary queue and continue
inspectQueue[name] = nil
end
end
end
 
if (not next(inspectQueue)) then
if (next(garbageQueue)) then
-- Retry initially failed inspects
lib.inspectQueue = garbageQueue
inspectQueue = lib.inspectQueue
lib.garbageQueue = {}
garbageQueue = lib.garbageQueue
else
frame:Hide()
end
end
end
 
-- NotifyInspect
if not lib.NotifyInspect then -- don't hook twice
hooksecurefunc("NotifyInspect", function(...) return lib:NotifyInspect(...) end)
end
function lib:NotifyInspect(unit)
if (not (UnitExists(unit) and UnitIsVisible(unit) and UnitIsConnected(unit) and CheckInteractDistance(unit, 4))) then
return
end
self.lastInspectUnit = unit
self.lastInspectGUID = UnitGUID(unit)
self.lastInspectTime = GetTime()
self.lastInspectName = UnitFullName(unit)
self.lastInspectPending = self.lastInspectPending + 1
local isnotplayer = not UnitIsUnit("player", unit)
self.lastInspectTree = GetTalentTabInfo(1, isnotplayer) -- Talent tree names are available immediately
end
 
-- Reset
function lib:Reset()
self.lastInspectPending = 0
self.lastInspectUnit = nil
self.lastInspectTime = nil
self.lastInspectName = nil
self.lastInspectGUID = nil
self.lastInspectTree = nil
end
 
-- INSPECT_TALENT_READY
function lib:INSPECT_TALENT_READY()
self.lastInspectPending = self.lastInspectPending - 1
 
-- Results are valid only when we have received as many events as we have posted notifies
if (self.lastInspectName and self.lastInspectPending == 0) then
-- Check unit ID is still pointing to same actual unit
if (UnitGUID(self.lastInspectUnit) == self.lastInspectGUID) then
local guid = inspectQueue[self.lastInspectName]
inspectQueue[self.lastInspectName] = nil
 
local name, realm = strsplit("-", self.lastInspectName)
 
self.lastQueuedInspectReceived = GetTime()
 
-- Notify of expected talent results
local isnotplayer = not UnitIsUnit("player", self.lastInspectName)
local group = GetActiveTalentGroup(isnotplayer)
local tree1, _, spent1 = GetTalentTabInfo(1, isnotplayer, nil, group)
if (tree1 ~= self.lastInspectTree) then
-- Expected talent tree name to be the same as it was when we triggered the NotifyInspect()
garbageQueue[self.lastInspectName] = self.lastInspectGUID
self:Reset()
self:CheckInspectQueue()
return
 
elseif (validateTrees) then
-- Double checking here. Check the tree name matches what we expect for this class
local _, class = UnitClass(self.lastInspectUnit)
if (tree1 ~= validateTrees[class]) then
garbageQueue[self.lastInspectName] = self.lastInspectGUID
self:Reset()
self:CheckInspectQueue()
return
end
end
 
local tree2, _, spent2 = GetTalentTabInfo(2, isnotplayer, nil, group)
local tree3, _, spent3 = GetTalentTabInfo(3, isnotplayer, nil, group)
if ((spent1 or 0) + (spent2 or 0) + (spent3 or 0) > 0) then
if (guid) then
-- It was in our queue
self.events:Fire("TalentQuery_Ready", name, realm, self.lastInspectUnit)
else
-- Also notify of non-expected ones, as it's entirely useful to refresh them if they're there
-- It is up to the receiving applicating to determine whether they want to receive the information
self.events:Fire("TalentQuery_Ready_Outsider", name, realm, self.lastInspectUnit)
end
else
-- Tree came back with zero points spent, probably an issue while logging in
garbageQueue[self.lastInspectName] = guid
end
end
 
self:Reset()
self:CheckInspectQueue()
end
end
 
function lib:PLAYER_ENTERING_WORLD()
-- We can't inspect other's talents until now
-- We just get 0/0/0 back even though we get an INSPECT_TALENT_READY event
enteredWorld = true
end
 
function lib:PLAYER_LEAVING_WORLD()
enteredWorld = nil
end
 
function lib:PLAYER_LOGIN()
validateTrees = {
DRUID = "Balance",
PRIEST = "Discipline",
ROGUE = "Assassination",
HUNTER = "Beast Mastery",
WARLOCK = "Affliction",
WARRIOR = "Arms",
DEATHKNIGHT = "Blood",
PALADIN = "Holy",
SHAMAN = "Elemental",
MAGE = "Arcane",
}
 
if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") then
-- LibBabble-TalentTree-3.0 only loaded if present and not enUS
local LBT = LibStub("LibBabble-TalentTree-3.0", true)
if (not LBT) then
LoadAddOn("LibBabble-TalentTree-3.0")
LBT = LibStub("LibBabble-TalentTree-3.0", true)
end
LBT = LBT and LBT:GetLookupTable()
if (LBT) then
for class,tree1 in pairs(validateTrees) do
validateTrees[class] = LBT[tree1]
end
else
validateTrees = nil
end
end
 
self.PLAYER_LOGIN = nil
end
 
lib:Reset()
LibTalentQuery-1.0/LibStub/LibStub.lua New file
0,0 → 1,30
-- 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
LibTalentQuery-1.0/LibStub/LibStub.toc New file
0,0 → 1,13
## Interface: 20400
## Title: Lib: LibStub
## Notes: Universal Library Stub
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
## X-Website: http://jira.wowace.com/browse/LS
## X-Category: Library
## X-License: Public Domain
## X-Curse-Packaged-Version: 3.3 Release 2
## X-Curse-Project-Name: LibTalentQuery-1.0
## X-Curse-Project-ID: libtalentquery-1-0
## X-Curse-Repository-ID: wow/libtalentquery-1-0/mainline
 
LibStub.lua
LibTalentQuery-1.0/LibTalentQuery-1.0.toc New file
0,0 → 1,18
## Interface: 30300
## LoadOnDemand: 1
## Title: Lib: TalentQuery 1.0
## Notes: Library to help with querying unit talents.
## Author: Peragor
## Version: $Rev: 82 $
## X-Category: Library
## X-ReleaseDate: $Date: 2009-12-09 11:08:46 +0000 (Wed, 09 Dec 2009) $
## X-Website: http://wowace.com/wiki/LibTalentQuery-1.0
## X-License: LGPL v2.1
## X-Curse-Packaged-Version: 3.3 Release 2
## X-Curse-Project-Name: LibTalentQuery-1.0
## X-Curse-Project-ID: libtalentquery-1-0
## X-Curse-Repository-ID: wow/libtalentquery-1-0/mainline
 
LibStub\LibStub.lua
CallbackHandler-1.0\CallbackHandler-1.0.lua
lib.xml
LibTalentQuery-1.0/lib.xml New file
0,0 → 1,3
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd">
<Script file="LibTalentQuery-1.0.lua"/>
</Ui>
\ No newline at end of file
LibTalentQuery-1.0/CallbackHandler-1.0/CallbackHandler-1.0.lua New file
0,0 → 1,239
--[[ $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.
 
LibTalentQuery-1.0/Changelog-LibTalentQuery-1.0-3.3 Release 2.txt New file
0,0 → 1,14
------------------------------------------------------------------------
r85 | zeksie | 2010-08-27 16:58:05 +0000 (Fri, 27 Aug 2010) | 1 line
Changed paths:
A /tags/3.3 Release 2 (from /trunk:84)
 
Tagging as 3.3 Release 2
------------------------------------------------------------------------
r84 | greltok | 2010-08-24 07:38:56 +0000 (Tue, 24 Aug 2010) | 2 lines
Changed paths:
M /trunk/LibTalentQuery-1.0.lua
 
Now handles "mouseover" when converting from guid to unitid.
Reported in Curse comments by kliegs.
------------------------------------------------------------------------
LibDeformat-3.0/LibDeformat-3.0.lua New file
0,0 → 1,272
--[[
Name: LibDeformat-3.0
Author(s): ckknight (ckknight@gmail.com)
Website: http://www.wowace.com/projects/libdeformat-3-0/
Description: A library to convert a post-formatted string back to its original arguments given its format string.
License: MIT
]]
 
local LibDeformat = LibStub:NewLibrary("LibDeformat-3.0", 1)
 
if not LibDeformat then
return
end
 
-- this function does nothing and returns nothing
local function do_nothing()
end
 
-- a dictionary of format to match entity
local FORMAT_SEQUENCES = {
["s"] = ".+",
["c"] = ".",
["%d*d"] = "%%-?%%d+",
["[fg]"] = "%%-?%%d+%%.?%%d*",
["%%%.%d[fg]"] = "%%-?%%d+%%.?%%d*",
}
 
-- a set of format sequences that are string-based, i.e. not numbers.
local STRING_BASED_SEQUENCES = {
["s"] = true,
["c"] = true,
}
 
local cache = setmetatable({}, {__mode='k'})
-- generate the deformat function for the pattern, or fetch from the cache.
local function get_deformat_function(pattern)
local func = cache[pattern]
if func then
return func
end
 
-- escape the pattern, so that string.match can use it properly
local unpattern = '^' .. pattern:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") .. '$'
 
-- a dictionary of index-to-boolean representing whether the index is a number rather than a string.
local number_indexes = {}
 
-- (if the pattern is a numbered format,) a dictionary of index-to-real index.
local index_translation = nil
 
-- the highest found index, also the number of indexes found.
local highest_index
if not pattern:find("%%1%$") then
-- not a numbered format
 
local i = 0
while true do
i = i + 1
local first_index
local first_sequence
for sequence in pairs(FORMAT_SEQUENCES) do
local index = unpattern:find("%%%%" .. sequence)
if index and (not first_index or index < first_index) then
first_index = index
first_sequence = sequence
end
end
if not first_index then
break
end
unpattern = unpattern:gsub("%%%%" .. first_sequence, "(" .. FORMAT_SEQUENCES[first_sequence] .. ")", 1)
number_indexes[i] = not STRING_BASED_SEQUENCES[first_sequence]
end
 
highest_index = i - 1
else
-- a numbered format
 
local i = 0
while true do
i = i + 1
local found_sequence
for sequence in pairs(FORMAT_SEQUENCES) do
if unpattern:find("%%%%" .. i .. "%%%$" .. sequence) then
found_sequence = sequence
break
end
end
if not found_sequence then
break
end
unpattern = unpattern:gsub("%%%%" .. i .. "%%%$" .. found_sequence, "(" .. FORMAT_SEQUENCES[found_sequence] .. ")", 1)
number_indexes[i] = not STRING_BASED_SEQUENCES[found_sequence]
end
highest_index = i - 1
 
i = 0
index_translation = {}
pattern:gsub("%%(%d)%$", function(w)
i = i + 1
index_translation[i] = tonumber(w)
end)
end
 
if highest_index == 0 then
cache[pattern] = do_nothing
else
--[=[
-- resultant function looks something like this:
local unpattern = ...
return function(text)
local a1, a2 = text:match(unpattern)
if not a1 then
return nil, nil
end
return a1+0, a2
end
 
-- or if it were a numbered pattern,
local unpattern = ...
return function(text)
local a2, a1 = text:match(unpattern)
if not a1 then
return nil, nil
end
return a1+0, a2
end
]=]
 
local t = {}
t[#t+1] = [=[
return function(text)
local ]=]
 
for i = 1, highest_index do
if i ~= 1 then
t[#t+1] = ", "
end
t[#t+1] = "a"
if not index_translation then
t[#t+1] = i
else
t[#t+1] = index_translation[i]
end
end
 
t[#t+1] = [=[ = text:match(]=]
t[#t+1] = ("%q"):format(unpattern)
t[#t+1] = [=[)
if not a1 then
return ]=]
 
for i = 1, highest_index do
if i ~= 1 then
t[#t+1] = ", "
end
t[#t+1] = "nil"
end
 
t[#t+1] = "\n"
t[#t+1] = [=[
end
]=]
 
t[#t+1] = "return "
for i = 1, highest_index do
if i ~= 1 then
t[#t+1] = ", "
end
t[#t+1] = "a"
t[#t+1] = i
if number_indexes[i] then
t[#t+1] = "+0"
end
end
t[#t+1] = "\n"
t[#t+1] = [=[
end
]=]
 
t = table.concat(t, "")
 
-- print(t)
 
cache[pattern] = assert(loadstring(t))()
end
 
return cache[pattern]
end
 
--- Return the arguments of the given format string as found in the text.
-- @param text The resultant formatted text.
-- @param pattern The pattern used to create said text.
-- @return a tuple of values, either strings or numbers, based on the pattern.
-- @usage LibDeformat.Deformat("Hello, friend", "Hello, %s") == "friend"
-- @usage LibDeformat.Deformat("Hello, friend", "Hello, %1$s") == "friend"
-- @usage LibDeformat.Deformat("Cost: $100", "Cost: $%d") == 100
-- @usage LibDeformat.Deformat("Cost: $100", "Cost: $%1$d") == 100
-- @usage LibDeformat.Deformat("Alpha, Bravo", "%s, %s") => "Alpha", "Bravo"
-- @usage LibDeformat.Deformat("Alpha, Bravo", "%1$s, %2$s") => "Alpha", "Bravo"
-- @usage LibDeformat.Deformat("Alpha, Bravo", "%2$s, %1$s") => "Bravo", "Alpha"
-- @usage LibDeformat.Deformat("Hello, friend", "Cost: $%d") == nil
-- @usage LibDeformat("Hello, friend", "Hello, %s") == "friend"
function LibDeformat.Deformat(text, pattern)
if type(text) ~= "string" then
error(("Argument #1 to `Deformat' must be a string, got %s (%s)."):format(type(text), text), 2)
elseif type(pattern) ~= "string" then
error(("Argument #2 to `Deformat' must be a string, got %s (%s)."):format(type(pattern), pattern), 2)
end
 
return get_deformat_function(pattern)(text)
end
 
--[===[@debug@
function LibDeformat.Test()
local function tuple(success, ...)
if success then
return true, { n = select('#', ...), ... }
else
return false, ...
end
end
 
local function check(text, pattern, ...)
local success, results = tuple(pcall(LibDeformat.Deformat, text, pattern))
if not success then
return false, results
end
 
if select('#', ...) ~= results.n then
return false, ("Differing number of return values. Expected: %d. Actual: %d."):format(select('#', ...), results.n)
end
 
for i = 1, results.n do
local expected = select(i, ...)
local actual = results[i]
if type(expected) ~= type(actual) then
return false, ("Return #%d differs by type. Expected: %s (%s). Actual: %s (%s)"):format(type(expected), expected, type(actual), actual)
elseif expected ~= actual then
return false, ("Return #%d differs. Expected: %s. Actual: %s"):format(expected, actual)
end
end
 
return true
end
 
local function test(text, pattern, ...)
local success, problem = check(text, pattern, ...)
if not success then
print(("Problem with (%q, %q): %s"):format(text, pattern, problem or ""))
end
end
 
test("Hello, friend", "Hello, %s", "friend")
test("Hello, friend", "Hello, %1$s", "friend")
test("Cost: $100", "Cost: $%d", 100)
test("Cost: $100", "Cost: $%1$d", 100)
test("Alpha, Bravo", "%s, %s", "Alpha", "Bravo")
test("Alpha, Bravo", "%1$s, %2$s", "Alpha", "Bravo")
test("Alpha, Bravo", "%2$s, %1$s", "Bravo", "Alpha")
test("Alpha, Bravo, Charlie, Delta, Echo", "%s, %s, %s, %s, %s", "Alpha", "Bravo", "Charlie", "Delta", "Echo")
test("Alpha, Bravo, Charlie, Delta, Echo", "%1$s, %2$s, %3$s, %4$s, %5$s", "Alpha", "Bravo", "Charlie", "Delta", "Echo")
test("Alpha, Bravo, Charlie, Delta, Echo", "%5$s, %4$s, %3$s, %2$s, %1$s", "Echo", "Delta", "Charlie", "Bravo", "Alpha")
test("Alpha, Bravo, Charlie, Delta, Echo", "%2$s, %3$s, %4$s, %5$s, %1$s", "Echo", "Alpha", "Bravo", "Charlie", "Delta")
test("Alpha, Bravo, Charlie, Delta, Echo", "%3$s, %4$s, %5$s, %1$s, %2$s", "Delta", "Echo", "Alpha", "Bravo", "Charlie")
test("Alpha, Bravo, Charlie, Delta", "%s, %s, %s, %s, %s", nil, nil, nil, nil, nil)
test("Hello, friend", "Cost: $%d", nil)
print("LibDeformat-3.0: Tests completed.")
end
--@end-debug@]===]
 
setmetatable(LibDeformat, { __call = function(self, ...) return self.Deformat(...) end })
\ No newline at end of file
LibDeformat-3.0/LibDeformat-3.0.toc New file
0,0 → 1,15
## Interface: 30300
## Title: Lib: Deformat-3.0
## Notes: A library to convert a post-formatted string back to its original arguments given its format string.
## Author: ckknight
## X-eMail: ckknight@gmail.com
## X-Category: Library
## X-License: MIT
## LoadOnDemand: 1
## X-Curse-Packaged-Version: v1
## X-Curse-Project-Name: LibDeformat-3.0
## X-Curse-Project-ID: libdeformat-3-0
## X-Curse-Repository-ID: wow/libdeformat-3-0/mainline
 
LibStub\LibStub.lua
lib.xml
LibDeformat-3.0/Changelog-LibDeformat-3.0-v1.txt New file
0,0 → 1,16
tag v1
0cb38e41ea596a557a070cd7b8157e81d3ef2d0b
Cameron Knight <ckknight@Cameron-Knights-MacBook-Pro.local>
2010-01-04 14:45:16 -0600
 
Tagging as v1
 
 
--------------------
 
Cameron Knight:
- Fix folder name in .pkgmeta
- Add tests to main LibDeformat-3.0.lua, accessible only in debug mode and LibDeformat.Test() must be called to run the tests. Remove other tests, as they're not really useful anymore.
- Add some comments and move some code around.
- change @example to @usage
- Initial commit
LibDeformat-3.0/LibStub/LibStub.lua New file
0,0 → 1,30
-- 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
LibDeformat-3.0/lib.xml New file
0,0 → 1,4
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Script file="LibDeformat-3.0.lua" />
</Ui>
\ No newline at end of file