vanilla-wow-addons – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | --[[ |
2 | Name: RosterLib-2.0 |
||
3 | Revision: $Revision: 8205 $ |
||
4 | X-ReleaseDate: "$Date: 2006-08-10 08:55:29 +0200 (Thu, 10 Aug 2006) $ |
||
5 | Author: Maia (maia.proudmoore@gmail.com) |
||
6 | Website: http://wiki.wowace.com/index.php/RosterLib-2.0 |
||
7 | Documentation: http://wiki.wowace.com/index.php/RosterLib-2.0_API_Documentation |
||
8 | SVN: http://svn.wowace.com/root/trunk/RosterLib-2.0/ |
||
9 | Description: party/raid roster management |
||
10 | Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0 |
||
11 | ]] |
||
12 | |||
13 | local MAJOR_VERSION = "RosterLib-2.0" |
||
14 | local MINOR_VERSION = "$Revision: 8205 $" |
||
15 | |||
16 | if not AceLibrary then error(vmajor .. " requires AceLibrary.") end |
||
17 | if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end |
||
18 | if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end |
||
19 | if not AceLibrary:HasInstance("AceEvent-2.0") then error(MAJOR_VERSION .. " requires AceEvent-2.0") end |
||
20 | |||
21 | local Compost = AceLibrary:HasInstance("Compost-2.0") and AceLibrary("Compost-2.0") |
||
22 | local rosterCount, addonCount, standby |
||
23 | local updatedUnits = Compost and Compost:Acquire() or {} |
||
24 | local RosterLib = {} |
||
25 | |||
26 | AceLibrary("AceEvent-2.0"):embed(RosterLib) |
||
27 | |||
28 | ------------------------------------------------ |
||
29 | -- activate, enable, disable |
||
30 | ------------------------------------------------ |
||
31 | |||
32 | local function print(text) |
||
33 | DEFAULT_CHAT_FRAME:AddMessage(text) |
||
34 | end |
||
35 | |||
36 | local function activate(self, oldLib, oldDeactivate) |
||
37 | RosterLib = self |
||
38 | if oldLib then |
||
39 | self.registry = oldLib.registry |
||
40 | end |
||
41 | if not self.registry then |
||
42 | self.registry = {} |
||
43 | end |
||
44 | if oldDeactivate then |
||
45 | oldDeactivate(oldLib) |
||
46 | end |
||
47 | addonCount = 0 |
||
48 | end |
||
49 | |||
50 | |||
51 | function RosterLib:Enable() |
||
52 | addonCount = addonCount + 1 |
||
53 | if addonCount > 1 then return end |
||
54 | if AceLibrary("AceEvent-2.0"):IsFullyInitialized() or standby then |
||
55 | RosterLib:AceEvent_FullyInitialized() |
||
56 | else |
||
57 | self:RegisterEvent("AceEvent_FullyInitialized") |
||
58 | end |
||
59 | standby = false |
||
60 | self.roster = Compost and Compost:Acquire() or {} |
||
61 | rosterCount = 0 |
||
62 | end |
||
63 | |||
64 | |||
65 | function RosterLib:Disable() |
||
66 | addonCount = addonCount - 1 |
||
67 | if addonCount > 0 then return end |
||
68 | self:TriggerEvent("RosterLib_Disabled") |
||
69 | self:UnregisterAllEvents() |
||
70 | standby = true |
||
71 | rosterAvailable = false |
||
72 | for name in pairs(self.roster) do |
||
73 | if Compost then Compost:Reclaim(self.roster[name]) end |
||
74 | self.roster[name] = nil |
||
75 | end |
||
76 | rosterCount = 0 |
||
77 | end |
||
78 | |||
79 | ------------------------------------------------ |
||
80 | -- Internal functions |
||
81 | ------------------------------------------------ |
||
82 | |||
83 | function RosterLib:AceEvent_FullyInitialized() |
||
84 | self:TriggerEvent("RosterLib_Enabled") |
||
85 | self:RegisterEvent("RAID_ROSTER_UPDATE","UpdateRoster") |
||
86 | self:RegisterEvent("PARTY_MEMBERS_CHANGED","UpdateRoster") |
||
87 | self:RegisterEvent("UNIT_PET","UpdateRoster") |
||
88 | self:CheckRoster() |
||
89 | end |
||
90 | |||
91 | --[[ |
||
92 | |||
93 | roster checks: |
||
94 | |||
95 | 1. AceEvent_FullyInitialized() is triggered when data *should* be available. It will |
||
96 | register party/raid/pet events as we don't want then to fire before. |
||
97 | Also, it will trigger CheckRoster(). |
||
98 | |||
99 | 2. CheckRoster() will compare the roster table with the currently available data. If |
||
100 | there's a difference, it will schedule UpdateRoster() with a delay of 0.5sec. |
||
101 | |||
102 | 3. UpdateRoster() will create/update the roster. To see if everything worked fine, |
||
103 | it will call CheckRoster() when it's done, this time with a silent flag. |
||
104 | |||
105 | 4. Now the saved data should be valid. If it is, it'll trigger the event |
||
106 | RosterLib_RosterChanged your addon can listen to. It'll pass a table with the unit |
||
107 | names that have changed as first argument. If not (server lag?) it'll send a |
||
108 | RosterLib_PendingRefresh event in case the roster was valid before (to not spam |
||
109 | this event when we're waiting for the server) |
||
110 | |||
111 | 5. WOW events that are related to roster updates directly trigger UpdateRoster(). |
||
112 | |||
113 | ]] |
||
114 | |||
115 | local function GetName(unit) |
||
116 | if not UnitExists(unit) then return nil end |
||
117 | local n = UnitName(unit) |
||
118 | if n and n ~= UNKNOWNOBJECT and n ~= UKNOWNBEING then return n end |
||
119 | end |
||
120 | |||
121 | |||
122 | local function UnitIterator() |
||
123 | local pmem, rmem = GetNumPartyMembers(), GetNumRaidMembers() |
||
124 | local playersent = false |
||
125 | local petsent = false |
||
126 | local unitcount = 1 |
||
127 | local petcount = 1 |
||
128 | local unit, name |
||
129 | return function() |
||
130 | -- STEP 1: pet |
||
131 | if not petsent then |
||
132 | petsent = true |
||
133 | if rmem == 0 then |
||
134 | unit = "pet" |
||
135 | name = GetName(unit) |
||
136 | if name and not UnitIsCharmed(unit) then |
||
137 | return unit, name |
||
138 | end |
||
139 | end |
||
140 | end |
||
141 | -- STEP 2: player |
||
142 | if not playersent then |
||
143 | playersent = true |
||
144 | if rmem == 0 then |
||
145 | unit = "player" |
||
146 | name = GetName(unit) |
||
147 | if name then |
||
148 | return unit, name |
||
149 | end |
||
150 | end |
||
151 | end |
||
152 | -- STEP 3: raid units |
||
153 | if rmem > 0 then |
||
154 | -- STEP 3a: pet units |
||
155 | for i = petcount, rmem do |
||
156 | unit = string.format("raidpet%d", i) |
||
157 | petcount = petcount + 1 |
||
158 | name = GetName(unit) |
||
159 | if name and not UnitIsCharmed(unit) then |
||
160 | return unit, name |
||
161 | end |
||
162 | end |
||
163 | -- STEP 3b: player units |
||
164 | for i = unitcount, rmem do |
||
165 | unit = string.format("raid%d", i) |
||
166 | unitcount = unitcount + 1 |
||
167 | name = GetName(unit) |
||
168 | if name then |
||
169 | return unit, name |
||
170 | end |
||
171 | end |
||
172 | -- STEP 4: party units |
||
173 | elseif pmem > 0 then |
||
174 | -- STEP 3a: pet units |
||
175 | for i = petcount, pmem do |
||
176 | unit = string.format("partypet%d", i) |
||
177 | petcount = petcount + 1 |
||
178 | name = GetName(unit) |
||
179 | if name and not UnitIsCharmed(unit) then |
||
180 | return unit, name |
||
181 | end |
||
182 | end |
||
183 | -- STEP 3b: player units |
||
184 | for i = unitcount, pmem do |
||
185 | unit = string.format("party%d", i) |
||
186 | unitcount = unitcount + 1 |
||
187 | name = GetName(unit) |
||
188 | if name then |
||
189 | return unit, name |
||
190 | end |
||
191 | end |
||
192 | end |
||
193 | end |
||
194 | end |
||
195 | |||
196 | |||
197 | |||
198 | local function RosterValid(silent) |
||
199 | local mem = 0 |
||
200 | local n, u |
||
201 | -- STEP 1: count how many units the roster should have |
||
202 | -- note: if the player has joined a raid but doesnt have a raid id yet |
||
203 | -- (can take a second or two), the count will be one less. |
||
204 | local testRoster = Compost and Compost:Acquire() or {} |
||
205 | for u,n in UnitIterator(true) do |
||
206 | testRoster[n] = true |
||
207 | end |
||
208 | for i in testRoster do |
||
209 | mem = mem + 1 |
||
210 | end |
||
211 | if Compost then Compost:Reclaim(testRoster) end |
||
212 | if mem ~= rosterCount then |
||
213 | if rosterAvailable then |
||
214 | print(string.format("RosterLib: count mismatch: roster:%d members:%d (TELL MAIA)", rosterCount, mem)) |
||
215 | end |
||
216 | return false |
||
217 | end |
||
218 | -- STEP 2: compare unit names |
||
219 | for n in pairs(RosterLib.roster) do |
||
220 | u = RosterLib.roster[n] |
||
221 | if n ~= UnitName(u.unitid) then |
||
222 | if rosterAvailable then |
||
223 | print(string.format("name mismatch: name:%s, UnitName(%s):%q (TELL MAIA)", n or "nil", u.unitid or "nil", UnitName(u.unitid) or "nil")) |
||
224 | end |
||
225 | return false |
||
226 | end |
||
227 | end |
||
228 | return true |
||
229 | end |
||
230 | |||
231 | |||
232 | function RosterLib:CheckRoster(silent) |
||
233 | local wasAvailable = rosterAvailable |
||
234 | if RosterValid(silent) then |
||
235 | rosterAvailable = true |
||
236 | if silent then |
||
237 | local cnt = 0 |
||
238 | for name in pairs(updatedUnits) do cnt = cnt + 1 end |
||
239 | if cnt > 0 then |
||
240 | self:TriggerEvent("RosterLib_RosterChanged", updatedUnits) |
||
241 | for name in pairs(updatedUnits) do |
||
242 | local u = updatedUnits[name] |
||
243 | self:TriggerEvent("RosterLib_UnitChanged", u.unitid, u.name, u.class, u.subgroup, u.rank, u.oldname, u.oldunitid, u.oldclass, u.oldsubgroup, u.oldrank) |
||
244 | if Compost then Compost:Reclaim(updatedUnits[name]) end |
||
245 | updatedUnits[name] = nil |
||
246 | end |
||
247 | end |
||
248 | end |
||
249 | else |
||
250 | rosterAvailable = false |
||
251 | if wasAvailable then |
||
252 | print("RosterLib_PendingRefresh") |
||
253 | self:TriggerEvent("RosterLib_PendingRefresh") |
||
254 | end |
||
255 | self:ScheduleEvent(self.UpdateRoster, 0.5, self) --what do we need to change here to convert it to a local schedule? |
||
256 | end |
||
257 | end |
||
258 | |||
259 | |||
260 | function RosterLib:UpdateRoster() |
||
261 | local unitid, name |
||
262 | -- print("RosterLib:UpdateRoster") |
||
263 | -- STEP 1: copy the roster to be able to compare it later. |
||
264 | local oldRoster = Compost and Compost:Acquire() or {} |
||
265 | for name in self.roster do |
||
266 | oldRoster[name] = Compost and Compost:Acquire() or {} |
||
267 | oldRoster[name].name = self.roster[name].name |
||
268 | oldRoster[name].unitid = self.roster[name].unitid |
||
269 | oldRoster[name].class = self.roster[name].class |
||
270 | oldRoster[name].rank = self.roster[name].rank |
||
271 | oldRoster[name].subgroup = self.roster[name].subgroup |
||
272 | end |
||
273 | -- STEP 2: clear the .unitids, as we're replacing them and removing old units later |
||
274 | for name in pairs(self.roster) do self.roster[name].unitid = nil end |
||
275 | -- STEP 3: add all units and see if something has changed. |
||
276 | for unitid, name in UnitIterator(true) do |
||
277 | -- object |
||
278 | if not self.roster[name] then |
||
279 | self.roster[name] = Compost and Compost:Acquire() or {} |
||
280 | end |
||
281 | -- name |
||
282 | self.roster[name].name = name |
||
283 | -- unitid |
||
284 | self.roster[name].unitid = unitid |
||
285 | -- class |
||
286 | if string.find(unitid,"pet") then |
||
287 | self.roster[name].class = "PET" |
||
288 | else |
||
289 | _,self.roster[name].class = UnitClass(unitid) |
||
290 | end |
||
291 | -- subgroup and rank |
||
292 | if GetNumRaidMembers() > 0 then |
||
293 | local _,_,num = string.find(unitid, "(%d+)") |
||
294 | _,self.roster[name].rank,self.roster[name].subgroup = GetRaidRosterInfo(num) |
||
295 | else |
||
296 | self.roster[name].subgroup = 1 |
||
297 | self.roster[name].rank = 0 |
||
298 | end |
||
299 | -- compare data |
||
300 | if not oldRoster[name] |
||
301 | or self.roster[name].name ~= oldRoster[name].name |
||
302 | or self.roster[name].unitid ~= oldRoster[name].unitid |
||
303 | or self.roster[name].class ~= oldRoster[name].class |
||
304 | or self.roster[name].subgroup ~= oldRoster[name].subgroup |
||
305 | or self.roster[name].rank ~= oldRoster[name].rank |
||
306 | then |
||
307 | updatedUnits[name] = Compost and Compost:Acquire() or {} |
||
308 | updatedUnits[name].oldname = (oldRoster[name] and oldRoster[name].name) or nil |
||
309 | updatedUnits[name].oldunitid = (oldRoster[name] and oldRoster[name].unitid) or nil |
||
310 | updatedUnits[name].oldclass = (oldRoster[name] and oldRoster[name].class) or nil |
||
311 | updatedUnits[name].oldsubgroup = (oldRoster[name] and oldRoster[name].subgroup) or nil |
||
312 | updatedUnits[name].oldrank = (oldRoster[name] and oldRoster[name].rank) or nil |
||
313 | updatedUnits[name].name = self.roster[name].name |
||
314 | updatedUnits[name].unitid = self.roster[name].unitid |
||
315 | updatedUnits[name].class = self.roster[name].class |
||
316 | updatedUnits[name].subgroup = self.roster[name].subgroup |
||
317 | updatedUnits[name].rank = self.roster[name].rank |
||
318 | end |
||
319 | end |
||
320 | -- STEP 4: now delete the old units in roster that have left your group |
||
321 | for name in pairs(self.roster) do |
||
322 | if not self.roster[name].unitid then |
||
323 | if Compost then Compost:Reclaim(self.roster[name]) end |
||
324 | self.roster[name] = nil |
||
325 | updatedUnits[name] = Compost and Compost:Acquire() or {} |
||
326 | updatedUnits[name].oldname = oldRoster[name].name |
||
327 | updatedUnits[name].oldunitid = oldRoster[name].unitid |
||
328 | updatedUnits[name].oldclass = oldRoster[name].class |
||
329 | updatedUnits[name].oldsubgroup = oldRoster[name].subgroup |
||
330 | updatedUnits[name].oldrank = oldRoster[name].rank |
||
331 | end |
||
332 | end |
||
333 | -- STEP 5: save the number of units, we need that in RosterValid() |
||
334 | rosterCount = 0 |
||
335 | for name in pairs(self.roster) do rosterCount = rosterCount + 1 end |
||
336 | -- STEP 6: clear oldRoster |
||
337 | if Compost then Compost:Reclaim(oldRoster, 1) end |
||
338 | -- STEP 6: we're done. Hopefully. |
||
339 | self:CheckRoster(true) |
||
340 | end |
||
341 | |||
342 | |||
343 | ------------------------------------------------ |
||
344 | -- API |
||
345 | ------------------------------------------------ |
||
346 | |||
347 | function RosterLib:GetUnitIDFromName(name) |
||
348 | if rosterAvailable and self.roster[name] then |
||
349 | return self.roster[name].unitid |
||
350 | else |
||
351 | return nil |
||
352 | end |
||
353 | end |
||
354 | |||
355 | |||
356 | function RosterLib:GetUnitIDFromUnit(unit) |
||
357 | local name = UnitName(unit) |
||
358 | if rosterAvailable and name and self.roster[name] then |
||
359 | return self.roster[name].unitid |
||
360 | else |
||
361 | return nil |
||
362 | end |
||
363 | end |
||
364 | |||
365 | |||
366 | function RosterLib:GetUnitObjectFromName(name) |
||
367 | if rosterAvailable and self.roster[name] then |
||
368 | return self.roster[name] |
||
369 | else |
||
370 | return nil |
||
371 | end |
||
372 | end |
||
373 | |||
374 | |||
375 | function RosterLib:GetUnitObjectFromUnit(unit) |
||
376 | local name = UnitName(unit) |
||
377 | if rosterAvailable and self.roster[name] then |
||
378 | return self.roster[name] |
||
379 | else |
||
380 | return nil |
||
381 | end |
||
382 | end |
||
383 | |||
384 | |||
385 | function RosterLib:IterateRoster(pets) |
||
386 | local key |
||
387 | return function() |
||
388 | key = next(self.roster, key) |
||
389 | if key and (pets or self.roster[key].class ~= "PET") then |
||
390 | return self.roster[key] |
||
391 | end |
||
392 | end |
||
393 | end |
||
394 | |||
395 | |||
396 | --[[ |
||
397 | TODO: |
||
398 | - use events AceEvent_EventRegistered, AceEvent_EventUnregistered plus :IsEventRegistered("event") to see if we can disable the library |
||
399 | - optimize GC in IterateRoster() |
||
400 | ]] |
||
401 | |||
402 | |||
403 | AceLibrary:Register(RosterLib, MAJOR_VERSION, MINOR_VERSION, activate) |
||
404 | RosterLib = AceLibrary(MAJOR_VERSION) |