Mercurial > wow > breuesk
comparison Lists.lua @ 49:f52d472f0b0a
Sweeping refactor to compartmentalize the various lists and to give them nice interfaces. Disentangled dependency web significantly
author | John@Yosemite-PC |
---|---|
date | Wed, 21 Mar 2012 22:52:42 -0400 |
parents | 6c0a8acccbc8 |
children | b155a875de42 |
comparison
equal
deleted
inserted
replaced
48:b3679847e292 | 49:f52d472f0b0a |
---|---|
83 local assert=assert | 83 local assert=assert |
84 local getmetatable=getmetatable | 84 local getmetatable=getmetatable |
85 local setmetatable=setmetatable | 85 local setmetatable=setmetatable |
86 setfenv(1,bsk) | 86 setfenv(1,bsk) |
87 | 87 |
88 lists = {} | 88 ListEntry = |
89 persons = {} | 89 { |
90 | 90 index = 0.0, |
91 raidNameP = {} -- "name" is present in raid | 91 id = 0, |
92 raidIdP = {} -- "id" is present in raid | 92 } |
93 reserveIdP = {} -- "reserve id present" | 93 ListEntry.__index = ListEntry |
94 local personName2id = {} -- given "name" get that person's id | 94 function ListEntry:new(arg1,arg2) |
95 local n = {} | |
96 setmetatable(n,ListEntry) | |
97 if type(arg1) == "number" and type(arg2) == "string" then | |
98 n.index, n.id = index,id | |
99 elseif type(arg1) == "table" and type(arg2) == "nil" then | |
100 n.index, n.id = arg1.roll, arg1.id | |
101 else | |
102 _G.error("Do not know how to construct a ListEntry from " .. type(arg1) .. " and " .. type(arg2)) | |
103 end | |
104 assert(n.index ~= nil) | |
105 assert(n.id ~= nil) | |
106 return n | |
107 end | |
108 function ListEntry:GetId() | |
109 return self.id | |
110 end | |
111 function ListEntry:GetIndex() | |
112 return self.index | |
113 end | |
114 function ListEntry:ReplaceId(newId) -- returns the old Id | |
115 local temp = self.id | |
116 self.id = newId | |
117 return temp | |
118 end | |
95 | 119 |
96 List = | 120 List = |
97 { | 121 { |
98 id = 0, | |
99 name = "", | 122 name = "", |
100 Sort = function() | 123 time = 0, |
101 end | 124 |
125 -- "private" functions. only private thing about them is that my | |
126 -- autocomplete won't pick them up when defined this way | |
127 Sort = function(self) | |
128 table.sort(self.data,function(a,b) return a:GetIndex() < b:GetIndex() end) | |
129 end, | |
130 GetLastIndex = function(self) | |
131 if not self.data or getn(self.data) == 0 then return 0.0 | |
132 else return self.data[#self.data]:GetIndex() end | |
133 end, | |
134 SetTime = function(self,time) | |
135 if time == nil or time == 0 then | |
136 assert("Dangerous things are afoot") | |
137 else | |
138 self.time = time | |
139 end | |
140 end | |
141 | |
102 } | 142 } |
103 function List:new() -- TODO: take args, build list | 143 List.__index = List |
104 n = {} | 144 function List:new(arg1, arg2, arg3) |
105 setmetatable(n,self) | 145 local n = {data={}} |
106 self.__index = self | 146 setmetatable(n,List) |
147 if type(arg1) == "string" and type(arg2) == "number" and type(arg3) == "nil" then | |
148 n.name = arg1 | |
149 n.time = arg2 | |
150 elseif type(arg1) == "table" and type(arg2) == "nil" and type(arg3) == "nil" then | |
151 n.name = arg1.name | |
152 if arg1.data then | |
153 for _,v in pairs(arg1.data) do | |
154 local le = ListEntry:new(v) | |
155 table.insert(n.data,entry) | |
156 end | |
157 n:Sort() | |
158 end | |
159 n:SetTime(arg1.time) | |
160 else | |
161 _G.error(sformat("Do not know how to construct list from: %s and %s and %s", type(arg1), type(arg2), type(arg3))) | |
162 end | |
107 return n | 163 return n |
108 end | 164 end |
109 function List:HasID(id) | 165 function List:HasId(id) |
110 for i = 1,#self do | 166 for le in self:OrderedIdIter() do |
111 if id == self[i].id then | 167 if id == le then |
112 return true | 168 return true |
113 end | 169 end |
114 end | 170 end |
115 return false | 171 return false |
116 end | 172 end |
117 function List:GetID() | 173 function List:GetTime() |
174 return self.time | |
175 end | |
176 function List:GetName() | |
177 return self.name | |
178 end | |
179 function List:GetId() | |
180 local listIndex = LootLists:IdOf(self) -- TODO: undo circular dep somehow | |
181 return listIndex | |
182 end | |
183 function List:Rename(packet,time) | |
184 self.name = packet.name | |
185 self:SetTime(time) | |
186 return packet | |
187 end | |
188 function List:RenameList(newName) | |
189 local listIndex = self:GetId() | |
190 return InitiateChange("Rename",self,{listIndex=listIndex,name=newName}) | |
191 end | |
192 function List:RemoveEntry(entry,time) | |
193 local pos = self:Select(entry.id) | |
194 if pos then | |
195 --print("Removing id " .. entry.id .. " pos " .. pos) | |
196 table.remove(self.data,pos) | |
197 self:Sort() | |
198 self:SetTime(time) | |
199 return pos | |
200 end | |
201 --print("Failed removal of ...") | |
202 --PrintTable(entry) | |
203 end | |
204 function List:Remove(id) | |
205 -- TODO: check id | |
206 local listIndex = self:GetId() | |
207 return InitiateChange("RemoveEntry",self,{listIndex=listIndex,id=id}) | |
208 end | |
209 function List:InsertEndEntry(packet,time) | |
210 if self:InsertEntry(packet,time) then | |
211 self.closedRandom = true | |
212 return packet | |
213 end | |
214 end | |
215 function List:InsertEntry(packet,time) | |
216 local le = ListEntry:new(packet) | |
217 table.insert(self.data,le) | |
218 self:Sort() | |
219 self:SetTime(time) | |
220 --printf("Inserting %s to %s", packet.id, packet.listIndex) | |
221 return le | |
222 end | |
223 function List:InsertEnd(id) -- returns the LE created | |
224 if self:Select(id) then | |
225 printf("Person %s is already on the reqeuested list",id) -- todo: lookup name | |
226 return false | |
227 end | |
228 local index = self:GetLastIndex() + 0.1 | |
229 local le = ListEntry:new(index,id) | |
230 local listIndex = self:GetId() | |
231 return InitiateChange("InsertEndEntry",self,{listIndex=listIndex,id=id,roll=index}) | |
232 end | |
233 function List:InsertRandom(id) -- returns the LE created | |
234 if self.closedRandom then | |
235 print("Cannot add person to list by random roll because an add-to-end operation has already occurred") | |
236 return false | |
237 end | |
238 if self:Select(id) then | |
239 printf("Person %s is already on the reqeuested list",id) -- todo: lookup name | |
240 return false | |
241 end | |
242 local index = math.random() | |
243 local listIndex = self:GetId() | |
244 return InitiateChange("InsertEntry",self,{listIndex=listIndex,id=id,roll=index}) | |
245 end | |
246 function List:OrderedListEntryIter() | |
247 local i = 0 | |
248 local n = #self.data | |
249 | |
250 return function() | |
251 i = i+1 | |
252 if i<=n then return self.data[i] end | |
253 end | |
254 end | |
255 function List:OrderedIdIter() | |
256 local i = 0 | |
257 local n = #self.data | |
258 return function() | |
259 i = i+1 | |
260 if i<=n then return self.data[i]:GetId() end | |
261 end | |
262 end | |
263 function List:GetAllIds() | |
264 local t = {} | |
265 for id in self:OrderedIdIter() do | |
266 table.insert(t,id) | |
267 end | |
268 return t | |
269 end | |
270 function List:Select(entry) -- returns the position and the entry | |
271 if type(entry) == "string" then -- search by id | |
272 local ids = self:GetAllIds() | |
273 for i,v in ipairs(ids) do | |
274 if v == entry then | |
275 return i, self.data[i] | |
276 end | |
277 end | |
278 return false, nil | |
279 else | |
280 assert("undone") | |
281 return false, nil | |
282 end | |
283 end | |
284 function List:Suicide(packet,time) | |
285 -- the goal here is to rotate the suicide list by 1 | |
286 -- then we can just mash it on top of the intersection between the original | |
287 -- list and the working copy | |
288 local affected = shallowCopy(packet.affect) | |
289 local replacement = shallowCopy(packet.affect) | |
290 local temp = table.remove(replacement,1) -- pop | |
291 tinsert(replacement,temp) -- push_back | |
292 --rintf(("Before suicide of %s on list %s",slist[1],list.name) | |
293 --PrintTable(list) | |
294 for le in self:OrderedListEntryIter() do | |
295 if le:GetId() == affected[1] then | |
296 le:ReplaceId(replacement[1]) | |
297 table.remove(affected,1) | |
298 table.remove(replacement,1) | |
299 end | |
300 end | |
301 -- TODO: flag error if affected and replacement aren't both empty now | |
302 self:SetTime(time) | |
303 return packet | |
304 end | |
305 function List:SuicidePerson(id) | |
306 -- first calculate the effect, then initiate the change | |
307 PersonList:RefreshRaidList() | |
308 local slist = {} | |
309 local pushing = false | |
310 for le in self:OrderedListEntryIter() do -- get all ids | |
311 local lid = le:GetId() | |
312 if lid == id then | |
313 pushing = true | |
314 end | |
315 if pushing and PersonList:IsActive(lid) then -- TODO: decouple | |
316 tinsert(slist,lid) | |
317 end | |
318 end | |
319 local listIndex = self:GetId() | |
320 return InitiateChange("Suicide",self,{listIndex=listIndex,affect=slist}) | |
321 end | |
322 | |
323 LootLists = | |
324 { | |
325 --l = {} -- list of List objects, keyed by id | |
326 } | |
327 | |
328 -- generate self, then generate sublists | |
329 function LootLists:ConstructFromDB(db) | |
330 self:Reset() | |
331 local saved = db.profile.lists | |
332 for i,v in pairs(saved) do -- upconvert saved to true list objects | |
333 self.l[i] = List:new(v) | |
334 end | |
335 end | |
336 function LootLists:SaveToDB(db) | |
337 db.profile.lists = self.l | |
338 end | |
339 function LootLists:CreateList(packet,time) | |
340 local le = List:new(packet.name,time) | |
341 if not packet.id then packet.id = time end -- uses the timestamp for the index - it's unique, unlike anything else I can think of | |
342 self.l[packet.id] = le | |
343 return le | |
344 end | |
345 function LootLists:Create(name) | |
346 return InitiateChange("CreateList",self,{name=name}) | |
347 end | |
348 function LootLists:DeleteList(packet,time) | |
349 self.l[packet.listIndex] = nil | |
350 return id | |
351 end | |
352 function LootLists:Delete(index) | |
353 -- TODO: is there anything to check first, or just fire away? | |
354 return InitiateChange("DeleteList",self,{listIndex=index}) | |
355 end | |
356 function LootLists:Select(id) | |
357 if type(id) == "number" then -- by id | |
358 return self.l[id] | |
359 elseif type(id) == "string" then -- name | |
360 for i,v in pairs(self.l) do | |
361 if v:GetName() == id then | |
362 return v | |
363 end | |
364 end | |
365 end | |
366 return nil | |
367 end | |
368 function LootLists:Reset() | |
369 self.l = {} | |
370 end | |
371 function LootLists:GetAllIds() | |
372 local t = {} | |
373 for i,v in pairs(self.l) do | |
374 table.insert(t,i) | |
375 end | |
376 return t | |
377 end | |
378 function LootLists:IdOf(list) | |
379 for i,v in pairs(self.l) do | |
380 if v == list then | |
381 return i | |
382 end | |
383 end | |
384 end | |
385 | |
386 Toon = | |
387 { | |
388 id=0, | |
389 name="", | |
390 class="" | |
391 } | |
392 Toon.__index = Toon | |
393 function Toon:new(arg1,arg2,arg3) | |
394 local t = {} | |
395 setmetatable(t,Toon) | |
396 if type(arg1) == "number" and type(arg2) == "string" and type(arg3) == "string" then | |
397 t.id, t.name, t.class = arg1, arg2, arg3 | |
398 elseif type(arg1) == "table" and arg2 == nil and arg3 == nil then | |
399 t.id, t.name, t.class = arg1.id, arg1.name, arg1.class | |
400 else | |
401 error("Cannot construct a toon object from types " .. type(arg1) .. ", " .. type(arg2) .. ", " .. type(arg3)) | |
402 end | |
403 return t | |
404 end | |
405 function Toon:GetName() | |
406 return self.name | |
407 end | |
408 function Toon:GetId() | |
118 return self.id | 409 return self.id |
119 end | 410 end |
120 function List:RemoveEntry() | 411 function Toon:GetClass() |
121 end | 412 return self.class |
122 function List:InsertEntry() | 413 end |
123 end | 414 |
124 function List:OrderedIter() | 415 PersonList = |
125 end | 416 { |
126 | 417 toons = {}, |
127 | 418 time = 0, |
128 | 419 active = { raid={}, reserve={} } |
129 | 420 } |
130 | 421 |
131 | 422 function PersonList:ConstructFromDB(db) |
132 | 423 self:Reset() |
133 | 424 local dbp = db.profile.persons |
134 | 425 self.time = dbp.time |
135 | 426 if dbp.toons == nil then return end |
136 | 427 for i,v in pairs(dbp.toons) do |
428 local te = Toon:new(v) | |
429 table.insert(self.toons,te) | |
430 end | |
431 end | |
432 function PersonList:SaveToDB(db) | |
433 db.profile.persons = { toons=self.toons, time=self.time } | |
434 end | |
435 function PersonList:Reset() | |
436 self.toons = {} | |
437 self.time = 0 | |
438 self.active = { raid={}, reserve={}, raidExtras={} } | |
439 end | |
440 function PersonList:Select(id) | |
441 -- both id and name are strings, but there won't be clashes | |
442 -- because an ID will contain either a number or all caps letters | |
443 -- and names must be long enough to ensure that one of those is true | |
444 if type(id) == "string" then | |
445 for i,v in pairs(self.toons) do | |
446 --print(i) | |
447 --PrintTable(v) | |
448 if v:GetName() == id or v:GetId() == id then | |
449 return v | |
450 end | |
451 end | |
452 end | |
453 end | |
454 function PersonList:AddToon(packet,time) | |
455 local te = Toon:new(packet) | |
456 table.insert(self.toons,te) | |
457 return te | |
458 end | |
459 function PersonList:Add(name) | |
460 local guid = _G.UnitGUID(name) | |
461 -- TODO: check guid to be sure it's a player | |
462 if not guid then | |
463 printf("Could not add player %s - they must be in range or group",name) | |
464 return | |
465 end | |
466 local _,englishClass = _G.UnitClass(name) | |
467 --print("Person " .. name .. " is class " .. englishClass) | |
468 local id = string.sub(guid,6) -- skip at least 0x0580 ... | |
469 id = id:gsub("^0*(.*)","%1") -- nom all leading zeroes remaining | |
470 | |
471 local pe = self:Select(id) | |
472 if pe and pe:GetName() ~= name then | |
473 printf("Namechange detected for %s - new is %s, please rename the existing entry", pe:GetName(), name) | |
474 return | |
475 end | |
476 if pe then | |
477 printf("%s is already in the persons list; disregarding", name) | |
478 return | |
479 end | |
480 | |
481 return InitiateChange("AddToon", self, {name=name, id=id, class=englishClass}) | |
482 end | |
483 function PersonList:RemoveToon(packet,time) | |
484 local id = packet.id | |
485 for i,v in pairs(self.toons) do | |
486 if v:GetId() == id then | |
487 table.remove(self.toons,i) | |
488 return v | |
489 end | |
490 end | |
491 end | |
492 function PersonList:Remove(ident) | |
493 local le = PersonList:Select(ident) | |
494 if not le then | |
495 printf("%s is not in the persons list, please check your spelling", ident) | |
496 return false | |
497 end | |
498 local id = le:GetId() | |
499 local listsTheyreOn = {} | |
500 | |
501 -- check if they're active on any loot list | |
502 local allListIds = LootLists:GetAllIds() | |
503 for _,v in pairs(allListIds) do | |
504 if LootLists:Select(v):HasId(id) then -- TODO: this is ineloquent | |
505 tinsert(listsTheyreOn,LootLists:Select(v):GetName()) | |
506 break | |
507 end | |
508 end | |
509 if getn(listsTheyreOn) > 0 then | |
510 printf("Cannot remove person %s because they are on one or more lists (%s)",ident,table.concat(listsTheyreOn,", ")) | |
511 return false | |
512 end | |
513 return InitiateChange("RemoveToon", self, {id=id}) | |
514 end | |
515 function PersonList:IsRegistered(id) | |
516 if self:Select(id) ~= nil then return true end | |
517 end | |
518 function PersonList:AddReserve(id) | |
519 local le = self:Select(id) | |
520 if le then | |
521 -- todo: check that they're not already reserved | |
522 self.active.reserve[le:GetId()] = true | |
523 end | |
524 end | |
525 -- todo: remove reserve | |
526 function PersonList:IsActive(id) | |
527 return self.active.raid[id] or self.active.reserve[id] | |
528 end | |
529 function PersonList:AddMissing() | |
530 self:RefreshRaidList() | |
531 for _,name in pairs(self.active.raidExtras) do | |
532 printf("Person %s is missing from the persons list - adding",name) | |
533 self:Add(name) | |
534 end | |
535 -- TODO: batch into a single op - no need to spam 25 messages in a row | |
536 end | |
537 function PersonList:GetAllActiveIds() | |
538 self:RefreshRaidList() | |
539 local t = {} | |
540 for i,v in pairs(self.active.raid) do | |
541 if v then table.insert(t,i) end | |
542 end | |
543 for i,v in pairs(self.active.reserve) do | |
544 if v then table.insert(t,i) end | |
545 end | |
546 return t | |
547 end | |
548 | |
549 -- The following (adapted) code is from Xinhuan (wowace forum member) | |
550 -- Pre-create the unitId strings we will use | |
551 local pId = {} | |
552 local rId = {} | |
553 for i = 1, 4 do | |
554 pId[i] = sformat("party%d", i) | |
555 end | |
556 for i = 1, 40 do | |
557 rId[i] = sformat("raid%d", i) | |
558 end | |
559 function PersonList:RefreshRaidList() | |
560 local inParty = _G.GetNumPartyMembers() | |
561 local inRaid = _G.GetNumRaidMembers() | |
562 local add = function(unitNameArg) | |
563 local name = _G.UnitName(unitNameArg) | |
564 local te = self:Select(name) | |
565 if te then | |
566 self.active.raid[te:GetId()]=true | |
567 else | |
568 table.insert(self.active.raidExtras,name) | |
569 end | |
570 --if personName2id[name] ~= nil then | |
571 -- raidIdP[personName2id[name]]=true | |
572 --end | |
573 end | |
574 | |
575 self.active.raid = {} | |
576 self.active.raidExtras = {} | |
577 if inRaid > 0 then | |
578 for i = 1, inRaid do | |
579 add(rId[i]) | |
580 end | |
581 elseif inParty > 0 then | |
582 for i = 1, inParty do | |
583 add(pId[i]) | |
584 end | |
585 -- Now add yourself as the last party member | |
586 add("player") | |
587 else | |
588 -- You're alone | |
589 add("player") | |
590 end | |
591 end | |
592 | |
593 | |
594 function GetSafeTimestamp() | |
595 local changes = db.profile.changes | |
596 local ctime = time() | |
597 local n = getn(changes) | |
598 if n > 0 then | |
599 if changes[n].time >= ctime then | |
600 ctime = changes[n].time + 1 | |
601 end | |
602 end | |
603 return ctime | |
604 end | |
605 | |
606 function InitiateChange(finalizeAction,acceptor,arg) | |
607 local change = {} | |
608 change.time = GetSafeTimestamp() | |
609 change.action = finalizeAction | |
610 change.arg = arg | |
611 | |
612 if acceptor[finalizeAction](acceptor,arg,change.time) then | |
613 table.insert(db.profile.changes,change) | |
614 -- TODO: broadcast | |
615 return arg | |
616 else | |
617 return nil | |
618 end | |
619 end | |
620 function ProcessChange(change) | |
621 -- try list-o-lists and persons - if has matching function, call it | |
622 local action = change.action | |
623 if PersonList[action] then | |
624 PersonList[action](PersonList,change.arg,change.time) | |
625 return | |
626 elseif LootLists[action] then | |
627 LootLists[action](LootLists,change.arg,change.time) | |
628 return | |
629 else | |
630 -- pray that the change has a listIndex in it ... | |
631 if change.arg.listIndex then | |
632 local l = LootLists:Select(change.arg.listIndex) | |
633 if l and l[action] then | |
634 l[action](l,change.arg,change.time) | |
635 return | |
636 end | |
637 end | |
638 end | |
639 _G.error("Could not process change: " .. change.action) | |
640 end | |
137 | 641 |
138 function SelfDestruct() | 642 function SelfDestruct() |
139 lists = {} | 643 LootLists:Reset() |
140 persons = {} | 644 PersonList:Reset() |
141 db.profile.persons = {} | 645 db.profile.persons = {} |
142 db.profile.changes = {} | 646 db.profile.changes = {} |
143 db.profile.lists = {} | 647 db.profile.lists = {} |
144 raidNameP = {} | |
145 raidIdP = {} | |
146 reserveIdP = {} | |
147 personName2id = {} | |
148 end | 648 end |
149 | 649 |
150 -- Debugging {{{ | 650 -- Debugging {{{ |
151 function PrettyPrintList(listIndex) | 651 function PrettyPrintList(listIndex) |
152 local list = lists[listIndex] | 652 PersonList:RefreshRaidList() |
153 print("List: " .. list.name .. " (" .. listIndex .. ") - last modified " .. date("%m/%d/%y %H:%M:%S", list.time) .. " (",list.time,")" ) | 653 local le = LootLists:Select(listIndex) |
154 for i = 1,#list do | 654 print("List: " .. le:GetName() .. " (" .. le:GetId() .. ") - last modified " .. date("%m/%d/%y %H:%M:%S", le:GetTime()) .. " ("..le:GetTime()..")" ) |
155 print(" " .. i .. " - " .. persons[list[i].id].main) | 655 local pos = 1 |
656 for i in le:OrderedIdIter() do -- ordered iterator | |
657 local s = "" | |
658 if PersonList:IsActive(i) then | |
659 s = "*" | |
660 end | |
661 | |
662 print(" " .. pos .. " - " .. PersonList:Select(i):GetName() .. " ("..i..")",s) | |
663 pos = pos + 1 | |
156 end | 664 end |
157 end | 665 end |
158 function PrettyPrintLists() | 666 function PrettyPrintLists() |
159 for i,_ in pairs(lists) do | 667 for _,i in pairs(LootLists:GetAllIds()) do |
160 PrettyPrintList(i) | 668 PrettyPrintList(i) |
161 end | 669 end |
162 end | 670 end |
163 function PrintLists() | 671 function PrintLists() |
164 PrintTable(lists) | 672 PrintTable(LootLists) |
165 end | 673 end |
166 function PrintChanges() | 674 function PrintChanges() |
167 PrintTable(db.profile.changes) | 675 PrintTable(db.profile.changes) |
168 end | 676 end |
169 function PrintPersons() | 677 function PrintPersons() |
170 PrintTable(persons) | 678 PrintTable(PersonList) |
171 end | 679 end |
172 function PrintAPI(object) | 680 function PrintAPI(object) |
173 for i,v in pairs(object) do | 681 for i,v in pairs(object) do |
174 if type(v) == "function" then | 682 if type(v) == "function" then |
175 print("function "..i.."()") | 683 print("function "..i.."()") |
176 end | 684 end |
177 end | 685 end |
178 end | 686 end |
179 function PrintRaidAndReserve() | |
180 print("RaidNameP") | |
181 PrintTable(raidNameP) | |
182 print("RaidIdP") | |
183 PrintTable(raidIdP) | |
184 print("ReserveP") | |
185 PrintTable(reserveIdP) | |
186 print("personName2id") | |
187 PrintTable(personName2id) | |
188 end | |
189 --}}} | 687 --}}} |
190 | |
191 function UpdatePersonsReverse() | |
192 for i,v in pairs(persons) do | |
193 if i ~= "time" then | |
194 personName2id[v.main] = i | |
195 end | |
196 end | |
197 end | |
198 | 688 |
199 -- Change processing {{{ | 689 -- Change processing {{{ |
200 function CreateWorkingStateFromChanges(changes) | 690 function CreateWorkingStateFromChanges(changes) |
201 local personsBase = db.profile.persons | |
202 local lists = db.profile.lists | |
203 | |
204 -- copy the base to the working state | 691 -- copy the base to the working state |
205 wipe(lists) | 692 LootLists:ConstructFromDB(db) |
206 wipe(persons) | 693 PersonList:ConstructFromDB(db) |
207 wipe(personName2id) | |
208 | |
209 tcopy(lists,lists) | |
210 tcopy(persons,personsBase) | |
211 | 694 |
212 -- now just go through the changes list applying each | 695 -- now just go through the changes list applying each |
213 for i,v in ipairs(changes) do | 696 for i,v in ipairs(changes) do |
214 ProcessChange(v) | 697 ProcessChange(v) |
215 end | 698 end |
216 | |
217 -- update the persons reverse list | |
218 UpdatePersonsReverse() | |
219 end | |
220 | |
221 function CreateChange(change) | |
222 -- sanity | |
223 assert(change) | |
224 assert(change.action) | |
225 assert(change.arg) | |
226 | |
227 StartChange(change) | |
228 CommitChange(change) | |
229 end | |
230 | |
231 function StartChange(change) | |
232 local changes = db.profile.changes | |
233 change.time = time() | |
234 local n = getn(changes) | |
235 if n > 0 then | |
236 if changes[n].time >= change.time then | |
237 change.time = changes[n].time + 1 | |
238 end | |
239 end | |
240 end | |
241 | |
242 function CommitChange(change) | |
243 local changes = db.profile.changes | |
244 tinsert(changes,change) | |
245 -- TODO: broadcast change | |
246 end | |
247 | |
248 function ProcessChange(change) | |
249 if change.action == "AddPerson" then | |
250 DoAddPerson(change) | |
251 elseif change.action == "RenameList" then | |
252 DoRenameList(change) | |
253 elseif change.action == "CreateList" then | |
254 DoCreateList(change) | |
255 elseif change.action == "DeleteList" then | |
256 DoDeleteList(change) | |
257 elseif change.action == "AddToListEnd" then | |
258 DoAddPersonToListEnd(change) | |
259 elseif change.action == "AddToListRand" then | |
260 DoAddPersonToListRandom(change) | |
261 elseif change.action == "RemovePerson" then | |
262 DoRemovePerson(change) | |
263 elseif change.action == "RemovePersonFromList" then | |
264 DoRemovePersonFromList(change) | |
265 elseif change.action == "SuicidePerson" then | |
266 DoSuicidePerson(change) | |
267 else | |
268 print("Unknown message encountered") | |
269 PrintTable(change) | |
270 assert(false) | |
271 end | |
272 end | 699 end |
273 | 700 |
274 --}}} | 701 --}}} |
275 -- | 702 |
276 -- holy crap long winded {{{ | 703 -- holy crap long winded {{{ |
277 -- timestamp logic: | 704 -- timestamp logic: |
278 -- use time() for comparisons - local clients use date() to make it pretty. only | 705 -- use time() for comparisons - local clients use date() to make it pretty. only |
279 -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns | 706 -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns |
280 -- out you can change timezones when you enter an instance server, so you really | 707 -- out you can change timezones when you enter an instance server, so you really |
319 -- it's assumed that the change has been vetted elsewhere. These are very blunt | 746 -- it's assumed that the change has been vetted elsewhere. These are very blunt |
320 -- routines. | 747 -- routines. |
321 -- | 748 -- |
322 -- Note that "undo" has no special voodoo to it. It's basically a change that | 749 -- Note that "undo" has no special voodoo to it. It's basically a change that |
323 -- reverses the prior change on the stack.--}}} | 750 -- reverses the prior change on the stack.--}}} |
324 function DoAddPerson(change)--{{{ | |
325 assert(change) | |
326 assert(change.arg.id) | |
327 -- require admin | |
328 local persons = persons | |
329 local name = change.arg.name | |
330 local id = change.arg.id | |
331 assert(persons[id]==nil) | |
332 persons[id] = {main=name,class=change.arg.class} | |
333 persons.time=change.time | |
334 personName2id[name] = id | |
335 return true | |
336 end--}}} | |
337 function AddPerson(name)--{{{ | 751 function AddPerson(name)--{{{ |
338 local persons = persons | 752 print("Adding ... " .. name) |
339 local guid = _G.UnitGUID(name) | 753 PersonList:Add(name) |
340 -- TODO: check guid to be sure it's a player | |
341 if not guid then | |
342 printf("Could not add player %s - they must be in range or group",name) | |
343 return | |
344 end | |
345 local _,englishClass = _G.UnitClass(name) | |
346 --print("Person " .. name .. " is class " .. englishClass) | |
347 local id = string.sub(guid,6) -- skip at least 0x0580 ... | |
348 id = id:gsub("^0*(.*)","%1") -- nom all leading zeroes remaining | |
349 | |
350 if persons[id] and persons[id] ~= name then | |
351 printf("Namechange detected for %s - new is %s, please rename the existing entry", persons[id].main, name) | |
352 return | |
353 end | |
354 if persons[id] ~= nil then | |
355 printf("%s is already in the persons list; disregarding", name) | |
356 return | |
357 end | |
358 local change = {action="AddPerson",arg={name=name,id=id,class=englishClass}} | |
359 if DoAddPerson(change) then | |
360 CreateChange(change) | |
361 end | |
362 end--}}} | |
363 function DoCreateList(change)--{{{ | |
364 --if GetListIndex(change.arg.name) then | |
365 -- rintf(("List %s already exists",v.name) | |
366 -- return false | |
367 --end | |
368 lists[change.arg.id]={name=change.arg.name,time=change.time} | |
369 return true | |
370 end--}}} | 754 end--}}} |
371 function CreateList(name)--{{{ | 755 function CreateList(name)--{{{ |
372 -- require admin | 756 -- require admin |
373 local change={action="CreateList",arg={name=name}} | |
374 StartChange(change) | |
375 change.arg.id=change.time -- use the creation timestamp as the list's index. it's as unique as anything... | |
376 print("Creating ... " .. name) | 757 print("Creating ... " .. name) |
377 if DoCreateList(change) then | 758 return LootLists:Create(name) |
378 CommitChange(change) | |
379 end | |
380 end--}}} | |
381 function DoAddPersonToListEnd(change)--{{{ | |
382 local list = lists[change.arg.listIndex] | |
383 local index | |
384 if getn(list) > 0 then | |
385 index = list[#list].index + 0.1 | |
386 else | |
387 index = 0.1 | |
388 end | |
389 local entry = {index=index, id=change.arg.id} | |
390 | |
391 tinsert(list,entry) | |
392 list.time = change.time | |
393 list.closedRandom = true | |
394 | |
395 return true | |
396 end--}}} | 759 end--}}} |
397 function AddPersonToListEnd(name,listName)--{{{ | 760 function AddPersonToListEnd(name,listName)--{{{ |
398 -- require admin | 761 -- require admin |
399 local listIndex = GetListIndex(listName) | 762 local l = LootLists:Select(listName) |
400 local id = personName2id[name] | 763 local te = PersonList:Select(name) |
401 if IdIsInList(id,lists[listIndex]) then | 764 -- TODO: if not te ... |
402 printf("Person %s is already on the reqeuested list",name) | 765 printf("Adding %s (%s) to list %s", name, te:GetId(), listName) |
403 return false | 766 return l:InsertEnd(te:GetId()) |
404 end | |
405 printf("Adding %s (%s) to list %s (%s)", name, id, listName, listIndex) | |
406 local change = {action="AddToListEnd",arg={id=id,listIndex=listIndex}} | |
407 StartChange(change) | |
408 if DoAddPersonToListEnd(change) then | |
409 CommitChange(change) | |
410 end | |
411 end--}}} | |
412 function DoAddPersonToListRandom(change)--{{{ | |
413 local list = lists[change.arg.listIndex] | |
414 local entry = {index=change.arg.roll, id=change.arg.id} | |
415 | |
416 tinsert(list,entry) | |
417 table.sort(list,function(a,b) return a.index < b.index end) | |
418 list.time = change.time | |
419 | |
420 return true | |
421 end--}}} | 767 end--}}} |
422 function AddPersonToListRandom(name,listName)--{{{ | 768 function AddPersonToListRandom(name,listName)--{{{ |
423 -- require admin | 769 -- require admin |
424 local listIndex = GetListIndex(listName) | 770 local l = LootLists:Select(listName) |
425 if lists[listIndex].closedRandom then | 771 local te = PersonList:Select(name) |
426 print("Cannot add person to list by random roll because an add-to-end operation has already occurred") | 772 -- TODO: if not te ... |
427 return false | 773 printf("Adding %s (%s) to list %s - randomly!", name, te:GetId(), listName) |
428 end | 774 return l:InsertRandom(te:GetId()) |
429 local id = personName2id[name] | |
430 if IdIsInList(id,lists[listIndex]) then | |
431 printf("Person %s is already on the reqeuested list",name) | |
432 return false | |
433 end | |
434 local roll = math.random() | |
435 printf("Adding %s (%s) to list %s (%s) with roll (%f)", name, id, listName, listIndex, roll) | |
436 local change = {action="AddToListRand",arg={id=id,listIndex=listIndex,roll=roll}} | |
437 StartChange(change) | |
438 if DoAddPersonToListRandom(change) then | |
439 CommitChange(change) | |
440 end | |
441 end--}}} | |
442 function DoRemovePerson(change)--{{{ | |
443 local person = persons[change.arg.id] | |
444 personName2id[person.main] = nil | |
445 persons[change.arg.id] = nil | |
446 persons.time = change.time | |
447 return true | |
448 end--}}} | |
449 function RemovePerson(name)--{{{ | |
450 local id = personName2id[name] | |
451 if not id then | |
452 printf("%s is not in the persons list, please check your spelling", name) | |
453 return false | |
454 end | |
455 local listsTheyreOn = {} | |
456 -- check if they're active on any loot list | |
457 for i,v in pairs(lists) do | |
458 if IdIsInList(id,v) then | |
459 tinsert(listsTheyreOn,v.name) | |
460 break | |
461 end | |
462 end | |
463 if getn(listsTheyreOn) > 0 then | |
464 printf("Cannot remove person %s because they are on one or more lists (%s)",name,table.concat(listsTheyreOn,", ")) | |
465 return false | |
466 end | |
467 local change = {action="RemovePerson",arg={id=id}} | |
468 StartChange(change) | |
469 if DoRemovePerson(change) then | |
470 CommitChange(change) | |
471 end | |
472 end--}}} | |
473 function DoSuicidePerson(change)--{{{ | |
474 local list = lists[change.arg.listIndex] | |
475 local affected = shallowCopy(change.arg.affect) | |
476 -- the goal here is to rotate the suicide list by 1 | |
477 -- then we can just mash it on top of the intersection between the original | |
478 -- list and the working copy | |
479 | |
480 local replacement = shallowCopy(change.arg.affect) | |
481 local temp = table.remove(replacement,1) -- pop | |
482 tinsert(replacement,temp) -- push_back | |
483 --rintf(("Before suicide of %s on list %s",slist[1],list.name) | |
484 --PrintTable(list) | |
485 for i = 1, #list do | |
486 if list[i].id == affected[1] then | |
487 table.remove(affected,1) | |
488 list[i].id = replacement[1] | |
489 table.remove(replacement,1) | |
490 end | |
491 end | |
492 list.time=change.time | |
493 return true | |
494 end--}}} | 775 end--}}} |
495 function SuicidePerson(name,listName)--{{{ | 776 function SuicidePerson(name,listName)--{{{ |
496 -- require admin | 777 -- require admin |
497 PopulateRaidList() | 778 PersonList:RefreshRaidList() |
498 local listIndex = GetListIndex(listName) | 779 local le = LootLists:Select(listName) |
499 local id = personName2id[name] | 780 local te = PersonList:Select(name) |
500 local affect=GetSuicideList(id,lists[listIndex]) | 781 return le:SuicidePerson(te:GetId()) |
501 local change = {action="SuicidePerson",arg={affect=affect,listIndex=listIndex}} | |
502 StartChange(change) | |
503 if DoSuicidePerson(change) then | |
504 CommitChange(change) | |
505 end | |
506 end--}}} | |
507 function DoRenameList(change)--{{{ | |
508 lists[change.arg.listIndex].name = change.arg.name | |
509 lists[change.arg.listIndex].time = change.time | |
510 return true | |
511 end--}}} | 782 end--}}} |
512 function RenameList(listName,newListName)--{{{ | 783 function RenameList(listName,newListName)--{{{ |
513 -- require admin | 784 -- require admin |
514 local listIndex = GetListIndex(listName) | 785 local le = LootLists:Select(listName) |
515 local change = {action="RenameList",arg={listIndex=listIndex,name=newListName}} | 786 return le:RenameList(newListName) |
516 StartChange(change) | |
517 if DoRenameList(change) then | |
518 CommitChange(change) | |
519 end | |
520 end--}}} | |
521 function DoDeleteList(change)--{{{ | |
522 lists[change.arg.listIndex] = nil | |
523 return true | |
524 end--}}} | 787 end--}}} |
525 function DeleteList(listName)--{{{ | 788 function DeleteList(listName)--{{{ |
526 local listIndex = GetListIndex(listName) | 789 return LootLists:DeleteList(LootLists:Select(listName):GetId()) |
527 local change = {action="DeleteList",arg={listIndex=listIndex}} | |
528 StartChange(change) | |
529 if DoDeleteList(change) then | |
530 CommitChange(change) | |
531 end | |
532 end--}}} | |
533 function DoRemovePersonFromList(change)--{{{ | |
534 local list = lists[change.arg.listIndex] | |
535 | |
536 for i,v in ipairs(list) do | |
537 if v.id == change.arg.id then | |
538 table.remove(list,i) | |
539 break | |
540 end | |
541 end | |
542 table.sort(list,function(a,b) return a.index < b.index end) | |
543 list.time = change.time | |
544 return true | |
545 end--}}} | 790 end--}}} |
546 function RemovePersonFromList(name,listName)--{{{ | 791 function RemovePersonFromList(name,listName)--{{{ |
547 local listIndex = GetListIndex(listName) | 792 local le = LootLists:Select(listName) |
548 local pid = personName2id[name] | 793 local te = PersonList:Select(name) |
549 -- todo: check that they're on the list in the first place | 794 return le:Remove(te:GetId()) |
550 local change = {action="RemovePersonFromList",arg={id=pid,listIndex=listIndex}} | |
551 StartChange(change) | |
552 if DoRemovePersonFromList(change) then | |
553 CommitChange(change) | |
554 end | |
555 end | 795 end |
556 --}}} | 796 --}}} |
797 function RemovePerson(person) | |
798 print("Removing " .. person) | |
799 PersonList:Remove(person) | |
800 end | |
801 function ReservePerson(person) | |
802 print("Reserving " .. person) | |
803 PersonList:AddReserve(person) | |
804 end | |
557 --}}} | 805 --}}} |
558 -- Higher order actions (ie calls other standard actions){{{ | 806 -- Higher order actions (ie calls other standard actions){{{ |
559 | 807 |
560 function TrimLists(time) | 808 function TrimLists(time) |
561 if not CheckListCausality() then | 809 if not CheckListCausality() then |
579 | 827 |
580 -- apply first half | 828 -- apply first half |
581 CreateWorkingStateFromChanges(before) | 829 CreateWorkingStateFromChanges(before) |
582 | 830 |
583 -- save this state permanently; trim the changes permanently | 831 -- save this state permanently; trim the changes permanently |
584 tcopy(db.profile.persons,persons) | 832 LootLists:SaveToDB(db) |
585 tcopy(db.profile.lists,lists) | 833 PersonList:SaveToDB(db) |
586 while db.profile.changes ~= nil and db.profile.changes[1] ~= nil and db.profile.changes[1].time <= time do | 834 while db.profile.changes ~= nil and db.profile.changes[1] ~= nil and db.profile.changes[1].time <= time do |
587 table.remove(db.profile.changes,1) | 835 table.remove(db.profile.changes,1) |
588 end | 836 end |
589 | 837 |
590 -- using the trimmed list and the new bases, recreate the working state | 838 -- using the trimmed list and the new bases, recreate the working state |
591 CreateWorkingStateFromChanges(db.profile.changes) | 839 CreateWorkingStateFromChanges(db.profile.changes) |
592 end | 840 end |
593 | 841 |
594 function AddMissingPersons() | |
595 PopulateRaidList() | |
596 local t = {} | |
597 for id,_ in pairs(persons) do | |
598 t[id] = true | |
599 end | |
600 for name,_ in pairs(raidNameP) do | |
601 if personName2id[name] == nil then | |
602 printf("Person %s is missing from the persons list - adding",name) | |
603 AddPerson(name) | |
604 end | |
605 end | |
606 -- TODO: batch into a single op - no need to spam 25 messages in a row | |
607 end | |
608 | 842 |
609 function PopulateListRandom(listIndex) | 843 function PopulateListRandom(listIndex) |
610 -- difference (raid+reserve)-list, then random shuffle that, then add | 844 -- difference (raid+reserve)-list, then random shuffle that, then add |
611 PopulateRaidList() | 845 local actives = PersonList:GetAllActiveIds() |
612 local list = lists[listIndex] | 846 local list = LootLists:Select(listIndex) |
613 | 847 |
614 local t = {} -- after loops, contains intersection of IDs present between raid and reserve | 848 --swap keys on actives |
615 for i,v in pairs(raidIdP) do | 849 local t = {} |
616 if v then t[i] = true end | 850 for _,v in pairs(actives) do t[v] = true end |
617 end | |
618 for i,v in pairs(reserveIdP) do | |
619 if v then t[i] = true end | |
620 end | |
621 | 851 |
622 -- now remove from t all of the people already present on the list | 852 -- now remove from t all of the people already present on the list |
623 if list then | 853 if t then |
624 for i = 1,#list do | 854 for id in list:OrderedIdIter() do -- id iterator |
625 if t[list[i].id] then | 855 if t[id] then |
626 t[list[i].id] = false | 856 t[id] = false |
627 end | 857 end |
628 end | 858 end |
629 end | 859 end |
630 | 860 |
631 -- add all remaining | 861 -- add all remaining |
632 for i,v in pairs(t) do | 862 for i,v in pairs(t) do |
633 if v then | 863 if v then |
634 AddPersonToListRandom(persons[i].main,list.name) -- TODO: APTLR keys off of string names. probably need to change this. | 864 AddPersonToListRandom(i,list:GetId()) |
635 end | 865 end |
636 end | 866 end |
637 end | 867 end |
638 | |
639 function NukePerson(name) -- delete from all lists and then from persons | 868 function NukePerson(name) -- delete from all lists and then from persons |
640 local pid = personName2id[name] | 869 for _,id in pairs(LootLists:GetAllIds()) do |
641 for i,v in pairs(lists) do | 870 RemovePersonFromList(name,id) |
642 RemovePersonFromList(name,v.name) | |
643 end | 871 end |
644 RemovePerson(name) | 872 RemovePerson(name) |
645 end | 873 end |
646 --}}} | 874 --}}} |
647 -- "Soft" actions- ie things that cause nonpermanent state {{{ | |
648 | |
649 -- reserves | |
650 function AddReserve(name) | |
651 print("Reserving" .. name) | |
652 reserveIdP[personName2id[name]]=true | |
653 -- TODO: communicate to others. don't store this in any way. | |
654 end | |
655 | |
656 function RemoveReserve(name) | |
657 reserveIdP[personName2id[name]]=false | |
658 -- TODO: communicate to others. don't store this in any way. | |
659 end | |
660 | |
661 | |
662 --function GetActiveList() | |
663 -- return lists[1] -- todo! | |
664 --end | |
665 | |
666 --}}} | |
667 | |
668 -- The following (adapted) code is from Xinhuan (wowace forum member) | |
669 -- Pre-create the unitID strings we will use | |
670 local pID = {} | |
671 local rID = {} | |
672 for i = 1, 4 do | |
673 pID[i] = sformat("party%d", i) | |
674 end | |
675 for i = 1, 40 do | |
676 rID[i] = sformat("raid%d", i) | |
677 end | |
678 function PopulateRaidList() | |
679 local inParty = _G.GetNumPartyMembers() | |
680 local inRaid = _G.GetNumRaidMembers() | |
681 local add = function(unitNameArg) | |
682 local name = _G.UnitName(unitNameArg) | |
683 raidNameP[name]=true | |
684 if personName2id[name] ~= nil then | |
685 raidIdP[personName2id[name]]=true | |
686 end | |
687 end | |
688 | |
689 wipe(raidNameP) | |
690 wipe(raidIdP) | |
691 if inRaid > 0 then | |
692 for i = 1, inRaid do | |
693 add(rID[i]) | |
694 end | |
695 elseif inParty > 0 then | |
696 for i = 1, inParty do | |
697 add(pID[i]) | |
698 end | |
699 -- Now add yourself as the last party member | |
700 add("player") | |
701 else | |
702 -- You're alone | |
703 add("player") | |
704 end | |
705 --PrintTable(raidNameP) | |
706 end | |
707 | 875 |
708 -- undo rules! | 876 -- undo rules! |
709 -- only the most recent event can be undone | 877 -- only the most recent event can be undone |
710 -- ^^^ on a given list? | 878 -- ^^^ on a given list? |
711 -- algorithm is easy, given "Suicide A B C" | 879 -- algorithm is easy, given "Suicide A B C" |
712 -- just find A,B,C in the list and replace in order from the s message | 880 -- just find A,B,C in the list and replace in order from the s message |
713 -- while undo is allowed *per-list*, certain events in the stream will | 881 -- while undo is allowed *per-list*, certain events in the stream will |
714 -- prevent proper undo, such as add/delete player or add/delete list | 882 -- prevent proper undo, such as add/delete player or add/delete list |
715 | |
716 | |
717 function GetSuicideList(id,list) | |
718 --print("Calculating changeset for "..name.." from list -") | |
719 --PrintTable(list) | |
720 local t = {} | |
721 local ret = {} | |
722 local pushing = false | |
723 for i = 1, #list do | |
724 if list[i].id == id then | |
725 pushing = true | |
726 end | |
727 if pushing and (raidIdP[list[i].id] or reserveIdP[list[i].id]) then | |
728 tinsert(ret,list[i].id) | |
729 end | |
730 end | |
731 --print("GSL") | |
732 --PrintTable(ret) | |
733 --print("GSL") | |
734 return ret | |
735 end | |
736 | |
737 function IdIsInList(id,listRef) | |
738 for i = 1,#listRef do | |
739 if id == listRef[i].id then | |
740 return true | |
741 end | |
742 end | |
743 return false | |
744 end | |
745 | 883 |
746 -- returns true if the events in the list are in time order | 884 -- returns true if the events in the list are in time order |
747 function CheckListCausality() | 885 function CheckListCausality() |
748 local t = nil | 886 local t = nil |
749 for i,v in ipairs(db.profile.changes) do | 887 for i,v in ipairs(db.profile.changes) do |
755 t = v.time | 893 t = v.time |
756 end | 894 end |
757 return true | 895 return true |
758 end | 896 end |
759 | 897 |
760 -- Support functions | |
761 | |
762 function GetListIndex(name) | |
763 for i,v in pairs(lists) do | |
764 if v.name == name then | |
765 return i | |
766 end | |
767 end | |
768 return nil | |
769 end | |
770 | |
771 |