annotate AskMrRobot-Serializer/AskMrRobot-Serializer.lua @ 127:65c285394049 v59

Fixed an issue with reading bank data.
author yellowfive
date Tue, 17 Jul 2018 16:32:12 -0700
parents e31b02b24488
children d9a059484b22
rev   line source
yellowfive@57 1 -- AskMrRobot-Serializer will serialize and communicate character data between users.
yellowfive@57 2
yellowfive@127 3 local MAJOR, MINOR = "AskMrRobot-Serializer", 59
yellowfive@57 4 local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
yellowfive@57 5
yellowfive@57 6 if not Amr then return end -- already loaded by something else
yellowfive@57 7
yellowfive@57 8 -- event and comm used for player snapshotting on entering combat
yellowfive@57 9 LibStub("AceEvent-3.0"):Embed(Amr)
yellowfive@57 10 LibStub("AceComm-3.0"):Embed(Amr)
yellowfive@57 11
yellowfive@57 12 ----------------------------------------------------------------------------------------
yellowfive@57 13 -- Constants
yellowfive@57 14 ----------------------------------------------------------------------------------------
yellowfive@57 15
yellowfive@57 16 -- prefix used for communicating gear snapshots created by the AMR serializer
yellowfive@57 17 Amr.ChatPrefix = "_AMRS"
yellowfive@57 18
yellowfive@57 19 -- map of region ids to AMR region names
yellowfive@57 20 Amr.RegionNames = {
yellowfive@57 21 [1] = "US",
yellowfive@57 22 [2] = "KR",
yellowfive@57 23 [3] = "EU",
yellowfive@57 24 [4] = "TW",
yellowfive@57 25 [5] = "CN"
yellowfive@57 26 }
yellowfive@57 27
yellowfive@57 28 -- map of the skillLine returned by profession API to the AMR profession name
yellowfive@57 29 Amr.ProfessionSkillLineToName = {
yellowfive@57 30 [794] = "Archaeology",
yellowfive@57 31 [171] = "Alchemy",
yellowfive@57 32 [164] = "Blacksmithing",
yellowfive@57 33 [185] = "Cooking",
yellowfive@57 34 [333] = "Enchanting",
yellowfive@57 35 [202] = "Engineering",
yellowfive@57 36 [129] = "First Aid",
yellowfive@57 37 [356] = "Fishing",
yellowfive@57 38 [182] = "Herbalism",
yellowfive@57 39 [773] = "Inscription",
yellowfive@57 40 [755] = "Jewelcrafting",
yellowfive@57 41 [165] = "Leatherworking",
yellowfive@57 42 [186] = "Mining",
yellowfive@57 43 [393] = "Skinning",
yellowfive@57 44 [197] = "Tailoring"
yellowfive@57 45 }
yellowfive@57 46
yellowfive@57 47 -- all slot IDs that we care about, ordered in AMR standard display order
yellowfive@57 48 Amr.SlotIds = { 16, 17, 1, 2, 3, 15, 5, 9, 10, 6, 7, 8, 11, 12, 13, 14 }
yellowfive@57 49
yellowfive@57 50 Amr.SpecIds = {
yellowfive@57 51 [250] = 1, -- DeathKnightBlood
yellowfive@57 52 [251] = 2, -- DeathKnightFrost
yellowfive@57 53 [252] = 3, -- DeathKnightUnholy
yellowfive@81 54 [577] = 4, -- DemonHunterHavoc
yellowfive@81 55 [581] = 5, -- DemonHunterVengeance
yellowfive@81 56 [102] = 6, -- DruidBalance
yellowfive@81 57 [103] = 7, -- DruidFeral
yellowfive@81 58 [104] = 8, -- DruidGuardian
yellowfive@81 59 [105] = 9, -- DruidRestoration
yellowfive@81 60 [253] = 10, -- HunterBeastMastery
yellowfive@81 61 [254] = 11, -- HunterMarksmanship
yellowfive@81 62 [255] = 12, -- HunterSurvival
yellowfive@81 63 [62] = 13, -- MageArcane
yellowfive@81 64 [63] = 14, -- MageFire
yellowfive@81 65 [64] = 15, -- MageFrost
yellowfive@81 66 [268] = 16, -- MonkBrewmaster
yellowfive@81 67 [270] = 17, -- MonkMistweaver
yellowfive@81 68 [269] = 18, -- MonkWindwalker
yellowfive@81 69 [65] = 19, -- PaladinHoly
yellowfive@81 70 [66] = 20, -- PaladinProtection
yellowfive@81 71 [70] = 21, -- PaladinRetribution
yellowfive@81 72 [256] = 22, -- PriestDiscipline
yellowfive@81 73 [257] = 23, -- PriestHoly
yellowfive@81 74 [258] = 24, -- PriestShadow
yellowfive@81 75 [259] = 25, -- RogueAssassination
yellowfive@81 76 [260] = 26, -- RogueOutlaw
yellowfive@81 77 [261] = 27, -- RogueSubtlety
yellowfive@81 78 [262] = 28, -- ShamanElemental
yellowfive@81 79 [263] = 29, -- ShamanEnhancement
yellowfive@81 80 [264] = 30, -- ShamanRestoration
yellowfive@81 81 [265] = 31, -- WarlockAffliction
yellowfive@81 82 [266] = 32, -- WarlockDemonology
yellowfive@81 83 [267] = 33, -- WarlockDestruction
yellowfive@81 84 [71] = 34, -- WarriorArms
yellowfive@81 85 [72] = 35, -- WarriorFury
yellowfive@81 86 [73] = 36 -- WarriorProtection
yellowfive@57 87 }
yellowfive@57 88
yellowfive@57 89 Amr.ClassIds = {
yellowfive@57 90 ["NONE"] = 0,
yellowfive@57 91 ["DEATHKNIGHT"] = 1,
yellowfive@81 92 ["DEMONHUNTER"] = 2,
yellowfive@81 93 ["DRUID"] = 3,
yellowfive@81 94 ["HUNTER"] = 4,
yellowfive@81 95 ["MAGE"] = 5,
yellowfive@81 96 ["MONK"] = 6,
yellowfive@81 97 ["PALADIN"] = 7,
yellowfive@81 98 ["PRIEST"] = 8,
yellowfive@81 99 ["ROGUE"] = 9,
yellowfive@81 100 ["SHAMAN"] = 10,
yellowfive@81 101 ["WARLOCK"] = 11,
yellowfive@81 102 ["WARRIOR"] = 12,
yellowfive@57 103 }
yellowfive@57 104
yellowfive@57 105 Amr.ProfessionIds = {
yellowfive@57 106 ["None"] = 0,
yellowfive@57 107 ["Mining"] = 1,
yellowfive@57 108 ["Skinning"] = 2,
yellowfive@57 109 ["Herbalism"] = 3,
yellowfive@57 110 ["Enchanting"] = 4,
yellowfive@57 111 ["Jewelcrafting"] = 5,
yellowfive@57 112 ["Engineering"] = 6,
yellowfive@57 113 ["Blacksmithing"] = 7,
yellowfive@57 114 ["Leatherworking"] = 8,
yellowfive@57 115 ["Inscription"] = 9,
yellowfive@57 116 ["Tailoring"] = 10,
yellowfive@57 117 ["Alchemy"] = 11,
yellowfive@57 118 ["Fishing"] = 12,
yellowfive@57 119 ["Cooking"] = 13,
yellowfive@57 120 ["First Aid"] = 14,
yellowfive@57 121 ["Archaeology"] = 15
yellowfive@57 122 }
yellowfive@57 123
yellowfive@57 124 Amr.RaceIds = {
yellowfive@57 125 ["None"] = 0,
yellowfive@57 126 ["BloodElf"] = 1,
yellowfive@57 127 ["Draenei"] = 2,
yellowfive@57 128 ["Dwarf"] = 3,
yellowfive@57 129 ["Gnome"] = 4,
yellowfive@57 130 ["Human"] = 5,
yellowfive@57 131 ["NightElf"] = 6,
yellowfive@57 132 ["Orc"] = 7,
yellowfive@57 133 ["Tauren"] = 8,
yellowfive@57 134 ["Troll"] = 9,
yellowfive@57 135 ["Scourge"] = 10,
yellowfive@57 136 ["Undead"] = 10,
yellowfive@57 137 ["Goblin"] = 11,
yellowfive@57 138 ["Worgen"] = 12,
yellowfive@120 139 ["Pandaren"] = 13,
yellowfive@120 140 ["Nightborne"] = 14,
yellowfive@120 141 ["HighmountainTauren"] = 15,
yellowfive@120 142 ["VoidElf"] = 16,
yellowfive@120 143 ["LightforgedDraenei"] = 17
yellowfive@57 144 }
yellowfive@57 145
yellowfive@57 146 Amr.FactionIds = {
yellowfive@57 147 ["None"] = 0,
yellowfive@57 148 ["Alliance"] = 1,
yellowfive@57 149 ["Horde"] = 2
yellowfive@57 150 }
yellowfive@57 151
yellowfive@57 152 Amr.InstanceIds = {
yellowfive@93 153 EmeraldNightmare = 1520,
yellowfive@104 154 Nighthold = 1530,
yellowfive@110 155 TrialOfValor = 1648,
yellowfive@118 156 TombOfSargeras = 1676,
yellowfive@118 157 Antorus = 1712
yellowfive@57 158 }
yellowfive@57 159
yellowfive@57 160 -- instances that AskMrRobot currently supports logging for
yellowfive@57 161 Amr.SupportedInstanceIds = {
yellowfive@93 162 [1520] = true,
yellowfive@104 163 [1530] = true,
yellowfive@110 164 [1648] = true,
yellowfive@118 165 [1676] = true,
yellowfive@118 166 [1712] = true
yellowfive@57 167 }
yellowfive@57 168
yellowfive@57 169
yellowfive@57 170 ----------------------------------------------------------------------------------------
yellowfive@57 171 -- Public Utility Methods
yellowfive@57 172 ----------------------------------------------------------------------------------------
yellowfive@57 173
yellowfive@81 174 local function readBonusIdList(parts, first, last)
yellowfive@124 175 local ret = {}
yellowfive@81 176 for i = first, last do
yellowfive@81 177 table.insert(ret, tonumber(parts[i]))
yellowfive@81 178 end
yellowfive@81 179 table.sort(ret)
yellowfive@81 180 return ret
yellowfive@81 181 end
yellowfive@81 182
yellowfive@124 183 -- 1 2 3 4 5 6 7 8 9 10 11 12
yellowfive@124 184 -- itemId:ench:gem1 :gem2 :gem3 :gem4:suf:uid:lvl:spec:flags :instdiffid:numbonusIDs:bonusIDs1...n :varies:?:relic bonus ids
yellowfive@124 185 --|cffe6cc80|Hitem:128866: :152046:147100:152025: : : :110:66 :16777472:9 :4 :736:1494:1490:1495:709 :1:3:3610:1472:3528:3:3562:1483:3528:3:3610:1477:3336|h[Truthguard]|h|r
yellowfive@124 186 --
yellowfive@57 187 -- get an object with all of the parts of the item link format that we care about
yellowfive@57 188 function Amr.ParseItemLink(itemLink)
yellowfive@57 189 if not itemLink then return nil end
yellowfive@57 190
yellowfive@57 191 local str = string.match(itemLink, "|Hitem:([\-%d:]+)|")
yellowfive@57 192 if not str then return nil end
yellowfive@57 193
yellowfive@57 194 local parts = { strsplit(":", str) }
yellowfive@57 195
yellowfive@124 196 local item = {}
yellowfive@124 197 item.link = itemLink
yellowfive@81 198 item.id = tonumber(parts[1]) or 0
yellowfive@81 199 item.enchantId = tonumber(parts[2]) or 0
yellowfive@81 200 item.gemIds = { tonumber(parts[3]) or 0, tonumber(parts[4]) or 0, tonumber(parts[5]) or 0, tonumber(parts[6]) or 0 }
yellowfive@81 201 item.suffixId = math.abs(tonumber(parts[7]) or 0) -- convert suffix to positive number, that's what we use in our code
yellowfive@81 202 -- part 8 is some unique ID... we never really used it
yellowfive@81 203 -- part 9 is current player level
yellowfive@81 204 -- part 10 is player spec
yellowfive@81 205 local upgradeIdType = tonumber(parts[11]) or 0 -- part 11 indicates what kind of upgrade ID is just after the bonus IDs
yellowfive@81 206 -- part 12 is instance difficulty id
yellowfive@57 207
yellowfive@81 208 local numBonuses = tonumber(parts[13]) or 0
yellowfive@81 209 local offset = numBonuses
yellowfive@81 210 if numBonuses > 0 then
yellowfive@81 211 item.bonusIds = readBonusIdList(parts, 14, 13 + numBonuses)
yellowfive@57 212 end
yellowfive@69 213
yellowfive@81 214 item.upgradeId = 0
yellowfive@81 215 item.level = 0
yellowfive@81 216
yellowfive@124 217 -- the next part after bonus IDs depends on the upgrade id type
yellowfive@81 218 if upgradeIdType == 4 then
yellowfive@81 219 item.upgradeId = tonumber(parts[14 + offset]) or 0
yellowfive@81 220 elseif upgradeIdType == 512 then
yellowfive@81 221 item.level = tonumber(parts[14 + offset]) or 0
yellowfive@124 222 elseif #parts > 16 + offset then
yellowfive@124 223 -- check for relic info
yellowfive@124 224 item.relicBonusIds = { nil, nil, nil }
yellowfive@124 225 numBonuses = tonumber(parts[16 + offset])
yellowfive@124 226 if numBonuses then
yellowfive@124 227 if numBonuses > 0 then
yellowfive@124 228 item.relicBonusIds[1] = readBonusIdList(parts, 17 + offset, 16 + offset + numBonuses)
yellowfive@124 229 end
yellowfive@124 230
yellowfive@124 231 offset= offset + numBonuses
yellowfive@124 232 if #parts > 17 + offset then
yellowfive@124 233 numBonuses = tonumber(parts[17 + offset])
yellowfive@124 234 if numBonuses > 0 then
yellowfive@124 235 item.relicBonusIds[2] = readBonusIdList(parts, 18 + offset, 17 + offset + numBonuses)
yellowfive@124 236 end
yellowfive@124 237
yellowfive@124 238 offset= offset + numBonuses
yellowfive@124 239 if #parts > 18 + offset then
yellowfive@124 240 numBonuses = tonumber(parts[18 + offset])
yellowfive@124 241 if numBonuses > 0 then
yellowfive@124 242 item.relicBonusIds[3] = readBonusIdList(parts, 19 + offset, 18 + offset + numBonuses)
yellowfive@124 243 end
yellowfive@124 244 end
yellowfive@124 245 end
yellowfive@124 246 end
yellowfive@69 247 end
yellowfive@81 248
yellowfive@57 249 return item
yellowfive@57 250 end
yellowfive@57 251
yellowfive@81 252 function Amr.GetItemUniqueId(item, noUpgrade)
yellowfive@81 253 if not item then return "" end
yellowfive@81 254 local ret = item.id .. ""
yellowfive@81 255 if item.bonusIds then
yellowfive@81 256 for i = 1, #item.bonusIds do
yellowfive@81 257 ret = ret .. "b" .. item.bonusIds[i]
yellowfive@81 258 end
yellowfive@81 259 end
yellowfive@81 260 if item.suffixId ~= 0 then
yellowfive@81 261 ret = ret .. "s" .. item.suffixId
yellowfive@81 262 end
yellowfive@81 263 if not noUpgrade and item.upgradeId ~= 0 then
yellowfive@81 264 ret = ret .. "u" .. item.upgradeId
yellowfive@81 265 end
yellowfive@81 266 if item.level ~= 0 then
yellowfive@81 267 ret = ret .. "v" .. item.level
yellowfive@81 268 end
yellowfive@81 269 return ret
yellowfive@81 270 end
yellowfive@81 271
yellowfive@57 272 -- returns true if this is an instance that AskMrRobot supports for logging
yellowfive@57 273 function Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 274 if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then
yellowfive@57 275 return true
yellowfive@57 276 else
yellowfive@57 277 return false
yellowfive@57 278 end
yellowfive@57 279 end
yellowfive@57 280
yellowfive@57 281 -- returns true if currently in a supported instance for logging
yellowfive@57 282 function Amr.IsSupportedInstance()
yellowfive@57 283 local zone, _, difficultyIndex, _, _, _, _, instanceMapID = GetInstanceInfo()
yellowfive@57 284 return Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 285 end
yellowfive@57 286
yellowfive@81 287 -- helper to iterate over a table in order by its keys
yellowfive@81 288 local function spairs(t, order)
yellowfive@81 289 -- collect the keys
yellowfive@81 290 local keys = {}
yellowfive@81 291 for k in pairs(t) do keys[#keys+1] = k end
yellowfive@81 292
yellowfive@81 293 -- if order function given, sort by it by passing the table and keys a, b,
yellowfive@81 294 -- otherwise just sort the keys
yellowfive@81 295 if order then
yellowfive@81 296 table.sort(keys, function(a,b) return order(t, a, b) end)
yellowfive@81 297 else
yellowfive@81 298 table.sort(keys)
yellowfive@81 299 end
yellowfive@81 300
yellowfive@81 301 -- return the iterator function
yellowfive@81 302 local i = 0
yellowfive@81 303 return function()
yellowfive@81 304 i = i + 1
yellowfive@81 305 if keys[i] then
yellowfive@81 306 return keys[i], t[keys[i]]
yellowfive@81 307 end
yellowfive@81 308 end
yellowfive@81 309 end
yellowfive@81 310
yellowfive@81 311 -- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
yellowfive@81 312 -- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
yellowfive@81 313 local _scanTt
yellowfive@81 314 function Amr.GetScanningTooltip()
yellowfive@81 315 if not _scanTt then
yellowfive@81 316 _scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
yellowfive@81 317 _scanTt:SetOwner(UIParent, "ANCHOR_NONE")
yellowfive@81 318 end
yellowfive@81 319 return _scanTt
yellowfive@81 320 end
yellowfive@81 321
yellowfive@81 322 -- get the item tooltip for the specified item in one of your bags, or if bagId is nil, an equipped item, or if slotId is also nil, the specified item link
yellowfive@81 323 function Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 324 local tt = Amr.GetScanningTooltip()
yellowfive@81 325 tt:ClearLines()
yellowfive@81 326 if bagId then
yellowfive@81 327 tt:SetBagItem(bagId, slotId)
yellowfive@81 328 elseif slotId then
yellowfive@81 329 tt:SetInventoryItem("player", slotId)
yellowfive@81 330 else
yellowfive@81 331 tt:SetHyperlink(link)
yellowfive@81 332 end
yellowfive@81 333 return tt
yellowfive@81 334 end
yellowfive@81 335
yellowfive@124 336 function Amr.GetItemLevel(bagId, slotId, link)
yellowfive@81 337 local itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)")
yellowfive@81 338 local tt = Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 339
yellowfive@81 340 local regions = { tt:GetRegions() }
yellowfive@81 341 for i, region in ipairs(regions) do
yellowfive@81 342 if region and region:GetObjectType() == "FontString" then
yellowfive@81 343 local text = region:GetText()
yellowfive@81 344 if text then
yellowfive@81 345 ilvl = tonumber(text:match(itemLevelPattern))
yellowfive@81 346 if ilvl then
yellowfive@81 347 return ilvl
yellowfive@81 348 end
yellowfive@81 349 end
yellowfive@81 350 end
yellowfive@81 351 end
yellowfive@81 352
yellowfive@81 353 -- 0 means we couldn't find it for whatever reason
yellowfive@81 354 return 0
yellowfive@81 355 end
yellowfive@81 356
yellowfive@57 357
yellowfive@57 358 ----------------------------------------------------------------------------------------
yellowfive@57 359 -- Character Reading
yellowfive@57 360 ----------------------------------------------------------------------------------------
yellowfive@57 361
yellowfive@57 362 local function readProfessionInfo(prof, ret)
yellowfive@57 363 if prof then
yellowfive@57 364 local name, icon, skillLevel, maxSkillLevel, numAbilities, spelloffset, skillLine, skillModifier = GetProfessionInfo(prof);
yellowfive@57 365 if Amr.ProfessionSkillLineToName[skillLine] ~= nil then
yellowfive@57 366 ret.Professions[Amr.ProfessionSkillLineToName[skillLine]] = skillLevel;
yellowfive@57 367 end
yellowfive@57 368 end
yellowfive@57 369 end
yellowfive@57 370
yellowfive@124 371 -- get specs
yellowfive@81 372 local function readSpecs(ret)
yellowfive@57 373
yellowfive@81 374 for pos = 1, 4 do
yellowfive@57 375 -- spec, convert game spec id to one of our spec ids
yellowfive@81 376 local specId = GetSpecializationInfo(pos)
yellowfive@57 377 if specId then
yellowfive@81 378 ret.Specs[pos] = Amr.SpecIds[specId]
yellowfive@57 379 end
yellowfive@57 380 end
yellowfive@57 381 end
yellowfive@57 382
yellowfive@124 383 local function dump(o)
yellowfive@124 384 if type(o) == 'table' then
yellowfive@124 385 local s = '{ '
yellowfive@124 386 for k,v in pairs(o) do
yellowfive@124 387 if type(k) ~= 'number' then k = '"'..k..'"' end
yellowfive@124 388 s = s .. '['..k..'] = ' .. dump(v) .. ','
yellowfive@124 389 end
yellowfive@124 390 return s .. '} '
yellowfive@124 391 else
yellowfive@124 392 return tostring(o)
yellowfive@124 393 end
yellowfive@124 394 end
yellowfive@124 395
yellowfive@124 396 -- read azerite powers on the item in loc and put it on itemData
yellowfive@124 397 function Amr.ReadAzeritePowers(loc)
yellowfive@124 398 local ret = {}
yellowfive@124 399 local hasSome = false
yellowfive@124 400
yellowfive@124 401 local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(loc)
yellowfive@124 402 for tier, tierInfo in ipairs(tiers) do
yellowfive@124 403 for _, power in ipairs(tierInfo.azeritePowerIDs) do
yellowfive@124 404 if C_AzeriteEmpoweredItem.IsPowerSelected(loc, power) then
yellowfive@124 405 local powerInfo = C_AzeriteEmpoweredItem.GetPowerInfo(power)
yellowfive@124 406 table.insert(ret, powerInfo.spellID)
yellowfive@124 407 hasSome = true
yellowfive@124 408 end
yellowfive@124 409 end
yellowfive@124 410 end
yellowfive@124 411
yellowfive@124 412 if hasSome then
yellowfive@124 413 return ret
yellowfive@124 414 else
yellowfive@124 415 return nil
yellowfive@124 416 end
yellowfive@124 417 end
yellowfive@124 418
yellowfive@57 419 -- get currently equipped items, store with currently active spec
yellowfive@57 420 local function readEquippedItems(ret)
yellowfive@124 421 local equippedItems = {};
yellowfive@124 422 local loc = ItemLocation.CreateEmpty()
yellowfive@57 423 for slotNum = 1, #Amr.SlotIds do
yellowfive@57 424 local slotId = Amr.SlotIds[slotNum]
yellowfive@57 425 local itemLink = GetInventoryItemLink("player", slotId)
yellowfive@57 426 if itemLink then
yellowfive@124 427 local itemData = Amr.ParseItemLink(itemLink)
yellowfive@124 428 if itemData then
yellowfive@124 429 -- see if this is an azerite item and read azerite power ids
yellowfive@124 430 loc:SetEquipmentSlot(slotId)
yellowfive@124 431 if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then
yellowfive@124 432 local powers = Amr.ReadAzeritePowers(loc)
yellowfive@124 433 if powers then
yellowfive@124 434 itemData.azerite = powers
yellowfive@124 435 end
yellowfive@124 436 end
yellowfive@124 437
yellowfive@124 438 equippedItems[slotId] = itemData
yellowfive@124 439 end
yellowfive@57 440 end
yellowfive@57 441 end
yellowfive@57 442
yellowfive@57 443 -- store last-seen equipped gear for each spec
yellowfive@81 444 ret.Equipped[GetSpecialization()] = equippedItems
yellowfive@57 445 end
yellowfive@57 446
yellowfive@124 447 local function readHeartOfAzerothLevel(ret)
yellowfive@124 448 local azeriteItemLocation = C_AzeriteItem.FindActiveAzeriteItem();
yellowfive@124 449 if azeriteItemLocation then
yellowfive@124 450 local azeriteItem = Item:CreateFromItemLocation(azeriteItemLocation);
yellowfive@124 451 ret.HeartOfAzerothLevel = C_AzeriteItem.GetPowerLevel(azeriteItemLocation)
yellowfive@124 452 else
yellowfive@124 453 ret.HeartOfAzerothLevel = 0
yellowfive@124 454 end
yellowfive@124 455 end
yellowfive@124 456
yellowfive@124 457 -- Get just the player's currently equipped gear
yellowfive@124 458 function Amr:GetEquipped()
yellowfive@124 459 local ret= {}
yellowfive@124 460 ret.Equipped = {}
yellowfive@124 461 readEquippedItems(ret)
yellowfive@124 462 return ret
yellowfive@124 463 end
yellowfive@124 464
yellowfive@57 465 -- Get all data about the player as an object, includes:
yellowfive@57 466 -- serializer version
yellowfive@57 467 -- region/realm/name
yellowfive@57 468 -- guild
yellowfive@57 469 -- race
yellowfive@57 470 -- faction
yellowfive@57 471 -- level
yellowfive@57 472 -- professions
yellowfive@81 473 -- spec/talent for all specs
yellowfive@57 474 -- equipped gear for the current spec
yellowfive@57 475 --
yellowfive@81 476 function Amr:GetPlayerData()
yellowfive@57 477
yellowfive@57 478 local ret = {}
yellowfive@57 479
yellowfive@57 480 ret.Region = Amr.RegionNames[GetCurrentRegion()]
yellowfive@57 481 ret.Realm = GetRealmName()
yellowfive@57 482 ret.Name = UnitName("player")
yellowfive@57 483 ret.Guild = GetGuildInfo("player")
yellowfive@81 484 ret.ActiveSpec = GetSpecialization()
yellowfive@57 485 ret.Level = UnitLevel("player");
yellowfive@124 486 readHeartOfAzerothLevel(ret)
yellowfive@124 487
yellowfive@57 488 local cls, clsEn = UnitClass("player")
yellowfive@57 489 ret.Class = clsEn;
yellowfive@57 490
yellowfive@57 491 local race, raceEn = UnitRace("player")
yellowfive@57 492 ret.Race = raceEn;
yellowfive@57 493 ret.Faction = UnitFactionGroup("player")
yellowfive@57 494
yellowfive@57 495 ret.Professions = {};
yellowfive@57 496 local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions();
yellowfive@57 497 readProfessionInfo(prof1, ret)
yellowfive@57 498 readProfessionInfo(prof2, ret)
yellowfive@57 499 readProfessionInfo(archaeology, ret)
yellowfive@57 500 readProfessionInfo(fishing, ret)
yellowfive@57 501 readProfessionInfo(cooking, ret)
yellowfive@57 502 readProfessionInfo(firstAid, ret)
yellowfive@57 503
yellowfive@57 504 ret.Specs = {}
yellowfive@57 505 ret.Talents = {}
yellowfive@81 506 readSpecs(ret)
yellowfive@81 507
yellowfive@124 508 ret.Equipped = {}
yellowfive@57 509 readEquippedItems(ret)
yellowfive@57 510
yellowfive@57 511 return ret
yellowfive@57 512 end
yellowfive@57 513
yellowfive@57 514
yellowfive@57 515 ----------------------------------------------------------------------------------------
yellowfive@57 516 -- Serialization
yellowfive@57 517 ----------------------------------------------------------------------------------------
yellowfive@57 518
yellowfive@57 519 local function toCompressedNumberList(list)
yellowfive@57 520 -- ensure the values are numbers, sorted from lowest to highest
yellowfive@57 521 local nums = {}
yellowfive@57 522 for i, v in ipairs(list) do
yellowfive@57 523 table.insert(nums, tonumber(v))
yellowfive@57 524 end
yellowfive@57 525 table.sort(nums)
yellowfive@57 526
yellowfive@57 527 local ret = {}
yellowfive@57 528 local prev = 0
yellowfive@57 529 for i, v in ipairs(nums) do
yellowfive@57 530 local diff = v - prev
yellowfive@57 531 table.insert(ret, diff)
yellowfive@57 532 prev = v
yellowfive@57 533 end
yellowfive@57 534
yellowfive@57 535 return table.concat(ret, ",")
yellowfive@57 536 end
yellowfive@57 537
yellowfive@57 538 -- make this utility publicly available
yellowfive@57 539 function Amr:ToCompressedNumberList(list)
yellowfive@57 540 return toCompressedNumberList(list)
yellowfive@57 541 end
yellowfive@57 542
yellowfive@57 543 -- appends a list of items to the export
yellowfive@57 544 local function appendItemsToExport(fields, itemObjects)
yellowfive@57 545
yellowfive@57 546 -- sort by item id so we can compress it more easily
yellowfive@57 547 table.sort(itemObjects, function(a, b) return a.id < b.id end)
yellowfive@57 548
yellowfive@57 549 -- append to the export string
yellowfive@57 550 local prevItemId = 0
yellowfive@57 551 local prevGemId = 0
yellowfive@57 552 local prevEnchantId = 0
yellowfive@57 553 local prevUpgradeId = 0
yellowfive@57 554 local prevBonusId = 0
yellowfive@81 555 local prevLevel = 0
yellowfive@124 556 local prevAzeriteId = 0
yellowfive@124 557 local prevRelicBonusId = 0
yellowfive@57 558 for i, itemData in ipairs(itemObjects) do
yellowfive@57 559 local itemParts = {}
yellowfive@57 560
yellowfive@57 561 table.insert(itemParts, itemData.id - prevItemId)
yellowfive@57 562 prevItemId = itemData.id
yellowfive@57 563
yellowfive@57 564 if itemData.slot ~= nil then table.insert(itemParts, "s" .. itemData.slot) end
yellowfive@124 565 --if itemData.suffixId ~= 0 then table.insert(itemParts, "f" .. itemData.suffixId) end
yellowfive@57 566 if itemData.upgradeId ~= 0 then
yellowfive@57 567 table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId))
yellowfive@57 568 prevUpgradeId = itemData.upgradeId
yellowfive@57 569 end
yellowfive@81 570 if itemData.level ~= 0 then
yellowfive@81 571 table.insert(itemParts, "v" .. (itemData.level - prevLevel))
yellowfive@81 572 prevLevel = itemData.level
yellowfive@81 573 end
yellowfive@57 574 if itemData.bonusIds then
yellowfive@57 575 for bIndex, bValue in ipairs(itemData.bonusIds) do
yellowfive@57 576 table.insert(itemParts, "b" .. (bValue - prevBonusId))
yellowfive@57 577 prevBonusId = bValue
yellowfive@57 578 end
yellowfive@124 579 end
yellowfive@124 580
yellowfive@124 581 if itemData.azerite then
yellowfive@124 582 for aIndex, aValue in ipairs(itemData.azerite) do
yellowfive@124 583 table.insert(itemParts, "a" .. (aValue - prevAzeriteId))
yellowfive@124 584 prevAzeriteId = aValue
yellowfive@124 585 end
yellowfive@124 586 end
yellowfive@81 587
yellowfive@81 588 if itemData.gemIds[1] ~= 0 then
yellowfive@81 589 table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
yellowfive@81 590 prevGemId = itemData.gemIds[1]
yellowfive@81 591 end
yellowfive@81 592 if itemData.gemIds[2] ~= 0 then
yellowfive@81 593 table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
yellowfive@81 594 prevGemId = itemData.gemIds[2]
yellowfive@81 595 end
yellowfive@81 596 if itemData.gemIds[3] ~= 0 then
yellowfive@81 597 table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
yellowfive@81 598 prevGemId = itemData.gemIds[3]
yellowfive@124 599 end
yellowfive@81 600
yellowfive@57 601 if itemData.enchantId ~= 0 then
yellowfive@57 602 table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId))
yellowfive@57 603 prevEnchantId = itemData.enchantId
yellowfive@57 604 end
yellowfive@124 605
yellowfive@124 606 if itemData.relicBonusIds and itemData.relicBonusIds[1] ~= nil then
yellowfive@124 607 for bIndex, bValue in ipairs(itemData.relicBonusIds[1]) do
yellowfive@124 608 table.insert(itemParts, "p" .. (bValue - prevRelicBonusId))
yellowfive@124 609 prevRelicBonusId = bValue
yellowfive@124 610 end
yellowfive@124 611 end
yellowfive@124 612
yellowfive@124 613 if itemData.relicBonusIds and itemData.relicBonusIds[2] ~= nil then
yellowfive@124 614 for bIndex, bValue in ipairs(itemData.relicBonusIds[2]) do
yellowfive@124 615 table.insert(itemParts, "q" .. (bValue - prevRelicBonusId))
yellowfive@124 616 prevRelicBonusId = bValue
yellowfive@124 617 end
yellowfive@124 618 end
yellowfive@124 619
yellowfive@124 620 if itemData.relicBonusIds and itemData.relicBonusIds[3] ~= nil then
yellowfive@124 621 for bIndex, bValue in ipairs(itemData.relicBonusIds[3]) do
yellowfive@124 622 table.insert(itemParts, "r" .. (bValue - prevRelicBonusId))
yellowfive@124 623 prevRelicBonusId = bValue
yellowfive@124 624 end
yellowfive@124 625 end
yellowfive@124 626
yellowfive@57 627 table.insert(fields, table.concat(itemParts, ""))
yellowfive@57 628 end
yellowfive@57 629 end
yellowfive@57 630
yellowfive@57 631 -- Serialize just the identity portion of a player (region/realm/name) in the same format used by the full serialization
yellowfive@57 632 function Amr:SerializePlayerIdentity(data)
yellowfive@57 633 local fields = {}
yellowfive@57 634 table.insert(fields, MINOR)
yellowfive@57 635 table.insert(fields, data.Region)
yellowfive@57 636 table.insert(fields, data.Realm)
yellowfive@57 637 table.insert(fields, data.Name)
yellowfive@57 638 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 639 end
yellowfive@57 640
yellowfive@57 641 -- Serialize player data gathered by GetPlayerData. This can be augmented with extra data if desired (augmenting used mainly by AskMrRobot addon).
yellowfive@57 642 -- Pass complete = true to do a complete export of this extra information, otherwise it is ignored.
yellowfive@57 643 -- Extra data can include:
yellowfive@57 644 -- equipped gear for the player's inactive spec, slot id to item link dictionary
yellowfive@57 645 -- Reputations
yellowfive@57 646 -- BagItems, BankItems, VoidItems, lists of item links
yellowfive@57 647 --
yellowfive@57 648 function Amr:SerializePlayerData(data, complete)
yellowfive@57 649
yellowfive@57 650 local fields = {}
yellowfive@57 651
yellowfive@57 652 -- compressed string uses a fixed order rather than inserting identifiers
yellowfive@57 653 table.insert(fields, MINOR)
yellowfive@57 654 table.insert(fields, data.Region)
yellowfive@57 655 table.insert(fields, data.Realm)
yellowfive@57 656 table.insert(fields, data.Name)
yellowfive@57 657
yellowfive@57 658 -- guild name
yellowfive@57 659 if data.Guild == nil then
yellowfive@57 660 table.insert(fields, "")
yellowfive@57 661 else
yellowfive@57 662 table.insert(fields, data.Guild)
yellowfive@57 663 end
yellowfive@57 664
yellowfive@57 665 -- race, default to pandaren if we can't read it for some reason
yellowfive@57 666 local raceval = Amr.RaceIds[data.Race]
yellowfive@57 667 if raceval == nil then raceval = 13 end
yellowfive@57 668 table.insert(fields, raceval)
yellowfive@57 669
yellowfive@57 670 -- faction, default to alliance if we can't read it for some reason
yellowfive@57 671 raceval = Amr.FactionIds[data.Faction]
yellowfive@57 672 if raceval == nil then raceval = 1 end
yellowfive@57 673 table.insert(fields, raceval)
yellowfive@57 674
yellowfive@124 675 table.insert(fields, data.Level)
yellowfive@124 676 table.insert(fields, data.HeartOfAzerothLevel)
yellowfive@57 677
yellowfive@57 678 local profs = {}
yellowfive@57 679 local noprofs = true
yellowfive@57 680 if data.Professions then
yellowfive@57 681 for k, v in pairs(data.Professions) do
yellowfive@57 682 local profval = Amr.ProfessionIds[k]
yellowfive@57 683 if profval ~= nil then
yellowfive@57 684 noprofs = false
yellowfive@57 685 table.insert(profs, profval .. ":" .. v)
yellowfive@57 686 end
yellowfive@57 687 end
yellowfive@57 688 end
yellowfive@57 689
yellowfive@57 690 if noprofs then
yellowfive@57 691 table.insert(profs, "0:0")
yellowfive@57 692 end
yellowfive@57 693
yellowfive@57 694 table.insert(fields, table.concat(profs, ","))
yellowfive@57 695
yellowfive@57 696 -- export specs
yellowfive@57 697 table.insert(fields, data.ActiveSpec)
yellowfive@81 698 for spec = 1, 4 do
yellowfive@57 699 if data.Specs[spec] and (complete or spec == data.ActiveSpec) then
yellowfive@57 700 table.insert(fields, ".s" .. spec) -- indicates the start of a spec block
yellowfive@81 701 table.insert(fields, data.Specs[spec])
yellowfive@124 702 table.insert(fields, data.Talents[spec] or "")
yellowfive@57 703 end
yellowfive@57 704 end
yellowfive@57 705
yellowfive@57 706 -- export equipped gear
yellowfive@57 707 if data.Equipped then
yellowfive@81 708 for spec = 1, 4 do
yellowfive@57 709 if data.Equipped[spec] and (complete or spec == data.ActiveSpec) then
yellowfive@57 710 table.insert(fields, ".q" .. spec) -- indicates the start of an equipped gear block
yellowfive@57 711
yellowfive@57 712 local itemObjects = {}
yellowfive@124 713 for k, itemData in pairs(data.Equipped[spec]) do
yellowfive@57 714 itemData.slot = k
yellowfive@57 715 table.insert(itemObjects, itemData)
yellowfive@57 716 end
yellowfive@57 717
yellowfive@57 718 appendItemsToExport(fields, itemObjects)
yellowfive@57 719 end
yellowfive@57 720 end
yellowfive@57 721 end
yellowfive@57 722
yellowfive@124 723 -- if doing a complete export, include bank/bag items too
yellowfive@124 724 if complete then
yellowfive@124 725
yellowfive@57 726 local itemObjects = {}
yellowfive@57 727 if data.BagItems then
yellowfive@124 728 for i, itemData in ipairs(data.BagItems) do
yellowfive@124 729 if itemData then
yellowfive@57 730 table.insert(itemObjects, itemData)
yellowfive@57 731 end
yellowfive@57 732 end
yellowfive@57 733 end
yellowfive@127 734 if data.BankItems then
yellowfive@124 735 for i, itemData in ipairs(data.BankItems) do
yellowfive@127 736 if itemData then
yellowfive@57 737 table.insert(itemObjects, itemData)
yellowfive@57 738 end
yellowfive@57 739 end
yellowfive@124 740 end
yellowfive@124 741
yellowfive@57 742 table.insert(fields, ".inv")
yellowfive@57 743 appendItemsToExport(fields, itemObjects)
yellowfive@57 744 end
yellowfive@57 745
yellowfive@57 746 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 747
yellowfive@57 748 end
yellowfive@57 749
yellowfive@57 750 -- Shortcut for the common use case: serialize the player's currently active setup with no extras.
yellowfive@57 751 function Amr:SerializePlayer()
yellowfive@57 752 local data = self:GetPlayerData()
yellowfive@57 753 return self:SerializePlayerData(data)
yellowfive@57 754 end
yellowfive@57 755
yellowfive@81 756 --[[
yellowfive@57 757 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 758 -- Character Snapshots
yellowfive@81 759 -- This feature snapshots a player's gear/talents/artifact when entering combat. It is enabled by default. Consumers
yellowfive@57 760 -- of this library can create a setting to enable/disable it as desired per a user setting.
yellowfive@57 761 --
yellowfive@57 762 -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that
yellowfive@57 763 -- your addon settings stay in sync with any other addon that may also be trying to control the enabled state.
yellowfive@57 764 --
yellowfive@57 765 -- Note that if a user has the main AMR addon installed, it will always enable snapshotting, and override any attempt
yellowfive@57 766 -- to disable it by immediately re-enabling it and thus re-triggering AMR_SNAPSHOT_STATE_CHANGED.
yellowfive@57 767 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 768 Amr._snapshotEnabled = true
yellowfive@57 769
yellowfive@57 770 -- Enable snapshotting of character data when entering combat. Sends this player's character data to anyone logging with the AskMrRobot addon.
yellowfive@57 771 function Amr:EnableSnapshots()
yellowfive@57 772 self._snapshotEnabled = true
yellowfive@57 773 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 774 end
yellowfive@57 775
yellowfive@57 776 -- Disable snapshotting of character data when entering combat.
yellowfive@57 777 function Amr:DisableSnapshots()
yellowfive@57 778 self._snapshotEnabled = false
yellowfive@57 779 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 780 end
yellowfive@57 781
yellowfive@57 782 function Amr:IsSnapshotEnabled()
yellowfive@57 783 return self._snapshotEnabled
yellowfive@57 784 end
yellowfive@57 785
yellowfive@57 786
yellowfive@57 787 function Amr:PLAYER_REGEN_DISABLED()
yellowfive@57 788 --function Amr:GARRISON_MISSION_NPC_OPENED()
yellowfive@57 789
yellowfive@57 790 -- send data about this character when a player enters combat in a supported zone
yellowfive@57 791 if self._snapshotEnabled and Amr.IsSupportedInstance() then
yellowfive@57 792 local t = time()
yellowfive@57 793 local player = self:GetPlayerData()
yellowfive@57 794 local msg = self:SerializePlayerData(player)
yellowfive@57 795 msg = string.format("%s\r%s\n%s\n%s\n%s\n%s", MINOR, t, player.Region, player.Realm, player.Name, msg)
yellowfive@57 796
yellowfive@57 797 self:SendCommMessage(Amr.ChatPrefix, msg, "RAID")
yellowfive@57 798 end
yellowfive@57 799 end
yellowfive@57 800
yellowfive@57 801 Amr:RegisterEvent("PLAYER_REGEN_DISABLED")
yellowfive@81 802 --Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table
yellowfive@122 803 ]]