annotate AskMrRobot-Serializer/AskMrRobot-Serializer.lua @ 165:3be9cc6f7d20 v77

Updated for 8.2, initial essence support.
author yellowfive
date Tue, 25 Jun 2019 10:27:20 -0700
parents 35612aee8e15
children 5c68d3fccff3
rev   line source
yellowfive@57 1 -- AskMrRobot-Serializer will serialize and communicate character data between users.
yellowfive@57 2
yellowfive@165 3 local MAJOR, MINOR = "AskMrRobot-Serializer", 77
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@135 143 ["LightforgedDraenei"] = 17,
yellowfive@135 144 ["DarkIronDwarf"] = 18,
yellowfive@155 145 ["MagharOrc"] = 19,
yellowfive@155 146 ["ZandalariTroll"] = 20,
yellowfive@157 147 ["KulTiran"] = 21
yellowfive@57 148 }
yellowfive@57 149
yellowfive@57 150 Amr.FactionIds = {
yellowfive@57 151 ["None"] = 0,
yellowfive@57 152 ["Alliance"] = 1,
yellowfive@57 153 ["Horde"] = 2
yellowfive@57 154 }
yellowfive@57 155
yellowfive@57 156 Amr.InstanceIds = {
yellowfive@153 157 Uldir = 1861,
yellowfive@155 158 Dazar = 2070,
yellowfive@155 159 Storms = 2096
yellowfive@57 160 }
yellowfive@57 161
yellowfive@57 162 -- instances that AskMrRobot currently supports logging for
yellowfive@57 163 Amr.SupportedInstanceIds = {
yellowfive@153 164 [1861] = true,
yellowfive@155 165 [2070] = true,
yellowfive@155 166 [2096] = 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@129 231 offset = offset + numBonuses
yellowfive@124 232 if #parts > 17 + offset then
yellowfive@124 233 numBonuses = tonumber(parts[17 + offset])
yellowfive@129 234 if numBonuses then
yellowfive@129 235 if numBonuses > 0 then
yellowfive@129 236 item.relicBonusIds[2] = readBonusIdList(parts, 18 + offset, 17 + offset + numBonuses)
yellowfive@129 237 end
yellowfive@129 238
yellowfive@129 239 offset= offset + numBonuses
yellowfive@129 240 if #parts > 18 + offset then
yellowfive@129 241 numBonuses = tonumber(parts[18 + offset])
yellowfive@129 242 if numBonuses then
yellowfive@129 243 if numBonuses > 0 then
yellowfive@129 244 item.relicBonusIds[3] = readBonusIdList(parts, 19 + offset, 18 + offset + numBonuses)
yellowfive@129 245 end
yellowfive@129 246 end
yellowfive@129 247 end
yellowfive@124 248 end
yellowfive@124 249 end
yellowfive@124 250 end
yellowfive@69 251 end
yellowfive@81 252
yellowfive@57 253 return item
yellowfive@57 254 end
yellowfive@57 255
yellowfive@135 256 local AZERITE_EMPOWERED_BONUS_ID = 4775
yellowfive@135 257
yellowfive@135 258 function Amr.GetItemUniqueId(item, noUpgrade, noAzeriteEmpoweredBonusId)
yellowfive@81 259 if not item then return "" end
yellowfive@81 260 local ret = item.id .. ""
yellowfive@81 261 if item.bonusIds then
yellowfive@135 262 for i = 1, #item.bonusIds do
yellowfive@135 263 if not noAzeriteEmpoweredBonusId or item.bonusIds[i] ~= AZERITE_EMPOWERED_BONUS_ID then
yellowfive@135 264 ret = ret .. "b" .. item.bonusIds[i]
yellowfive@135 265 end
yellowfive@81 266 end
yellowfive@81 267 end
yellowfive@81 268 if item.suffixId ~= 0 then
yellowfive@81 269 ret = ret .. "s" .. item.suffixId
yellowfive@81 270 end
yellowfive@81 271 if not noUpgrade and item.upgradeId ~= 0 then
yellowfive@81 272 ret = ret .. "u" .. item.upgradeId
yellowfive@81 273 end
yellowfive@81 274 if item.level ~= 0 then
yellowfive@81 275 ret = ret .. "v" .. item.level
yellowfive@81 276 end
yellowfive@81 277 return ret
yellowfive@81 278 end
yellowfive@81 279
yellowfive@57 280 -- returns true if this is an instance that AskMrRobot supports for logging
yellowfive@57 281 function Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 282 if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then
yellowfive@57 283 return true
yellowfive@57 284 else
yellowfive@57 285 return false
yellowfive@57 286 end
yellowfive@57 287 end
yellowfive@57 288
yellowfive@57 289 -- returns true if currently in a supported instance for logging
yellowfive@57 290 function Amr.IsSupportedInstance()
yellowfive@133 291 local _, _, _, _, _, _, _, instanceMapID = GetInstanceInfo()
yellowfive@57 292 return Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 293 end
yellowfive@57 294
yellowfive@133 295 --[[
yellowfive@81 296 -- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
yellowfive@81 297 -- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
yellowfive@81 298 local _scanTt
yellowfive@81 299 function Amr.GetScanningTooltip()
yellowfive@81 300 if not _scanTt then
yellowfive@81 301 _scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
yellowfive@81 302 _scanTt:SetOwner(UIParent, "ANCHOR_NONE")
yellowfive@81 303 end
yellowfive@81 304 return _scanTt
yellowfive@81 305 end
yellowfive@81 306
yellowfive@81 307 -- 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 308 function Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 309 local tt = Amr.GetScanningTooltip()
yellowfive@81 310 tt:ClearLines()
yellowfive@81 311 if bagId then
yellowfive@81 312 tt:SetBagItem(bagId, slotId)
yellowfive@81 313 elseif slotId then
yellowfive@81 314 tt:SetInventoryItem("player", slotId)
yellowfive@81 315 else
yellowfive@81 316 tt:SetHyperlink(link)
yellowfive@81 317 end
yellowfive@81 318 return tt
yellowfive@81 319 end
yellowfive@133 320 ]]
yellowfive@81 321
yellowfive@133 322 --[[
yellowfive@124 323 function Amr.GetItemLevel(bagId, slotId, link)
yellowfive@81 324 local itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)")
yellowfive@81 325 local tt = Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 326
yellowfive@81 327 local regions = { tt:GetRegions() }
yellowfive@81 328 for i, region in ipairs(regions) do
yellowfive@81 329 if region and region:GetObjectType() == "FontString" then
yellowfive@81 330 local text = region:GetText()
yellowfive@81 331 if text then
yellowfive@81 332 ilvl = tonumber(text:match(itemLevelPattern))
yellowfive@81 333 if ilvl then
yellowfive@81 334 return ilvl
yellowfive@81 335 end
yellowfive@81 336 end
yellowfive@81 337 end
yellowfive@81 338 end
yellowfive@81 339
yellowfive@81 340 -- 0 means we couldn't find it for whatever reason
yellowfive@81 341 return 0
yellowfive@81 342 end
yellowfive@133 343 ]]
yellowfive@81 344
yellowfive@57 345
yellowfive@57 346 ----------------------------------------------------------------------------------------
yellowfive@57 347 -- Character Reading
yellowfive@57 348 ----------------------------------------------------------------------------------------
yellowfive@57 349
yellowfive@57 350 local function readProfessionInfo(prof, ret)
yellowfive@57 351 if prof then
yellowfive@133 352 local _, _, skillLevel, _, _, _, skillLine = GetProfessionInfo(prof);
yellowfive@57 353 if Amr.ProfessionSkillLineToName[skillLine] ~= nil then
yellowfive@57 354 ret.Professions[Amr.ProfessionSkillLineToName[skillLine]] = skillLevel;
yellowfive@57 355 end
yellowfive@57 356 end
yellowfive@57 357 end
yellowfive@57 358
yellowfive@124 359 -- get specs
yellowfive@81 360 local function readSpecs(ret)
yellowfive@57 361
yellowfive@81 362 for pos = 1, 4 do
yellowfive@57 363 -- spec, convert game spec id to one of our spec ids
yellowfive@81 364 local specId = GetSpecializationInfo(pos)
yellowfive@57 365 if specId then
yellowfive@81 366 ret.Specs[pos] = Amr.SpecIds[specId]
yellowfive@57 367 end
yellowfive@57 368 end
yellowfive@57 369 end
yellowfive@57 370
yellowfive@124 371 local function dump(o)
yellowfive@124 372 if type(o) == 'table' then
yellowfive@124 373 local s = '{ '
yellowfive@124 374 for k,v in pairs(o) do
yellowfive@124 375 if type(k) ~= 'number' then k = '"'..k..'"' end
yellowfive@124 376 s = s .. '['..k..'] = ' .. dump(v) .. ','
yellowfive@124 377 end
yellowfive@124 378 return s .. '} '
yellowfive@124 379 else
yellowfive@124 380 return tostring(o)
yellowfive@124 381 end
yellowfive@124 382 end
yellowfive@124 383
yellowfive@124 384 -- read azerite powers on the item in loc and put it on itemData
yellowfive@124 385 function Amr.ReadAzeritePowers(loc)
yellowfive@124 386 local ret = {}
yellowfive@124 387 local hasSome = false
yellowfive@124 388
yellowfive@124 389 local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(loc)
yellowfive@124 390 for tier, tierInfo in ipairs(tiers) do
yellowfive@124 391 for _, power in ipairs(tierInfo.azeritePowerIDs) do
yellowfive@124 392 if C_AzeriteEmpoweredItem.IsPowerSelected(loc, power) then
yellowfive@124 393 local powerInfo = C_AzeriteEmpoweredItem.GetPowerInfo(power)
yellowfive@124 394 table.insert(ret, powerInfo.spellID)
yellowfive@124 395 hasSome = true
yellowfive@124 396 end
yellowfive@124 397 end
yellowfive@124 398 end
yellowfive@124 399
yellowfive@124 400 if hasSome then
yellowfive@124 401 return ret
yellowfive@124 402 else
yellowfive@124 403 return nil
yellowfive@124 404 end
yellowfive@124 405 end
yellowfive@124 406
yellowfive@57 407 -- get currently equipped items, store with currently active spec
yellowfive@57 408 local function readEquippedItems(ret)
yellowfive@124 409 local equippedItems = {};
yellowfive@124 410 local loc = ItemLocation.CreateEmpty()
yellowfive@57 411 for slotNum = 1, #Amr.SlotIds do
yellowfive@57 412 local slotId = Amr.SlotIds[slotNum]
yellowfive@57 413 local itemLink = GetInventoryItemLink("player", slotId)
yellowfive@57 414 if itemLink then
yellowfive@124 415 local itemData = Amr.ParseItemLink(itemLink)
yellowfive@124 416 if itemData then
yellowfive@124 417 -- see if this is an azerite item and read azerite power ids
yellowfive@124 418 loc:SetEquipmentSlot(slotId)
yellowfive@124 419 if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then
yellowfive@124 420 local powers = Amr.ReadAzeritePowers(loc)
yellowfive@124 421 if powers then
yellowfive@124 422 itemData.azerite = powers
yellowfive@124 423 end
yellowfive@124 424 end
yellowfive@124 425
yellowfive@124 426 equippedItems[slotId] = itemData
yellowfive@124 427 end
yellowfive@57 428 end
yellowfive@57 429 end
yellowfive@57 430
yellowfive@57 431 -- store last-seen equipped gear for each spec
yellowfive@81 432 ret.Equipped[GetSpecialization()] = equippedItems
yellowfive@57 433 end
yellowfive@57 434
yellowfive@124 435 local function readHeartOfAzerothLevel(ret)
yellowfive@124 436 local azeriteItemLocation = C_AzeriteItem.FindActiveAzeriteItem();
yellowfive@124 437 if azeriteItemLocation then
yellowfive@124 438 local azeriteItem = Item:CreateFromItemLocation(azeriteItemLocation);
yellowfive@124 439 ret.HeartOfAzerothLevel = C_AzeriteItem.GetPowerLevel(azeriteItemLocation)
yellowfive@124 440 else
yellowfive@124 441 ret.HeartOfAzerothLevel = 0
yellowfive@124 442 end
yellowfive@124 443 end
yellowfive@124 444
yellowfive@124 445 -- Get just the player's currently equipped gear
yellowfive@124 446 function Amr:GetEquipped()
yellowfive@124 447 local ret= {}
yellowfive@124 448 ret.Equipped = {}
yellowfive@124 449 readEquippedItems(ret)
yellowfive@124 450 return ret
yellowfive@124 451 end
yellowfive@124 452
yellowfive@57 453 -- Get all data about the player as an object, includes:
yellowfive@57 454 -- serializer version
yellowfive@57 455 -- region/realm/name
yellowfive@57 456 -- guild
yellowfive@57 457 -- race
yellowfive@57 458 -- faction
yellowfive@57 459 -- level
yellowfive@57 460 -- professions
yellowfive@81 461 -- spec/talent for all specs
yellowfive@57 462 -- equipped gear for the current spec
yellowfive@57 463 --
yellowfive@81 464 function Amr:GetPlayerData()
yellowfive@57 465
yellowfive@57 466 local ret = {}
yellowfive@57 467
yellowfive@57 468 ret.Region = Amr.RegionNames[GetCurrentRegion()]
yellowfive@57 469 ret.Realm = GetRealmName()
yellowfive@57 470 ret.Name = UnitName("player")
yellowfive@57 471 ret.Guild = GetGuildInfo("player")
yellowfive@81 472 ret.ActiveSpec = GetSpecialization()
yellowfive@57 473 ret.Level = UnitLevel("player");
yellowfive@124 474 readHeartOfAzerothLevel(ret)
yellowfive@124 475
yellowfive@133 476 local _, clsEn = UnitClass("player")
yellowfive@57 477 ret.Class = clsEn;
yellowfive@57 478
yellowfive@133 479 local _, raceEn = UnitRace("player")
yellowfive@57 480 ret.Race = raceEn;
yellowfive@57 481 ret.Faction = UnitFactionGroup("player")
yellowfive@57 482
yellowfive@57 483 ret.Professions = {};
yellowfive@57 484 local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions();
yellowfive@57 485 readProfessionInfo(prof1, ret)
yellowfive@57 486 readProfessionInfo(prof2, ret)
yellowfive@57 487 readProfessionInfo(archaeology, ret)
yellowfive@57 488 readProfessionInfo(fishing, ret)
yellowfive@57 489 readProfessionInfo(cooking, ret)
yellowfive@57 490 readProfessionInfo(firstAid, ret)
yellowfive@57 491
yellowfive@57 492 ret.Specs = {}
yellowfive@57 493 ret.Talents = {}
yellowfive@81 494 readSpecs(ret)
yellowfive@165 495
yellowfive@165 496 -- these get updated later, since need to cache info for inactive specs
yellowfive@165 497 ret.UnlockedEssences = {}
yellowfive@165 498 ret.Essences = {}
yellowfive@81 499
yellowfive@124 500 ret.Equipped = {}
yellowfive@57 501 readEquippedItems(ret)
yellowfive@57 502
yellowfive@57 503 return ret
yellowfive@57 504 end
yellowfive@57 505
yellowfive@57 506
yellowfive@57 507 ----------------------------------------------------------------------------------------
yellowfive@57 508 -- Serialization
yellowfive@57 509 ----------------------------------------------------------------------------------------
yellowfive@57 510
yellowfive@57 511 local function toCompressedNumberList(list)
yellowfive@57 512 -- ensure the values are numbers, sorted from lowest to highest
yellowfive@57 513 local nums = {}
yellowfive@57 514 for i, v in ipairs(list) do
yellowfive@57 515 table.insert(nums, tonumber(v))
yellowfive@57 516 end
yellowfive@57 517 table.sort(nums)
yellowfive@57 518
yellowfive@57 519 local ret = {}
yellowfive@57 520 local prev = 0
yellowfive@57 521 for i, v in ipairs(nums) do
yellowfive@57 522 local diff = v - prev
yellowfive@57 523 table.insert(ret, diff)
yellowfive@57 524 prev = v
yellowfive@57 525 end
yellowfive@57 526
yellowfive@57 527 return table.concat(ret, ",")
yellowfive@57 528 end
yellowfive@57 529
yellowfive@57 530 -- make this utility publicly available
yellowfive@57 531 function Amr:ToCompressedNumberList(list)
yellowfive@57 532 return toCompressedNumberList(list)
yellowfive@57 533 end
yellowfive@57 534
yellowfive@57 535 -- appends a list of items to the export
yellowfive@57 536 local function appendItemsToExport(fields, itemObjects)
yellowfive@57 537
yellowfive@57 538 -- sort by item id so we can compress it more easily
yellowfive@57 539 table.sort(itemObjects, function(a, b) return a.id < b.id end)
yellowfive@57 540
yellowfive@57 541 -- append to the export string
yellowfive@57 542 local prevItemId = 0
yellowfive@57 543 local prevGemId = 0
yellowfive@57 544 local prevEnchantId = 0
yellowfive@57 545 local prevUpgradeId = 0
yellowfive@57 546 local prevBonusId = 0
yellowfive@81 547 local prevLevel = 0
yellowfive@124 548 local prevAzeriteId = 0
yellowfive@124 549 local prevRelicBonusId = 0
yellowfive@57 550 for i, itemData in ipairs(itemObjects) do
yellowfive@57 551 local itemParts = {}
yellowfive@57 552
yellowfive@57 553 table.insert(itemParts, itemData.id - prevItemId)
yellowfive@57 554 prevItemId = itemData.id
yellowfive@57 555
yellowfive@57 556 if itemData.slot ~= nil then table.insert(itemParts, "s" .. itemData.slot) end
yellowfive@124 557 --if itemData.suffixId ~= 0 then table.insert(itemParts, "f" .. itemData.suffixId) end
yellowfive@57 558 if itemData.upgradeId ~= 0 then
yellowfive@57 559 table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId))
yellowfive@57 560 prevUpgradeId = itemData.upgradeId
yellowfive@57 561 end
yellowfive@81 562 if itemData.level ~= 0 then
yellowfive@81 563 table.insert(itemParts, "v" .. (itemData.level - prevLevel))
yellowfive@81 564 prevLevel = itemData.level
yellowfive@81 565 end
yellowfive@57 566 if itemData.bonusIds then
yellowfive@57 567 for bIndex, bValue in ipairs(itemData.bonusIds) do
yellowfive@57 568 table.insert(itemParts, "b" .. (bValue - prevBonusId))
yellowfive@57 569 prevBonusId = bValue
yellowfive@57 570 end
yellowfive@124 571 end
yellowfive@124 572
yellowfive@124 573 if itemData.azerite then
yellowfive@124 574 for aIndex, aValue in ipairs(itemData.azerite) do
yellowfive@124 575 table.insert(itemParts, "a" .. (aValue - prevAzeriteId))
yellowfive@124 576 prevAzeriteId = aValue
yellowfive@124 577 end
yellowfive@124 578 end
yellowfive@81 579
yellowfive@81 580 if itemData.gemIds[1] ~= 0 then
yellowfive@81 581 table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
yellowfive@81 582 prevGemId = itemData.gemIds[1]
yellowfive@81 583 end
yellowfive@81 584 if itemData.gemIds[2] ~= 0 then
yellowfive@81 585 table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
yellowfive@81 586 prevGemId = itemData.gemIds[2]
yellowfive@81 587 end
yellowfive@81 588 if itemData.gemIds[3] ~= 0 then
yellowfive@81 589 table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
yellowfive@81 590 prevGemId = itemData.gemIds[3]
yellowfive@124 591 end
yellowfive@81 592
yellowfive@57 593 if itemData.enchantId ~= 0 then
yellowfive@57 594 table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId))
yellowfive@57 595 prevEnchantId = itemData.enchantId
yellowfive@57 596 end
yellowfive@124 597
yellowfive@124 598 if itemData.relicBonusIds and itemData.relicBonusIds[1] ~= nil then
yellowfive@124 599 for bIndex, bValue in ipairs(itemData.relicBonusIds[1]) do
yellowfive@124 600 table.insert(itemParts, "p" .. (bValue - prevRelicBonusId))
yellowfive@124 601 prevRelicBonusId = bValue
yellowfive@124 602 end
yellowfive@124 603 end
yellowfive@124 604
yellowfive@124 605 if itemData.relicBonusIds and itemData.relicBonusIds[2] ~= nil then
yellowfive@124 606 for bIndex, bValue in ipairs(itemData.relicBonusIds[2]) do
yellowfive@124 607 table.insert(itemParts, "q" .. (bValue - prevRelicBonusId))
yellowfive@124 608 prevRelicBonusId = bValue
yellowfive@124 609 end
yellowfive@124 610 end
yellowfive@124 611
yellowfive@124 612 if itemData.relicBonusIds and itemData.relicBonusIds[3] ~= nil then
yellowfive@124 613 for bIndex, bValue in ipairs(itemData.relicBonusIds[3]) do
yellowfive@124 614 table.insert(itemParts, "r" .. (bValue - prevRelicBonusId))
yellowfive@124 615 prevRelicBonusId = bValue
yellowfive@124 616 end
yellowfive@124 617 end
yellowfive@124 618
yellowfive@57 619 table.insert(fields, table.concat(itemParts, ""))
yellowfive@57 620 end
yellowfive@57 621 end
yellowfive@57 622
yellowfive@57 623 -- Serialize just the identity portion of a player (region/realm/name) in the same format used by the full serialization
yellowfive@57 624 function Amr:SerializePlayerIdentity(data)
yellowfive@57 625 local fields = {}
yellowfive@57 626 table.insert(fields, MINOR)
yellowfive@57 627 table.insert(fields, data.Region)
yellowfive@57 628 table.insert(fields, data.Realm)
yellowfive@57 629 table.insert(fields, data.Name)
yellowfive@57 630 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 631 end
yellowfive@57 632
yellowfive@57 633 -- Serialize player data gathered by GetPlayerData. This can be augmented with extra data if desired (augmenting used mainly by AskMrRobot addon).
yellowfive@57 634 -- Pass complete = true to do a complete export of this extra information, otherwise it is ignored.
yellowfive@57 635 -- Extra data can include:
yellowfive@57 636 -- equipped gear for the player's inactive spec, slot id to item link dictionary
yellowfive@57 637 -- Reputations
yellowfive@57 638 -- BagItems, BankItems, VoidItems, lists of item links
yellowfive@57 639 --
yellowfive@57 640 function Amr:SerializePlayerData(data, complete)
yellowfive@57 641
yellowfive@57 642 local fields = {}
yellowfive@57 643
yellowfive@57 644 -- compressed string uses a fixed order rather than inserting identifiers
yellowfive@57 645 table.insert(fields, MINOR)
yellowfive@57 646 table.insert(fields, data.Region)
yellowfive@57 647 table.insert(fields, data.Realm)
yellowfive@57 648 table.insert(fields, data.Name)
yellowfive@57 649
yellowfive@57 650 -- guild name
yellowfive@57 651 if data.Guild == nil then
yellowfive@57 652 table.insert(fields, "")
yellowfive@57 653 else
yellowfive@57 654 table.insert(fields, data.Guild)
yellowfive@57 655 end
yellowfive@57 656
yellowfive@57 657 -- race, default to pandaren if we can't read it for some reason
yellowfive@57 658 local raceval = Amr.RaceIds[data.Race]
yellowfive@57 659 if raceval == nil then raceval = 13 end
yellowfive@57 660 table.insert(fields, raceval)
yellowfive@57 661
yellowfive@57 662 -- faction, default to alliance if we can't read it for some reason
yellowfive@57 663 raceval = Amr.FactionIds[data.Faction]
yellowfive@57 664 if raceval == nil then raceval = 1 end
yellowfive@57 665 table.insert(fields, raceval)
yellowfive@57 666
yellowfive@124 667 table.insert(fields, data.Level)
yellowfive@124 668 table.insert(fields, data.HeartOfAzerothLevel)
yellowfive@57 669
yellowfive@57 670 local profs = {}
yellowfive@57 671 local noprofs = true
yellowfive@57 672 if data.Professions then
yellowfive@57 673 for k, v in pairs(data.Professions) do
yellowfive@57 674 local profval = Amr.ProfessionIds[k]
yellowfive@57 675 if profval ~= nil then
yellowfive@57 676 noprofs = false
yellowfive@57 677 table.insert(profs, profval .. ":" .. v)
yellowfive@57 678 end
yellowfive@57 679 end
yellowfive@57 680 end
yellowfive@57 681
yellowfive@57 682 if noprofs then
yellowfive@57 683 table.insert(profs, "0:0")
yellowfive@57 684 end
yellowfive@57 685
yellowfive@57 686 table.insert(fields, table.concat(profs, ","))
yellowfive@57 687
yellowfive@57 688 -- export specs
yellowfive@57 689 table.insert(fields, data.ActiveSpec)
yellowfive@81 690 for spec = 1, 4 do
yellowfive@57 691 if data.Specs[spec] and (complete or spec == data.ActiveSpec) then
yellowfive@57 692 table.insert(fields, ".s" .. spec) -- indicates the start of a spec block
yellowfive@81 693 table.insert(fields, data.Specs[spec])
yellowfive@165 694 table.insert(fields, data.Talents[spec] or "")
yellowfive@165 695
yellowfive@165 696 local essences = {}
yellowfive@165 697 if data.Essences and data.Essences[spec] then
yellowfive@165 698 for i, ess in ipairs(data.Essences[spec]) do
yellowfive@165 699 table.insert(essences, table.concat(ess, "."))
yellowfive@165 700 end
yellowfive@165 701 end
yellowfive@165 702 table.insert(fields, table.concat(essences, "_"))
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@165 722
yellowfive@165 723 -- export unlocked essences
yellowfive@165 724 if data.UnlockedEssences then
yellowfive@165 725 table.insert(fields, ".ess")
yellowfive@165 726 for i, ess in ipairs(data.UnlockedEssences) do
yellowfive@165 727 table.insert(fields, table.concat(ess, "_"))
yellowfive@165 728 end
yellowfive@165 729 end
yellowfive@57 730
yellowfive@124 731 -- if doing a complete export, include bank/bag items too
yellowfive@124 732 if complete then
yellowfive@124 733
yellowfive@57 734 local itemObjects = {}
yellowfive@57 735 if data.BagItems then
yellowfive@124 736 for i, itemData in ipairs(data.BagItems) do
yellowfive@124 737 if itemData then
yellowfive@57 738 table.insert(itemObjects, itemData)
yellowfive@57 739 end
yellowfive@57 740 end
yellowfive@57 741 end
yellowfive@127 742 if data.BankItems then
yellowfive@124 743 for i, itemData in ipairs(data.BankItems) do
yellowfive@127 744 if itemData then
yellowfive@57 745 table.insert(itemObjects, itemData)
yellowfive@57 746 end
yellowfive@57 747 end
yellowfive@124 748 end
yellowfive@124 749
yellowfive@57 750 table.insert(fields, ".inv")
yellowfive@57 751 appendItemsToExport(fields, itemObjects)
yellowfive@57 752 end
yellowfive@57 753
yellowfive@57 754 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 755
yellowfive@57 756 end
yellowfive@57 757
yellowfive@165 758 --[[
yellowfive@57 759 -- Shortcut for the common use case: serialize the player's currently active setup with no extras.
yellowfive@57 760 function Amr:SerializePlayer()
yellowfive@57 761 local data = self:GetPlayerData()
yellowfive@57 762 return self:SerializePlayerData(data)
yellowfive@57 763 end
yellowfive@165 764 ]]
yellowfive@57 765
yellowfive@81 766 --[[
yellowfive@57 767 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 768 -- Character Snapshots
yellowfive@81 769 -- This feature snapshots a player's gear/talents/artifact when entering combat. It is enabled by default. Consumers
yellowfive@57 770 -- of this library can create a setting to enable/disable it as desired per a user setting.
yellowfive@57 771 --
yellowfive@57 772 -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that
yellowfive@57 773 -- your addon settings stay in sync with any other addon that may also be trying to control the enabled state.
yellowfive@57 774 --
yellowfive@57 775 -- Note that if a user has the main AMR addon installed, it will always enable snapshotting, and override any attempt
yellowfive@57 776 -- to disable it by immediately re-enabling it and thus re-triggering AMR_SNAPSHOT_STATE_CHANGED.
yellowfive@57 777 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 778 Amr._snapshotEnabled = true
yellowfive@57 779
yellowfive@57 780 -- Enable snapshotting of character data when entering combat. Sends this player's character data to anyone logging with the AskMrRobot addon.
yellowfive@57 781 function Amr:EnableSnapshots()
yellowfive@57 782 self._snapshotEnabled = true
yellowfive@57 783 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 784 end
yellowfive@57 785
yellowfive@57 786 -- Disable snapshotting of character data when entering combat.
yellowfive@57 787 function Amr:DisableSnapshots()
yellowfive@57 788 self._snapshotEnabled = false
yellowfive@57 789 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 790 end
yellowfive@57 791
yellowfive@57 792 function Amr:IsSnapshotEnabled()
yellowfive@57 793 return self._snapshotEnabled
yellowfive@57 794 end
yellowfive@57 795
yellowfive@57 796
yellowfive@57 797 function Amr:PLAYER_REGEN_DISABLED()
yellowfive@57 798 --function Amr:GARRISON_MISSION_NPC_OPENED()
yellowfive@57 799
yellowfive@57 800 -- send data about this character when a player enters combat in a supported zone
yellowfive@57 801 if self._snapshotEnabled and Amr.IsSupportedInstance() then
yellowfive@57 802 local t = time()
yellowfive@57 803 local player = self:GetPlayerData()
yellowfive@57 804 local msg = self:SerializePlayerData(player)
yellowfive@57 805 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 806
yellowfive@57 807 self:SendCommMessage(Amr.ChatPrefix, msg, "RAID")
yellowfive@57 808 end
yellowfive@57 809 end
yellowfive@57 810
yellowfive@57 811 Amr:RegisterEvent("PLAYER_REGEN_DISABLED")
yellowfive@81 812 --Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table
yellowfive@122 813 ]]