Mercurial > wow > askmrrobot
comparison TeamOptimizer.lua @ 57:01b63b8ed811 v21
total rewrite to version 21
| author | yellowfive |
|---|---|
| date | Fri, 05 Jun 2015 11:05:15 -0700 |
| parents | |
| children | adec0972d4e1 |
comparison
equal
deleted
inserted
replaced
| 56:75431c084aa0 | 57:01b63b8ed811 |
|---|---|
| 1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot") | |
| 2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true) | |
| 3 local AceGUI = LibStub("AceGUI-3.0") | |
| 4 | |
| 5 local _panelSplash | |
| 6 local _panelStartLoot | |
| 7 local _lblStartLoot | |
| 8 local _btnStartLoot | |
| 9 local _scrollHistory | |
| 10 local _tabs | |
| 11 | |
| 12 local _messagePrefixes = { | |
| 13 RosterRequestGear = "_TRR", | |
| 14 RosterGear = "_TRG", | |
| 15 ItemExportRequestGear = "_TLR", | |
| 16 ItemExportGear = "_TLG", | |
| 17 ItemExportLoot = "_TLL", | |
| 18 SyncRequest = "_TSR", | |
| 19 Sync = "_TSS" | |
| 20 } | |
| 21 | |
| 22 Amr.LootMessagePrefixes = { | |
| 23 Start = "_TCS", | |
| 24 Roll = "_TCR", | |
| 25 Veto = "_TCV", | |
| 26 Rand = "_TCD", | |
| 27 Give = "_TCG", | |
| 28 Finish = "_TCF" | |
| 29 } | |
| 30 | |
| 31 local function renderExportWindow(container, instructions, text) | |
| 32 | |
| 33 local bg = Amr:RenderCoverChrome(container, 800, 450) | |
| 34 | |
| 35 local lbl = AceGUI:Create("AmrUiLabel") | |
| 36 lbl:SetWidth(750) | |
| 37 lbl:SetText(L.TeamExportHelp) | |
| 38 lbl:SetPoint("TOP", bg.content, "TOP", 0, -10) | |
| 39 bg:AddChild(lbl) | |
| 40 | |
| 41 local lbl2 = AceGUI:Create("AmrUiLabel") | |
| 42 lbl2:SetWidth(750) | |
| 43 lbl2:SetText(instructions) | |
| 44 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -10) | |
| 45 bg:AddChild(lbl2) | |
| 46 | |
| 47 local txt = AceGUI:Create("AmrUiTextarea") | |
| 48 txt:SetWidth(750) | |
| 49 txt:SetHeight(300) | |
| 50 txt:SetPoint("TOP", lbl2.frame, "BOTTOM", 0, -10) | |
| 51 txt:SetFont(Amr.CreateFont("Regular", 12, Amr.Colors.Text)) | |
| 52 txt:SetText(text) | |
| 53 bg:AddChild(txt) | |
| 54 | |
| 55 local btn = AceGUI:Create("AmrUiButton") | |
| 56 btn:SetText(L.TeamButtonExportClose) | |
| 57 btn:SetBackgroundColor(Amr.Colors.Green) | |
| 58 btn:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 59 btn:SetWidth(120) | |
| 60 btn:SetHeight(28) | |
| 61 btn:SetPoint("TOPLEFT", txt.frame, "BOTTOMLEFT", 0, -10) | |
| 62 btn:SetCallback("OnClick", function(widget) Amr:HideCover() end) | |
| 63 bg:AddChild(btn) | |
| 64 | |
| 65 return txt | |
| 66 end | |
| 67 | |
| 68 local function renderImportWindow(container) | |
| 69 | |
| 70 local bg = Amr:RenderCoverChrome(container, 700, 450) | |
| 71 | |
| 72 local lbl = AceGUI:Create("AmrUiLabel") | |
| 73 lbl:SetWidth(600) | |
| 74 lbl:SetText(L.TeamImportRankingsHeader) | |
| 75 lbl:SetPoint("TOP", bg.content, "TOP", 0, -10) | |
| 76 bg:AddChild(lbl) | |
| 77 | |
| 78 local txt = AceGUI:Create("AmrUiTextarea") | |
| 79 txt:SetWidth(600) | |
| 80 txt:SetHeight(300) | |
| 81 txt:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -10) | |
| 82 txt:SetFont(Amr.CreateFont("Regular", 12, Amr.Colors.Text)) | |
| 83 bg:AddChild(txt) | |
| 84 | |
| 85 local btnImportOk = AceGUI:Create("AmrUiButton") | |
| 86 btnImportOk:SetText(L.ImportButtonOk) | |
| 87 btnImportOk:SetBackgroundColor(Amr.Colors.Green) | |
| 88 btnImportOk:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 89 btnImportOk:SetWidth(120) | |
| 90 btnImportOk:SetHeight(28) | |
| 91 btnImportOk:SetPoint("TOPLEFT", txt.frame, "BOTTOMLEFT", 0, -10) | |
| 92 bg:AddChild(btnImportOk) | |
| 93 | |
| 94 local btnImportCancel = AceGUI:Create("AmrUiButton") | |
| 95 btnImportCancel:SetText(L.ImportButtonCancel) | |
| 96 btnImportCancel:SetBackgroundColor(Amr.Colors.Green) | |
| 97 btnImportCancel:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 98 btnImportCancel:SetWidth(120) | |
| 99 btnImportCancel:SetHeight(28) | |
| 100 btnImportCancel:SetPoint("LEFT", btnImportOk.frame, "RIGHT", 20, 0) | |
| 101 btnImportCancel:SetCallback("OnClick", function(widget) Amr:HideCover() end) | |
| 102 bg:AddChild(btnImportCancel) | |
| 103 | |
| 104 local lblErr = AceGUI:Create("AmrUiLabel") | |
| 105 lblErr:SetWidth(600) | |
| 106 lblErr:SetFont(Amr.CreateFont("Bold", 14, Amr.Colors.Red)) | |
| 107 lblErr:SetText("") | |
| 108 lblErr:SetPoint("TOPLEFT", btnImportOk.frame, "BOTTOMLEFT", 0, -20) | |
| 109 bg:AddChild(lblErr) | |
| 110 | |
| 111 btnImportOk:SetCallback("OnClick", function(widget) | |
| 112 local msg = txt:GetText() | |
| 113 local err = Amr:ImportRankings(msg) | |
| 114 if err then | |
| 115 lblErr:SetText(err) | |
| 116 txt:SetFocus(true) | |
| 117 else | |
| 118 Amr:HideCover() | |
| 119 Amr:RefreshTeamUi() | |
| 120 end | |
| 121 end) | |
| 122 | |
| 123 return txt | |
| 124 end | |
| 125 | |
| 126 local function renderVersionWindow(container) | |
| 127 | |
| 128 local windowWidth = 500 | |
| 129 local lbl, lbl2 | |
| 130 local bg, border = Amr:RenderCoverChrome(container, windowWidth, 600) | |
| 131 | |
| 132 lbl = AceGUI:Create("AmrUiLabel") | |
| 133 lbl:SetWidth(windowWidth - 60) | |
| 134 lbl:SetJustifyH("CENTER") | |
| 135 lbl:SetFont(Amr.CreateFont("Bold", 24, Amr.Colors.TextHeaderActive)) | |
| 136 lbl:SetText(L.TeamVersionTitle) | |
| 137 lbl:SetPoint("TOP", bg.content, "TOP", 0, -10) | |
| 138 bg:AddChild(lbl) | |
| 139 | |
| 140 if not IsInGroup() and not IsInRaid() then | |
| 141 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 142 lbl2:SetWidth(windowWidth - 20) | |
| 143 lbl2:SetJustifyH("CENTER") | |
| 144 lbl2:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) | |
| 145 lbl2:SetText(L.TeamVersionNoGroup) | |
| 146 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -25) | |
| 147 bg:AddChild(lbl2) | |
| 148 border:SetHeight(150) | |
| 149 else | |
| 150 local units = Amr:GetGroupUnitIdentifiers() | |
| 151 | |
| 152 local missing = {} | |
| 153 local tooLow = {} | |
| 154 | |
| 155 for i, unitId in ipairs(units) do | |
| 156 local realm, name = Amr:GetRealmAndName(unitId) | |
| 157 if realm then | |
| 158 local ver = Amr:GetAddonVersion(realm, name) | |
| 159 if ver == 0 then | |
| 160 table.insert(missing, { unitId, realm, name }) | |
| 161 elseif ver < Amr.MIN_ADDON_VERSION then | |
| 162 table.insert(tooLow, { unitId, realm, name, ver }) | |
| 163 end | |
| 164 end | |
| 165 end | |
| 166 | |
| 167 if #missing == 0 and #tooLow == 0 then | |
| 168 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 169 lbl2:SetWidth(windowWidth - 20) | |
| 170 lbl2:SetJustifyH("CENTER") | |
| 171 lbl2:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) | |
| 172 lbl2:SetText(L.TeamVersionGood) | |
| 173 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -25) | |
| 174 bg:AddChild(lbl2) | |
| 175 border:SetHeight(150) | |
| 176 else | |
| 177 local prev = lbl | |
| 178 local h = 0 | |
| 179 | |
| 180 -- helper to render a player name | |
| 181 local function renderItem(obj, showVer) | |
| 182 lbl = AceGUI:Create("AmrUiLabel") | |
| 183 lbl:SetWidth(120) | |
| 184 | |
| 185 local cls, clsEn = UnitClass(obj[1]) | |
| 186 local color = clsEn and Amr.Colors.Classes[clsEn] or Amr.Colors.TextHeaderDisabled | |
| 187 lbl:SetFont(Amr.CreateFont("Regular", 14, color)) | |
| 188 | |
| 189 lbl:SetText(obj[3]) | |
| 190 lbl:SetPoint("TOPLEFT", prev.frame, "BOTTOMLEFT", 0, -5) | |
| 191 bg:AddChild(lbl) | |
| 192 prev = lbl | |
| 193 h = h + lbl:GetHeight() + 5 | |
| 194 | |
| 195 if showVer then | |
| 196 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 197 lbl2:SetWidth(60) | |
| 198 lbl2:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) | |
| 199 lbl2:SetText("v" .. obj[4]) | |
| 200 lbl2:SetPoint("LEFT", lbl.frame, "RIGHT", 5, 0) | |
| 201 bg:AddChild(lbl2) | |
| 202 end | |
| 203 end | |
| 204 | |
| 205 if #missing > 0 then | |
| 206 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 207 lbl2:SetWidth(180) | |
| 208 lbl2:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.Red)) | |
| 209 lbl2:SetText(L.TeamVersionMissing) | |
| 210 lbl2:SetJustifyH("CENTER") | |
| 211 lbl2:SetPoint("TOP", prev.frame, "BOTTOM", 0, -20) | |
| 212 bg:AddChild(lbl2) | |
| 213 h = h + lbl2:GetHeight() + 20 | |
| 214 | |
| 215 prev = lbl2 | |
| 216 for i, obj in ipairs(missing) do | |
| 217 renderItem(obj) | |
| 218 end | |
| 219 end | |
| 220 | |
| 221 if #tooLow > 0 then | |
| 222 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 223 lbl2:SetWidth(180) | |
| 224 lbl2:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.Gold)) | |
| 225 lbl2:SetText(L.TeamVersionOld) | |
| 226 lbl2:SetJustifyH("CENTER") | |
| 227 lbl2:SetPoint("TOP", prev.frame, "BOTTOM", 0, -20) | |
| 228 bg:AddChild(lbl2) | |
| 229 h = h + lbl2:GetHeight() + 20 | |
| 230 | |
| 231 prev = lbl2 | |
| 232 for i, obj in ipairs(tooLow) do | |
| 233 renderItem(obj, true) | |
| 234 end | |
| 235 end | |
| 236 | |
| 237 border:SetHeight(h + 100) | |
| 238 end | |
| 239 end | |
| 240 | |
| 241 local btn = AceGUI:Create("AmrUiButton") | |
| 242 btn:SetText(L.TeamButtonExportClose) | |
| 243 btn:SetBackgroundColor(Amr.Colors.Green) | |
| 244 btn:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 245 btn:SetWidth(120) | |
| 246 btn:SetHeight(28) | |
| 247 btn:SetPoint("BOTTOM", bg.content, "BOTTOM", 0, 10) | |
| 248 btn:SetCallback("OnClick", function(widget) Amr:HideCover() end) | |
| 249 bg:AddChild(btn) | |
| 250 end | |
| 251 | |
| 252 local function onVersionClick() | |
| 253 -- show a window with players who do not have the addon or too low a version | |
| 254 Amr:ShowCover(renderVersionWindow) | |
| 255 end | |
| 256 | |
| 257 local function onExportRosterClick() | |
| 258 | |
| 259 Amr:ShowCover(L.TeamExportRosterLoading) | |
| 260 | |
| 261 Amr:ExportRosterAsync(function(txt) | |
| 262 Amr:HideCover() | |
| 263 | |
| 264 if not txt then | |
| 265 Amr:ShowAlert(L.TeamAlertNoGroup, L.AlertOk) | |
| 266 return | |
| 267 end | |
| 268 | |
| 269 Amr:ShowCover(function(container) | |
| 270 local textbox = renderExportWindow(container, L.TeamExportRosterText, txt) | |
| 271 textbox:SetFocus(true) | |
| 272 end) | |
| 273 end) | |
| 274 | |
| 275 end | |
| 276 | |
| 277 local function onExportLootClick() | |
| 278 | |
| 279 Amr:ShowCover(L.TeamExportRosterLoading) | |
| 280 | |
| 281 Amr:ExportLootAsync(function(txt) | |
| 282 Amr:HideCover() | |
| 283 | |
| 284 if txt == "NOGROUP" then | |
| 285 Amr:ShowAlert(L.TeamAlertNoGroup, L.AlertOk) | |
| 286 return | |
| 287 elseif txt == "NOLOOT" then | |
| 288 Amr:ShowAlert(L.TeamAlertNoLoot, L.AlertOk) | |
| 289 return | |
| 290 else | |
| 291 Amr:ShowCover(function(container) | |
| 292 local textbox = renderExportWindow(container, L.TeamExportLootText, txt) | |
| 293 textbox:SetFocus(true) | |
| 294 end) | |
| 295 end | |
| 296 end) | |
| 297 end | |
| 298 | |
| 299 local function onImportRankingsClick() | |
| 300 Amr:ShowCover(function(container) | |
| 301 local textbox = renderImportWindow(container) | |
| 302 textbox:SetFocus(true) | |
| 303 end) | |
| 304 end | |
| 305 | |
| 306 local function renderTab(tab, container) | |
| 307 | |
| 308 local lbl, lbl2 | |
| 309 | |
| 310 if tab == "Member" then | |
| 311 local lbl = AceGUI:Create("AmrUiLabel") | |
| 312 lbl:SetWidth(500) | |
| 313 lbl:SetFont(Amr.CreateFont("Regular", 24, Amr.Colors.TextTan)) | |
| 314 lbl:SetText(L.TeamMemberText) | |
| 315 lbl:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -40) | |
| 316 container:AddChild(lbl) | |
| 317 | |
| 318 -- if loot is still going on, show a button to re-show the loot window | |
| 319 if Amr.db.char.TeamOpt.LootInProgress then | |
| 320 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 321 lbl2:SetWidth(500) | |
| 322 lbl2:SetFont(Amr.CreateFont("Italic", 18, Amr.Colors.TextTan)) | |
| 323 lbl2:SetText(L.TeamMemberShowLootLabel) | |
| 324 lbl2:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -60) | |
| 325 container:AddChild(lbl2) | |
| 326 | |
| 327 local btn = AceGUI:Create("AmrUiButton") | |
| 328 btn:SetWidth(180) | |
| 329 btn:SetHeight(26) | |
| 330 btn:SetBackgroundColor(Amr.Colors.Blue) | |
| 331 btn:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 332 btn:SetText(L.TeamMemberShowLoot) | |
| 333 btn:SetPoint("TOPLEFT", lbl2.frame, "BOTTOMLEFT", 0, -10) | |
| 334 btn:SetCallback("OnClick", function(widget) | |
| 335 Amr:ShowLootWindow() | |
| 336 Amr:RefreshLootWindow() | |
| 337 Amr:RefreshLootRolls() | |
| 338 end) | |
| 339 container:AddChild(btn) | |
| 340 end | |
| 341 | |
| 342 elseif tab == "Leader" then | |
| 343 | |
| 344 local lblNum = AceGUI:Create("AmrUiLabel") | |
| 345 lblNum:SetFont(Amr.CreateFont("Bold", 26, Amr.Colors.White)) | |
| 346 lblNum:SetText("0.") | |
| 347 lblNum:SetWidth(40) | |
| 348 lblNum:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, -40) | |
| 349 container:AddChild(lblNum) | |
| 350 | |
| 351 local btnVersion = AceGUI:Create("AmrUiButton") | |
| 352 btnVersion:SetText(L.TeamButtonVersionText) | |
| 353 btnVersion:SetBackgroundColor(Amr.Colors.Orange) | |
| 354 btnVersion:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 355 btnVersion:SetWidth(180) | |
| 356 btnVersion:SetHeight(26) | |
| 357 btnVersion:SetPoint("LEFT", lblNum.frame, "RIGHT", 0, -1) | |
| 358 btnVersion:SetCallback("OnClick", onVersionClick) | |
| 359 container:AddChild(btnVersion) | |
| 360 | |
| 361 lbl = AceGUI:Create("AmrUiLabel") | |
| 362 lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) | |
| 363 lbl:SetText(L.TeamExportVersionLabel) | |
| 364 lbl:SetWidth(400) | |
| 365 lbl:SetPoint("TOPLEFT", btnVersion.frame, "TOPRIGHT", 20, 0) | |
| 366 container:AddChild(lbl) | |
| 367 | |
| 368 lblNum = AceGUI:Create("AmrUiLabel") | |
| 369 lblNum:SetFont(Amr.CreateFont("Bold", 26, Amr.Colors.White)) | |
| 370 lblNum:SetText("1.") | |
| 371 lblNum:SetWidth(40) | |
| 372 lblNum:SetPoint("TOPRIGHT", btnVersion.frame, "BOTTOMLEFT", 0, -39) | |
| 373 container:AddChild(lblNum) | |
| 374 | |
| 375 local btnRoster = AceGUI:Create("AmrUiButton") | |
| 376 btnRoster:SetText(L.TeamButtonExportRosterText) | |
| 377 btnRoster:SetBackgroundColor(Amr.Colors.Orange) | |
| 378 btnRoster:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 379 btnRoster:SetWidth(180) | |
| 380 btnRoster:SetHeight(26) | |
| 381 btnRoster:SetPoint("LEFT", lblNum.frame, "RIGHT", 0, -1) | |
| 382 btnRoster:SetCallback("OnClick", onExportRosterClick) | |
| 383 container:AddChild(btnRoster) | |
| 384 | |
| 385 lbl = AceGUI:Create("AmrUiLabel") | |
| 386 lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) | |
| 387 lbl:SetText(L.TeamExportRosterLabel) | |
| 388 lbl:SetWidth(400) | |
| 389 lbl:SetPoint("TOPLEFT", btnRoster.frame, "TOPRIGHT", 20, 0) | |
| 390 container:AddChild(lbl) | |
| 391 | |
| 392 lblNum = AceGUI:Create("AmrUiLabel") | |
| 393 lblNum:SetFont(Amr.CreateFont("Bold", 26, Amr.Colors.White)) | |
| 394 lblNum:SetText("2.") | |
| 395 lblNum:SetWidth(40) | |
| 396 lblNum:SetPoint("TOPRIGHT", btnRoster.frame, "BOTTOMLEFT", 0, -89) | |
| 397 container:AddChild(lblNum) | |
| 398 | |
| 399 local btnLoot = AceGUI:Create("AmrUiButton") | |
| 400 btnLoot:SetText(L.TeamButtonExportLootText) | |
| 401 btnLoot:SetBackgroundColor(Amr.Colors.Orange) | |
| 402 btnLoot:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 403 btnLoot:SetWidth(180) | |
| 404 btnLoot:SetHeight(26) | |
| 405 btnLoot:SetPoint("LEFT", lblNum.frame, "RIGHT", 0, -1) | |
| 406 btnLoot:SetCallback("OnClick", onExportLootClick) | |
| 407 container:AddChild(btnLoot) | |
| 408 | |
| 409 lbl = AceGUI:Create("AmrUiLabel") | |
| 410 lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) | |
| 411 lbl:SetText(L.TeamExportLootLabel) | |
| 412 lbl:SetWidth(400) | |
| 413 lbl:SetPoint("TOPLEFT", btnLoot.frame, "TOPRIGHT", 20, 0) | |
| 414 container:AddChild(lbl) | |
| 415 | |
| 416 lbl2 = AceGUI:Create("AmrUiLabel") | |
| 417 lbl2:SetFont(Amr.CreateFont("Bold", 14, Amr.Colors.Blue)) | |
| 418 lbl2:SetText(L.TeamExportLootLabel2) | |
| 419 lbl2:SetWidth(400) | |
| 420 lbl2:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -5) | |
| 421 container:AddChild(lbl2) | |
| 422 | |
| 423 lblNum = AceGUI:Create("AmrUiLabel") | |
| 424 lblNum:SetFont(Amr.CreateFont("Bold", 26, Amr.Colors.White)) | |
| 425 lblNum:SetText("3.") | |
| 426 lblNum:SetWidth(40) | |
| 427 lblNum:SetPoint("TOPRIGHT", btnLoot.frame, "BOTTOMLEFT", 0, -89) | |
| 428 container:AddChild(lblNum) | |
| 429 | |
| 430 local btnRank = AceGUI:Create("AmrUiButton") | |
| 431 btnRank:SetText(L.TeamButtonImportRankingsText) | |
| 432 btnRank:SetBackgroundColor(Amr.Colors.Green) | |
| 433 btnRank:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 434 btnRank:SetWidth(180) | |
| 435 btnRank:SetHeight(26) | |
| 436 btnRank:SetPoint("LEFT", lblNum.frame, "RIGHT", 0, -1) | |
| 437 btnRank:SetCallback("OnClick", onImportRankingsClick) | |
| 438 container:AddChild(btnRank) | |
| 439 | |
| 440 lbl = AceGUI:Create("AmrUiLabel") | |
| 441 lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) | |
| 442 lbl:SetText(L.TeamImportRankingsLabel) | |
| 443 lbl:SetWidth(400) | |
| 444 lbl:SetPoint("TOPLEFT", btnRank.frame, "TOPRIGHT", 20, 0) | |
| 445 container:AddChild(lbl) | |
| 446 | |
| 447 _panelStartLoot = AceGUI:Create("AmrUiPanel") | |
| 448 _panelStartLoot:SetLayout("None") | |
| 449 _panelStartLoot:SetBackgroundColor(Amr.Colors.Black, 0) | |
| 450 _panelStartLoot:SetPoint("TOPLEFT", lblNum.frame, "BOTTOMLEFT", 0, -90) | |
| 451 container:AddChild(_panelStartLoot) | |
| 452 _panelStartLoot:SetVisible(false) | |
| 453 | |
| 454 lblNum = AceGUI:Create("AmrUiLabel") | |
| 455 lblNum:SetFont(Amr.CreateFont("Bold", 26, Amr.Colors.White)) | |
| 456 lblNum:SetText("4.") | |
| 457 lblNum:SetWidth(40) | |
| 458 lblNum:SetPoint("TOPLEFT", _panelStartLoot.content, "TOPLEFT") | |
| 459 _panelStartLoot:AddChild(lblNum) | |
| 460 | |
| 461 _btnStartLoot = AceGUI:Create("AmrUiButton") | |
| 462 _btnStartLoot:SetText(L.TeamButtonStartLootText) | |
| 463 _btnStartLoot:SetBackgroundColor(Amr.Colors.Blue) | |
| 464 _btnStartLoot:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) | |
| 465 _btnStartLoot:SetWidth(180) | |
| 466 _btnStartLoot:SetHeight(26) | |
| 467 _btnStartLoot:SetPoint("LEFT", lblNum.frame, "RIGHT", 0, -1) | |
| 468 _btnStartLoot:SetCallback("OnClick", function(widget) | |
| 469 if Amr.db.char.TeamOpt.LootInProgress then | |
| 470 Amr:ShowLootWindow() | |
| 471 Amr:RefreshLootWindow() | |
| 472 Amr:RefreshLootRolls() | |
| 473 else | |
| 474 Amr:StartLoot() | |
| 475 end | |
| 476 end) | |
| 477 _panelStartLoot:AddChild(_btnStartLoot) | |
| 478 | |
| 479 _lblStartLoot = AceGUI:Create("AmrUiLabel") | |
| 480 _lblStartLoot:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.Text)) | |
| 481 _lblStartLoot:SetWidth(400) | |
| 482 _lblStartLoot:SetPoint("LEFT", _btnStartLoot.frame, "RIGHT", 20, 0) | |
| 483 _panelStartLoot:AddChild(_lblStartLoot) | |
| 484 end | |
| 485 | |
| 486 -- loot history shows on either tab | |
| 487 lbl = AceGUI:Create("AmrUiLabel") | |
| 488 lbl:SetFont(Amr.CreateFont("Regular", 16, Amr.Colors.TextTan)) | |
| 489 lbl:SetText(L.TeamHistoryTitle) | |
| 490 lbl:SetWidth(280) | |
| 491 lbl:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", 0, -12) | |
| 492 container:AddChild(lbl) | |
| 493 | |
| 494 local panelHistory = AceGUI:Create("AmrUiPanel") | |
| 495 panelHistory:SetLayout("Fill") | |
| 496 panelHistory:SetBackgroundColor(Amr.Colors.Black, 0.3) | |
| 497 panelHistory:SetPoint("TOPRIGHT", lbl.frame, "BOTTOMRIGHT", 0, -5) | |
| 498 panelHistory:SetPoint("BOTTOMLEFT", container.content, "BOTTOMRIGHT", -280, 0) | |
| 499 container:AddChild(panelHistory) | |
| 500 | |
| 501 _scrollHistory = AceGUI:Create("AmrUiScrollFrame") | |
| 502 _scrollHistory:SetLayout("List") | |
| 503 panelHistory:AddChild(_scrollHistory) | |
| 504 end | |
| 505 | |
| 506 local function renderHistory() | |
| 507 if not _scrollHistory then return end | |
| 508 _scrollHistory:ReleaseChildren() | |
| 509 | |
| 510 -- history is list of objects with: | |
| 511 -- link, result, class, name | |
| 512 | |
| 513 local history = Amr.db.char.TeamOpt.History | |
| 514 local historyWidth = 260 | |
| 515 | |
| 516 local emptyMsg = nil | |
| 517 if not IsInGroup() and not IsInRaid() then | |
| 518 emptyMsg = L.TeamHistoryNoGroup | |
| 519 elseif not history or #history == 0 then | |
| 520 emptyMsg = L.TeamHistoryEmpty | |
| 521 end | |
| 522 | |
| 523 if emptyMsg then | |
| 524 local panel = AceGUI:Create("AmrUiPanel") | |
| 525 panel:SetLayout("None") | |
| 526 panel:SetBackgroundColor(Amr.Colors.Black, 0) | |
| 527 panel:SetWidth(historyWidth) | |
| 528 panel:SetHeight(30) | |
| 529 _scrollHistory:AddChild(panel) | |
| 530 | |
| 531 local lbl = AceGUI:Create("AmrUiLabel") | |
| 532 lbl:SetWidth(historyWidth) | |
| 533 lbl:SetJustifyH("CENTER") | |
| 534 lbl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) | |
| 535 lbl:SetText(emptyMsg) | |
| 536 lbl:SetPoint("LEFT", panel.content, "LEFT", 8, 0) | |
| 537 panel:AddChild(lbl) | |
| 538 else | |
| 539 for i = #history, 1, -1 do | |
| 540 local obj = history[i] | |
| 541 local itemLink = obj.link | |
| 542 | |
| 543 local panel = AceGUI:Create("AmrUiPanel") | |
| 544 panel:SetLayout("None") | |
| 545 panel:SetBackgroundColor(Amr.Colors.Black, 0) | |
| 546 panel:SetWidth(historyWidth) | |
| 547 panel:SetHeight(45) | |
| 548 _scrollHistory:AddChild(panel) | |
| 549 | |
| 550 local lbl = AceGUI:Create("AmrUiLabel") | |
| 551 lbl:SetWidth(historyWidth - 5) | |
| 552 lbl:SetWordWrap(false) | |
| 553 lbl:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.Text)) | |
| 554 lbl:SetPoint("TOPLEFT", panel.content, "TOPLEFT", 5, -5) | |
| 555 panel:AddChild(lbl) | |
| 556 | |
| 557 Amr.GetItemInfo(itemLink, function(obj, name, link) | |
| 558 -- set item name, tooltip | |
| 559 obj:SetText(link:gsub("%[", ""):gsub("%]", "")) | |
| 560 Amr:SetItemTooltip(obj, link, "ANCHOR_BOTTOMRIGHT", 0, obj.frame:GetHeight()) | |
| 561 end, lbl) | |
| 562 | |
| 563 lbl = AceGUI:Create("AmrUiLabel") | |
| 564 lbl:SetWidth(historyWidth - 5) | |
| 565 lbl:SetWordWrap(false) | |
| 566 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.White)) | |
| 567 | |
| 568 if obj.result == "Disenchant" then | |
| 569 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.TextHeaderDisabled)) | |
| 570 lbl:SetText(L.TeamLootOptionDisenchant) | |
| 571 else | |
| 572 local color = obj.class and Amr.Colors.Classes[obj.class] or Amr.Colors.TextHeaderDisabled | |
| 573 lbl:SetText((obj.result == "??" and "" or L["TeamLootOption" .. obj.result] .. ": ") .."|c" .. Amr.ColorToHex(color, 1) .. obj.name .. "|r") | |
| 574 end | |
| 575 | |
| 576 lbl:SetPoint("BOTTOMLEFT", panel.content, "BOTTOMLEFT", 5, 8) | |
| 577 panel:AddChild(lbl) | |
| 578 | |
| 579 local line = AceGUI:Create("AmrUiPanel") | |
| 580 line:SetBackgroundColor(Amr.Colors.Black, 1) | |
| 581 line:SetWidth(historyWidth) | |
| 582 line:SetHeight(1) | |
| 583 line:SetPoint("BOTTOM", panel.content, "BOTTOM") | |
| 584 panel:AddChild(line) | |
| 585 end | |
| 586 end | |
| 587 end | |
| 588 | |
| 589 local function onTabSelected(container, event, group) | |
| 590 container:ReleaseChildren() | |
| 591 | |
| 592 -- clear references to tab elements | |
| 593 _panelStartLoot = nil | |
| 594 _lblStartLoot = nil | |
| 595 _btnStartLoot = nil | |
| 596 _scrollHistory = nil | |
| 597 | |
| 598 Amr.db.char.TeamOpt.Role = group | |
| 599 renderTab(group, container) | |
| 600 Amr:RefreshTeamUi() | |
| 601 end | |
| 602 | |
| 603 -- renders the main UI for the Team Optimizer tab | |
| 604 function Amr:RenderTabTeam(container) | |
| 605 | |
| 606 -- splash screen to customize team optimizer ui for the user | |
| 607 if not Amr.db.char.TeamOpt.Role then | |
| 608 _panelSplash = AceGUI:Create("AmrUiPanel") | |
| 609 _panelSplash:SetLayout("None") | |
| 610 _panelSplash:SetBackgroundColor(Amr.Colors.Black, 0) | |
| 611 _panelSplash:SetPoint("TOPLEFT", container.content, "TOPLEFT") | |
| 612 _panelSplash:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") | |
| 613 container:AddChild(_panelSplash) | |
| 614 | |
| 615 local lblSplash = AceGUI:Create("AmrUiLabel") | |
| 616 lblSplash:SetWidth(800) | |
| 617 lblSplash:SetJustifyH("CENTER") | |
| 618 lblSplash:SetFont(Amr.CreateFont("Regular", 24, Amr.Colors.Text)) | |
| 619 lblSplash:SetText(L.TeamSplashHeader) | |
| 620 lblSplash:SetPoint("TOP", _panelSplash.content, "TOP", 0, -40) | |
| 621 _panelSplash:AddChild(lblSplash) | |
| 622 | |
| 623 local btn = AceGUI:Create("AmrUiButton") | |
| 624 btn:SetText(L.TeamTabLeaderText) | |
| 625 btn:SetBackgroundColor(Amr.Colors.Orange) | |
| 626 btn:SetFont(Amr.CreateFont("Bold", 24, Amr.Colors.White)) | |
| 627 btn:SetWidth(280) | |
| 628 btn:SetHeight(60) | |
| 629 btn:SetPoint("TOPRIGHT", lblSplash.frame, "BOTTOM", -50, -50) | |
| 630 btn:SetCallback("OnClick", function(widget) | |
| 631 Amr.db.char.TeamOpt.Role = "Leader" | |
| 632 _panelSplash:SetVisible(false) | |
| 633 _tabs:SetVisible(true) | |
| 634 _tabs:SelectTab("Leader") | |
| 635 end) | |
| 636 _panelSplash:AddChild(btn) | |
| 637 | |
| 638 local lbl = AceGUI:Create("AmrUiLabel") | |
| 639 lbl:SetWidth(280) | |
| 640 lbl:SetJustifyH("CENTER") | |
| 641 lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) | |
| 642 lbl:SetText(L.TeamSplashLeaderLabel) | |
| 643 lbl:SetPoint("TOP", btn.frame, "BOTTOM", 0, -20) | |
| 644 _panelSplash:AddChild(lbl) | |
| 645 | |
| 646 btn = AceGUI:Create("AmrUiButton") | |
| 647 btn:SetText(L.TeamTabMemberText) | |
| 648 btn:SetBackgroundColor(Amr.Colors.Orange) | |
| 649 btn:SetFont(Amr.CreateFont("Bold", 24, Amr.Colors.White)) | |
| 650 btn:SetWidth(280) | |
| 651 btn:SetHeight(60) | |
| 652 btn:SetPoint("TOPLEFT", lblSplash.frame, "BOTTOM", 50, -50) | |
| 653 btn:SetCallback("OnClick", function(widget) | |
| 654 Amr.db.char.TeamOpt.Role = "Member" | |
| 655 _panelSplash:SetVisible(false) | |
| 656 _tabs:SetVisible(true) | |
| 657 _tabs:SelectTab("Member") | |
| 658 end) | |
| 659 _panelSplash:AddChild(btn) | |
| 660 | |
| 661 lbl = AceGUI:Create("AmrUiLabel") | |
| 662 lbl:SetWidth(280) | |
| 663 lbl:SetJustifyH("CENTER") | |
| 664 lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) | |
| 665 lbl:SetText(L.TeamSplashMemberLabel) | |
| 666 lbl:SetPoint("TOP", btn.frame, "BOTTOM", 0, -20) | |
| 667 _panelSplash:AddChild(lbl) | |
| 668 end | |
| 669 | |
| 670 -- tabstrip | |
| 671 _tabs = AceGUI:Create("AmrUiTabGroup") | |
| 672 _tabs:SetLayout("None") | |
| 673 _tabs:SetTabs({ | |
| 674 {text=L.TeamTabLeaderText, value="Leader", style="bold"}, | |
| 675 {text=L.TeamTabMemberText, value="Member", style="bold"} | |
| 676 }) | |
| 677 _tabs:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, -30) | |
| 678 _tabs:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") | |
| 679 _tabs:SetCallback("OnGroupSelected", onTabSelected) | |
| 680 container:AddChild(_tabs) | |
| 681 | |
| 682 local role = Amr.db.char.TeamOpt.Role | |
| 683 | |
| 684 _tabs:SetVisible(not not role) | |
| 685 if role then | |
| 686 -- if a role has been chosen, select the proper tab (which will also refresh the UI) | |
| 687 _tabs:SelectTab(role) | |
| 688 else | |
| 689 -- no role, refresh the UI manually | |
| 690 self:RefreshTeamUi() | |
| 691 end | |
| 692 | |
| 693 end | |
| 694 | |
| 695 function Amr:ReleaseTabTeam() | |
| 696 _panelSplash = nil | |
| 697 _panelStartLoot = nil | |
| 698 _lblStartLoot = nil | |
| 699 _btnStartLoot = nil | |
| 700 _scrollHistory = nil | |
| 701 _tabs = nil | |
| 702 end | |
| 703 | |
| 704 function Amr:RefreshTeamUi() | |
| 705 | |
| 706 -- if rankings have been loaded, render the 'start loot' panel | |
| 707 if _panelStartLoot then | |
| 708 local rankString = Amr.db.global.TeamOpt.RankingString | |
| 709 if rankString then | |
| 710 _panelStartLoot:SetVisible(true) | |
| 711 _lblStartLoot:SetText(L.TeamStartLootLabel(#Amr.db.global.TeamOpt.Rankings)) | |
| 712 _btnStartLoot:SetText(Amr.db.char.TeamOpt.LootInProgress and L.TeamButtonResumeLootText or L.TeamButtonStartLootText) | |
| 713 else | |
| 714 _panelStartLoot:SetVisible(false) | |
| 715 end | |
| 716 end | |
| 717 | |
| 718 -- render loot history | |
| 719 renderHistory() | |
| 720 end | |
| 721 | |
| 722 local function getItemIdsFromLinks(all, list) | |
| 723 for i, v in ipairs(list) do | |
| 724 local obj = Amr.ParseItemLink(v) | |
| 725 local id = Amr.GetItemUniqueId(obj) | |
| 726 if id then | |
| 727 table.insert(all, id) | |
| 728 end | |
| 729 end | |
| 730 end | |
| 731 | |
| 732 -- update AllItems, used to determine when a new item is actually a new equippable item | |
| 733 local function snapshotAllItems(data) | |
| 734 | |
| 735 local all = {} | |
| 736 for k, v in pairs(data.Equipped[data.ActiveSpec]) do | |
| 737 local obj = Amr.ParseItemLink(v) | |
| 738 local id = Amr.GetItemUniqueId(obj) | |
| 739 if id then | |
| 740 table.insert(all, id) | |
| 741 end | |
| 742 end | |
| 743 getItemIdsFromLinks(all, data.BagItems) | |
| 744 getItemIdsFromLinks(all, data.BankItems) | |
| 745 getItemIdsFromLinks(all, data.VoidItems) | |
| 746 | |
| 747 table.sort(all) | |
| 748 return all | |
| 749 end | |
| 750 | |
| 751 local function sendGear(prefix, empty) | |
| 752 | |
| 753 local region = Amr.RegionNames[GetCurrentRegion()] | |
| 754 local realm = GetRealmName() | |
| 755 local name = UnitName("player") | |
| 756 | |
| 757 -- get all data, including inventory | |
| 758 local txt = "_" | |
| 759 if not empty then | |
| 760 local data = Amr:ExportCharacter() | |
| 761 txt = Amr.Serializer:SerializePlayerData(data, true) | |
| 762 | |
| 763 -- snapshot items when gear is sent | |
| 764 Amr.db.char.TeamOpt.AllItems = snapshotAllItems(data) | |
| 765 end | |
| 766 | |
| 767 local msg = string.format("%s\n%s\n%s\n%s\n%s", prefix, region, realm, name, txt) | |
| 768 Amr:SendAmrCommMessage(msg) | |
| 769 end | |
| 770 | |
| 771 local function toPlayerKey(realm, name) | |
| 772 return name .. "-" .. realm | |
| 773 end | |
| 774 | |
| 775 | |
| 776 ------------------------------------------------------------------------------------------------ | |
| 777 -- Loot Export | |
| 778 ------------------------------------------------------------------------------------------------ | |
| 779 | |
| 780 -- prune out any characters no longer in the player's group | |
| 781 local function pruneGearForItemExport() | |
| 782 | |
| 783 local newInfo = {} | |
| 784 local units = Amr:GetGroupUnitIdentifiers() | |
| 785 | |
| 786 for i, unitId in ipairs(units) do | |
| 787 local realm, name = Amr:GetRealmAndName(unitId) | |
| 788 if realm then | |
| 789 local key = toPlayerKey(realm, name) | |
| 790 newInfo[key] = Amr.db.global.TeamOpt.LootGear[key] | |
| 791 end | |
| 792 end | |
| 793 | |
| 794 Amr.db.global.TeamOpt.LootGear = newInfo | |
| 795 end | |
| 796 | |
| 797 local function scanMasterLoot() | |
| 798 -- only care if we are in a raid or group | |
| 799 if not IsInGroup() and not IsInRaid() then return end | |
| 800 | |
| 801 -- we only care about the master looter | |
| 802 if not IsMasterLooter() then return end | |
| 803 | |
| 804 -- guid of the unit being looted | |
| 805 local npcGuid = UnitGUID("target") | |
| 806 if not npcGuid then | |
| 807 -- this could wack shit out... but no raid bosses drop loot from containers right now, so should be fine | |
| 808 npcGuid = "container" | |
| 809 end | |
| 810 | |
| 811 -- if we already have loot data for this unit, then we can ignore | |
| 812 if Amr.db.char.TeamOpt.LootGuid == npcGuid then return end | |
| 813 | |
| 814 local loot = {} | |
| 815 for i = 1, GetNumLootItems() do | |
| 816 --local texture, item, quantity, quality, locked = GetLootSlotInfo(i) | |
| 817 local lootType = GetLootSlotType(i) | |
| 818 if lootType == 1 then | |
| 819 local link = GetLootSlotLink(i) | |
| 820 table.insert(loot, link) | |
| 821 end | |
| 822 end | |
| 823 | |
| 824 Amr.db.char.TeamOpt.LootGuid = npcGuid | |
| 825 Amr.db.char.TeamOpt.Loot = loot | |
| 826 | |
| 827 -- publish loot information to everyone else running the addon in case team optimizer user is not the master looter | |
| 828 local msg = _messagePrefixes.ItemExportLoot .. "\n" .. npcGuid .. "\n" .. table.concat(loot, "\n") | |
| 829 Amr:SendAmrCommMessage(msg) | |
| 830 end | |
| 831 | |
| 832 local function onLootReceived(parts) | |
| 833 | |
| 834 Amr.db.char.TeamOpt.LootGuid = parts[2] | |
| 835 | |
| 836 local loot = {} | |
| 837 for i = 3, #parts do | |
| 838 table.insert(loot, parts[i]) | |
| 839 end | |
| 840 Amr.db.char.TeamOpt.Loot = loot | |
| 841 end | |
| 842 | |
| 843 local function onLeaveGroup() | |
| 844 -- if the current player is no longer in a group or raid, finish any looting in progress | |
| 845 Amr:FinishLoot(true) | |
| 846 | |
| 847 -- clear loot when leave a group | |
| 848 Amr.db.char.TeamOpt.Loot = {} | |
| 849 Amr.db.char.TeamOpt.LootGuid = nil | |
| 850 Amr.db.global.TeamOpt.LootGear = {} | |
| 851 end | |
| 852 | |
| 853 local function onGroupChanged() | |
| 854 | |
| 855 if not IsInGroup() and not IsInRaid() then | |
| 856 onLeaveGroup() | |
| 857 end | |
| 858 end | |
| 859 | |
| 860 | |
| 861 local _lootExPlayersRemaining = 0 | |
| 862 local _lootExRoster = nil | |
| 863 local _lootExCallback = nil | |
| 864 | |
| 865 local function serializeLootExport() | |
| 866 if not IsInGroup() and not IsInRaid() then return "NOGROUP" end | |
| 867 | |
| 868 local loot = Amr.db.char.TeamOpt.Loot | |
| 869 if not loot or #loot == 0 then return "NOLOOT" end | |
| 870 | |
| 871 local itemObjects = {} | |
| 872 for i, link in ipairs(loot) do | |
| 873 local obj = Amr.ParseItemLink(link) | |
| 874 if obj then | |
| 875 table.insert(itemObjects, obj) | |
| 876 end | |
| 877 end | |
| 878 | |
| 879 -- DEBUG: just grab all currently equipped items | |
| 880 --[[ | |
| 881 itemObjects = {} | |
| 882 local blah = Amr:ExportCharacter() | |
| 883 for k, v in pairs(blah.Equipped[blah.ActiveSpec]) do | |
| 884 local obj = Amr.ParseItemLink(v) | |
| 885 if obj then | |
| 886 table.insert(itemObjects, obj) | |
| 887 end | |
| 888 end | |
| 889 ]] | |
| 890 | |
| 891 local parts = {} | |
| 892 | |
| 893 -- unique ids of items | |
| 894 local lootPart = {} | |
| 895 for i, obj in ipairs(itemObjects) do | |
| 896 table.insert(lootPart, Amr.GetItemUniqueId(obj)) | |
| 897 end | |
| 898 table.insert(parts, table.concat(lootPart, ";")) | |
| 899 | |
| 900 -- gear for players who have gained loot since the last item import or roster export | |
| 901 pruneGearForItemExport() | |
| 902 local lootGear = Amr.db.global.TeamOpt.LootGear | |
| 903 for k, v in pairs(lootGear) do | |
| 904 table.insert(parts, v) | |
| 905 end | |
| 906 | |
| 907 return table.concat(parts, "\n") | |
| 908 end | |
| 909 | |
| 910 local function onLootExportCompleted() | |
| 911 | |
| 912 -- fill in LootGear with just those players who have changed | |
| 913 Amr.db.global.TeamOpt.LootGear = _lootExRoster | |
| 914 | |
| 915 if _lootExCallback then | |
| 916 local txt = serializeLootExport() | |
| 917 _lootExCallback(txt) | |
| 918 end | |
| 919 | |
| 920 -- reset state | |
| 921 _lootExPlayersRemaining = 0 | |
| 922 _lootExRoster = nil | |
| 923 _lootExCallback = nil | |
| 924 end | |
| 925 | |
| 926 -- called when this player's gear info has been requested by someone exporting loot | |
| 927 local function onGearForLootExportRequested() | |
| 928 | |
| 929 local hasNewItem = false | |
| 930 local oldItems = Amr.db.char.TeamOpt.AllItems | |
| 931 | |
| 932 if oldItems and #oldItems > 0 then | |
| 933 -- see if any new equippable items have been gained by comparing to the last snapshot | |
| 934 local data = Amr:ExportCharacter() | |
| 935 local allItems = snapshotAllItems(data) | |
| 936 | |
| 937 if #oldItems ~= #allItems then | |
| 938 hasNewItem = true | |
| 939 else | |
| 940 -- go through items from front to back, if there are any that don't match then something has changed | |
| 941 for i = 1, #allItems do | |
| 942 local oldItem = oldItems[i] | |
| 943 local newItem = allItems[i] | |
| 944 if oldItem ~= newItem then | |
| 945 hasNewItem = true | |
| 946 break | |
| 947 end | |
| 948 end | |
| 949 end | |
| 950 end | |
| 951 | |
| 952 -- whenever a new item is received, send out updated gear information that should be added to the next item export | |
| 953 sendGear(_messagePrefixes.ItemExportGear, not hasNewItem) | |
| 954 end | |
| 955 | |
| 956 local function onGearForLootExportReceived(region, realm, name, data) | |
| 957 -- if I am not listening for incoming gear data for an item export, then ignore this message | |
| 958 if _lootExPlayersRemaining == 0 then return end | |
| 959 | |
| 960 local key = toPlayerKey(realm, name) | |
| 961 if not data or data == "_" then | |
| 962 _lootExRoster[key] = nil | |
| 963 else | |
| 964 _lootExRoster[key] = data | |
| 965 end | |
| 966 | |
| 967 _lootExPlayersRemaining = _lootExPlayersRemaining - 1 | |
| 968 if _lootExPlayersRemaining <= 0 then | |
| 969 onLootExportCompleted() | |
| 970 end | |
| 971 end | |
| 972 | |
| 973 -- Export the current loot, including any known gear data for players with the in-game addon, but only if it has changed since the last snapshot. | |
| 974 -- This is asynchronous because it needs to wait for gear data to arrive from each player. | |
| 975 function Amr:ExportLootAsync(callback) | |
| 976 | |
| 977 if not IsInGroup() and not IsInRaid() then | |
| 978 callback("NOGROUP") | |
| 979 end | |
| 980 | |
| 981 local loot = Amr.db.char.TeamOpt.Loot | |
| 982 if not loot or #loot == 0 then | |
| 983 callback("NOLOOT") | |
| 984 end | |
| 985 | |
| 986 local playersNoGear = {} | |
| 987 _lootExPlayersRemaining = 0 | |
| 988 | |
| 989 local units = self:GetGroupUnitIdentifiers() | |
| 990 for i, unitId in ipairs(units) do | |
| 991 local realm, name = self:GetRealmAndName(unitId) | |
| 992 if realm then | |
| 993 local ver = self:GetAddonVersion(realm, name) | |
| 994 local key = toPlayerKey(realm, name) | |
| 995 | |
| 996 if ver >= Amr.MIN_ADDON_VERSION then | |
| 997 _lootExPlayersRemaining = _lootExPlayersRemaining + 1 | |
| 998 else | |
| 999 table.insert(playersNoGear, unitId) | |
| 1000 end | |
| 1001 end | |
| 1002 end | |
| 1003 | |
| 1004 _lootExRoster = {} | |
| 1005 _lootExCallback = callback | |
| 1006 | |
| 1007 if _lootExPlayersRemaining > 0 then | |
| 1008 -- send a message to receive player data, when the last player is received onLootExportCompleted will be called | |
| 1009 Amr:SendAmrCommMessage(_messagePrefixes.ItemExportRequestGear) | |
| 1010 else | |
| 1011 -- don't need to wait for anybody, just call immediately | |
| 1012 onLootExportCompleted() | |
| 1013 end | |
| 1014 end | |
| 1015 | |
| 1016 | |
| 1017 ------------------------------------------------------------------------------------------------ | |
| 1018 -- Roster Export | |
| 1019 ------------------------------------------------------------------------------------------------ | |
| 1020 | |
| 1021 local _rosterPlayersRemaining = 0 | |
| 1022 local _roster = nil | |
| 1023 local _rosterCallback = nil | |
| 1024 | |
| 1025 local function onRosterCompleted() | |
| 1026 | |
| 1027 if _rosterCallback then | |
| 1028 -- serialize the roster | |
| 1029 local parts = {} | |
| 1030 for key, data in pairs(_roster) do | |
| 1031 table.insert(parts, data) | |
| 1032 end | |
| 1033 local msg = table.concat(parts, "\n") | |
| 1034 | |
| 1035 -- send to callback | |
| 1036 _rosterCallback(msg) | |
| 1037 end | |
| 1038 | |
| 1039 -- reset state | |
| 1040 _rosterPlayersRemaining = 0 | |
| 1041 _roster = nil | |
| 1042 _rosterCallback = nil | |
| 1043 | |
| 1044 -- clear out loot gear needed, an export will refresh everyone at the time of export | |
| 1045 Amr.db.global.TeamOpt.LootGear = {} | |
| 1046 end | |
| 1047 | |
| 1048 -- called when this player's gear info has been requested by someone exporting the raid roster | |
| 1049 local function onGearForRosterRequested() | |
| 1050 | |
| 1051 sendGear(_messagePrefixes.RosterGear) | |
| 1052 end | |
| 1053 | |
| 1054 local function onGearForRosterReceived(region, realm, name, data) | |
| 1055 -- if I am not listening for incoming gear data for the roster, then ignore this message | |
| 1056 if _rosterPlayersRemaining == 0 then return end | |
| 1057 | |
| 1058 local key = toPlayerKey(realm, name) | |
| 1059 _roster[key] = data | |
| 1060 | |
| 1061 _rosterPlayersRemaining = _rosterPlayersRemaining - 1 | |
| 1062 if _rosterPlayersRemaining <= 0 then | |
| 1063 onRosterCompleted() | |
| 1064 end | |
| 1065 end | |
| 1066 | |
| 1067 -- Export the current roster, including any known gear data for players with the in-game addon. | |
| 1068 -- This is asynchronous because it needs to wait for gear data to arrive from each player. | |
| 1069 function Amr:ExportRosterAsync(callback) | |
| 1070 if not IsInGroup() and not IsInRaid() then | |
| 1071 callback() | |
| 1072 return | |
| 1073 end | |
| 1074 | |
| 1075 local playersNoGear = {} | |
| 1076 _rosterPlayersRemaining = 0 | |
| 1077 | |
| 1078 local units = self:GetGroupUnitIdentifiers() | |
| 1079 for i, unitId in ipairs(units) do | |
| 1080 local realm, name = self:GetRealmAndName(unitId) | |
| 1081 if realm then | |
| 1082 local ver = self:GetAddonVersion(realm, name) | |
| 1083 local key = toPlayerKey(realm, name) | |
| 1084 | |
| 1085 if ver >= Amr.MIN_ADDON_VERSION then | |
| 1086 _rosterPlayersRemaining = _rosterPlayersRemaining + 1 | |
| 1087 else | |
| 1088 table.insert(playersNoGear, unitId) | |
| 1089 end | |
| 1090 end | |
| 1091 end | |
| 1092 | |
| 1093 -- fill the roster with any players who can't send us data | |
| 1094 _roster = {} | |
| 1095 for i, unitId in ipairs(playersNoGear) do | |
| 1096 local realm, name = self:GetRealmAndName(unitId) | |
| 1097 if realm then | |
| 1098 local key = toPlayerKey(realm, name) | |
| 1099 local obj = { | |
| 1100 Region = Amr.RegionNames[GetCurrentRegion()], | |
| 1101 Realm = realm, | |
| 1102 Name = name | |
| 1103 } | |
| 1104 _roster[key] = Amr.Serializer:SerializePlayerIdentity(obj) | |
| 1105 end | |
| 1106 end | |
| 1107 | |
| 1108 _rosterCallback = callback | |
| 1109 | |
| 1110 if _rosterPlayersRemaining > 0 then | |
| 1111 -- send a message to receive player data, when the last player is received onRosterCompleted will be called | |
| 1112 Amr:SendAmrCommMessage(_messagePrefixes.RosterRequestGear) | |
| 1113 else | |
| 1114 -- don't need to wait for anybody, just call immediately | |
| 1115 onRosterCompleted() | |
| 1116 end | |
| 1117 end | |
| 1118 | |
| 1119 | |
| 1120 ------------------------------------------------------------------------------------------------ | |
| 1121 -- Ranking Import | |
| 1122 ------------------------------------------------------------------------------------------------ | |
| 1123 | |
| 1124 -- helper to parse import item identifier format into an item object | |
| 1125 local function parseItemIdentifier(ident) | |
| 1126 | |
| 1127 local parts = { strsplit(":", ident) } | |
| 1128 local item = {} | |
| 1129 item.id = tonumber(parts[1]) | |
| 1130 item.enchantId = 0 | |
| 1131 item.gemIds = { 0, 0, 0, 0 } | |
| 1132 item.suffixId = math.abs(tonumber(parts[2])) | |
| 1133 item.upgradeId = tonumber(parts[3]) | |
| 1134 | |
| 1135 if #parts > 3 then | |
| 1136 item.bonusIds = {} | |
| 1137 for b = 4, #parts do | |
| 1138 table.insert(item.bonusIds, tonumber(parts[b])) | |
| 1139 end | |
| 1140 table.sort(item.bonusIds) | |
| 1141 end | |
| 1142 | |
| 1143 return item | |
| 1144 end | |
| 1145 | |
| 1146 function Amr:ParseRankingString(data) | |
| 1147 local rankings = {} | |
| 1148 | |
| 1149 local player = Amr:ExportCharacter() | |
| 1150 local myUnitId = Amr:GetUnitId(player.Realm, player.Name) | |
| 1151 local ml = IsMasterLooter() | |
| 1152 | |
| 1153 local itemList = { strsplit("\n", data) } | |
| 1154 for i = 1, #itemList do | |
| 1155 local ranking = {} | |
| 1156 | |
| 1157 local itemParts = { strsplit("_", itemList[i]) } | |
| 1158 | |
| 1159 -- first part has the item identifier | |
| 1160 ranking.item = parseItemIdentifier(itemParts[1]) | |
| 1161 | |
| 1162 -- second part has item info | |
| 1163 local infoParts = { strsplit(";", itemParts[2]) } | |
| 1164 ranking.itemInfo = { | |
| 1165 slot = infoParts[1], | |
| 1166 subclass = infoParts[2], | |
| 1167 weaponType = infoParts[3], | |
| 1168 armorType = infoParts[4] | |
| 1169 } | |
| 1170 | |
| 1171 local meInList = false | |
| 1172 | |
| 1173 -- parse each ranking | |
| 1174 ranking.ranks = {} | |
| 1175 for j = 3, #itemParts do | |
| 1176 local rankParts = { strsplit(";", itemParts[j]) } | |
| 1177 | |
| 1178 local rank = {} | |
| 1179 rank.realm = rankParts[1] | |
| 1180 rank.name = rankParts[2] | |
| 1181 rank.specId = tonumber(rankParts[3]) | |
| 1182 if rankParts[4] ~= "--" then | |
| 1183 rank.equipped = parseItemIdentifier(rankParts[4]) | |
| 1184 end | |
| 1185 rank.score = tonumber(rankParts[5]) | |
| 1186 rank.isEquipped = rankParts[6] == "t" | |
| 1187 rank.notRanked = rankParts[7] == "t" | |
| 1188 rank.offspec = rankParts[8] == "t" | |
| 1189 rank.enchantingSkill = tonumber(rankParts[9]) | |
| 1190 | |
| 1191 table.insert(ranking.ranks, rank) | |
| 1192 | |
| 1193 if myUnitId == Amr:GetUnitId(rank.realm, rank.name) then | |
| 1194 meInList = true | |
| 1195 rank.isMasterLooter = ml | |
| 1196 end | |
| 1197 end | |
| 1198 | |
| 1199 -- if the current player is the master looter and he is not in the list, then add him at the end | |
| 1200 if ml and not meInList then | |
| 1201 local rank = { | |
| 1202 realm = player.Realm, | |
| 1203 name = player.Name, | |
| 1204 specId = player.Specs[player.ActiveSpec], | |
| 1205 equipped = "--", | |
| 1206 score = 0, | |
| 1207 isEquipped = false, | |
| 1208 notRanked = true, | |
| 1209 offspec = false, | |
| 1210 enchantingSkill = 0, | |
| 1211 isMasterLooter = true | |
| 1212 } | |
| 1213 table.insert(ranking.ranks, rank) | |
| 1214 end | |
| 1215 | |
| 1216 table.insert(rankings, ranking) | |
| 1217 end | |
| 1218 | |
| 1219 return rankings | |
| 1220 end | |
| 1221 | |
| 1222 -- import rankings from the website, save into the database, returns a string error if can't import for some reason | |
| 1223 function Amr:ImportRankings(data) | |
| 1224 | |
| 1225 if not data or string.len(data) == 0 then | |
| 1226 return L.ImportErrorEmpty | |
| 1227 end | |
| 1228 | |
| 1229 local success, rankings = pcall(Amr.ParseRankingString, self, data) | |
| 1230 | |
| 1231 if not success then | |
| 1232 return L.ImportErrorFormat | |
| 1233 end | |
| 1234 | |
| 1235 -- finish any looting in progress, effectively canceling it, user will have to press Start Loot again | |
| 1236 Amr:FinishLoot() | |
| 1237 | |
| 1238 -- save the rankings | |
| 1239 Amr.db.global.TeamOpt.Rankings = rankings | |
| 1240 Amr.db.global.TeamOpt.RankingString = data | |
| 1241 | |
| 1242 -- clear loot gear needed on successful ranking import | |
| 1243 Amr.db.global.TeamOpt.LootGear = {} | |
| 1244 end | |
| 1245 | |
| 1246 | |
| 1247 ------------------------------------------------------------------------------------------------ | |
| 1248 -- Loot Distribution | |
| 1249 ------------------------------------------------------------------------------------------------ | |
| 1250 | |
| 1251 function Amr:StartLoot() | |
| 1252 | |
| 1253 if not IsInGroup() and not IsInRaid() then | |
| 1254 Amr:ShowAlert(L.TeamAlertNoGroup, L.AlertOk) | |
| 1255 return | |
| 1256 end | |
| 1257 | |
| 1258 -- broadcast the loot data to everyone, this triggers the loot window to show | |
| 1259 local msg = string.format("%s\n%s", Amr.LootMessagePrefixes.Start, Amr.db.global.TeamOpt.RankingString) | |
| 1260 Amr:SendAmrCommMessage(msg) | |
| 1261 end | |
| 1262 | |
| 1263 function Amr:FinishLoot(clearHistory) | |
| 1264 | |
| 1265 -- reset all state | |
| 1266 Amr.db.char.TeamOpt.LootInProgress = false | |
| 1267 | |
| 1268 -- don't reset these for now... only reset these if someone leaves a group | |
| 1269 --Amr.db.char.TeamOpt.Loot = {} | |
| 1270 --Amr.db.char.TeamOpt.LootGuid = nil | |
| 1271 --Amr.db.global.TeamOpt.LootGear = {} | |
| 1272 | |
| 1273 Amr.db.global.TeamOpt.Rankings = {} | |
| 1274 Amr.db.global.TeamOpt.RankingString = nil | |
| 1275 | |
| 1276 Amr.db.char.TeamOpt.Rolls = {} | |
| 1277 | |
| 1278 if clearHistory then | |
| 1279 Amr.db.char.TeamOpt.History = {} | |
| 1280 end | |
| 1281 | |
| 1282 -- close the loot window | |
| 1283 Amr:HideLootWindow() | |
| 1284 | |
| 1285 -- re-render the team optimizer UI | |
| 1286 Amr:RefreshTeamUi() | |
| 1287 end | |
| 1288 | |
| 1289 | |
| 1290 ------------------------------------------------------------------------------------------------ | |
| 1291 -- Synchronization | |
| 1292 ------------------------------------------------------------------------------------------------ | |
| 1293 local _waitingForSync = false | |
| 1294 | |
| 1295 -- check if we need to synchronize | |
| 1296 local function checkSync() | |
| 1297 -- if loot is in progress and this person is not the ML, send a request to synchronize on startup, this player may have missed some data | |
| 1298 if not IsMasterLooter() and Amr.db.char.TeamOpt.LootInProgress then | |
| 1299 _waitingForSync = true | |
| 1300 Amr:SendAmrCommMessage(_messagePrefixes.SyncRequest) | |
| 1301 end | |
| 1302 end | |
| 1303 | |
| 1304 -- send data to anyone who needs to synchronize their loot data: history, rolls, rankings | |
| 1305 local function sendSyncData() | |
| 1306 -- only the master looter sends sync data to ensure that everyone gets the same stuff and we don't spam | |
| 1307 if not IsMasterLooter() then return end | |
| 1308 | |
| 1309 local msgParts = {} | |
| 1310 table.insert(msgParts, _messagePrefixes.Sync) | |
| 1311 table.insert(msgParts, Amr:Serialize(Amr.db.char.TeamOpt.History)) | |
| 1312 table.insert(msgParts, Amr:Serialize(Amr.db.char.TeamOpt.Rolls)) | |
| 1313 table.insert(msgParts, Amr:Serialize(Amr.db.global.TeamOpt.Rankings)) | |
| 1314 | |
| 1315 Amr:SendAmrCommMessage(table.concat(msgParts, "\n")) | |
| 1316 end | |
| 1317 | |
| 1318 local function receiveSyncData(parts) | |
| 1319 if not _waitingForSync then return end | |
| 1320 _waitingForSync = false | |
| 1321 | |
| 1322 local success, obj = Amr:Deserialize(parts[2]) | |
| 1323 if success then | |
| 1324 Amr.db.char.TeamOpt.History = obj | |
| 1325 end | |
| 1326 | |
| 1327 success, obj = Amr:Deserialize(parts[3]) | |
| 1328 if success then | |
| 1329 Amr.db.char.TeamOpt.Rolls = obj | |
| 1330 end | |
| 1331 | |
| 1332 success, obj = Amr:Deserialize(parts[4]) | |
| 1333 if success then | |
| 1334 Amr.db.global.TeamOpt.Rankings = obj | |
| 1335 end | |
| 1336 | |
| 1337 -- refresh any windows that may be visible | |
| 1338 Amr:RefreshTeamUi() | |
| 1339 Amr:RefreshLootWindow() | |
| 1340 Amr:RefreshLootRolls() | |
| 1341 end | |
| 1342 | |
| 1343 | |
| 1344 function Amr:ProcessTeamMessage(message) | |
| 1345 | |
| 1346 local parts = {} | |
| 1347 for part in string.gmatch(message, "([^\n]+)") do | |
| 1348 table.insert(parts, part) | |
| 1349 end | |
| 1350 | |
| 1351 local prefix = parts[1] | |
| 1352 | |
| 1353 if prefix == _messagePrefixes.RosterRequestGear then | |
| 1354 -- request for me to send my gear data | |
| 1355 onGearForRosterRequested() | |
| 1356 elseif prefix == _messagePrefixes.ItemExportRequestGear then | |
| 1357 -- request for me to send my gear data | |
| 1358 onGearForLootExportRequested() | |
| 1359 elseif prefix == _messagePrefixes.ItemExportLoot then | |
| 1360 -- the last loot that dropped | |
| 1361 onLootReceived(parts) | |
| 1362 elseif prefix == _messagePrefixes.SyncRequest then | |
| 1363 sendSyncData() | |
| 1364 elseif prefix == _messagePrefixes.Sync then | |
| 1365 receiveSyncData(parts) | |
| 1366 elseif prefix == Amr.LootMessagePrefixes.Start then | |
| 1367 Amr:OnStartLootReceived(parts) | |
| 1368 elseif prefix == Amr.LootMessagePrefixes.Roll then | |
| 1369 Amr:OnLootRollReceived(parts) | |
| 1370 elseif prefix == Amr.LootMessagePrefixes.Veto then | |
| 1371 Amr:OnLootVetoReceived(parts) | |
| 1372 elseif prefix == Amr.LootMessagePrefixes.Rand then | |
| 1373 Amr:OnLootRandReceived(parts) | |
| 1374 elseif prefix == Amr.LootMessagePrefixes.Give then | |
| 1375 Amr:OnLootGiveReceived(parts) | |
| 1376 elseif prefix == Amr.LootMessagePrefixes.Finish then | |
| 1377 Amr:FinishLoot() | |
| 1378 else | |
| 1379 -- message will be of format: prefix\nregion\nrealm\nname\n[stuff] | |
| 1380 local region = parts[2] | |
| 1381 local realm = parts[3] | |
| 1382 local name = parts[4] | |
| 1383 local data = parts[5] | |
| 1384 | |
| 1385 if prefix == _messagePrefixes.RosterGear then | |
| 1386 -- receive gear data from someone | |
| 1387 onGearForRosterReceived(region, realm, name, data) | |
| 1388 elseif prefix == _messagePrefixes.ItemExportGear then | |
| 1389 -- receive gear data for item export | |
| 1390 onGearForLootExportReceived(region, realm, name, data) | |
| 1391 end | |
| 1392 end | |
| 1393 end | |
| 1394 | |
| 1395 function Amr:InitializeTeamOpt() | |
| 1396 | |
| 1397 if not IsInGroup() and not IsInRaid() then | |
| 1398 onLeaveGroup() | |
| 1399 end | |
| 1400 | |
| 1401 Amr:AddEventHandler("LOOT_OPENED", scanMasterLoot) | |
| 1402 Amr:AddEventHandler("GROUP_ROSTER_UPDATE", onGroupChanged) | |
| 1403 | |
| 1404 checkSync() | |
| 1405 end |
