--[[ Name: Roster-2.1 Revision: $Revision: 21071 $ X-ReleaseDate: $Date: 2006-12-17 13:56:50 -0500 (Sun, 17 Dec 2006) $ Author: Maia (maia.proudmoore@gmail.com) Website: http://www.wowace.com/ Documentation: http://www.wowace.com/wiki/Roster-2.1 SVN: http://svn.wowace.com/wowace/trunk/RosterLib/ Description: Party/raid roster management Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0 ]] local MAJOR_VERSION = "Roster-2.1" local MINOR_VERSION = "$Revision: 21071 $" if not AceLibrary then error(MAJOR_VERSION .. " 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 unknownUnits = {} local Lib = {} local roster local new, del do local cache = setmetatable({}, {__mode='k'}) function new() local t = next(cache) if t then cache[t] = nil return t else return {} end end function del(t) for k in pairs(t) do t[k] = nil end cache[t] = true return nil end end ------------------------------------------------ -- activate, enable, disable ------------------------------------------------ local function activate(self, oldLib, oldDeactivate) if oldLib then if oldLib.roster then self.roster = oldLib.roster end oldLib:UnregisterAllEvents() oldLib:CancelAllScheduledEvents() end if not self.roster then self.roster = {} end if oldDeactivate then oldDeactivate(oldLib) end roster = self.roster end local function external(self, major, instance) if major == "AceEvent-2.0" then instance:embed(self) if instance:IsFullyInitialized() then self:AceEvent_FullyInitialized() else self:RegisterEvent("AceEvent_FullyInitialized") end end end ------------------------------------------------ -- Internal functions ------------------------------------------------ function Lib:AceEvent_FullyInitialized() self:TriggerEvent("RosterLib_Enabled") self:RegisterBucketEvent({"RAID_ROSTER_UPDATE", "PARTY_MEMBERS_CHANGED"}, 0.2, "ScanFullRoster") self:RegisterBucketEvent("UNIT_PET", 0.2, "ScanPet") self:ScanFullRoster() end ------------------------------------------------ -- Unit iterator ------------------------------------------------ local UnitIterator do local rmem, pmem, step, count local function SelfIterator() while step do local unit if step == 1 then -- STEP 1: player unit = "player" step = 2 elseif step == 2 then -- STEP 2: pet unit = "pet" step = nil end if unit and UnitExists(unit) then return unit end end end local function SelfAndPartyIterator() while step do local unit if step <= 2 then unit = SelfIterator() if not step then step = 3 end elseif step == 3 then -- STEP 3: party units unit = string.format("party%d", count) step = 4 elseif step == 4 then -- STEP 4: party pets unit = string.format("partypet%d", count) count = count + 1 step = count <= pmem and 3 or nil end if unit and UnitExists(unit) then return unit end end end local function RaidIterator() while step do local unit if step == 1 then -- STEP 1: raid units unit = string.format("raid%d", count) step = 2 elseif step == 2 then -- STEP 2: raid pets unit = string.format("raidpet%d", count) count = count + 1 step = count <= rmem and 1 or nil end if unit and UnitExists(unit) then return unit end end end function UnitIterator() rmem = GetNumRaidMembers() step = 1 if rmem == 0 then pmem = GetNumPartyMembers() if pmem == 0 then return SelfIterator, false else count = 1 return SelfAndPartyIterator, false end else count = 1 return RaidIterator, true end end end ------------------------------------------------ -- Roster code ------------------------------------------------ local rosterScanCache = {} function Lib:ScanFullRoster() local changed local it, isInRaid = UnitIterator() -- save all units we currently have, this way we can check who to remove from roster later. for name in pairs(roster) do rosterScanCache[name] = true end -- update data for unitid in it do local name = self:CreateOrUpdateUnit(unitid, isInRaid) -- we successfully added a unit, so we don't need to remove it next step if name then rosterScanCache[name] = nil changed = true end end -- clear units we had in roster that either left the raid or are unknown for some reason. for name in pairs(rosterScanCache) do self:RemoveUnit(name) rosterScanCache[name] = nil changed = true end self:ProcessRoster() if changed then self:TriggerEvent("RosterLib_RosterUpdated") end end function Lib:ScanPet(owner_list) local changed for owner in pairs(owner_list) do local unitid = self:GetPetFromOwner(owner) if not unitid then return end if not UnitExists(unitid) then unknownUnits[unitid] = nil -- find the pet in the roster we need to delete for _,u in pairs(roster) do if u.unitid == unitid then self:RemoveUnit(u.name) changed = true end end else _, changed = self:CreateOrUpdateUnit(unitid) end self:ProcessRoster() end if changed then self:TriggerEvent("RosterLib_RosterUpdated") end end function Lib:GetPetFromOwner(id) -- convert party3 crap to raid IDs when in raid. local owner = self:GetUnitIDFromUnit(id) if not owner then return end -- get ID if owner:find("raid") then return owner:gsub("raid", "raidpet") elseif owner:find("party") then return owner:gsub("party", "partypet") elseif owner == "player" then return "pet" else return nil end end function Lib:ScanUnknownUnits() local changed for unitid in pairs(unknownUnits) do local name, c if UnitExists(unitid) then name, c = self:CreateOrUpdateUnit(unitid) else unknownUnits[unitid] = nil end -- some pets never have a name. too bad for them, farewell! if not name and unitid:find("pet") then unknownUnits[unitid] = nil else changed = changed or c end end self:ProcessRoster() if changed then self:TriggerEvent("RosterLib_RosterUpdated") end end function Lib:ProcessRoster() if next(unknownUnits, nil) then self:CancelScheduledEvent("ScanUnknownUnits") self:ScheduleEvent("ScanUnknownUnits", self.ScanUnknownUnits, 1, self) end end function Lib:CreateOrUpdateUnit(unitid, isInRaid) -- check for name local name = UnitName(unitid) if name and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING and not UnitIsCharmed(unitid) then local unit = roster[name] local isPet = unitid:find("pet") -- clear stuff unknownUnits[unitid] = nil -- return if a pet attempts to replace a player name -- this doesnt fix the problem with 2 pets overwriting each other FIXME if isPet and unit and unit.class ~= "PET" then return name end -- save old data if existing local old_name, old_unitid, old_class, old_rank, old_subgroup, old_online if unit then old_name = unit.name old_unitid = unit.unitid old_class = unit.class old_rank = unit.rank old_subgroup = unit.subgroup old_online = unit.online else -- object unit = new() roster[name] = unit end local new_name, new_unitid, new_class, new_rank, new_subgroup, new_online -- name new_name = name -- unitid new_unitid = unitid -- class if isPet then new_class = "PET" else _, new_class = UnitClass(unitid) end if isInRaid == nil and GetNumRaidMembers() > 0 then isInRaid = true end -- subgroup and rank new_subgroup = 1 new_rank = 0 -- XXX Support party leader? if isInRaid then local _,_,num = unitid:find("(%d+)") if num then _, new_rank, new_subgroup = GetRaidRosterInfo(num) end end -- online/offline status new_online = UnitIsConnected(unitid) -- compare data if not old_name or new_name ~= old_name or new_unitid ~= old_unitid or new_class ~= old_class or new_subgroup ~= old_subgroup or new_rank ~= old_rank or new_online ~= old_online then unit.name = new_name unit.unitid = new_unitid unit.class = new_class unit.subgroup = new_subgroup unit.rank = new_rank unit.online = new_online self:TriggerEvent("RosterLib_UnitChanged", new_unitid, new_name, new_class, new_subgroup, new_rank, old_name, old_unitid, old_class, old_subgroup, old_rank) return name, true end return name else unknownUnits[unitid] = true return false end end function Lib:RemoveUnit(name) local r = roster[name] roster[name] = nil self:TriggerEvent("RosterLib_UnitChanged", nil, nil, nil, nil, nil, r.name, r.unitid, r.class, r.subgroup, r.rank) r = del(r) end ------------------------------------------------ -- API ------------------------------------------------ function Lib:GetUnitIDFromName(name) if roster[name] then return roster[name].unitid else return nil end end function Lib:GetUnitIDFromUnit(unit) local name = UnitName(unit) if name and roster[name] then return roster[name].unitid else return nil end end function Lib:GetUnitObjectFromName(name) return roster[name] end function Lib:GetUnitObjectFromUnit(unit) local name = UnitName(unit) return roster[name] end local function iter(t) local key = t.key local pets = t.pets repeat key = next(roster, key) if not key then t = del(t) return nil end until (pets or roster[key].class ~= "PET") t.key = key return roster[key] end function Lib:IterateRoster(pets) local t = new() t.pets = pets return iter, t end AceLibrary:Register(Lib, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)