annotate Lists.lua @ 37:6362fe301d43

Storing class name in the persons so we can color them in later Exposing some data lists, but this will likely be retracted later in favor of interface functions
author John@Yosemite-PC
date Tue, 13 Mar 2012 23:17:18 -0400
parents ecd37523ae04
children ecef0cba2913
rev   line source
John@0 1 -- lists consist of three things
John@0 2 -- 1) a base state - agreed on by one or more list holders
John@0 3 -- 2) change sets - incremental list changes (can be rolled forwards or
John@0 4 -- backwards)
John@0 5 -- 3) working state - not saved because it can be so easily calculated
John@0 6 --
John@0 7 -- A separate user list is held - lists index into this
John@0 8
John@0 9
John@27 10 -- TODO: switch all action functions to use identifiers rather than names
John@27 11 -- TODO: collaborative list trimming
John@24 12 -- TODO: collapse slists into delimited strings for space (premature optimization?)
John@18 13 -- TODO: organize working state data a little more carefully - hard to keep
John@18 14 -- track of all the arrays that are floating out there
John@35 15 -- TODO: players list, drop the "main" sub-entry for people with no alts
John@18 16
John@17 17 -- holy crap long notes {{{
John@4 18 -- notes on list storage:
John@7 19 -- Using names as keys as I do now is atrocious.
John@4 20 -- It prevents insertions (twss) to the middle of the list because then it acts
John@4 21 -- as a side effect onto all the others. ie ABCD -> AXBCD would be phrased as
John@4 22 -- "insert X and shift down B,C,D" which sucks. BCD haven't really been affected
John@4 23 -- (yet) because their relative positions to the others are still intact - ie
John@4 24 -- they are still below A right where they belong. But really X hasn't done
John@4 25 -- anything to affect their relative standing.
John@4 26 --
John@4 27 -- Ok so we can't use names.
John@4 28 --
John@4 29 -- We can't use monotonic integers either because it suffers the same problem.
John@4 30 -- Also consider, randoming in someone to a list of ABCD. Say they roll spot 2.
John@4 31 -- What if someone else runs a separate raid and also randoms someone into slot
John@4 32 -- 2? How do you handle that conflict? Difficult. Also, consider this:
John@4 33 -- List of ABCD on night 1.
John@4 34 -- Admin 1 on night 2 rolls in 30 new people. ABCD's indexes are shuffled to be
John@4 35 -- between 1-35.
John@4 36 -- Admin 2 on night 3 rolls in 5 new ones and people ABCD and PQRST now all have
John@4 37 -- indexes between 1-9.
John@4 38 -- When these two are resolved against one another, do the 1-9 peopole end up on
John@4 39 -- top of the list compared to those other 30?
John@4 40 --
John@4 41 -- Solution:
John@4 42 -- Need a huge random space with purposely left gaps to leave plenty of room for
John@4 43 -- conflicts.
John@4 44 -- So if ABCD had randomed on a space of say, 10,000 and then were sorted into
John@4 45 -- order, then the next 30 could roll into that same space and have a proper
John@4 46 -- ordering. Then the next 5, etc.
John@4 47 --
John@4 48 -- Handling conflicts:
John@4 49 --
John@9 50 -- Executive decision: random on a range of [0,1], ie math.random
John@9 51 -- then on an add-to-end event just do last + .1
John@9 52 -- disallow random after any add-to-end event occurs
John@9 53 -- because the list either elongates beyond 1 OR becomes
John@9 54 -- ridiculously bottom heavy, thus meaning that randoms
John@9 55 -- don't get an even distibution from then on (in fact
John@9 56 -- they'll end up getting top favor)
John@9 57 -- * if a stream contains a random-add after an add-to-end
John@9 58 -- it is declared invalid. tough tits. it's just not a fair
John@9 59 -- distribution at that point.
John@10 60 -- * actually, fuck it. I'll give them an unlock command and
John@10 61 -- let them screw over their lists :)
John@17 62 --}}}
John@18 63
John@17 64 -- there are some dep chains here. for instance, to have a raidIdP value, a
John@17 65 -- person must have a bsk.persons value which leads to a personName2id which
John@17 66 -- leads to a raidIdP
John@0 67
John@0 68 bsk.lists = {}
John@8 69 bsk.persons = {}
John@0 70
John@37 71 bsk.raidNameP = {} -- "name" is present in raid
John@37 72 bsk.raidIdP = {} -- "id" is present in raid
John@37 73 bsk.reserveIdP = {} -- "reserve id present"
John@8 74 local activeListKey = 1 -- temporary
John@16 75 local personName2id = {} -- given "name" get that person's id
John@0 76
John@0 77 local tinsert = table.insert
John@0 78 local sformat = string.format
John@0 79 local getn = table.getn
John@0 80
John@17 81 function bsk:SelfDestruct()
John@17 82 bsk.lists = {}
John@17 83 bsk.persons = {}
John@17 84 bsk.db.profile.persons = {}
John@17 85 bsk.db.profile.changes = {}
John@34 86 bsk.db.profile.lists = {}
John@37 87 bsk.raidNameP = {}
John@37 88 bsk.raidIdP = {}
John@37 89 bsk.reserveIdP = {}
John@17 90 personName2id = {}
John@17 91 end
John@0 92 function bsk:tcopy(to, from)
John@0 93 for k,v in pairs(from) do
John@0 94 if(type(v)=="table") then
John@0 95 to[k] = {}
John@0 96 bsk:tcopy(to[k], v);
John@0 97 else
John@0 98 to[k] = v;
John@0 99 end
John@0 100 end
John@0 101 end
John@0 102 local shallowCopy = function(t)
John@0 103 local u = { }
John@0 104 for k, v in pairs(t) do u[k] = v end
John@0 105 return setmetatable(u, getmetatable(t))
John@0 106 end
John@0 107
John@1 108 -- Debugging {{{
John@9 109 function bsk:PrettyPrintList(listIndex)
John@9 110 local list = bsk.lists[listIndex]
John@19 111 bsk:Print("List: " .. list.name .. " (" .. listIndex .. ") - last modified " .. date("%m/%d/%y %H:%M:%S", list.time) .. " (",list.time,")" )
John@9 112 for i = 1,#list do
John@12 113 bsk:Print(" " .. i .. " - " .. bsk.persons[list[i].id].main)
John@9 114 end
John@9 115 end
John@9 116 function bsk:PrettyPrintLists()
John@9 117 for i,_ in pairs(bsk.lists) do
John@9 118 bsk:PrettyPrintList(i)
John@9 119 end
John@9 120 end
John@1 121 function bsk:PrintLists()
John@1 122 bsk:PrintTable(bsk.lists)
John@1 123 end
John@1 124 function bsk:PrintChanges()
John@1 125 bsk:PrintTable(bsk.db.profile.changes)
John@1 126 end
John@8 127 function bsk:PrintPersons()
John@8 128 bsk:PrintTable(bsk.persons)
John@1 129 end
John@0 130 function bsk:PrintTable(table, depth)
John@0 131 depth = depth or ""
John@0 132 if not table then return end
John@37 133 if #depth > 3*5 then self:Print(depth.."Recursion too deep - stopping"); return end
John@0 134 for i,v in pairs(table) do
John@0 135 if( type(v) == "string" ) then
John@0 136 self:Print(depth .. i .. " - " .. v)
John@0 137 elseif( type(v) == "number" ) then
John@0 138 self:Print(depth .. i .. " - " .. tostring(v))
John@0 139 elseif( type(v) == "table" ) then
John@0 140 self:Print(depth .. i .." - ")
John@0 141 self:PrintTable(v,depth.." ")
John@0 142 elseif( type(v) == "boolean" ) then
John@0 143 self:Print(depth .. i .. " - " .. tostring(v))
John@37 144 elseif( type(v) == "function" ) then
John@37 145 self:Print(depth .. "function " .. i .. "()")
John@0 146 else
John@0 147 self:Print(depth .. i .. " - not sure how to print type: " .. type(v) )
John@0 148 end
John@0 149 end
John@0 150 end
John@0 151
John@17 152 function bsk:PrintRaidAndReserve()
John@17 153 bsk:Print("RaidNameP")
John@37 154 bsk:PrintTable(bsk.raidNameP)
John@17 155 bsk:Print("RaidIdP")
John@37 156 bsk:PrintTable(bsk.raidIdP)
John@17 157 bsk:Print("ReserveP")
John@37 158 bsk:PrintTable(bsk.reserveIdP)
John@17 159 bsk:Print("personName2id")
John@17 160 bsk:PrintTable(personName2id)
John@17 161 end
John@0 162 --}}}
John@0 163
John@9 164 function bsk:UpdatePersonsReverse()
John@9 165 for i,v in pairs(bsk.persons) do
John@9 166 if i ~= "time" then
John@16 167 personName2id[v.main] = i
John@9 168 end
John@9 169 end
John@9 170 end
John@9 171
John@16 172 -- Change processing {{{
John@5 173 function bsk:CreateWorkingStateFromChanges(changes)
John@8 174 local personsBase = self.db.profile.persons
John@34 175 local lists = self.db.profile.lists
John@0 176
John@0 177 -- copy the base to the working state
John@0 178 wipe(bsk.lists)
John@8 179 wipe(bsk.persons)
John@16 180 wipe(personName2id)
John@8 181
John@34 182 bsk:tcopy(bsk.lists,lists)
John@8 183 bsk:tcopy(bsk.persons,personsBase)
John@0 184
John@0 185 -- now just go through the changes list applying each
John@5 186 for i,v in ipairs(changes) do
John@0 187 bsk:ProcessChange(v)
John@0 188 end
John@9 189
John@9 190 -- update the persons reverse list
John@9 191 bsk:UpdatePersonsReverse()
John@0 192 end
John@0 193
John@0 194 function bsk:CreateChange(change)
John@0 195 -- sanity
John@0 196 assert(change)
John@0 197 assert(change.action)
John@0 198 assert(change.arg)
John@0 199
John@0 200 bsk:StartChange(change)
John@0 201 bsk:CommitChange(change)
John@0 202 end
John@0 203
John@0 204 function bsk:StartChange(change)
John@0 205 local changes = self.db.profile.changes
John@0 206 change.time = time()
John@0 207 local n = getn(changes)
John@0 208 if n > 0 then
John@0 209 if changes[n].time >= change.time then
John@0 210 change.time = changes[n].time + 1
John@0 211 end
John@0 212 end
John@0 213 end
John@0 214
John@0 215 function bsk:CommitChange(change)
John@0 216 local changes = self.db.profile.changes
John@0 217 tinsert(changes,change)
John@0 218 -- TODO: broadcast change
John@0 219 end
John@0 220
John@16 221 function bsk:ProcessChange(change)
John@16 222 if change.action == "AddPerson" then
John@16 223 bsk:DoAddPerson(change)
John@20 224 elseif change.action == "RenameList" then
John@20 225 bsk:DoRenameList(change)
John@16 226 elseif change.action == "CreateList" then
John@16 227 bsk:DoCreateList(change)
John@21 228 elseif change.action == "DeleteList" then
John@21 229 bsk:DoDeleteList(change)
John@16 230 elseif change.action == "AddToListEnd" then
John@16 231 bsk:DoAddPersonToListEnd(change)
John@16 232 elseif change.action == "AddToListRand" then
John@16 233 bsk:DoAddPersonToListRandom(change)
John@27 234 elseif change.action == "RemovePerson" then
John@27 235 bsk:DoRemovePerson(change)
John@22 236 elseif change.action == "RemovePersonFromList" then
John@22 237 bsk:DoRemovePersonFromList(change)
John@16 238 elseif change.action == "SuicidePerson" then
John@16 239 bsk:DoSuicidePerson(change)
John@16 240 else
John@16 241 bsk:Print("Unknown message encountered")
John@16 242 bsk:PrintTable(change)
John@16 243 assert(false)
John@16 244 end
John@16 245 end
John@16 246
John@16 247 --}}}
John@27 248 -- holy crap long winded {{{
John@0 249 -- timestamp logic:
John@0 250 -- use time() for comparisons - local clients use date() to make it pretty. only
John@0 251 -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns
John@0 252 -- out you can change timezones when you enter an instance server, so you really
John@0 253 -- never know what time it is.
John@0 254 -- There's unfortunately no hard-and-proven method for determining the true time
John@0 255 -- difference between local time and server time. You can't just query the two
John@0 256 -- and compare them because your server timezone can change (!) if you go into
John@0 257 -- an instance server with a different timezone. This is apparently a big
John@0 258 -- problem on Oceanic realms.
John@0 259 --
John@0 260 -- Timestamp handling (brainstorming how to deal with drift):
John@0 261 -- (not an issue) if someone sends you time in the future, update your offset so you won't
John@0 262 -- send out events in the "past" to that person
John@0 263 -- (not an issue - using local UTC now) on change-zone-event: check if you've changed timezones - might need update
John@0 264 -- each time you add a change, check the tail of the change list; if this is
John@0 265 -- less than that, you have a problem. Print a message. if this is equal, then
John@0 266 -- that's ok, just bump it by 1 second. This could happen in the case of, say,
John@0 267 -- spam-clicking the undo button or adding names to the list. The recipients
John@0 268 -- should be ok with this since they'll follow the same algorithm. The only
John@0 269 -- real chance for a problem is if two people click within the 1 second window?
John@0 270 -- if someone sends you a past event,
John@0 271 -- it's ok if it's newer than anything in the changes list
John@0 272 -- otherwise ... causality has been violated.
John@0 273 -- Whenever an admin signon event happens, have the admins each perform a
John@0 274 -- timestamp check. Issue warnings for anyone with a clock that's more than
John@0 275 -- X seconds out of sync with the others. Seriously, why isn't NTP a standard
John@0 276 -- setting on all operating systems ...
John@27 277 --}}}
John@0 278
John@1 279 -- Action and DoAction defs {{{
John@27 280 -- Action Discussion {{{
John@0 281 -- The actual actions for changes start here
John@0 282 --
John@0 283 -- Each action occurs as a pair of functions. The bsk:Action() function is from
John@0 284 -- a list admin's point of view. Each will check for admin status, then create a
John@0 285 -- change bundle, call the handler for that change (ie the DoAction func), and
John@0 286 -- then record/transmist the bundle. These are simple and repetitive functions.
John@0 287 --
John@0 288 -- The bsk:DoAction() function is tasked with executing the bundle and is what
John@0 289 -- non-admins and admins alike will call to transform their working state via a
John@0 290 -- change packet. Each Do() function will accept *only* a change packet, and
John@0 291 -- it's assumed that the change has been vetted elsewhere. These are very blunt
John@0 292 -- routines.
John@0 293 --
John@0 294 -- Note that "undo" has no special voodoo to it. It's basically a change that
John@27 295 -- reverses the prior change on the stack.--}}}
John@26 296 function bsk:DoAddPerson(change)--{{{
John@0 297 assert(change)
John@8 298 assert(change.arg.id)
John@0 299 -- require admin
John@8 300 local persons = bsk.persons
John@37 301 local name = change.arg.name
John@37 302 local id = change.arg.id
John@8 303 assert(persons[id]==nil)
John@37 304 persons[id] = {main=name,class=change.arg.class}
John@8 305 persons.time=change.time
John@16 306 personName2id[name] = id
John@0 307 return true
John@26 308 end--}}}
John@26 309 function bsk:AddPerson(name)--{{{
John@8 310 local persons = bsk.persons
John@0 311 local guid = UnitGUID(name)
John@0 312 -- TODO: check guid to be sure it's a player
John@0 313 if not guid then
John@0 314 self:Print(sformat("Could not add player %s - they must be in range or group",name))
John@0 315 return
John@0 316 end
John@37 317 local _,englishClass = UnitClass(name)
John@37 318 --bsk:Print("Person " .. name .. " is class " .. englishClass)
John@8 319 local id = string.sub(guid,6) -- skip at least 0x0580 ...
John@8 320 id = id:gsub("^0*(.*)","%1") -- nom all leading zeroes remaining
John@8 321
John@8 322 if persons[id] and persons[id] ~= name then
John@17 323 self:Print(sformat("Namechange detected for %s - new is %s, please rename the existing entry", persons[id].main, name))
John@0 324 return
John@0 325 end
John@8 326 if persons[id] ~= nil then
John@8 327 self:Print(sformat("%s is already in the persons list; disregarding", name))
John@0 328 return
John@0 329 end
John@37 330 local change = {action="AddPerson",arg={name=name,id=id,class=englishClass}}
John@8 331 if bsk:DoAddPerson(change) then
John@0 332 bsk:CreateChange(change)
John@0 333 end
John@26 334 end--}}}
John@26 335 function bsk:DoCreateList(change)--{{{
John@18 336 --if bsk:GetListIndex(change.arg.name) then
John@18 337 -- self:Print(sformat("List %s already exists",v.name))
John@18 338 -- return false
John@18 339 --end
John@19 340 bsk.lists[change.arg.id]={name=change.arg.name,time=change.time}
John@0 341 return true
John@26 342 end--}}}
John@26 343 function bsk:CreateList(name)--{{{
John@0 344 -- require admin
John@0 345 local change={action="CreateList",arg={name=name}}
John@0 346 bsk:StartChange(change)
John@18 347 change.arg.id=change.time -- use the creation timestamp as the list's index. it's as unique as anything...
John@0 348 self:Print("Creating ... " .. name)
John@0 349 if bsk:DoCreateList(change) then
John@0 350 bsk:CommitChange(change)
John@0 351 end
John@26 352 end--}}}
John@26 353 function bsk:DoAddPersonToListEnd(change)--{{{
John@10 354 local list = bsk.lists[change.arg.listIndex]
John@22 355 local index
John@22 356 if getn(list) > 0 then
John@22 357 index = list[#list].index + 0.1
John@22 358 else
John@22 359 index = 0.1
John@22 360 end
John@10 361 local entry = {index=index, id=change.arg.id}
John@0 362
John@10 363 tinsert(list,entry)
John@10 364 list.time = change.time
John@10 365 list.closedRandom = true
John@10 366
John@0 367 return true
John@26 368 end--}}}
John@26 369 function bsk:AddPersonToListEnd(name,listName)--{{{
John@0 370 -- require admin
John@16 371 local listIndex = bsk:GetListIndex(listName)
John@16 372 local id = personName2id[name]
John@13 373 if bsk:IdIsInList(id,bsk.lists[listIndex]) then
John@13 374 bsk:Print(sformat("Person %s is already on the reqeuested list",name))
John@17 375 return false
John@13 376 end
John@16 377 bsk:Print(sformat("Adding %s (%s) to list %s (%s)", name, id, listName, listIndex))
John@10 378 local change = {action="AddToListEnd",arg={id=id,listIndex=listIndex}}
John@0 379 bsk:StartChange(change)
John@10 380 if bsk:DoAddPersonToListEnd(change) then
John@10 381 bsk:CommitChange(change)
John@10 382 end
John@26 383 end--}}}
John@26 384 function bsk:DoAddPersonToListRandom(change)--{{{
John@10 385 local list = bsk.lists[change.arg.listIndex]
John@10 386 local entry = {index=change.arg.roll, id=change.arg.id}
John@10 387
John@10 388 tinsert(list,entry)
John@12 389 table.sort(list,function(a,b) return a.index < b.index end)
John@10 390 list.time = change.time
John@10 391
John@10 392 return true
John@26 393 end--}}}
John@26 394 function bsk:AddPersonToListRandom(name,listName)--{{{
John@10 395 -- require admin
John@16 396 local listIndex = bsk:GetListIndex(listName)
John@10 397 if bsk.lists[listIndex].closedRandom then
John@10 398 self:Print("Cannot add person to list by random roll because an add-to-end operation has already occurred")
John@12 399 return false
John@10 400 end
John@17 401 local id = personName2id[name]
John@13 402 if bsk:IdIsInList(id,bsk.lists[listIndex]) then
John@13 403 bsk:Print(sformat("Person %s is already on the reqeuested list",name))
John@17 404 return false
John@13 405 end
John@10 406 local roll = math.random()
John@16 407 bsk:Print(sformat("Adding %s (%s) to list %s (%s) with roll (%f)", name, id, listName, listIndex, roll))
John@10 408 local change = {action="AddToListRand",arg={id=id,listIndex=listIndex,roll=roll}}
John@10 409 bsk:StartChange(change)
John@10 410 if bsk:DoAddPersonToListRandom(change) then
John@0 411 bsk:CommitChange(change)
John@0 412 end
John@26 413 end--}}}
John@26 414 function bsk:DoRemovePerson(change)--{{{
John@27 415 local person = bsk.persons[change.arg.id]
John@27 416 personName2id[person.main] = nil
John@27 417 bsk.persons[change.arg.id] = nil
John@27 418 bsk.persons.time = change.time
John@27 419 return true
John@26 420 end--}}}
John@26 421 function bsk:RemovePerson(name)--{{{
John@27 422 local id = personName2id[name]
John@27 423 if not id then
John@27 424 bsk:Print(sformat("%s is not in the persons list, please check your spelling", name))
John@27 425 return false
John@27 426 end
John@28 427 local listsTheyreOn = {}
John@28 428 -- check if they're active on any loot list
John@28 429 for i,v in pairs(bsk.lists) do
John@29 430 if bsk:IdIsInList(id,v) then
John@29 431 tinsert(listsTheyreOn,v.name)
John@29 432 break
John@28 433 end
John@28 434 end
John@28 435 if getn(listsTheyreOn) > 0 then
John@28 436 self:Print(sformat("Cannot remove person %s because they are on one or more lists (%s)",name,table.concat(listsTheyreOn,", ")))
John@28 437 return false
John@28 438 end
John@27 439 local change = {action="RemovePerson",arg={id=id}}
John@27 440 bsk:StartChange(change)
John@28 441 if bsk:DoRemovePerson(change) then
John@27 442 bsk:CommitChange(change)
John@27 443 end
John@26 444 end--}}}
John@26 445 function bsk:DoSuicidePerson(change)--{{{
John@16 446 local list = bsk.lists[change.arg.listIndex]
John@16 447 local affected = shallowCopy(change.arg.affect)
John@0 448 -- the goal here is to rotate the suicide list by 1
John@0 449 -- then we can just mash it on top of the intersection between the original
John@0 450 -- list and the working copy
John@17 451
John@16 452 local replacement = shallowCopy(change.arg.affect)
John@16 453 local temp = table.remove(replacement,1) -- pop
John@16 454 tinsert(replacement,temp) -- push_back
John@0 455 --bsk:Print(sformat("Before suicide of %s on list %s",slist[1],list.name))
John@0 456 --bsk:PrintTable(list)
John@0 457 for i = 1, #list do
John@17 458 if list[i].id == affected[1] then
John@17 459 table.remove(affected,1)
John@16 460 list[i].id = replacement[1]
John@16 461 table.remove(replacement,1)
John@0 462 end
John@0 463 end
John@0 464 list.time=change.time
John@0 465 return true
John@26 466 end--}}}
John@26 467 function bsk:SuicidePerson(name,listName)--{{{
John@0 468 -- require admin
John@0 469 bsk:PopulateRaidList()
John@16 470 local listIndex = bsk:GetListIndex(listName)
John@16 471 local id = personName2id[name]
John@16 472 local affect=bsk:GetSuicideList(id,bsk.lists[listIndex])
John@16 473 local change = {action="SuicidePerson",arg={affect=affect,listIndex=listIndex}}
John@0 474 bsk:StartChange(change)
John@8 475 if bsk:DoSuicidePerson(change) then
John@0 476 bsk:CommitChange(change)
John@0 477 end
John@26 478 end--}}}
John@26 479 function bsk:DoRenameList(change)--{{{
John@20 480 bsk.lists[change.arg.listIndex].name = change.arg.name
John@20 481 bsk.lists[change.arg.listIndex].time = change.time
John@20 482 return true
John@26 483 end--}}}
John@26 484 function bsk:RenameList(listName,newListName)--{{{
John@20 485 -- require admin
John@20 486 local listIndex = bsk:GetListIndex(listName)
John@20 487 local change = {action="RenameList",arg={listIndex=listIndex,name=newListName}}
John@20 488 bsk:StartChange(change)
John@20 489 if bsk:DoRenameList(change) then
John@20 490 bsk:CommitChange(change)
John@20 491 end
John@26 492 end--}}}
John@26 493 function bsk:DoDeleteList(change)--{{{
John@21 494 bsk.lists[change.arg.listIndex] = nil
John@21 495 return true
John@26 496 end--}}}
John@26 497 function bsk:DeleteList(listName)--{{{
John@21 498 local listIndex = bsk:GetListIndex(listName)
John@21 499 local change = {action="DeleteList",arg={listIndex=listIndex}}
John@21 500 bsk:StartChange(change)
John@21 501 if bsk:DoDeleteList(change) then
John@21 502 bsk:CommitChange(change)
John@21 503 end
John@26 504 end--}}}
John@26 505 function bsk:DoRemovePersonFromList(change)--{{{
John@22 506 local list = bsk.lists[change.arg.listIndex]
John@22 507
John@29 508 for i,v in ipairs(list) do
John@22 509 if v.id == change.arg.id then
John@22 510 table.remove(list,i)
John@22 511 break
John@22 512 end
John@22 513 end
John@22 514 table.sort(list,function(a,b) return a.index < b.index end)
John@22 515 list.time = change.time
John@22 516 return true
John@26 517 end--}}}
John@26 518 function bsk:RemovePersonFromList(name,listName)--{{{
John@22 519 local listIndex = bsk:GetListIndex(listName)
John@22 520 local pid = personName2id[name]
John@29 521 -- todo: check that they're on the list in the first place
John@22 522 local change = {action="RemovePersonFromList",arg={id=pid,listIndex=listIndex}}
John@22 523 bsk:StartChange(change)
John@22 524 if bsk:DoRemovePersonFromList(change) then
John@22 525 bsk:CommitChange(change)
John@22 526 end
John@22 527 end
John@20 528 --}}}
John@26 529 --}}}
John@20 530 -- Higher order actions (ie calls other standard actions){{{
John@20 531
John@5 532 function bsk:TrimLists(time)
John@5 533 if not bsk:CheckListCausality() then
John@18 534 self:Print("Unable to trim changelist due to violated causality")
John@5 535 return false
John@5 536 end
John@5 537
John@5 538 if type(time) ~= "number" then
John@5 539 time = tonumber(time)
John@5 540 end
John@5 541
John@5 542 -- bisect the changes list by "time"
John@5 543 local before = {}
John@5 544 for i,v in ipairs(self.db.profile.changes) do
John@5 545 if v.time <= time then
John@5 546 tinsert(before,v)
John@5 547 else
John@5 548 break
John@5 549 end
John@5 550 end
John@5 551
John@5 552 -- apply first half
John@5 553 bsk:CreateWorkingStateFromChanges(before)
John@5 554
John@5 555 -- save this state permanently; trim the changes permanently
John@8 556 bsk:tcopy(bsk.db.profile.persons,bsk.persons)
John@34 557 bsk:tcopy(bsk.db.profile.lists,bsk.lists)
John@8 558 while bsk.db.profile.changes ~= nil and bsk.db.profile.changes[1] ~= nil and bsk.db.profile.changes[1].time <= time do
John@5 559 table.remove(bsk.db.profile.changes,1)
John@5 560 end
John@5 561
John@5 562 -- using the trimmed list and the new bases, recreate the working state
John@5 563 bsk:CreateWorkingStateFromChanges(bsk.db.profile.changes)
John@5 564 end
John@5 565
John@8 566 function bsk:AddMissingPersons()
John@1 567 bsk:PopulateRaidList()
John@1 568 local t = {}
John@17 569 for id,_ in pairs(bsk.persons) do
John@17 570 t[id] = true
John@1 571 end
John@37 572 for name,_ in pairs(bsk.raidNameP) do
John@17 573 if personName2id[name] == nil then
John@17 574 bsk:Print(sformat("Person %s is missing from the persons list - adding",name))
John@17 575 bsk:AddPerson(name)
John@1 576 end
John@1 577 end
John@1 578 -- TODO: batch into a single op - no need to spam 25 messages in a row
John@1 579 end
John@29 580
John@17 581 function bsk:PopulateListRandom(listIndex)
John@17 582 -- difference (raid+reserve)-list, then random shuffle that, then add
John@3 583 bsk:PopulateRaidList()
John@17 584 local list = bsk.lists[listIndex]
John@3 585
John@17 586 local t = {} -- after loops, contains intersection of IDs present between raid and reserve
John@37 587 for i,v in pairs(bsk.raidIdP) do
John@17 588 if v then t[i] = true end
John@17 589 end
John@37 590 for i,v in pairs(bsk.reserveIdP) do
John@17 591 if v then t[i] = true end
John@17 592 end
John@17 593
John@17 594 -- now remove from t all of the people already present on the list
John@21 595 if list then
John@21 596 for i = 1,#list do
John@21 597 if t[list[i].id] then
John@21 598 t[list[i].id] = false
John@21 599 end
John@17 600 end
John@17 601 end
John@17 602
John@17 603 -- add all remaining
John@17 604 for i,v in pairs(t) do
John@17 605 if v then
John@17 606 bsk:AddPersonToListRandom(bsk.persons[i].main,list.name) -- TODO: APTLR keys off of string names. probably need to change this.
John@17 607 end
John@17 608 end
John@3 609 end
John@30 610
John@30 611 function bsk:NukePerson(name) -- delete from all lists and then from persons
John@30 612 local pid = personName2id[name]
John@30 613 for i,v in pairs(bsk.lists) do
John@30 614 bsk:RemovePersonFromList(name,v.name)
John@30 615 end
John@30 616 bsk:RemovePerson(name)
John@30 617 end
John@1 618 --}}}
John@1 619 -- "Soft" actions- ie things that cause nonpermanent state {{{
John@1 620
John@1 621 -- reserves
John@1 622 function bsk:AddReserve(name)
John@37 623 bsk.reserveIdP[personName2id[name]]=true
John@1 624 -- TODO: communicate to others. don't store this in any way.
John@1 625 end
John@1 626
John@1 627 function bsk:RemoveReserve(name)
John@37 628 bsk.reserveIdP[personName2id[name]]=false
John@1 629 -- TODO: communicate to others. don't store this in any way.
John@1 630 end
John@1 631
John@1 632
John@1 633 --function bsk:GetActiveList()
John@1 634 -- return bsk.lists[1] -- todo!
John@1 635 --end
John@1 636
John@1 637 --}}}
John@0 638
John@17 639 -- The following (adapted) code is from Xinhuan (wowace forum member)
John@0 640 -- Pre-create the unitID strings we will use
John@0 641 local pID = {}
John@0 642 local rID = {}
John@0 643 for i = 1, 4 do
John@0 644 pID[i] = format("party%d", i)
John@0 645 end
John@0 646 for i = 1, 40 do
John@0 647 rID[i] = format("raid%d", i)
John@0 648 end
John@0 649 function bsk:PopulateRaidList()
John@0 650 local inParty = GetNumPartyMembers()
John@0 651 local inRaid = GetNumRaidMembers()
John@17 652 local add = function(unitNameArg)
John@17 653 local name = UnitName(unitNameArg)
John@37 654 bsk.raidNameP[name]=true
John@17 655 if personName2id[name] ~= nil then
John@37 656 bsk.raidIdP[personName2id[name]]=true
John@17 657 end
John@17 658 end
John@0 659
John@37 660 wipe(bsk.raidNameP)
John@37 661 wipe(bsk.raidIdP)
John@0 662 if inRaid > 0 then
John@0 663 for i = 1, inRaid do
John@17 664 add(rID[i])
John@0 665 end
John@0 666 elseif inParty > 0 then
John@0 667 for i = 1, inParty do
John@17 668 add(pID[i])
John@0 669 end
John@0 670 -- Now add yourself as the last party member
John@17 671 add("player")
John@0 672 else
John@0 673 -- You're alone
John@17 674 add("player")
John@0 675 end
John@37 676 --bsk:PrintTable(bsk.raidNameP)
John@0 677 end
John@0 678
John@0 679 -- undo rules!
John@0 680 -- only the most recent event can be undone
John@0 681 -- ^^^ on a given list?
John@0 682 -- algorithm is easy, given "Suicide A B C"
John@0 683 -- just find A,B,C in the list and replace in order from the s message
John@0 684 -- while undo is allowed *per-list*, certain events in the stream will
John@0 685 -- prevent proper undo, such as add/delete player or add/delete list
John@0 686
John@0 687
John@12 688 function bsk:GetSuicideList(id,list)
John@1 689 --self:Print("Calculating changeset for "..name.." from list -")
John@1 690 --self:PrintTable(list)
John@1 691 local t = {}
John@1 692 local ret = {}
John@1 693 local pushing = false
John@1 694 for i = 1, #list do
John@12 695 if list[i].id == id then
John@1 696 pushing = true
John@1 697 end
John@37 698 if pushing and (bsk.raidIdP[list[i].id] or bsk.reserveIdP[list[i].id]) then
John@10 699 tinsert(ret,list[i].id)
John@1 700 end
John@1 701 end
John@16 702 --bsk:Print("GSL")
John@16 703 --bsk:PrintTable(ret)
John@16 704 --bsk:Print("GSL")
John@1 705 return ret
John@0 706 end
John@0 707
John@13 708 function bsk:IdIsInList(id,listRef)
John@13 709 for i = 1,#listRef do
John@13 710 if id == listRef[i].id then
John@13 711 return true
John@13 712 end
John@13 713 end
John@13 714 return false
John@13 715 end
John@13 716
John@5 717 -- returns true if the events in the list are in time order
John@5 718 function bsk:CheckListCausality()
John@5 719 local t = nil
John@5 720 for i,v in ipairs(bsk.db.profile.changes) do
John@5 721 if t ~= nil then
John@5 722 if v.time <= t then
John@5 723 return false
John@5 724 end
John@5 725 end
John@5 726 t = v.time
John@5 727 end
John@5 728 return true
John@5 729 end
John@0 730
John@0 731 -- Support functions
John@0 732
John@0 733 function bsk:GetListIndex(name)
John@0 734 for i,v in pairs(bsk.lists) do
John@0 735 if v.name == name then
John@0 736 return i
John@0 737 end
John@0 738 end
John@17 739 return nil
John@0 740 end
John@1 741
John@3 742 local shuffleArray = function(array)
John@3 743 local arrayCount = #array
John@3 744 for i = arrayCount, 2, -1 do
John@3 745 local j = math.random(1, i)
John@3 746 array[i], array[j] = array[j], array[i]
John@3 747 end
John@3 748 return array
John@3 749 end
John@3 750