annotate Lists.lua @ 2:00286ba3b9c4

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