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 |