comparison AskMrRobot-Serializer/AskMrRobot-Serializer.lua @ 57:01b63b8ed811 v21

total rewrite to version 21
author yellowfive
date Fri, 05 Jun 2015 11:05:15 -0700
parents
children ee701ce45354
comparison
equal deleted inserted replaced
56:75431c084aa0 57:01b63b8ed811
1 -- AskMrRobot-Serializer will serialize and communicate character data between users.
2 -- This is used primarily to associate character information to logs uploaded to askmrrobot.com.
3
4 local MAJOR, MINOR = "AskMrRobot-Serializer", 21
5 local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
6
7 if not Amr then return end -- already loaded by something else
8
9 -- event and comm used for player snapshotting on entering combat
10 LibStub("AceEvent-3.0"):Embed(Amr)
11 LibStub("AceComm-3.0"):Embed(Amr)
12
13 ----------------------------------------------------------------------------------------
14 -- Constants
15 ----------------------------------------------------------------------------------------
16
17 -- prefix used for communicating gear snapshots created by the AMR serializer
18 Amr.ChatPrefix = "_AMRS"
19
20 -- map of region ids to AMR region names
21 Amr.RegionNames = {
22 [1] = "US",
23 [2] = "KR",
24 [3] = "EU",
25 [4] = "TW",
26 [5] = "CN"
27 }
28
29 -- map of the skillLine returned by profession API to the AMR profession name
30 Amr.ProfessionSkillLineToName = {
31 [794] = "Archaeology",
32 [171] = "Alchemy",
33 [164] = "Blacksmithing",
34 [185] = "Cooking",
35 [333] = "Enchanting",
36 [202] = "Engineering",
37 [129] = "First Aid",
38 [356] = "Fishing",
39 [182] = "Herbalism",
40 [773] = "Inscription",
41 [755] = "Jewelcrafting",
42 [165] = "Leatherworking",
43 [186] = "Mining",
44 [393] = "Skinning",
45 [197] = "Tailoring"
46 }
47
48 -- all slot IDs that we care about, ordered in AMR standard display order
49 Amr.SlotIds = { 16, 17, 1, 2, 3, 15, 5, 9, 10, 6, 7, 8, 11, 12, 13, 14 }
50
51 Amr.SpecIds = {
52 [250] = 1, -- DeathKnightBlood
53 [251] = 2, -- DeathKnightFrost
54 [252] = 3, -- DeathKnightUnholy
55 [102] = 4, -- DruidBalance
56 [103] = 5, -- DruidFeral
57 [104] = 6, -- DruidGuardian
58 [105] = 7, -- DruidRestoration
59 [253] = 8, -- HunterBeastMastery
60 [254] = 9, -- HunterMarksmanship
61 [255] = 10, -- HunterSurvival
62 [62] = 11, -- MageArcane
63 [63] = 12, -- MageFire
64 [64] = 13, -- MageFrost
65 [268] = 14, -- MonkBrewmaster
66 [270] = 15, -- MonkMistweaver
67 [269] = 16, -- MonkWindwalker
68 [65] = 17, -- PaladinHoly
69 [66] = 18, -- PaladinProtection
70 [70] = 19, -- PaladinRetribution
71 [256] = 20, -- PriestDiscipline
72 [257] = 21, -- PriestHoly
73 [258] = 22, -- PriestShadow
74 [259] = 23, -- RogueAssassination
75 [260] = 24, -- RogueCombat
76 [261] = 25, -- RogueSubtlety
77 [262] = 26, -- ShamanElemental
78 [263] = 27, -- ShamanEnhancement
79 [264] = 28, -- ShamanRestoration
80 [265] = 29, -- WarlockAffliction
81 [266] = 30, -- WarlockDemonology
82 [267] = 31, -- WarlockDestruction
83 [71] = 32, -- WarriorArms
84 [72] = 33, -- WarriorFury
85 [73] = 34 -- WarriorProtection
86 }
87
88 Amr.ClassIds = {
89 ["NONE"] = 0,
90 ["DEATHKNIGHT"] = 1,
91 ["DRUID"] = 2,
92 ["HUNTER"] = 3,
93 ["MAGE"] = 4,
94 ["MONK"] = 5,
95 ["PALADIN"] = 6,
96 ["PRIEST"] = 7,
97 ["ROGUE"] = 8,
98 ["SHAMAN"] = 9,
99 ["WARLOCK"] = 10,
100 ["WARRIOR"] = 11,
101 }
102
103 Amr.ProfessionIds = {
104 ["None"] = 0,
105 ["Mining"] = 1,
106 ["Skinning"] = 2,
107 ["Herbalism"] = 3,
108 ["Enchanting"] = 4,
109 ["Jewelcrafting"] = 5,
110 ["Engineering"] = 6,
111 ["Blacksmithing"] = 7,
112 ["Leatherworking"] = 8,
113 ["Inscription"] = 9,
114 ["Tailoring"] = 10,
115 ["Alchemy"] = 11,
116 ["Fishing"] = 12,
117 ["Cooking"] = 13,
118 ["First Aid"] = 14,
119 ["Archaeology"] = 15
120 }
121
122 Amr.RaceIds = {
123 ["None"] = 0,
124 ["BloodElf"] = 1,
125 ["Draenei"] = 2,
126 ["Dwarf"] = 3,
127 ["Gnome"] = 4,
128 ["Human"] = 5,
129 ["NightElf"] = 6,
130 ["Orc"] = 7,
131 ["Tauren"] = 8,
132 ["Troll"] = 9,
133 ["Scourge"] = 10,
134 ["Undead"] = 10,
135 ["Goblin"] = 11,
136 ["Worgen"] = 12,
137 ["Pandaren"] = 13
138 }
139
140 Amr.FactionIds = {
141 ["None"] = 0,
142 ["Alliance"] = 1,
143 ["Horde"] = 2
144 }
145
146 Amr.InstanceIds = {
147 Auchindoun = 1182,
148 BloodmaulSlagMines = 1175,
149 GrimrailDepot = 1208,
150 IronDocks = 1195,
151 ShadowmoonBurialGrounds = 1176,
152 Skyreach = 1209,
153 TheEverbloom = 1279,
154 UpperBlackrockSpire = 1358,
155 Highmaul = 1228,
156 BlackrockFoundry = 1205
157 }
158
159 -- instances that AskMrRobot currently supports logging for
160 Amr.SupportedInstanceIds = {
161 --[1182] = true,
162 --[1175] = true,
163 --[1208] = true,
164 --[1195] = true,
165 --[1176] = true,
166 --[1209] = true,
167 --[1279] = true,
168 --[1358] = true,
169 [1228] = true,
170 [1205] = true
171 }
172
173 Amr.SPEC_WARRIORPROTECTION = 34
174 Amr.SUBSPEC_WARRIORPROTECTION = 38
175 Amr.SUBSPEC_WARRIORPROTECTIONGLAD = 39
176 Amr.SPELL_ID_GLADIATOR_STANCE = 156291
177 Amr.SPELL_ID_DEFENSIVE_STANCE = 71
178
179 -- IDs of set tokens that we would care about in a player's inventory
180 Amr.SetTokenIds = {
181 [120285] = true,
182 [120284] = true,
183 [120283] = true,
184 [120282] = true,
185 [120281] = true,
186 [120280] = true,
187 [120279] = true,
188 [120278] = true,
189 [120277] = true,
190 [120256] = true,
191 [120255] = true,
192 [120254] = true,
193 [120253] = true,
194 [120252] = true,
195 [120251] = true,
196 [120250] = true,
197 [120249] = true,
198 [120248] = true,
199 [120247] = true,
200 [120246] = true,
201 [120245] = true,
202 [120244] = true,
203 [120243] = true,
204 [120242] = true,
205 [120241] = true,
206 [120240] = true,
207 [120239] = true,
208 [120238] = true,
209 [120237] = true,
210 [120236] = true,
211 [120235] = true,
212 [120234] = true,
213 [120233] = true,
214 [120232] = true,
215 [120231] = true,
216 [120230] = true,
217 [120229] = true,
218 [120228] = true,
219 [120227] = true,
220 [120226] = true,
221 [120225] = true,
222 [120224] = true,
223 [120223] = true,
224 [120222] = true,
225 [120221] = true,
226 [120220] = true,
227 [120219] = true,
228 [120218] = true,
229 [120217] = true,
230 [120216] = true,
231 [120215] = true,
232 [120214] = true,
233 [120213] = true,
234 [120212] = true,
235 [120211] = true,
236 [120210] = true,
237 [120209] = true,
238 [120208] = true,
239 [120207] = true,
240 [120206] = true,
241 [119323] = true,
242 [119322] = true,
243 [119321] = true,
244 [119320] = true,
245 [119319] = true,
246 [119318] = true,
247 [119316] = true,
248 [119315] = true,
249 [119314] = true,
250 [119313] = true,
251 [119312] = true,
252 [119311] = true,
253 [119310] = true,
254 [119309] = true,
255 [119308] = true,
256 [119307] = true,
257 [119306] = true,
258 [119305] = true,
259 [105868] = true,
260 [105867] = true,
261 [105866] = true,
262 [105865] = true,
263 [105864] = true,
264 [105863] = true,
265 [105862] = true,
266 [105861] = true,
267 [105860] = true,
268 [105859] = true,
269 [105858] = true,
270 [105857] = true,
271 [99756] = true,
272 [99755] = true,
273 [99754] = true,
274 [99753] = true,
275 [99752] = true,
276 [99751] = true,
277 [99750] = true,
278 [99749] = true,
279 [99748] = true,
280 [99747] = true,
281 [99746] = true,
282 [99745] = true,
283 [99744] = true,
284 [99743] = true,
285 [99742] = true,
286 [99740] = true,
287 [99739] = true,
288 [99738] = true,
289 [99737] = true,
290 [99736] = true,
291 [99735] = true,
292 [99734] = true,
293 [99733] = true,
294 [99732] = true,
295 [99731] = true,
296 [99730] = true,
297 [99729] = true,
298 [99728] = true,
299 [99727] = true,
300 [99726] = true,
301 [99725] = true,
302 [99724] = true,
303 [99723] = true,
304 [99722] = true,
305 [99721] = true,
306 [99720] = true,
307 [99719] = true,
308 [99718] = true,
309 [99717] = true,
310 [99716] = true,
311 [99715] = true,
312 [99714] = true,
313 [99713] = true,
314 [99712] = true,
315 [99711] = true,
316 [99710] = true,
317 [99709] = true,
318 [99708] = true,
319 [99707] = true,
320 [99706] = true,
321 [99705] = true,
322 [99704] = true,
323 [99703] = true,
324 [99702] = true,
325 [99701] = true,
326 [99700] = true,
327 [99699] = true,
328 [99698] = true,
329 [99697] = true,
330 [99696] = true,
331 [99695] = true,
332 [99694] = true,
333 [99693] = true,
334 [99692] = true,
335 [99691] = true,
336 [99690] = true,
337 [99689] = true,
338 [99688] = true,
339 [99687] = true,
340 [99686] = true,
341 [99685] = true,
342 [99684] = true,
343 [99683] = true,
344 [99682] = true,
345 [99681] = true,
346 [99680] = true,
347 [99679] = true,
348 [99678] = true,
349 [99677] = true,
350 [99676] = true,
351 [99675] = true,
352 [99674] = true,
353 [99673] = true,
354 [99672] = true,
355 [99671] = true,
356 [99670] = true,
357 [99669] = true,
358 [99668] = true,
359 [99667] = true,
360 [96701] = true,
361 [96700] = true,
362 [96699] = true,
363 [96633] = true,
364 [96632] = true,
365 [96631] = true,
366 [96625] = true,
367 [96624] = true,
368 [96623] = true,
369 [96601] = true,
370 [96600] = true,
371 [96599] = true,
372 [96568] = true,
373 [96567] = true,
374 [96566] = true,
375 [95957] = true,
376 [95956] = true,
377 [95955] = true,
378 [95889] = true,
379 [95888] = true,
380 [95887] = true,
381 [95881] = true,
382 [95880] = true,
383 [95879] = true,
384 [95857] = true,
385 [95856] = true,
386 [95855] = true,
387 [95824] = true,
388 [95823] = true,
389 [95822] = true,
390 [95583] = true,
391 [95582] = true,
392 [95581] = true,
393 [95580] = true,
394 [95579] = true,
395 [95578] = true,
396 [95577] = true,
397 [95576] = true,
398 [95575] = true,
399 [95574] = true,
400 [95573] = true,
401 [95572] = true,
402 [95571] = true,
403 [95570] = true,
404 [95569] = true,
405 [89278] = true,
406 [89277] = true,
407 [89276] = true,
408 [89275] = true,
409 [89274] = true,
410 [89273] = true,
411 [89272] = true,
412 [89271] = true,
413 [89270] = true,
414 [89269] = true,
415 [89268] = true,
416 [89267] = true,
417 [89266] = true,
418 [89265] = true,
419 [89264] = true,
420 [89263] = true,
421 [89262] = true,
422 [89261] = true,
423 [89260] = true,
424 [89259] = true,
425 [89258] = true,
426 [89257] = true,
427 [89256] = true,
428 [89255] = true,
429 [89254] = true,
430 [89253] = true,
431 [89252] = true,
432 [89251] = true,
433 [89250] = true,
434 [89249] = true,
435 [89248] = true,
436 [89247] = true,
437 [89246] = true,
438 [89245] = true,
439 [89244] = true,
440 [89243] = true,
441 [89242] = true,
442 [89241] = true,
443 [89240] = true,
444 [89239] = true,
445 [89238] = true,
446 [89237] = true,
447 [89236] = true,
448 [89235] = true,
449 [89234] = true,
450 [78876] = true,
451 [78875] = true,
452 [78874] = true,
453 [78873] = true,
454 [78872] = true,
455 [78871] = true,
456 [78867] = true,
457 [78866] = true,
458 [78865] = true,
459 [78864] = true,
460 [78863] = true,
461 [78862] = true,
462 [78861] = true,
463 [78860] = true,
464 [78859] = true,
465 [78858] = true,
466 [78857] = true,
467 [78856] = true,
468 [78855] = true,
469 [78854] = true,
470 [78853] = true,
471 [78849] = true,
472 [78848] = true,
473 [78847] = true,
474 [78184] = true,
475 [78183] = true,
476 [78181] = true,
477 [78180] = true,
478 [78179] = true,
479 [78178] = true,
480 [78176] = true,
481 [78175] = true,
482 [78174] = true,
483 [78173] = true,
484 [78171] = true,
485 [78170] = true,
486 [71687] = true,
487 [71686] = true,
488 [71685] = true,
489 [71683] = true,
490 [71682] = true,
491 [71680] = true,
492 [71679] = true,
493 [71678] = true,
494 [71676] = true,
495 [71675] = true,
496 [71673] = true,
497 [71672] = true,
498 [71671] = true,
499 [71669] = true,
500 [71668] = true,
501 [67431] = true,
502 [67430] = true,
503 [67429] = true,
504 [67428] = true,
505 [67427] = true,
506 [67426] = true,
507 [67425] = true,
508 [67424] = true,
509 [67423] = true,
510 [66998] = true,
511 [65089] = true,
512 [65088] = true,
513 [65087] = true,
514 [63684] = true,
515 [63683] = true,
516 [63682] = true,
517 [45652] = true,
518 [45651] = true,
519 [45650] = true,
520 [45649] = true,
521 [45648] = true,
522 [45647] = true,
523 [45643] = true,
524 [45642] = true,
525 [45641] = true,
526 [40630] = true,
527 [40629] = true,
528 [40628] = true,
529 [40621] = true,
530 [40620] = true,
531 [40619] = true,
532 [40618] = true,
533 [40617] = true,
534 [40616] = true,
535 [34544] = true,
536 [31100] = true,
537 [31099] = true,
538 [31098] = true,
539 [31097] = true,
540 [31096] = true,
541 [31095] = true,
542 [30247] = true,
543 [30246] = true,
544 [30245] = true,
545 [30244] = true,
546 [30243] = true,
547 [30242] = true,
548 [29767] = true,
549 [29766] = true,
550 [29765] = true,
551 [29761] = true,
552 [29760] = true,
553 [29759] = true
554 }
555
556
557 ----------------------------------------------------------------------------------------
558 -- Public Utility Methods
559 ----------------------------------------------------------------------------------------
560
561 -- item link format: |cffa335ee|Hitem:itemID:enchant:gem1:gem2:gem3:gem4:suffixID:uniqueID:level:upgradeId:instanceDifficultyID:numBonusIDs:bonusID1:bonusID2...|h[item name]|h|r
562 -- get an object with all of the parts of the item link format that we care about
563 function Amr.ParseItemLink(itemLink)
564 if not itemLink then return nil end
565
566 local str = string.match(itemLink, "|Hitem:([\-%d:]+)|")
567 if not str then return nil end
568
569 local parts = { strsplit(":", str) }
570
571 local item = {}
572 item.id = tonumber(parts[1])
573 item.enchantId = tonumber(parts[2])
574 item.gemIds = { tonumber(parts[3]), tonumber(parts[4]), tonumber(parts[5]), tonumber(parts[6]) }
575 item.suffixId = math.abs(tonumber(parts[7])) -- convert suffix to positive number, that's what we use in our code
576 --item.uniqueId = tonumber(parts[8])
577 --item.level = tonumber(parts[9])
578 item.upgradeId = tonumber(parts[10])
579 --item.difficultyId = tonumber(parts[11])
580
581 local numBonuses = tonumber(parts[12])
582 if numBonuses and numBonuses > 0 then
583 item.bonusIds = {}
584 for i = 13, 12 + numBonuses do
585 table.insert(item.bonusIds, tonumber(parts[i]))
586 end
587 table.sort(item.bonusIds)
588 end
589
590 return item
591 end
592
593 -- returns true if this is an instance that AskMrRobot supports for logging
594 function Amr.IsSupportedInstanceId(instanceMapID)
595 if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then
596 return true
597 else
598 return false
599 end
600 end
601
602 -- returns true if currently in a supported instance for logging
603 function Amr.IsSupportedInstance()
604 local zone, _, difficultyIndex, _, _, _, _, instanceMapID = GetInstanceInfo()
605 return Amr.IsSupportedInstanceId(instanceMapID)
606 end
607
608
609 ----------------------------------------------------------------------------------------
610 -- Character Reading
611 ----------------------------------------------------------------------------------------
612
613 local function readProfessionInfo(prof, ret)
614 if prof then
615 local name, icon, skillLevel, maxSkillLevel, numAbilities, spelloffset, skillLine, skillModifier = GetProfessionInfo(prof);
616 if Amr.ProfessionSkillLineToName[skillLine] ~= nil then
617 ret.Professions[Amr.ProfessionSkillLineToName[skillLine]] = skillLevel;
618 end
619 end
620 end
621
622 local function getSpecId(specGroup)
623 local spec = GetSpecialization(false, false, specGroup);
624 return spec and GetSpecializationInfo(spec);
625 end
626
627 local function getTalents(specGroup)
628 local talentInfo = {}
629 local maxTiers = 7
630 for tier = 1, maxTiers do
631 for col = 1, 3 do
632 local id, name, texture, selected, available = GetTalentInfo(tier, col, specGroup)
633 if selected then
634 talentInfo[tier] = col
635 end
636 end
637 end
638
639 local str = ""
640 for i = 1, maxTiers do
641 if talentInfo[i] then
642 str = str .. talentInfo[i]
643 else
644 str = str .. '0'
645 end
646 end
647
648 return str
649 end
650
651 local function getGlyphs(specGroup)
652 local glyphs = {}
653 for i = 1, NUM_GLYPH_SLOTS do
654 local _, _, _, glyphSpellID, _, glyphID = GetGlyphSocketInfo(i, specGroup)
655 if (glyphID) then
656 table.insert(glyphs, glyphSpellID)
657 end
658 end
659 return glyphs;
660 end
661
662 -- get specs, talents, and glyphs
663 local function readSpecs(ret, subspecs)
664
665 for group = 1, GetNumSpecGroups() do
666 -- spec, convert game spec id to one of our spec ids
667 local specId = getSpecId(group)
668 if specId then
669 ret.Specs[group] = Amr.SpecIds[specId]
670
671 -- if this is a protection warrior, use buffs to determine subspec
672 if ret.Specs[group] == Amr.SPEC_WARRIORPROTECTION then
673 local subspec = 0
674
675 if ret.ActiveSpec ~= group then
676 -- this spec isn't active, so we can't use current buffs to determine spec, see if any old data is compatible
677 if subspecs and (subspecs[group] == Amr.SUBSPEC_WARRIORPROTECTION or subspecs[group] == Amr.SUBSPEC_WARRIORPROTECTIONGLAD) then
678 subspec = subspecs[group]
679 end
680 else
681 for i=1,40 do
682 local name,_,_,_,_,_,_,_,_,_,spellId = UnitAura("player", i, "HELPFUL")
683 if not name then break end
684
685 if spellId == Amr.SPELL_ID_DEFENSIVE_STANCE then
686 subspec = Amr.SUBSPEC_WARRIORPROTECTION
687 break
688 elseif spellId == Amr.SPELL_ID_GLADIATOR_STANCE then
689 subspec = Amr.SUBSPEC_WARRIORPROTECTIONGLAD
690 break
691 end
692 end
693 end
694
695 if subspec == 0 then
696 ret.SubSpecs[group] = nil
697 else
698 ret.SubSpecs[group] = subspec
699 end
700 end
701 else
702 ret.Specs[group] = 0
703 end
704
705 ret.Talents[group] = getTalents(group)
706 ret.Glyphs[group] = getGlyphs(group)
707 end
708 end
709
710 -- get currently equipped items, store with currently active spec
711 local function readEquippedItems(ret)
712 local equippedItems = {};
713 for slotNum = 1, #Amr.SlotIds do
714 local slotId = Amr.SlotIds[slotNum]
715 local itemLink = GetInventoryItemLink("player", slotId)
716 if itemLink then
717 equippedItems[slotId] = itemLink
718 end
719 end
720
721 -- store last-seen equipped gear for each spec
722 ret.Equipped[GetActiveSpecGroup()] = equippedItems
723 end
724
725 -- Get all data about the player as an object, includes:
726 -- serializer version
727 -- region/realm/name
728 -- guild
729 -- race
730 -- faction
731 -- level
732 -- professions
733 -- spec/talent/glyphs for both specs
734 -- equipped gear for the current spec
735 --
736 function Amr:GetPlayerData(subspecs)
737
738 local ret = {}
739
740 ret.Region = Amr.RegionNames[GetCurrentRegion()]
741 ret.Realm = GetRealmName()
742 ret.Name = UnitName("player")
743 ret.Guild = GetGuildInfo("player")
744 ret.ActiveSpec = GetActiveSpecGroup()
745 ret.Level = UnitLevel("player");
746
747 local cls, clsEn = UnitClass("player")
748 ret.Class = clsEn;
749
750 local race, raceEn = UnitRace("player")
751 ret.Race = raceEn;
752 ret.Faction = UnitFactionGroup("player")
753
754 ret.Professions = {};
755 local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions();
756 readProfessionInfo(prof1, ret)
757 readProfessionInfo(prof2, ret)
758 readProfessionInfo(archaeology, ret)
759 readProfessionInfo(fishing, ret)
760 readProfessionInfo(cooking, ret)
761 readProfessionInfo(firstAid, ret)
762
763 ret.Specs = {}
764 ret.SubSpecs = {} -- only filled in for ambiguous cases, right now just prot/glad warrior
765 ret.Talents = {}
766 ret.Glyphs = {}
767 readSpecs(ret, subspecs)
768
769 ret.Equipped = {}
770 readEquippedItems(ret)
771
772 return ret
773 end
774
775
776 ----------------------------------------------------------------------------------------
777 -- Serialization
778 ----------------------------------------------------------------------------------------
779
780 local function toCompressedNumberList(list)
781 -- ensure the values are numbers, sorted from lowest to highest
782 local nums = {}
783 for i, v in ipairs(list) do
784 table.insert(nums, tonumber(v))
785 end
786 table.sort(nums)
787
788 local ret = {}
789 local prev = 0
790 for i, v in ipairs(nums) do
791 local diff = v - prev
792 table.insert(ret, diff)
793 prev = v
794 end
795
796 return table.concat(ret, ",")
797 end
798
799 -- make this utility publicly available
800 function Amr:ToCompressedNumberList(list)
801 return toCompressedNumberList(list)
802 end
803
804 -- appends a list of items to the export
805 local function appendItemsToExport(fields, itemObjects)
806
807 -- sort by item id so we can compress it more easily
808 table.sort(itemObjects, function(a, b) return a.id < b.id end)
809
810 -- append to the export string
811 local prevItemId = 0
812 local prevGemId = 0
813 local prevEnchantId = 0
814 local prevUpgradeId = 0
815 local prevBonusId = 0
816 for i, itemData in ipairs(itemObjects) do
817 local itemParts = {}
818
819 table.insert(itemParts, itemData.id - prevItemId)
820 prevItemId = itemData.id
821
822 if itemData.slot ~= nil then table.insert(itemParts, "s" .. itemData.slot) end
823 if itemData.suffixId ~= 0 then table.insert(itemParts, "f" .. itemData.suffixId) end
824 if itemData.upgradeId ~= 0 then
825 table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId))
826 prevUpgradeId = itemData.upgradeId
827 end
828 if itemData.bonusIds then
829 for bIndex, bValue in ipairs(itemData.bonusIds) do
830 table.insert(itemParts, "b" .. (bValue - prevBonusId))
831 prevBonusId = bValue
832 end
833 end
834 if itemData.gemIds[1] ~= 0 then
835 table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
836 prevGemId = itemData.gemIds[1]
837 end
838 if itemData.gemIds[2] ~= 0 then
839 table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
840 prevGemId = itemData.gemIds[2]
841 end
842 if itemData.gemIds[3] ~= 0 then
843 table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
844 prevGemId = itemData.gemIds[3]
845 end
846 if itemData.enchantId ~= 0 then
847 table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId))
848 prevEnchantId = itemData.enchantId
849 end
850
851 table.insert(fields, table.concat(itemParts, ""))
852 end
853 end
854
855 -- Serialize just the identity portion of a player (region/realm/name) in the same format used by the full serialization
856 function Amr:SerializePlayerIdentity(data)
857 local fields = {}
858 table.insert(fields, MINOR)
859 table.insert(fields, data.Region)
860 table.insert(fields, data.Realm)
861 table.insert(fields, data.Name)
862 return "$" .. table.concat(fields, ";") .. "$"
863 end
864
865 -- Serialize player data gathered by GetPlayerData. This can be augmented with extra data if desired (augmenting used mainly by AskMrRobot addon).
866 -- Pass complete = true to do a complete export of this extra information, otherwise it is ignored.
867 -- Extra data can include:
868 -- equipped gear for the player's inactive spec, slot id to item link dictionary
869 -- Reputations
870 -- BagItems, BankItems, VoidItems, lists of item links
871 --
872 function Amr:SerializePlayerData(data, complete)
873
874 local fields = {}
875
876 -- compressed string uses a fixed order rather than inserting identifiers
877 table.insert(fields, MINOR)
878 table.insert(fields, data.Region)
879 table.insert(fields, data.Realm)
880 table.insert(fields, data.Name)
881
882 -- guild name
883 if data.Guild == nil then
884 table.insert(fields, "")
885 else
886 table.insert(fields, data.Guild)
887 end
888
889 -- race, default to pandaren if we can't read it for some reason
890 local raceval = Amr.RaceIds[data.Race]
891 if raceval == nil then raceval = 13 end
892 table.insert(fields, raceval)
893
894 -- faction, default to alliance if we can't read it for some reason
895 raceval = Amr.FactionIds[data.Faction]
896 if raceval == nil then raceval = 1 end
897 table.insert(fields, raceval)
898
899 table.insert(fields, data.Level)
900
901 local profs = {}
902 local noprofs = true
903 if data.Professions then
904 for k, v in pairs(data.Professions) do
905 local profval = Amr.ProfessionIds[k]
906 if profval ~= nil then
907 noprofs = false
908 table.insert(profs, profval .. ":" .. v)
909 end
910 end
911 end
912
913 if noprofs then
914 table.insert(profs, "0:0")
915 end
916
917 table.insert(fields, table.concat(profs, ","))
918
919 -- export specs
920 table.insert(fields, data.ActiveSpec)
921 for spec = 1, 2 do
922 if data.Specs[spec] and (complete or spec == data.ActiveSpec) then
923 table.insert(fields, ".s" .. spec) -- indicates the start of a spec block
924
925 -- we use subspec for some ambiguous specs like prot/glad warrior
926 if data.SubSpecs[spec] then
927 table.insert(fields, string.format("s%s", data.SubSpecs[spec]))
928 else
929 table.insert(fields, data.Specs[spec])
930 end
931
932 table.insert(fields, data.Talents[spec])
933 table.insert(fields, toCompressedNumberList(data.Glyphs[spec]))
934 end
935 end
936
937 -- export equipped gear
938 if data.Equipped then
939 for spec = 1, 2 do
940 if data.Equipped[spec] and (complete or spec == data.ActiveSpec) then
941 table.insert(fields, ".q" .. spec) -- indicates the start of an equipped gear block
942
943 local itemObjects = {}
944 for k, v in pairs(data.Equipped[spec]) do
945 local itemData = Amr.ParseItemLink(v)
946 itemData.slot = k
947 table.insert(itemObjects, itemData)
948 end
949
950 appendItemsToExport(fields, itemObjects)
951 end
952 end
953 end
954
955 -- if doing a complete export, include reputations and bank/bag items too
956 if complete then
957
958 -- export reputations
959 local reps = {}
960 local noreps = true
961 if data.Reputations then
962 for k, v in pairs(data.Reputations) do
963 noreps = false
964 table.insert(reps, k .. ":" .. v)
965 end
966 end
967 if noreps then
968 table.insert(reps, "_")
969 end
970
971 table.insert(fields, ".r")
972 table.insert(fields, table.concat(reps, ","))
973
974 -- export bag and bank
975 local itemObjects = {}
976 if data.BagItems then
977 for i, v in ipairs(data.BagItems) do
978 local itemData = Amr.ParseItemLink(v)
979 if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
980 table.insert(itemObjects, itemData)
981 end
982 end
983 end
984 if data.BankItems then
985 for i, v in ipairs(data.BankItems) do
986 local itemData = Amr.ParseItemLink(v)
987 if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
988 table.insert(itemObjects, itemData)
989 end
990 end
991 end
992 if data.VoidItems then
993 for i, v in ipairs(data.VoidItems) do
994 local itemData = Amr.ParseItemLink(v)
995 if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
996 table.insert(itemObjects, itemData)
997 end
998 end
999 end
1000
1001 table.insert(fields, ".inv")
1002 appendItemsToExport(fields, itemObjects)
1003 end
1004
1005 return "$" .. table.concat(fields, ";") .. "$"
1006
1007 end
1008
1009 -- Shortcut for the common use case: serialize the player's currently active setup with no extras.
1010 function Amr:SerializePlayer()
1011 local data = self:GetPlayerData()
1012 return self:SerializePlayerData(data)
1013 end
1014
1015
1016 ----------------------------------------------------------------------------------------------------------------------
1017 -- Character Snapshots
1018 -- This feature snapshots a player's gear/talents/glyphs when entering combat. It is enabled by default. Consumers
1019 -- of this library can create a setting to enable/disable it as desired per a user setting.
1020 --
1021 -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that
1022 -- your addon settings stay in sync with any other addon that may also be trying to control the enabled state.
1023 --
1024 -- Note that if a user has the main AMR addon installed, it will always enable snapshotting, and override any attempt
1025 -- to disable it by immediately re-enabling it and thus re-triggering AMR_SNAPSHOT_STATE_CHANGED.
1026 ----------------------------------------------------------------------------------------------------------------------
1027 Amr._snapshotEnabled = true
1028
1029 -- Enable snapshotting of character data when entering combat. Sends this player's character data to anyone logging with the AskMrRobot addon.
1030 function Amr:EnableSnapshots()
1031 self._snapshotEnabled = true
1032 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
1033 end
1034
1035 -- Disable snapshotting of character data when entering combat.
1036 function Amr:DisableSnapshots()
1037 self._snapshotEnabled = false
1038 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
1039 end
1040
1041 function Amr:IsSnapshotEnabled()
1042 return self._snapshotEnabled
1043 end
1044
1045
1046 function Amr:PLAYER_REGEN_DISABLED()
1047 --function Amr:GARRISON_MISSION_NPC_OPENED()
1048
1049 -- send data about this character when a player enters combat in a supported zone
1050 if self._snapshotEnabled and Amr.IsSupportedInstance() then
1051 local t = time()
1052 local player = self:GetPlayerData()
1053 local msg = self:SerializePlayerData(player)
1054 msg = string.format("%s\r%s\n%s\n%s\n%s\n%s", MINOR, t, player.Region, player.Realm, player.Name, msg)
1055
1056 self:SendCommMessage(Amr.ChatPrefix, msg, "RAID")
1057 end
1058 end
1059
1060 Amr:RegisterEvent("PLAYER_REGEN_DISABLED")
1061 --Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table