--[[ 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)