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