John@0: -- lists consist of three things John@0: -- 1) a base state - agreed on by one or more list holders John@0: -- 2) change sets - incremental list changes (can be rolled forwards or John@0: -- backwards) John@0: -- 3) working state - not saved because it can be so easily calculated John@0: -- John@0: -- A separate user list is held - lists index into this John@0: John@0: John@27: -- TODO: switch all action functions to use identifiers rather than names John@27: -- TODO: collaborative list trimming John@24: -- TODO: collapse slists into delimited strings for space (premature optimization?) John@18: -- TODO: organize working state data a little more carefully - hard to keep John@18: -- track of all the arrays that are floating out there John@35: -- TODO: players list, drop the "main" sub-entry for people with no alts John@18: John@17: -- holy crap long notes {{{ John@4: -- notes on list storage: John@7: -- Using names as keys as I do now is atrocious. John@4: -- It prevents insertions (twss) to the middle of the list because then it acts John@4: -- as a side effect onto all the others. ie ABCD -> AXBCD would be phrased as John@4: -- "insert X and shift down B,C,D" which sucks. BCD haven't really been affected John@4: -- (yet) because their relative positions to the others are still intact - ie John@4: -- they are still below A right where they belong. But really X hasn't done John@4: -- anything to affect their relative standing. John@4: -- John@4: -- Ok so we can't use names. John@4: -- John@4: -- We can't use monotonic integers either because it suffers the same problem. John@4: -- Also consider, randoming in someone to a list of ABCD. Say they roll spot 2. John@4: -- What if someone else runs a separate raid and also randoms someone into slot John@4: -- 2? How do you handle that conflict? Difficult. Also, consider this: John@4: -- List of ABCD on night 1. John@4: -- Admin 1 on night 2 rolls in 30 new people. ABCD's indexes are shuffled to be John@4: -- between 1-35. John@4: -- Admin 2 on night 3 rolls in 5 new ones and people ABCD and PQRST now all have John@4: -- indexes between 1-9. John@4: -- When these two are resolved against one another, do the 1-9 peopole end up on John@4: -- top of the list compared to those other 30? John@4: -- John@4: -- Solution: John@4: -- Need a huge random space with purposely left gaps to leave plenty of room for John@4: -- conflicts. John@4: -- So if ABCD had randomed on a space of say, 10,000 and then were sorted into John@4: -- order, then the next 30 could roll into that same space and have a proper John@4: -- ordering. Then the next 5, etc. John@4: -- John@4: -- Handling conflicts: John@4: -- John@9: -- Executive decision: random on a range of [0,1], ie math.random John@9: -- then on an add-to-end event just do last + .1 John@9: -- disallow random after any add-to-end event occurs John@9: -- because the list either elongates beyond 1 OR becomes John@9: -- ridiculously bottom heavy, thus meaning that randoms John@9: -- don't get an even distibution from then on (in fact John@9: -- they'll end up getting top favor) John@9: -- * if a stream contains a random-add after an add-to-end John@9: -- it is declared invalid. tough tits. it's just not a fair John@9: -- distribution at that point. John@10: -- * actually, fuck it. I'll give them an unlock command and John@10: -- let them screw over their lists :) John@17: --}}} John@18: John@17: -- there are some dep chains here. for instance, to have a raidIdP value, a John@42: -- person must have a persons value which leads to a personName2id which John@17: -- leads to a raidIdP John@42: local bsk = bsk John@42: local _G=_G John@42: local table=table John@42: local string=string John@0: local tinsert = table.insert John@0: local sformat = string.format John@0: local getn = table.getn John@42: local wipe = wipe John@42: local pairs=pairs John@42: local ipairs=ipairs John@42: local tonumber=tonumber John@42: local tostring=tostring John@42: local time=time John@42: local date=date John@42: local math=math John@42: local type=type John@42: local assert=assert John@42: local getmetatable=getmetatable John@42: local setmetatable=setmetatable John@42: setfenv(1,bsk) John@0: John@42: lists = {} John@42: persons = {} John@42: John@42: raidNameP = {} -- "name" is present in raid John@42: raidIdP = {} -- "id" is present in raid John@42: reserveIdP = {} -- "reserve id present" John@42: local personName2id = {} -- given "name" get that person's id John@42: John@45: List = John@45: { John@46: id = 0, John@46: name = "", John@45: Sort = function() John@45: end John@45: } John@45: function List:new() -- TODO: take args, build list John@45: n = {} John@45: setmetatable(n,self) John@45: self.__index = self John@45: return n John@45: end John@45: function List:HasID(id) John@45: for i = 1,#self do John@45: if id == self[i].id then John@45: return true John@45: end John@45: end John@45: return false John@45: end John@45: function List:GetID() John@45: return self.id John@45: end John@45: function List:RemoveEntry() John@45: end John@45: function List:InsertEntry() John@45: end John@45: function List:OrderedIter() John@45: end John@45: John@45: John@45: John@45: John@45: John@45: John@45: John@45: John@45: John@45: John@45: John@42: John@42: function SelfDestruct() John@42: lists = {} John@42: persons = {} John@42: db.profile.persons = {} John@42: db.profile.changes = {} John@42: db.profile.lists = {} John@42: raidNameP = {} John@42: raidIdP = {} John@42: reserveIdP = {} John@17: personName2id = {} John@17: end John@0: John@1: -- Debugging {{{ John@42: function PrettyPrintList(listIndex) John@42: local list = lists[listIndex] John@43: print("List: " .. list.name .. " (" .. listIndex .. ") - last modified " .. date("%m/%d/%y %H:%M:%S", list.time) .. " (",list.time,")" ) John@9: for i = 1,#list do John@43: print(" " .. i .. " - " .. persons[list[i].id].main) John@9: end John@9: end John@42: function PrettyPrintLists() John@42: for i,_ in pairs(lists) do John@42: PrettyPrintList(i) John@9: end John@9: end John@42: function PrintLists() John@42: PrintTable(lists) John@1: end John@42: function PrintChanges() John@42: PrintTable(db.profile.changes) John@1: end John@42: function PrintPersons() John@42: PrintTable(persons) John@1: end John@42: function PrintAPI(object) John@39: for i,v in pairs(object) do John@39: if type(v) == "function" then John@43: print("function "..i.."()") John@39: end John@39: end John@39: end John@42: function PrintRaidAndReserve() John@43: print("RaidNameP") John@42: PrintTable(raidNameP) John@43: print("RaidIdP") John@42: PrintTable(raidIdP) John@43: print("ReserveP") John@42: PrintTable(reserveIdP) John@43: print("personName2id") John@42: PrintTable(personName2id) John@17: end John@0: --}}} John@0: John@42: function UpdatePersonsReverse() John@42: for i,v in pairs(persons) do John@9: if i ~= "time" then John@16: personName2id[v.main] = i John@9: end John@9: end John@9: end John@9: John@43: -- Change processing {{{ John@42: function CreateWorkingStateFromChanges(changes) John@42: local personsBase = db.profile.persons John@42: local lists = db.profile.lists John@0: John@0: -- copy the base to the working state John@42: wipe(lists) John@42: wipe(persons) John@16: wipe(personName2id) John@8: John@42: tcopy(lists,lists) John@42: tcopy(persons,personsBase) John@0: John@0: -- now just go through the changes list applying each John@5: for i,v in ipairs(changes) do John@42: ProcessChange(v) John@0: end John@9: John@9: -- update the persons reverse list John@42: UpdatePersonsReverse() John@0: end John@0: John@42: function CreateChange(change) John@0: -- sanity John@0: assert(change) John@0: assert(change.action) John@0: assert(change.arg) John@0: John@42: StartChange(change) John@42: CommitChange(change) John@0: end John@0: John@42: function StartChange(change) John@42: local changes = db.profile.changes John@0: change.time = time() John@0: local n = getn(changes) John@0: if n > 0 then John@0: if changes[n].time >= change.time then John@0: change.time = changes[n].time + 1 John@0: end John@0: end John@0: end John@0: John@42: function CommitChange(change) John@42: local changes = db.profile.changes John@0: tinsert(changes,change) John@0: -- TODO: broadcast change John@0: end John@0: John@42: function ProcessChange(change) John@16: if change.action == "AddPerson" then John@42: DoAddPerson(change) John@20: elseif change.action == "RenameList" then John@42: DoRenameList(change) John@16: elseif change.action == "CreateList" then John@42: DoCreateList(change) John@21: elseif change.action == "DeleteList" then John@42: DoDeleteList(change) John@16: elseif change.action == "AddToListEnd" then John@42: DoAddPersonToListEnd(change) John@16: elseif change.action == "AddToListRand" then John@42: DoAddPersonToListRandom(change) John@27: elseif change.action == "RemovePerson" then John@42: DoRemovePerson(change) John@22: elseif change.action == "RemovePersonFromList" then John@42: DoRemovePersonFromList(change) John@16: elseif change.action == "SuicidePerson" then John@42: DoSuicidePerson(change) John@16: else John@43: print("Unknown message encountered") John@42: PrintTable(change) John@16: assert(false) John@16: end John@16: end John@16: John@16: --}}} John@42: -- John@27: -- holy crap long winded {{{ John@0: -- timestamp logic: John@0: -- use time() for comparisons - local clients use date() to make it pretty. only John@0: -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns John@0: -- out you can change timezones when you enter an instance server, so you really John@0: -- never know what time it is. John@0: -- There's unfortunately no hard-and-proven method for determining the true time John@0: -- difference between local time and server time. You can't just query the two John@0: -- and compare them because your server timezone can change (!) if you go into John@0: -- an instance server with a different timezone. This is apparently a big John@0: -- problem on Oceanic realms. John@0: -- John@0: -- Timestamp handling (brainstorming how to deal with drift): John@0: -- (not an issue) if someone sends you time in the future, update your offset so you won't John@0: -- send out events in the "past" to that person John@0: -- (not an issue - using local UTC now) on change-zone-event: check if you've changed timezones - might need update John@0: -- each time you add a change, check the tail of the change list; if this is John@0: -- less than that, you have a problem. Print a message. if this is equal, then John@0: -- that's ok, just bump it by 1 second. This could happen in the case of, say, John@0: -- spam-clicking the undo button or adding names to the list. The recipients John@0: -- should be ok with this since they'll follow the same algorithm. The only John@0: -- real chance for a problem is if two people click within the 1 second window? John@0: -- if someone sends you a past event, John@0: -- it's ok if it's newer than anything in the changes list John@0: -- otherwise ... causality has been violated. John@0: -- Whenever an admin signon event happens, have the admins each perform a John@0: -- timestamp check. Issue warnings for anyone with a clock that's more than John@0: -- X seconds out of sync with the others. Seriously, why isn't NTP a standard John@0: -- setting on all operating systems ... John@27: --}}} John@0: John@1: -- Action and DoAction defs {{{ John@27: -- Action Discussion {{{ John@0: -- The actual actions for changes start here John@0: -- John@42: -- Each action occurs as a pair of functions. The Action() function is from John@0: -- a list admin's point of view. Each will check for admin status, then create a John@0: -- change bundle, call the handler for that change (ie the DoAction func), and John@0: -- then record/transmist the bundle. These are simple and repetitive functions. John@0: -- John@42: -- The DoAction() function is tasked with executing the bundle and is what John@0: -- non-admins and admins alike will call to transform their working state via a John@0: -- change packet. Each Do() function will accept *only* a change packet, and John@0: -- it's assumed that the change has been vetted elsewhere. These are very blunt John@0: -- routines. John@0: -- John@0: -- Note that "undo" has no special voodoo to it. It's basically a change that John@27: -- reverses the prior change on the stack.--}}} John@42: function DoAddPerson(change)--{{{ John@0: assert(change) John@8: assert(change.arg.id) John@0: -- require admin John@42: local persons = persons John@37: local name = change.arg.name John@37: local id = change.arg.id John@8: assert(persons[id]==nil) John@37: persons[id] = {main=name,class=change.arg.class} John@8: persons.time=change.time John@16: personName2id[name] = id John@0: return true John@26: end--}}} John@42: function AddPerson(name)--{{{ John@42: local persons = persons John@42: local guid = _G.UnitGUID(name) John@0: -- TODO: check guid to be sure it's a player John@0: if not guid then John@43: printf("Could not add player %s - they must be in range or group",name) John@0: return John@0: end John@42: local _,englishClass = _G.UnitClass(name) John@43: --print("Person " .. name .. " is class " .. englishClass) John@8: local id = string.sub(guid,6) -- skip at least 0x0580 ... John@8: id = id:gsub("^0*(.*)","%1") -- nom all leading zeroes remaining John@8: John@8: if persons[id] and persons[id] ~= name then John@43: printf("Namechange detected for %s - new is %s, please rename the existing entry", persons[id].main, name) John@0: return John@0: end John@8: if persons[id] ~= nil then John@43: printf("%s is already in the persons list; disregarding", name) John@0: return John@0: end John@37: local change = {action="AddPerson",arg={name=name,id=id,class=englishClass}} John@42: if DoAddPerson(change) then John@42: CreateChange(change) John@0: end John@26: end--}}} John@42: function DoCreateList(change)--{{{ John@42: --if GetListIndex(change.arg.name) then John@43: -- rintf(("List %s already exists",v.name) John@18: -- return false John@18: --end John@42: lists[change.arg.id]={name=change.arg.name,time=change.time} John@0: return true John@26: end--}}} John@42: function CreateList(name)--{{{ John@0: -- require admin John@0: local change={action="CreateList",arg={name=name}} John@42: StartChange(change) John@18: change.arg.id=change.time -- use the creation timestamp as the list's index. it's as unique as anything... John@43: print("Creating ... " .. name) John@42: if DoCreateList(change) then John@42: CommitChange(change) John@0: end John@26: end--}}} John@42: function DoAddPersonToListEnd(change)--{{{ John@42: local list = lists[change.arg.listIndex] John@22: local index John@22: if getn(list) > 0 then John@22: index = list[#list].index + 0.1 John@22: else John@22: index = 0.1 John@22: end John@10: local entry = {index=index, id=change.arg.id} John@0: John@10: tinsert(list,entry) John@10: list.time = change.time John@10: list.closedRandom = true John@10: John@0: return true John@26: end--}}} John@42: function AddPersonToListEnd(name,listName)--{{{ John@0: -- require admin John@42: local listIndex = GetListIndex(listName) John@16: local id = personName2id[name] John@42: if IdIsInList(id,lists[listIndex]) then John@43: printf("Person %s is already on the reqeuested list",name) John@17: return false John@13: end John@43: printf("Adding %s (%s) to list %s (%s)", name, id, listName, listIndex) John@10: local change = {action="AddToListEnd",arg={id=id,listIndex=listIndex}} John@42: StartChange(change) John@42: if DoAddPersonToListEnd(change) then John@42: CommitChange(change) John@10: end John@26: end--}}} John@42: function DoAddPersonToListRandom(change)--{{{ John@42: local list = lists[change.arg.listIndex] John@10: local entry = {index=change.arg.roll, id=change.arg.id} John@10: John@10: tinsert(list,entry) John@12: table.sort(list,function(a,b) return a.index < b.index end) John@10: list.time = change.time John@10: John@10: return true John@26: end--}}} John@42: function AddPersonToListRandom(name,listName)--{{{ John@10: -- require admin John@42: local listIndex = GetListIndex(listName) John@42: if lists[listIndex].closedRandom then John@43: print("Cannot add person to list by random roll because an add-to-end operation has already occurred") John@12: return false John@10: end John@17: local id = personName2id[name] John@42: if IdIsInList(id,lists[listIndex]) then John@43: printf("Person %s is already on the reqeuested list",name) John@17: return false John@13: end John@10: local roll = math.random() John@43: printf("Adding %s (%s) to list %s (%s) with roll (%f)", name, id, listName, listIndex, roll) John@10: local change = {action="AddToListRand",arg={id=id,listIndex=listIndex,roll=roll}} John@42: StartChange(change) John@42: if DoAddPersonToListRandom(change) then John@42: CommitChange(change) John@0: end John@26: end--}}} John@42: function DoRemovePerson(change)--{{{ John@42: local person = persons[change.arg.id] John@27: personName2id[person.main] = nil John@42: persons[change.arg.id] = nil John@42: persons.time = change.time John@27: return true John@26: end--}}} John@42: function RemovePerson(name)--{{{ John@27: local id = personName2id[name] John@27: if not id then John@43: printf("%s is not in the persons list, please check your spelling", name) John@27: return false John@27: end John@28: local listsTheyreOn = {} John@28: -- check if they're active on any loot list John@42: for i,v in pairs(lists) do John@42: if IdIsInList(id,v) then John@29: tinsert(listsTheyreOn,v.name) John@29: break John@28: end John@28: end John@28: if getn(listsTheyreOn) > 0 then John@43: printf("Cannot remove person %s because they are on one or more lists (%s)",name,table.concat(listsTheyreOn,", ")) John@28: return false John@28: end John@27: local change = {action="RemovePerson",arg={id=id}} John@42: StartChange(change) John@42: if DoRemovePerson(change) then John@42: CommitChange(change) John@27: end John@26: end--}}} John@42: function DoSuicidePerson(change)--{{{ John@42: local list = lists[change.arg.listIndex] John@16: local affected = shallowCopy(change.arg.affect) John@0: -- the goal here is to rotate the suicide list by 1 John@0: -- then we can just mash it on top of the intersection between the original John@0: -- list and the working copy John@17: John@16: local replacement = shallowCopy(change.arg.affect) John@16: local temp = table.remove(replacement,1) -- pop John@16: tinsert(replacement,temp) -- push_back John@43: --rintf(("Before suicide of %s on list %s",slist[1],list.name) John@42: --PrintTable(list) John@0: for i = 1, #list do John@17: if list[i].id == affected[1] then John@17: table.remove(affected,1) John@16: list[i].id = replacement[1] John@16: table.remove(replacement,1) John@0: end John@0: end John@0: list.time=change.time John@0: return true John@26: end--}}} John@42: function SuicidePerson(name,listName)--{{{ John@0: -- require admin John@42: PopulateRaidList() John@42: local listIndex = GetListIndex(listName) John@16: local id = personName2id[name] John@42: local affect=GetSuicideList(id,lists[listIndex]) John@16: local change = {action="SuicidePerson",arg={affect=affect,listIndex=listIndex}} John@42: StartChange(change) John@42: if DoSuicidePerson(change) then John@42: CommitChange(change) John@0: end John@26: end--}}} John@42: function DoRenameList(change)--{{{ John@42: lists[change.arg.listIndex].name = change.arg.name John@42: lists[change.arg.listIndex].time = change.time John@20: return true John@26: end--}}} John@42: function RenameList(listName,newListName)--{{{ John@20: -- require admin John@42: local listIndex = GetListIndex(listName) John@20: local change = {action="RenameList",arg={listIndex=listIndex,name=newListName}} John@42: StartChange(change) John@42: if DoRenameList(change) then John@42: CommitChange(change) John@20: end John@26: end--}}} John@42: function DoDeleteList(change)--{{{ John@42: lists[change.arg.listIndex] = nil John@21: return true John@26: end--}}} John@42: function DeleteList(listName)--{{{ John@42: local listIndex = GetListIndex(listName) John@21: local change = {action="DeleteList",arg={listIndex=listIndex}} John@42: StartChange(change) John@42: if DoDeleteList(change) then John@42: CommitChange(change) John@21: end John@26: end--}}} John@42: function DoRemovePersonFromList(change)--{{{ John@42: local list = lists[change.arg.listIndex] John@22: John@29: for i,v in ipairs(list) do John@22: if v.id == change.arg.id then John@22: table.remove(list,i) John@22: break John@22: end John@22: end John@22: table.sort(list,function(a,b) return a.index < b.index end) John@22: list.time = change.time John@22: return true John@26: end--}}} John@42: function RemovePersonFromList(name,listName)--{{{ John@42: local listIndex = GetListIndex(listName) John@22: local pid = personName2id[name] John@29: -- todo: check that they're on the list in the first place John@22: local change = {action="RemovePersonFromList",arg={id=pid,listIndex=listIndex}} John@42: StartChange(change) John@42: if DoRemovePersonFromList(change) then John@42: CommitChange(change) John@22: end John@22: end John@20: --}}} John@26: --}}} John@20: -- Higher order actions (ie calls other standard actions){{{ John@20: John@42: function TrimLists(time) John@42: if not CheckListCausality() then John@43: print("Unable to trim changelist due to violated causality") John@5: return false John@5: end John@5: John@5: if type(time) ~= "number" then John@5: time = tonumber(time) John@5: end John@5: John@5: -- bisect the changes list by "time" John@5: local before = {} John@42: for i,v in ipairs(db.profile.changes) do John@5: if v.time <= time then John@5: tinsert(before,v) John@5: else John@5: break John@5: end John@5: end John@5: John@5: -- apply first half John@42: CreateWorkingStateFromChanges(before) John@5: John@5: -- save this state permanently; trim the changes permanently John@42: tcopy(db.profile.persons,persons) John@42: tcopy(db.profile.lists,lists) John@42: while db.profile.changes ~= nil and db.profile.changes[1] ~= nil and db.profile.changes[1].time <= time do John@42: table.remove(db.profile.changes,1) John@5: end John@5: John@5: -- using the trimmed list and the new bases, recreate the working state John@42: CreateWorkingStateFromChanges(db.profile.changes) John@5: end John@5: John@42: function AddMissingPersons() John@42: PopulateRaidList() John@1: local t = {} John@42: for id,_ in pairs(persons) do John@17: t[id] = true John@1: end John@42: for name,_ in pairs(raidNameP) do John@17: if personName2id[name] == nil then John@43: printf("Person %s is missing from the persons list - adding",name) John@42: AddPerson(name) John@1: end John@1: end John@1: -- TODO: batch into a single op - no need to spam 25 messages in a row John@1: end John@29: John@42: function PopulateListRandom(listIndex) John@17: -- difference (raid+reserve)-list, then random shuffle that, then add John@42: PopulateRaidList() John@42: local list = lists[listIndex] John@3: John@17: local t = {} -- after loops, contains intersection of IDs present between raid and reserve John@42: for i,v in pairs(raidIdP) do John@17: if v then t[i] = true end John@17: end John@42: for i,v in pairs(reserveIdP) do John@17: if v then t[i] = true end John@17: end John@17: John@17: -- now remove from t all of the people already present on the list John@21: if list then John@21: for i = 1,#list do John@21: if t[list[i].id] then John@21: t[list[i].id] = false John@21: end John@17: end John@17: end John@17: John@17: -- add all remaining John@17: for i,v in pairs(t) do John@17: if v then John@42: AddPersonToListRandom(persons[i].main,list.name) -- TODO: APTLR keys off of string names. probably need to change this. John@17: end John@17: end John@3: end John@30: John@42: function NukePerson(name) -- delete from all lists and then from persons John@30: local pid = personName2id[name] John@42: for i,v in pairs(lists) do John@42: RemovePersonFromList(name,v.name) John@30: end John@42: RemovePerson(name) John@30: end John@1: --}}} John@1: -- "Soft" actions- ie things that cause nonpermanent state {{{ John@1: John@1: -- reserves John@42: function AddReserve(name) John@43: print("Reserving" .. name) John@42: reserveIdP[personName2id[name]]=true John@1: -- TODO: communicate to others. don't store this in any way. John@1: end John@1: John@42: function RemoveReserve(name) John@42: reserveIdP[personName2id[name]]=false John@1: -- TODO: communicate to others. don't store this in any way. John@1: end John@1: John@1: John@42: --function GetActiveList() John@42: -- return lists[1] -- todo! John@1: --end John@1: John@1: --}}} John@0: John@17: -- The following (adapted) code is from Xinhuan (wowace forum member) John@0: -- Pre-create the unitID strings we will use John@0: local pID = {} John@0: local rID = {} John@0: for i = 1, 4 do John@42: pID[i] = sformat("party%d", i) John@0: end John@0: for i = 1, 40 do John@42: rID[i] = sformat("raid%d", i) John@0: end John@42: function PopulateRaidList() John@42: local inParty = _G.GetNumPartyMembers() John@42: local inRaid = _G.GetNumRaidMembers() John@17: local add = function(unitNameArg) John@42: local name = _G.UnitName(unitNameArg) John@42: raidNameP[name]=true John@17: if personName2id[name] ~= nil then John@42: raidIdP[personName2id[name]]=true John@17: end John@17: end John@0: John@42: wipe(raidNameP) John@42: wipe(raidIdP) John@0: if inRaid > 0 then John@0: for i = 1, inRaid do John@17: add(rID[i]) John@0: end John@0: elseif inParty > 0 then John@0: for i = 1, inParty do John@17: add(pID[i]) John@0: end John@0: -- Now add yourself as the last party member John@17: add("player") John@0: else John@0: -- You're alone John@17: add("player") John@0: end John@42: --PrintTable(raidNameP) John@0: end John@0: John@0: -- undo rules! John@0: -- only the most recent event can be undone John@0: -- ^^^ on a given list? John@0: -- algorithm is easy, given "Suicide A B C" John@0: -- just find A,B,C in the list and replace in order from the s message John@0: -- while undo is allowed *per-list*, certain events in the stream will John@0: -- prevent proper undo, such as add/delete player or add/delete list John@0: John@0: John@42: function GetSuicideList(id,list) John@43: --print("Calculating changeset for "..name.." from list -") John@42: --PrintTable(list) John@1: local t = {} John@1: local ret = {} John@1: local pushing = false John@1: for i = 1, #list do John@12: if list[i].id == id then John@1: pushing = true John@1: end John@42: if pushing and (raidIdP[list[i].id] or reserveIdP[list[i].id]) then John@10: tinsert(ret,list[i].id) John@1: end John@1: end John@43: --print("GSL") John@42: --PrintTable(ret) John@43: --print("GSL") John@1: return ret John@0: end John@0: John@42: function IdIsInList(id,listRef) John@13: for i = 1,#listRef do John@13: if id == listRef[i].id then John@13: return true John@13: end John@13: end John@13: return false John@13: end John@13: John@5: -- returns true if the events in the list are in time order John@42: function CheckListCausality() John@5: local t = nil John@42: for i,v in ipairs(db.profile.changes) do John@5: if t ~= nil then John@5: if v.time <= t then John@5: return false John@5: end John@5: end John@5: t = v.time John@5: end John@5: return true John@5: end John@0: John@0: -- Support functions John@0: John@42: function GetListIndex(name) John@42: for i,v in pairs(lists) do John@0: if v.name == name then John@0: return i John@0: end John@0: end John@17: return nil John@0: end John@1: John@3: