Mercurial > wow > breuesk
comparison Lists.lua @ 0:47fac96968e1
First commit
author | John@Yosemite-PC |
---|---|
date | Fri, 02 Mar 2012 00:15:09 -0500 |
parents | |
children | 21c58930f74e |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:47fac96968e1 |
---|---|
1 -- lists consist of three things | |
2 -- 1) a base state - agreed on by one or more list holders | |
3 -- 2) change sets - incremental list changes (can be rolled forwards or | |
4 -- backwards) | |
5 -- 3) working state - not saved because it can be so easily calculated | |
6 -- | |
7 -- A separate user list is held - lists index into this | |
8 | |
9 | |
10 -- TODO: rename player | |
11 | |
12 | |
13 | |
14 bsk.lists = {} | |
15 bsk.players = {} | |
16 | |
17 local RaidList = {} | |
18 local ReserveList = {} | |
19 local activeList = 0 -- temporary | |
20 | |
21 local tinsert = table.insert | |
22 local sformat = string.format | |
23 local getn = table.getn | |
24 | |
25 function bsk:tcopy(to, from) | |
26 for k,v in pairs(from) do | |
27 if(type(v)=="table") then | |
28 to[k] = {} | |
29 bsk:tcopy(to[k], v); | |
30 else | |
31 to[k] = v; | |
32 end | |
33 end | |
34 end | |
35 local shallowCopy = function(t) | |
36 local u = { } | |
37 for k, v in pairs(t) do u[k] = v end | |
38 return setmetatable(u, getmetatable(t)) | |
39 end | |
40 | |
41 function bsk:PrintTable(table, depth) | |
42 depth = depth or "" | |
43 if not table then return end | |
44 for i,v in pairs(table) do | |
45 if( type(v) == "string" ) then | |
46 self:Print(depth .. i .. " - " .. v) | |
47 elseif( type(v) == "number" ) then | |
48 self:Print(depth .. i .. " - " .. tostring(v)) | |
49 elseif( type(v) == "table" ) then | |
50 self:Print(depth .. i .." - ") | |
51 self:PrintTable(v,depth.." ") | |
52 elseif( type(v) == "boolean" ) then | |
53 self:Print(depth .. i .. " - " .. tostring(v)) | |
54 else | |
55 self:Print(depth .. i .. " - not sure how to print type: " .. type(v) ) | |
56 end | |
57 end | |
58 end | |
59 | |
60 -- Debugging {{{ | |
61 function bsk:PrintLists() | |
62 bsk:PrintTable(bsk.lists) | |
63 end | |
64 function bsk:PrintChanges() | |
65 bsk:PrintTable(bsk.db.profile.changes) | |
66 end | |
67 function bsk:PrintPlayers() | |
68 bsk:PrintTable(bsk.players) | |
69 end | |
70 --}}} | |
71 | |
72 function bsk:CreateWorkingStateFromChanges() | |
73 local playerBase = self.db.profile.players | |
74 local listBase = self.db.profile.listBase | |
75 local changes = self.db.profile.changes | |
76 | |
77 -- copy the base to the working state | |
78 wipe(bsk.lists) | |
79 wipe(bsk.players) | |
80 bsk:tcopy(bsk.lists,listBase) | |
81 bsk:tcopy(bsk.players,playerBase) | |
82 | |
83 -- now just go through the changes list applying each | |
84 for i,v in pairs(changes) do | |
85 bsk:ProcessChange(v) | |
86 end | |
87 end | |
88 | |
89 function bsk:CreateChange(change) | |
90 -- sanity | |
91 assert(change) | |
92 assert(change.action) | |
93 assert(change.arg) | |
94 | |
95 bsk:StartChange(change) | |
96 bsk:CommitChange(change) | |
97 end | |
98 | |
99 function bsk:StartChange(change) | |
100 local changes = self.db.profile.changes | |
101 change.time = time() | |
102 local n = getn(changes) | |
103 if n > 0 then | |
104 if changes[n].time >= change.time then | |
105 change.time = changes[n].time + 1 | |
106 end | |
107 end | |
108 end | |
109 | |
110 function bsk:CommitChange(change) | |
111 local changes = self.db.profile.changes | |
112 tinsert(changes,change) | |
113 -- TODO: broadcast change | |
114 end | |
115 | |
116 | |
117 -- timestamp logic: | |
118 -- use time() for comparisons - local clients use date() to make it pretty. only | |
119 -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns | |
120 -- out you can change timezones when you enter an instance server, so you really | |
121 -- never know what time it is. | |
122 -- There's unfortunately no hard-and-proven method for determining the true time | |
123 -- difference between local time and server time. You can't just query the two | |
124 -- and compare them because your server timezone can change (!) if you go into | |
125 -- an instance server with a different timezone. This is apparently a big | |
126 -- problem on Oceanic realms. | |
127 -- | |
128 -- Timestamp handling (brainstorming how to deal with drift): | |
129 -- (not an issue) if someone sends you time in the future, update your offset so you won't | |
130 -- send out events in the "past" to that person | |
131 -- (not an issue - using local UTC now) on change-zone-event: check if you've changed timezones - might need update | |
132 -- each time you add a change, check the tail of the change list; if this is | |
133 -- less than that, you have a problem. Print a message. if this is equal, then | |
134 -- that's ok, just bump it by 1 second. This could happen in the case of, say, | |
135 -- spam-clicking the undo button or adding names to the list. The recipients | |
136 -- should be ok with this since they'll follow the same algorithm. The only | |
137 -- real chance for a problem is if two people click within the 1 second window? | |
138 -- if someone sends you a past event, | |
139 -- it's ok if it's newer than anything in the changes list | |
140 -- otherwise ... causality has been violated. | |
141 -- Whenever an admin signon event happens, have the admins each perform a | |
142 -- timestamp check. Issue warnings for anyone with a clock that's more than | |
143 -- X seconds out of sync with the others. Seriously, why isn't NTP a standard | |
144 -- setting on all operating systems ... | |
145 | |
146 function bsk:ProcessChange(change) | |
147 if change.action == "AddPlayer" then | |
148 bsk:DoAddPlayer(change) | |
149 elseif change.action == "CreateList" then | |
150 bsk:DoCreateList(change) | |
151 elseif change.action == "AddPlayerToList" then | |
152 bsk:DoAddPlayerToList(change) | |
153 elseif change.action == "SuicidePlayer" then | |
154 bsk:DoSuicidePlayer(change) | |
155 else | |
156 bsk:Print("Unknown message encountered") | |
157 bsk:PrintTable(change) | |
158 assert(false) | |
159 end | |
160 end | |
161 | |
162 | |
163 -- | |
164 -- The actual actions for changes start here | |
165 -- | |
166 -- Each action occurs as a pair of functions. The bsk:Action() function is from | |
167 -- a list admin's point of view. Each will check for admin status, then create a | |
168 -- change bundle, call the handler for that change (ie the DoAction func), and | |
169 -- then record/transmist the bundle. These are simple and repetitive functions. | |
170 -- | |
171 -- The bsk:DoAction() function is tasked with executing the bundle and is what | |
172 -- non-admins and admins alike will call to transform their working state via a | |
173 -- change packet. Each Do() function will accept *only* a change packet, and | |
174 -- it's assumed that the change has been vetted elsewhere. These are very blunt | |
175 -- routines. | |
176 -- | |
177 -- Note that "undo" has no special voodoo to it. It's basically a change that | |
178 -- reverses the prior change on the stack. | |
179 | |
180 -- Players list | |
181 function bsk:DoAddPlayer(change) | |
182 assert(change) | |
183 assert(change.arg.guid) | |
184 local arg = change.arg | |
185 -- require admin | |
186 local players = bsk.players | |
187 local name = arg.name | |
188 local guid = arg.guid | |
189 assert(players[guid]==nil) | |
190 players[guid] = name | |
191 players.time=change.time | |
192 return true | |
193 end | |
194 | |
195 function bsk:AddPlayer(name) | |
196 local players = bsk.players | |
197 local guid = UnitGUID(name) | |
198 -- TODO: check guid to be sure it's a player | |
199 if not guid then | |
200 self:Print(sformat("Could not add player %s - they must be in range or group",name)) | |
201 return | |
202 end | |
203 if players[guid] and players[guid] ~= name then | |
204 self:Print(sformat("Namechange detected for %s - new is %s, please rename the existing entry", players[guid], name)) | |
205 return | |
206 end | |
207 if players[guid] ~= nil then | |
208 self:Print(sformat("%s is already in the players list; disregarding", name)) | |
209 return | |
210 end | |
211 local change = {action="AddPlayer",arg={name=name,guid=guid}} | |
212 if bsk:DoAddPlayer(change) then | |
213 bsk:CreateChange(change) | |
214 end | |
215 end | |
216 | |
217 function bsk:CreateFakeLists() | |
218 -- testing only | |
219 end | |
220 | |
221 function bsk:DoCreateList(change) | |
222 -- TODO: this segment will probably be useful as bsk:SearchForListByName | |
223 local lists = bsk.lists | |
224 for i,v in pairs(lists) do | |
225 if v.name == change.arg.name then | |
226 self:Print(sformat("List %s already exists",v.name)) | |
227 return false | |
228 end | |
229 end | |
230 tinsert(lists,{name=change.arg.name,time=change.time}) | |
231 return true | |
232 end | |
233 | |
234 function bsk:CreateList(name) | |
235 -- require admin | |
236 local change={action="CreateList",arg={name=name}} | |
237 bsk:StartChange(change) | |
238 self:Print("Creating ... " .. name) | |
239 if bsk:DoCreateList(change) then | |
240 bsk:CommitChange(change) | |
241 end | |
242 end | |
243 | |
244 function bsk:DoAddPlayerToList(change) | |
245 local listIndex = change.arg.listIndex | |
246 local slist = change.arg.slist | |
247 local list = bsk.lists[listIndex] | |
248 | |
249 if #slist == 1 then -- end of list insertion - just one person | |
250 tinsert(list,slist[1]) | |
251 list.time = change.time | |
252 else | |
253 self:Print("Adding to middle of list is not yet supported") | |
254 return false | |
255 end | |
256 return true | |
257 end | |
258 | |
259 function bsk:AddPlayerToList(name,list) | |
260 -- require admin | |
261 local listIndex = bsk:GetListIndex(list) | |
262 local slist = {name} -- TODO: support adding to elsewhere besides the end | |
263 local change = {action="AddPlayerToList",arg={name=name,listIndex=listIndex,slist=slist}} | |
264 bsk:StartChange(change) | |
265 if bsk:DoAddPlayerToList(change) then | |
266 bsk:CommitChange(change) | |
267 end | |
268 end | |
269 | |
270 function bsk:DoRemovePlayer(change) | |
271 | |
272 -- return true | |
273 end | |
274 | |
275 function bsk:RemovePlayer(name) | |
276 -- from both players and lists | |
277 end | |
278 | |
279 function bsk:GetSuicideList(name,list) | |
280 --self:Print("Calculating changeset for "..name.." from list -") | |
281 --self:PrintTable(list) | |
282 local t = {} | |
283 local ret = {} | |
284 local pushing = false | |
285 for i = 1, #list do | |
286 if list[i] == name then | |
287 pushing = true | |
288 end | |
289 if pushing and (RaidList[list[i]] or ReserveList[list[i]]) then | |
290 tinsert(ret,list[i]) | |
291 end | |
292 end | |
293 return ret | |
294 end | |
295 | |
296 function bsk:GetActiveList() | |
297 return bsk.lists[1] -- todo! | |
298 end | |
299 | |
300 function bsk:DoSuicidePlayer(change) | |
301 local listIndex = change.arg.listIndex | |
302 local list = bsk.lists[listIndex] | |
303 local slist = shallowCopy(change.arg.list) | |
304 -- the goal here is to rotate the suicide list by 1 | |
305 -- then we can just mash it on top of the intersection between the original | |
306 -- list and the working copy | |
307 local stemp = shallowCopy(change.arg.list) | |
308 local temp = table.remove(stemp,1) -- pop | |
309 tinsert(stemp,temp) -- push_back | |
310 --bsk:Print(sformat("Before suicide of %s on list %s",slist[1],list.name)) | |
311 --bsk:PrintTable(list) | |
312 for i = 1, #list do | |
313 if list[i] == slist[1] then | |
314 table.remove(slist,1) | |
315 list[i] = stemp[1] | |
316 table.remove(stemp,1) | |
317 end | |
318 end | |
319 list.time=change.time | |
320 --bsk:Print("After") | |
321 --bsk:PrintTable(list) | |
322 return true | |
323 end | |
324 | |
325 function bsk:SuicidePlayer(name,list) | |
326 -- require admin | |
327 local l=bsk:GetActiveList() | |
328 bsk:PopulateRaidList() | |
329 local slist=bsk:GetSuicideList(name,l) | |
330 local listIndex = bsk:GetListIndex(list) | |
331 local change = {action="SuicidePlayer",arg={names=names,list=slist,listIndex=listIndex}} | |
332 bsk:StartChange(change) | |
333 if bsk:DoSuicidePlayer(change) then | |
334 bsk:CommitChange(change) | |
335 end | |
336 end | |
337 | |
338 -- The following code is from Xinhuan (wowace forum member) | |
339 -- Pre-create the unitID strings we will use | |
340 local pID = {} | |
341 local rID = {} | |
342 for i = 1, 4 do | |
343 pID[i] = format("party%d", i) | |
344 end | |
345 for i = 1, 40 do | |
346 rID[i] = format("raid%d", i) | |
347 end | |
348 function bsk:PopulateRaidList() | |
349 local inParty = GetNumPartyMembers() | |
350 local inRaid = GetNumRaidMembers() | |
351 | |
352 wipe(RaidList) | |
353 if inRaid > 0 then | |
354 for i = 1, inRaid do | |
355 RaidList[UnitName(rID[i])]=true | |
356 end | |
357 elseif inParty > 0 then | |
358 for i = 1, inParty do | |
359 RaidList[UnitName(pID[i])]=true | |
360 end | |
361 -- Now add yourself as the last party member | |
362 RaidList[UnitName("player")]=true | |
363 else | |
364 -- You're alone | |
365 RaidList[UnitName("player")]=true | |
366 end | |
367 end | |
368 | |
369 -- undo rules! | |
370 -- only the most recent event can be undone | |
371 -- ^^^ on a given list? | |
372 -- algorithm is easy, given "Suicide A B C" | |
373 -- just find A,B,C in the list and replace in order from the s message | |
374 -- while undo is allowed *per-list*, certain events in the stream will | |
375 -- prevent proper undo, such as add/delete player or add/delete list | |
376 | |
377 | |
378 | |
379 | |
380 -- reserves | |
381 function bsk:AddReserve(name) | |
382 ReserveList[name]=true | |
383 -- TODO: communicate to others. don't store this in any way. | |
384 end | |
385 | |
386 function bsk:RemoveReserve(name) | |
387 ReserveList[name]=false | |
388 -- TODO: communicate to others. don't store this in any way. | |
389 end | |
390 | |
391 | |
392 | |
393 | |
394 | |
395 | |
396 | |
397 | |
398 | |
399 -- Support functions | |
400 | |
401 function bsk:GetListIndex(name) | |
402 for i,v in pairs(bsk.lists) do | |
403 if v.name == name then | |
404 return i | |
405 end | |
406 end | |
407 assert(false) | |
408 end |