vanilla-wow-addons – Rev 1
?pathlinks?
--[[
Name: RosterLib-2.0
Revision: $Revision: 8205 $
X-ReleaseDate: "$Date: 2006-08-10 08:55:29 +0200 (Thu, 10 Aug 2006) $
Author: Maia (maia.proudmoore@gmail.com)
Website: http://wiki.wowace.com/index.php/RosterLib-2.0
Documentation: http://wiki.wowace.com/index.php/RosterLib-2.0_API_Documentation
SVN: http://svn.wowace.com/root/trunk/RosterLib-2.0/
Description: party/raid roster management
Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0
]]
local MAJOR_VERSION = "RosterLib-2.0"
local MINOR_VERSION = "$Revision: 8205 $"
if not AceLibrary then error(vmajor .. " requires AceLibrary.") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
if not AceLibrary:HasInstance("AceEvent-2.0") then error(MAJOR_VERSION .. " requires AceEvent-2.0") end
local Compost = AceLibrary:HasInstance("Compost-2.0") and AceLibrary("Compost-2.0")
local rosterCount, addonCount, standby
local updatedUnits = Compost and Compost:Acquire() or {}
local RosterLib = {}
AceLibrary("AceEvent-2.0"):embed(RosterLib)
------------------------------------------------
-- activate, enable, disable
------------------------------------------------
local function print(text)
DEFAULT_CHAT_FRAME:AddMessage(text)
end
local function activate(self, oldLib, oldDeactivate)
RosterLib = self
if oldLib then
self.registry = oldLib.registry
end
if not self.registry then
self.registry = {}
end
if oldDeactivate then
oldDeactivate(oldLib)
end
addonCount = 0
end
function RosterLib:Enable()
addonCount = addonCount + 1
if addonCount > 1 then return end
if AceLibrary("AceEvent-2.0"):IsFullyInitialized() or standby then
RosterLib:AceEvent_FullyInitialized()
else
self:RegisterEvent("AceEvent_FullyInitialized")
end
standby = false
self.roster = Compost and Compost:Acquire() or {}
rosterCount = 0
end
function RosterLib:Disable()
addonCount = addonCount - 1
if addonCount > 0 then return end
self:TriggerEvent("RosterLib_Disabled")
self:UnregisterAllEvents()
standby = true
rosterAvailable = false
for name in pairs(self.roster) do
if Compost then Compost:Reclaim(self.roster[name]) end
self.roster[name] = nil
end
rosterCount = 0
end
------------------------------------------------
-- Internal functions
------------------------------------------------
function RosterLib:AceEvent_FullyInitialized()
self:TriggerEvent("RosterLib_Enabled")
self:RegisterEvent("RAID_ROSTER_UPDATE","UpdateRoster")
self:RegisterEvent("PARTY_MEMBERS_CHANGED","UpdateRoster")
self:RegisterEvent("UNIT_PET","UpdateRoster")
self:CheckRoster()
end
--[[
roster checks:
1. AceEvent_FullyInitialized() is triggered when data *should* be available. It will
register party/raid/pet events as we don't want then to fire before.
Also, it will trigger CheckRoster().
2. CheckRoster() will compare the roster table with the currently available data. If
there's a difference, it will schedule UpdateRoster() with a delay of 0.5sec.
3. UpdateRoster() will create/update the roster. To see if everything worked fine,
it will call CheckRoster() when it's done, this time with a silent flag.
4. Now the saved data should be valid. If it is, it'll trigger the event
RosterLib_RosterChanged your addon can listen to. It'll pass a table with the unit
names that have changed as first argument. If not (server lag?) it'll send a
RosterLib_PendingRefresh event in case the roster was valid before (to not spam
this event when we're waiting for the server)
5. WOW events that are related to roster updates directly trigger UpdateRoster().
]]
local function GetName(unit)
if not UnitExists(unit) then return nil end
local n = UnitName(unit)
if n and n ~= UNKNOWNOBJECT and n ~= UKNOWNBEING then return n end
end
local function UnitIterator()
local pmem, rmem = GetNumPartyMembers(), GetNumRaidMembers()
local playersent = false
local petsent = false
local unitcount = 1
local petcount = 1
local unit, name
return function()
-- STEP 1: pet
if not petsent then
petsent = true
if rmem == 0 then
unit = "pet"
name = GetName(unit)
if name and not UnitIsCharmed(unit) then
return unit, name
end
end
end
-- STEP 2: player
if not playersent then
playersent = true
if rmem == 0 then
unit = "player"
name = GetName(unit)
if name then
return unit, name
end
end
end
-- STEP 3: raid units
if rmem > 0 then
-- STEP 3a: pet units
for i = petcount, rmem do
unit = string.format("raidpet%d", i)
petcount = petcount + 1
name = GetName(unit)
if name and not UnitIsCharmed(unit) then
return unit, name
end
end
-- STEP 3b: player units
for i = unitcount, rmem do
unit = string.format("raid%d", i)
unitcount = unitcount + 1
name = GetName(unit)
if name then
return unit, name
end
end
-- STEP 4: party units
elseif pmem > 0 then
-- STEP 3a: pet units
for i = petcount, pmem do
unit = string.format("partypet%d", i)
petcount = petcount + 1
name = GetName(unit)
if name and not UnitIsCharmed(unit) then
return unit, name
end
end
-- STEP 3b: player units
for i = unitcount, pmem do
unit = string.format("party%d", i)
unitcount = unitcount + 1
name = GetName(unit)
if name then
return unit, name
end
end
end
end
end
local function RosterValid(silent)
local mem = 0
local n, u
-- STEP 1: count how many units the roster should have
-- note: if the player has joined a raid but doesnt have a raid id yet
-- (can take a second or two), the count will be one less.
local testRoster = Compost and Compost:Acquire() or {}
for u,n in UnitIterator(true) do
testRoster[n] = true
end
for i in testRoster do
mem = mem + 1
end
if Compost then Compost:Reclaim(testRoster) end
if mem ~= rosterCount then
if rosterAvailable then
print(string.format("RosterLib: count mismatch: roster:%d members:%d (TELL MAIA)", rosterCount, mem))
end
return false
end
-- STEP 2: compare unit names
for n in pairs(RosterLib.roster) do
u = RosterLib.roster[n]
if n ~= UnitName(u.unitid) then
if rosterAvailable then
print(string.format("name mismatch: name:%s, UnitName(%s):%q (TELL MAIA)", n or "nil", u.unitid or "nil", UnitName(u.unitid) or "nil"))
end
return false
end
end
return true
end
function RosterLib:CheckRoster(silent)
local wasAvailable = rosterAvailable
if RosterValid(silent) then
rosterAvailable = true
if silent then
local cnt = 0
for name in pairs(updatedUnits) do cnt = cnt + 1 end
if cnt > 0 then
self:TriggerEvent("RosterLib_RosterChanged", updatedUnits)
for name in pairs(updatedUnits) do
local u = updatedUnits[name]
self:TriggerEvent("RosterLib_UnitChanged", u.unitid, u.name, u.class, u.subgroup, u.rank, u.oldname, u.oldunitid, u.oldclass, u.oldsubgroup, u.oldrank)
if Compost then Compost:Reclaim(updatedUnits[name]) end
updatedUnits[name] = nil
end
end
end
else
rosterAvailable = false
if wasAvailable then
print("RosterLib_PendingRefresh")
self:TriggerEvent("RosterLib_PendingRefresh")
end
self:ScheduleEvent(self.UpdateRoster, 0.5, self) --what do we need to change here to convert it to a local schedule?
end
end
function RosterLib:UpdateRoster()
local unitid, name
-- print("RosterLib:UpdateRoster")
-- STEP 1: copy the roster to be able to compare it later.
local oldRoster = Compost and Compost:Acquire() or {}
for name in self.roster do
oldRoster[name] = Compost and Compost:Acquire() or {}
oldRoster[name].name = self.roster[name].name
oldRoster[name].unitid = self.roster[name].unitid
oldRoster[name].class = self.roster[name].class
oldRoster[name].rank = self.roster[name].rank
oldRoster[name].subgroup = self.roster[name].subgroup
end
-- STEP 2: clear the .unitids, as we're replacing them and removing old units later
for name in pairs(self.roster) do self.roster[name].unitid = nil end
-- STEP 3: add all units and see if something has changed.
for unitid, name in UnitIterator(true) do
-- object
if not self.roster[name] then
self.roster[name] = Compost and Compost:Acquire() or {}
end
-- name
self.roster[name].name = name
-- unitid
self.roster[name].unitid = unitid
-- class
if string.find(unitid,"pet") then
self.roster[name].class = "PET"
else
_,self.roster[name].class = UnitClass(unitid)
end
-- subgroup and rank
if GetNumRaidMembers() > 0 then
local _,_,num = string.find(unitid, "(%d+)")
_,self.roster[name].rank,self.roster[name].subgroup = GetRaidRosterInfo(num)
else
self.roster[name].subgroup = 1
self.roster[name].rank = 0
end
-- compare data
if not oldRoster[name]
or self.roster[name].name ~= oldRoster[name].name
or self.roster[name].unitid ~= oldRoster[name].unitid
or self.roster[name].class ~= oldRoster[name].class
or self.roster[name].subgroup ~= oldRoster[name].subgroup
or self.roster[name].rank ~= oldRoster[name].rank
then
updatedUnits[name] = Compost and Compost:Acquire() or {}
updatedUnits[name].oldname = (oldRoster[name] and oldRoster[name].name) or nil
updatedUnits[name].oldunitid = (oldRoster[name] and oldRoster[name].unitid) or nil
updatedUnits[name].oldclass = (oldRoster[name] and oldRoster[name].class) or nil
updatedUnits[name].oldsubgroup = (oldRoster[name] and oldRoster[name].subgroup) or nil
updatedUnits[name].oldrank = (oldRoster[name] and oldRoster[name].rank) or nil
updatedUnits[name].name = self.roster[name].name
updatedUnits[name].unitid = self.roster[name].unitid
updatedUnits[name].class = self.roster[name].class
updatedUnits[name].subgroup = self.roster[name].subgroup
updatedUnits[name].rank = self.roster[name].rank
end
end
-- STEP 4: now delete the old units in roster that have left your group
for name in pairs(self.roster) do
if not self.roster[name].unitid then
if Compost then Compost:Reclaim(self.roster[name]) end
self.roster[name] = nil
updatedUnits[name] = Compost and Compost:Acquire() or {}
updatedUnits[name].oldname = oldRoster[name].name
updatedUnits[name].oldunitid = oldRoster[name].unitid
updatedUnits[name].oldclass = oldRoster[name].class
updatedUnits[name].oldsubgroup = oldRoster[name].subgroup
updatedUnits[name].oldrank = oldRoster[name].rank
end
end
-- STEP 5: save the number of units, we need that in RosterValid()
rosterCount = 0
for name in pairs(self.roster) do rosterCount = rosterCount + 1 end
-- STEP 6: clear oldRoster
if Compost then Compost:Reclaim(oldRoster, 1) end
-- STEP 6: we're done. Hopefully.
self:CheckRoster(true)
end
------------------------------------------------
-- API
------------------------------------------------
function RosterLib:GetUnitIDFromName(name)
if rosterAvailable and self.roster[name] then
return self.roster[name].unitid
else
return nil
end
end
function RosterLib:GetUnitIDFromUnit(unit)
local name = UnitName(unit)
if rosterAvailable and name and self.roster[name] then
return self.roster[name].unitid
else
return nil
end
end
function RosterLib:GetUnitObjectFromName(name)
if rosterAvailable and self.roster[name] then
return self.roster[name]
else
return nil
end
end
function RosterLib:GetUnitObjectFromUnit(unit)
local name = UnitName(unit)
if rosterAvailable and self.roster[name] then
return self.roster[name]
else
return nil
end
end
function RosterLib:IterateRoster(pets)
local key
return function()
key = next(self.roster, key)
if key and (pets or self.roster[key].class ~= "PET") then
return self.roster[key]
end
end
end
--[[
TODO:
- use events AceEvent_EventRegistered, AceEvent_EventUnregistered plus :IsEventRegistered("event") to see if we can disable the library
- optimize GC in IterateRoster()
]]
AceLibrary:Register(RosterLib, MAJOR_VERSION, MINOR_VERSION, activate)
RosterLib = AceLibrary(MAJOR_VERSION)