annotate AskMrRobot-Serializer/AskMrRobot-Serializer.lua @ 191:4aeedce4c995 v93

Added better identification of duplicate items.
author yellowfive
date Fri, 30 Oct 2020 21:14:57 -0700
parents 21a69c63fee8
children cb7eb9b9cc24
rev   line source
yellowfive@57 1 -- AskMrRobot-Serializer will serialize and communicate character data between users.
yellowfive@57 2
yellowfive@191 3 local MAJOR, MINOR = "AskMrRobot-Serializer", 93
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@173 147 ["KulTiran"] = 21,
yellowfive@173 148 ["Vulpera"] = 22,
yellowfive@173 149 ["Mechagnome"] = 23
yellowfive@57 150 }
yellowfive@57 151
yellowfive@57 152 Amr.FactionIds = {
yellowfive@57 153 ["None"] = 0,
yellowfive@57 154 ["Alliance"] = 1,
yellowfive@57 155 ["Horde"] = 2
yellowfive@57 156 }
yellowfive@57 157
yellowfive@57 158 Amr.InstanceIds = {
yellowfive@189 159 Uldir = 1861,
yellowfive@189 160 Dazar = 2070,
yellowfive@189 161 Storms = 2096,
yellowfive@189 162 Palace = 2164,
yellowfive@189 163 Nyalotha = 2217,
yellowfive@185 164 Nathria = 2296
yellowfive@57 165 }
yellowfive@57 166
yellowfive@57 167 -- instances that AskMrRobot currently supports logging for
yellowfive@57 168 Amr.SupportedInstanceIds = {
yellowfive@189 169 [1861] = true,
yellowfive@189 170 [2070] = true,
yellowfive@189 171 [2096] = true,
yellowfive@189 172 [2164] = true,
yellowfive@189 173 [2217] = true,
yellowfive@185 174 [2296] = true
yellowfive@57 175 }
yellowfive@57 176
yellowfive@57 177
yellowfive@57 178 ----------------------------------------------------------------------------------------
yellowfive@57 179 -- Public Utility Methods
yellowfive@57 180 ----------------------------------------------------------------------------------------
yellowfive@57 181
yellowfive@81 182 local function readBonusIdList(parts, first, last)
yellowfive@124 183 local ret = {}
yellowfive@81 184 for i = first, last do
yellowfive@81 185 table.insert(ret, tonumber(parts[i]))
yellowfive@81 186 end
yellowfive@81 187 table.sort(ret)
yellowfive@81 188 return ret
yellowfive@81 189 end
yellowfive@81 190
yellowfive@124 191 -- 1 2 3 4 5 6 7 8 9 10 11 12
yellowfive@124 192 -- itemId:ench:gem1 :gem2 :gem3 :gem4:suf:uid:lvl:spec:flags :instdiffid:numbonusIDs:bonusIDs1...n :varies:?:relic bonus ids
yellowfive@124 193 --|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 194 --
yellowfive@57 195 -- get an object with all of the parts of the item link format that we care about
yellowfive@57 196 function Amr.ParseItemLink(itemLink)
yellowfive@57 197 if not itemLink then return nil end
yellowfive@57 198
yellowfive@57 199 local str = string.match(itemLink, "|Hitem:([\-%d:]+)|")
yellowfive@57 200 if not str then return nil end
yellowfive@57 201
yellowfive@57 202 local parts = { strsplit(":", str) }
yellowfive@57 203
yellowfive@124 204 local item = {}
yellowfive@124 205 item.link = itemLink
yellowfive@81 206 item.id = tonumber(parts[1]) or 0
yellowfive@81 207 item.enchantId = tonumber(parts[2]) or 0
yellowfive@81 208 item.gemIds = { tonumber(parts[3]) or 0, tonumber(parts[4]) or 0, tonumber(parts[5]) or 0, tonumber(parts[6]) or 0 }
yellowfive@81 209 item.suffixId = math.abs(tonumber(parts[7]) or 0) -- convert suffix to positive number, that's what we use in our code
yellowfive@81 210 -- part 8 is some unique ID... we never really used it
yellowfive@81 211 -- part 9 is current player level
yellowfive@81 212 -- part 10 is player spec
yellowfive@185 213 -- unsure what 11 is now --local upgradeIdType = tonumber(parts[11]) or 0 -- part 11 indicates what kind of upgrade ID is just after the bonus IDs
yellowfive@81 214 -- part 12 is instance difficulty id
yellowfive@185 215
yellowfive@185 216 -- 13 is num bonus IDs, followed by bonus IDs
yellowfive@81 217 local numBonuses = tonumber(parts[13]) or 0
yellowfive@81 218 local offset = numBonuses
yellowfive@81 219 if numBonuses > 0 then
yellowfive@81 220 item.bonusIds = readBonusIdList(parts, 14, 13 + numBonuses)
yellowfive@57 221 end
yellowfive@69 222
yellowfive@81 223 item.upgradeId = 0
yellowfive@81 224 item.level = 0
yellowfive@185 225
yellowfive@185 226 -- part 14 + numBonuses, unsure what this is... sometimes it is "2"
yellowfive@185 227 -- part 15 + numBonuses, unsure what this is... may indicate what part 16 will mean?
yellowfive@185 228 -- part 16 + numBonuses, is player level at drop when applicable
yellowfive@185 229 -- part 17 + numBonuses, unsure what this is...
yellowfive@185 230 -- part 18 + numBonuses, unsure what this is...
yellowfive@185 231 -- part 19 + numBonuses, relic info would be here for legion artifacts
yellowfive@185 232
yellowfive@185 233 local someNumber = tonumber(parts[15 + offset]) or 0
yellowfive@185 234 if someNumber ~= 0 then
yellowfive@185 235 local lvl = tonumber(parts[16 + offset]) or 0
yellowfive@185 236 if lvl <= 60 then
yellowfive@185 237 item.level = lvl
yellowfive@185 238 end
yellowfive@185 239 end
yellowfive@185 240
yellowfive@185 241 -- we don't need relic information anymore
yellowfive@185 242 --[[elseif #parts > 19 + offset then
yellowfive@124 243 -- check for relic info
yellowfive@124 244 item.relicBonusIds = { nil, nil, nil }
yellowfive@124 245 numBonuses = tonumber(parts[16 + offset])
yellowfive@124 246 if numBonuses then
yellowfive@124 247 if numBonuses > 0 then
yellowfive@124 248 item.relicBonusIds[1] = readBonusIdList(parts, 17 + offset, 16 + offset + numBonuses)
yellowfive@124 249 end
yellowfive@124 250
yellowfive@129 251 offset = offset + numBonuses
yellowfive@124 252 if #parts > 17 + offset then
yellowfive@124 253 numBonuses = tonumber(parts[17 + offset])
yellowfive@129 254 if numBonuses then
yellowfive@129 255 if numBonuses > 0 then
yellowfive@129 256 item.relicBonusIds[2] = readBonusIdList(parts, 18 + offset, 17 + offset + numBonuses)
yellowfive@129 257 end
yellowfive@129 258
yellowfive@129 259 offset= offset + numBonuses
yellowfive@129 260 if #parts > 18 + offset then
yellowfive@129 261 numBonuses = tonumber(parts[18 + offset])
yellowfive@129 262 if numBonuses then
yellowfive@129 263 if numBonuses > 0 then
yellowfive@129 264 item.relicBonusIds[3] = readBonusIdList(parts, 19 + offset, 18 + offset + numBonuses)
yellowfive@129 265 end
yellowfive@129 266 end
yellowfive@129 267 end
yellowfive@124 268 end
yellowfive@124 269 end
yellowfive@124 270 end
yellowfive@185 271 end]]
yellowfive@81 272
yellowfive@57 273 return item
yellowfive@57 274 end
yellowfive@57 275
yellowfive@135 276 local AZERITE_EMPOWERED_BONUS_ID = 4775
yellowfive@135 277
yellowfive@135 278 function Amr.GetItemUniqueId(item, noUpgrade, noAzeriteEmpoweredBonusId)
yellowfive@81 279 if not item then return "" end
yellowfive@81 280 local ret = item.id .. ""
yellowfive@81 281 if item.bonusIds then
yellowfive@135 282 for i = 1, #item.bonusIds do
yellowfive@135 283 if not noAzeriteEmpoweredBonusId or item.bonusIds[i] ~= AZERITE_EMPOWERED_BONUS_ID then
yellowfive@135 284 ret = ret .. "b" .. item.bonusIds[i]
yellowfive@135 285 end
yellowfive@81 286 end
yellowfive@81 287 end
yellowfive@81 288 if item.suffixId ~= 0 then
yellowfive@81 289 ret = ret .. "s" .. item.suffixId
yellowfive@81 290 end
yellowfive@81 291 if not noUpgrade and item.upgradeId ~= 0 then
yellowfive@81 292 ret = ret .. "u" .. item.upgradeId
yellowfive@81 293 end
yellowfive@81 294 if item.level ~= 0 then
yellowfive@81 295 ret = ret .. "v" .. item.level
yellowfive@81 296 end
yellowfive@81 297 return ret
yellowfive@81 298 end
yellowfive@81 299
yellowfive@57 300 -- returns true if this is an instance that AskMrRobot supports for logging
yellowfive@57 301 function Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 302 if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then
yellowfive@57 303 return true
yellowfive@57 304 else
yellowfive@57 305 return false
yellowfive@57 306 end
yellowfive@57 307 end
yellowfive@57 308
yellowfive@57 309 -- returns true if currently in a supported instance for logging
yellowfive@57 310 function Amr.IsSupportedInstance()
yellowfive@133 311 local _, _, _, _, _, _, _, instanceMapID = GetInstanceInfo()
yellowfive@57 312 return Amr.IsSupportedInstanceId(instanceMapID)
yellowfive@57 313 end
yellowfive@57 314
yellowfive@133 315 --[[
yellowfive@81 316 -- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
yellowfive@81 317 -- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
yellowfive@81 318 local _scanTt
yellowfive@81 319 function Amr.GetScanningTooltip()
yellowfive@81 320 if not _scanTt then
yellowfive@81 321 _scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
yellowfive@81 322 _scanTt:SetOwner(UIParent, "ANCHOR_NONE")
yellowfive@81 323 end
yellowfive@81 324 return _scanTt
yellowfive@81 325 end
yellowfive@81 326
yellowfive@81 327 -- 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 328 function Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 329 local tt = Amr.GetScanningTooltip()
yellowfive@81 330 tt:ClearLines()
yellowfive@81 331 if bagId then
yellowfive@81 332 tt:SetBagItem(bagId, slotId)
yellowfive@81 333 elseif slotId then
yellowfive@81 334 tt:SetInventoryItem("player", slotId)
yellowfive@81 335 else
yellowfive@81 336 tt:SetHyperlink(link)
yellowfive@81 337 end
yellowfive@81 338 return tt
yellowfive@81 339 end
yellowfive@133 340 ]]
yellowfive@81 341
yellowfive@133 342 --[[
yellowfive@124 343 function Amr.GetItemLevel(bagId, slotId, link)
yellowfive@81 344 local itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)")
yellowfive@81 345 local tt = Amr.GetItemTooltip(bagId, slotId, link)
yellowfive@81 346
yellowfive@81 347 local regions = { tt:GetRegions() }
yellowfive@81 348 for i, region in ipairs(regions) do
yellowfive@81 349 if region and region:GetObjectType() == "FontString" then
yellowfive@81 350 local text = region:GetText()
yellowfive@81 351 if text then
yellowfive@81 352 ilvl = tonumber(text:match(itemLevelPattern))
yellowfive@81 353 if ilvl then
yellowfive@81 354 return ilvl
yellowfive@81 355 end
yellowfive@81 356 end
yellowfive@81 357 end
yellowfive@81 358 end
yellowfive@81 359
yellowfive@81 360 -- 0 means we couldn't find it for whatever reason
yellowfive@81 361 return 0
yellowfive@81 362 end
yellowfive@133 363 ]]
yellowfive@81 364
yellowfive@57 365
yellowfive@57 366 ----------------------------------------------------------------------------------------
yellowfive@57 367 -- Character Reading
yellowfive@57 368 ----------------------------------------------------------------------------------------
yellowfive@57 369
yellowfive@57 370 local function readProfessionInfo(prof, ret)
yellowfive@57 371 if prof then
yellowfive@133 372 local _, _, skillLevel, _, _, _, skillLine = GetProfessionInfo(prof);
yellowfive@57 373 if Amr.ProfessionSkillLineToName[skillLine] ~= nil then
yellowfive@57 374 ret.Professions[Amr.ProfessionSkillLineToName[skillLine]] = skillLevel;
yellowfive@57 375 end
yellowfive@57 376 end
yellowfive@57 377 end
yellowfive@57 378
yellowfive@124 379 -- get specs
yellowfive@81 380 local function readSpecs(ret)
yellowfive@57 381
yellowfive@81 382 for pos = 1, 4 do
yellowfive@57 383 -- spec, convert game spec id to one of our spec ids
yellowfive@81 384 local specId = GetSpecializationInfo(pos)
yellowfive@57 385 if specId then
yellowfive@81 386 ret.Specs[pos] = Amr.SpecIds[specId]
yellowfive@57 387 end
yellowfive@57 388 end
yellowfive@57 389 end
yellowfive@57 390
yellowfive@124 391 local function dump(o)
yellowfive@124 392 if type(o) == 'table' then
yellowfive@124 393 local s = '{ '
yellowfive@124 394 for k,v in pairs(o) do
yellowfive@124 395 if type(k) ~= 'number' then k = '"'..k..'"' end
yellowfive@124 396 s = s .. '['..k..'] = ' .. dump(v) .. ','
yellowfive@124 397 end
yellowfive@124 398 return s .. '} '
yellowfive@124 399 else
yellowfive@124 400 return tostring(o)
yellowfive@124 401 end
yellowfive@124 402 end
yellowfive@124 403
yellowfive@185 404 --[[
yellowfive@124 405 -- read azerite powers on the item in loc and put it on itemData
yellowfive@124 406 function Amr.ReadAzeritePowers(loc)
yellowfive@124 407 local ret = {}
yellowfive@124 408 local hasSome = false
yellowfive@124 409
yellowfive@124 410 local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(loc)
yellowfive@124 411 for tier, tierInfo in ipairs(tiers) do
yellowfive@124 412 for _, power in ipairs(tierInfo.azeritePowerIDs) do
yellowfive@124 413 if C_AzeriteEmpoweredItem.IsPowerSelected(loc, power) then
yellowfive@124 414 local powerInfo = C_AzeriteEmpoweredItem.GetPowerInfo(power)
yellowfive@124 415 table.insert(ret, powerInfo.spellID)
yellowfive@124 416 hasSome = true
yellowfive@124 417 end
yellowfive@124 418 end
yellowfive@124 419 end
yellowfive@124 420
yellowfive@124 421 if hasSome then
yellowfive@124 422 return ret
yellowfive@124 423 else
yellowfive@124 424 return nil
yellowfive@124 425 end
yellowfive@124 426 end
yellowfive@185 427 ]]
yellowfive@124 428
yellowfive@57 429 -- get currently equipped items, store with currently active spec
yellowfive@57 430 local function readEquippedItems(ret)
yellowfive@124 431 local equippedItems = {};
yellowfive@191 432 --local loc = ItemLocation.CreateEmpty()
yellowfive@191 433 local item
yellowfive@57 434 for slotNum = 1, #Amr.SlotIds do
yellowfive@57 435 local slotId = Amr.SlotIds[slotNum]
yellowfive@57 436 local itemLink = GetInventoryItemLink("player", slotId)
yellowfive@57 437 if itemLink then
yellowfive@124 438 local itemData = Amr.ParseItemLink(itemLink)
yellowfive@124 439 if itemData then
yellowfive@191 440 item = Item:CreateFromEquipmentSlot(slotId)
yellowfive@191 441
yellowfive@191 442 -- seems to be of the form Item-1147-0-4000000XXXXXXXXX, so we take just the last 9 digits
yellowfive@191 443 itemData.guid = item:GetItemGUID()
yellowfive@191 444 if itemData.guid and strlen(itemData.guid) > 9 then
yellowfive@191 445 itemData.guid = strsub(itemData.guid, -9)
yellowfive@191 446 end
yellowfive@191 447
yellowfive@185 448 --[[
yellowfive@124 449 -- see if this is an azerite item and read azerite power ids
yellowfive@124 450 loc:SetEquipmentSlot(slotId)
yellowfive@124 451 if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then
yellowfive@124 452 local powers = Amr.ReadAzeritePowers(loc)
yellowfive@124 453 if powers then
yellowfive@124 454 itemData.azerite = powers
yellowfive@124 455 end
yellowfive@124 456 end
yellowfive@185 457 ]]
yellowfive@124 458
yellowfive@124 459 equippedItems[slotId] = itemData
yellowfive@124 460 end
yellowfive@57 461 end
yellowfive@57 462 end
yellowfive@57 463
yellowfive@57 464 -- store last-seen equipped gear for each spec
yellowfive@81 465 ret.Equipped[GetSpecialization()] = equippedItems
yellowfive@57 466 end
yellowfive@57 467
yellowfive@185 468 --[[
yellowfive@124 469 local function readHeartOfAzerothLevel(ret)
yellowfive@124 470 local azeriteItemLocation = C_AzeriteItem.FindActiveAzeriteItem();
yellowfive@124 471 if azeriteItemLocation then
yellowfive@124 472 local azeriteItem = Item:CreateFromItemLocation(azeriteItemLocation);
yellowfive@124 473 ret.HeartOfAzerothLevel = C_AzeriteItem.GetPowerLevel(azeriteItemLocation)
yellowfive@124 474 else
yellowfive@124 475 ret.HeartOfAzerothLevel = 0
yellowfive@124 476 end
yellowfive@124 477 end
yellowfive@185 478 ]]
yellowfive@124 479
yellowfive@124 480 -- Get just the player's currently equipped gear
yellowfive@124 481 function Amr:GetEquipped()
yellowfive@124 482 local ret= {}
yellowfive@124 483 ret.Equipped = {}
yellowfive@124 484 readEquippedItems(ret)
yellowfive@124 485 return ret
yellowfive@124 486 end
yellowfive@124 487
yellowfive@57 488 -- Get all data about the player as an object, includes:
yellowfive@57 489 -- serializer version
yellowfive@57 490 -- region/realm/name
yellowfive@57 491 -- guild
yellowfive@57 492 -- race
yellowfive@57 493 -- faction
yellowfive@57 494 -- level
yellowfive@57 495 -- professions
yellowfive@81 496 -- spec/talent for all specs
yellowfive@57 497 -- equipped gear for the current spec
yellowfive@57 498 --
yellowfive@81 499 function Amr:GetPlayerData()
yellowfive@57 500
yellowfive@57 501 local ret = {}
yellowfive@57 502
yellowfive@57 503 ret.Region = Amr.RegionNames[GetCurrentRegion()]
yellowfive@57 504 ret.Realm = GetRealmName()
yellowfive@57 505 ret.Name = UnitName("player")
yellowfive@57 506 ret.Guild = GetGuildInfo("player")
yellowfive@81 507 ret.ActiveSpec = GetSpecialization()
yellowfive@57 508 ret.Level = UnitLevel("player");
yellowfive@185 509 --readHeartOfAzerothLevel(ret)
yellowfive@124 510
yellowfive@133 511 local _, clsEn = UnitClass("player")
yellowfive@57 512 ret.Class = clsEn;
yellowfive@57 513
yellowfive@133 514 local _, raceEn = UnitRace("player")
yellowfive@57 515 ret.Race = raceEn;
yellowfive@57 516 ret.Faction = UnitFactionGroup("player")
yellowfive@57 517
yellowfive@57 518 ret.Professions = {};
yellowfive@57 519 local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions();
yellowfive@57 520 readProfessionInfo(prof1, ret)
yellowfive@57 521 readProfessionInfo(prof2, ret)
yellowfive@57 522 readProfessionInfo(archaeology, ret)
yellowfive@57 523 readProfessionInfo(fishing, ret)
yellowfive@57 524 readProfessionInfo(cooking, ret)
yellowfive@57 525 readProfessionInfo(firstAid, ret)
yellowfive@57 526
yellowfive@57 527 ret.Specs = {}
yellowfive@57 528 ret.Talents = {}
yellowfive@81 529 readSpecs(ret)
yellowfive@165 530
yellowfive@165 531 -- these get updated later, since need to cache info for inactive specs
yellowfive@185 532 --ret.UnlockedEssences = {}
yellowfive@185 533 --ret.Essences = {}
yellowfive@81 534
yellowfive@124 535 ret.Equipped = {}
yellowfive@57 536 readEquippedItems(ret)
yellowfive@57 537
yellowfive@57 538 return ret
yellowfive@57 539 end
yellowfive@57 540
yellowfive@57 541
yellowfive@57 542 ----------------------------------------------------------------------------------------
yellowfive@57 543 -- Serialization
yellowfive@57 544 ----------------------------------------------------------------------------------------
yellowfive@57 545
yellowfive@57 546 local function toCompressedNumberList(list)
yellowfive@57 547 -- ensure the values are numbers, sorted from lowest to highest
yellowfive@57 548 local nums = {}
yellowfive@57 549 for i, v in ipairs(list) do
yellowfive@57 550 table.insert(nums, tonumber(v))
yellowfive@57 551 end
yellowfive@57 552 table.sort(nums)
yellowfive@57 553
yellowfive@57 554 local ret = {}
yellowfive@57 555 local prev = 0
yellowfive@57 556 for i, v in ipairs(nums) do
yellowfive@57 557 local diff = v - prev
yellowfive@57 558 table.insert(ret, diff)
yellowfive@57 559 prev = v
yellowfive@57 560 end
yellowfive@57 561
yellowfive@57 562 return table.concat(ret, ",")
yellowfive@57 563 end
yellowfive@57 564
yellowfive@57 565 -- make this utility publicly available
yellowfive@57 566 function Amr:ToCompressedNumberList(list)
yellowfive@57 567 return toCompressedNumberList(list)
yellowfive@57 568 end
yellowfive@57 569
yellowfive@57 570 -- appends a list of items to the export
yellowfive@57 571 local function appendItemsToExport(fields, itemObjects)
yellowfive@57 572
yellowfive@57 573 -- sort by item id so we can compress it more easily
yellowfive@57 574 table.sort(itemObjects, function(a, b) return a.id < b.id end)
yellowfive@57 575
yellowfive@57 576 -- append to the export string
yellowfive@57 577 local prevItemId = 0
yellowfive@57 578 local prevGemId = 0
yellowfive@57 579 local prevEnchantId = 0
yellowfive@57 580 local prevUpgradeId = 0
yellowfive@57 581 local prevBonusId = 0
yellowfive@81 582 local prevLevel = 0
yellowfive@185 583 --local prevAzeriteId = 0
yellowfive@124 584 local prevRelicBonusId = 0
yellowfive@191 585
yellowfive@57 586 for i, itemData in ipairs(itemObjects) do
yellowfive@57 587 local itemParts = {}
yellowfive@57 588
yellowfive@57 589 table.insert(itemParts, itemData.id - prevItemId)
yellowfive@57 590 prevItemId = itemData.id
yellowfive@57 591
yellowfive@57 592 if itemData.slot ~= nil then table.insert(itemParts, "s" .. itemData.slot) end
yellowfive@124 593 --if itemData.suffixId ~= 0 then table.insert(itemParts, "f" .. itemData.suffixId) end
yellowfive@57 594 if itemData.upgradeId ~= 0 then
yellowfive@57 595 table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId))
yellowfive@57 596 prevUpgradeId = itemData.upgradeId
yellowfive@57 597 end
yellowfive@81 598 if itemData.level ~= 0 then
yellowfive@81 599 table.insert(itemParts, "v" .. (itemData.level - prevLevel))
yellowfive@81 600 prevLevel = itemData.level
yellowfive@81 601 end
yellowfive@57 602 if itemData.bonusIds then
yellowfive@57 603 for bIndex, bValue in ipairs(itemData.bonusIds) do
yellowfive@57 604 table.insert(itemParts, "b" .. (bValue - prevBonusId))
yellowfive@57 605 prevBonusId = bValue
yellowfive@57 606 end
yellowfive@124 607 end
yellowfive@124 608
yellowfive@185 609 --[[
yellowfive@124 610 if itemData.azerite then
yellowfive@124 611 for aIndex, aValue in ipairs(itemData.azerite) do
yellowfive@124 612 table.insert(itemParts, "a" .. (aValue - prevAzeriteId))
yellowfive@124 613 prevAzeriteId = aValue
yellowfive@124 614 end
yellowfive@124 615 end
yellowfive@185 616 ]]
yellowfive@185 617
yellowfive@81 618 if itemData.gemIds[1] ~= 0 then
yellowfive@81 619 table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
yellowfive@81 620 prevGemId = itemData.gemIds[1]
yellowfive@81 621 end
yellowfive@81 622 if itemData.gemIds[2] ~= 0 then
yellowfive@81 623 table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
yellowfive@81 624 prevGemId = itemData.gemIds[2]
yellowfive@81 625 end
yellowfive@81 626 if itemData.gemIds[3] ~= 0 then
yellowfive@81 627 table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
yellowfive@81 628 prevGemId = itemData.gemIds[3]
yellowfive@124 629 end
yellowfive@81 630
yellowfive@57 631 if itemData.enchantId ~= 0 then
yellowfive@57 632 table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId))
yellowfive@57 633 prevEnchantId = itemData.enchantId
yellowfive@57 634 end
yellowfive@124 635
yellowfive@124 636 if itemData.relicBonusIds and itemData.relicBonusIds[1] ~= nil then
yellowfive@124 637 for bIndex, bValue in ipairs(itemData.relicBonusIds[1]) do
yellowfive@124 638 table.insert(itemParts, "p" .. (bValue - prevRelicBonusId))
yellowfive@124 639 prevRelicBonusId = bValue
yellowfive@124 640 end
yellowfive@124 641 end
yellowfive@124 642
yellowfive@124 643 if itemData.relicBonusIds and itemData.relicBonusIds[2] ~= nil then
yellowfive@124 644 for bIndex, bValue in ipairs(itemData.relicBonusIds[2]) do
yellowfive@124 645 table.insert(itemParts, "q" .. (bValue - prevRelicBonusId))
yellowfive@124 646 prevRelicBonusId = bValue
yellowfive@124 647 end
yellowfive@124 648 end
yellowfive@124 649
yellowfive@124 650 if itemData.relicBonusIds and itemData.relicBonusIds[3] ~= nil then
yellowfive@124 651 for bIndex, bValue in ipairs(itemData.relicBonusIds[3]) do
yellowfive@124 652 table.insert(itemParts, "r" .. (bValue - prevRelicBonusId))
yellowfive@124 653 prevRelicBonusId = bValue
yellowfive@124 654 end
yellowfive@124 655 end
yellowfive@191 656
yellowfive@191 657 if itemData.guid then
yellowfive@191 658 table.insert(itemParts, "!" .. itemData.guid)
yellowfive@191 659 end
yellowfive@124 660
yellowfive@57 661 table.insert(fields, table.concat(itemParts, ""))
yellowfive@57 662 end
yellowfive@57 663 end
yellowfive@57 664
yellowfive@57 665 -- Serialize just the identity portion of a player (region/realm/name) in the same format used by the full serialization
yellowfive@57 666 function Amr:SerializePlayerIdentity(data)
yellowfive@57 667 local fields = {}
yellowfive@57 668 table.insert(fields, MINOR)
yellowfive@57 669 table.insert(fields, data.Region)
yellowfive@57 670 table.insert(fields, data.Realm)
yellowfive@57 671 table.insert(fields, data.Name)
yellowfive@57 672 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 673 end
yellowfive@57 674
yellowfive@57 675 -- Serialize player data gathered by GetPlayerData. This can be augmented with extra data if desired (augmenting used mainly by AskMrRobot addon).
yellowfive@57 676 -- Pass complete = true to do a complete export of this extra information, otherwise it is ignored.
yellowfive@57 677 -- Extra data can include:
yellowfive@57 678 -- equipped gear for the player's inactive spec, slot id to item link dictionary
yellowfive@57 679 -- Reputations
yellowfive@57 680 -- BagItems, BankItems, VoidItems, lists of item links
yellowfive@57 681 --
yellowfive@57 682 function Amr:SerializePlayerData(data, complete)
yellowfive@57 683
yellowfive@57 684 local fields = {}
yellowfive@57 685
yellowfive@57 686 -- compressed string uses a fixed order rather than inserting identifiers
yellowfive@57 687 table.insert(fields, MINOR)
yellowfive@57 688 table.insert(fields, data.Region)
yellowfive@57 689 table.insert(fields, data.Realm)
yellowfive@57 690 table.insert(fields, data.Name)
yellowfive@57 691
yellowfive@57 692 -- guild name
yellowfive@57 693 if data.Guild == nil then
yellowfive@57 694 table.insert(fields, "")
yellowfive@57 695 else
yellowfive@57 696 table.insert(fields, data.Guild)
yellowfive@57 697 end
yellowfive@57 698
yellowfive@57 699 -- race, default to pandaren if we can't read it for some reason
yellowfive@57 700 local raceval = Amr.RaceIds[data.Race]
yellowfive@57 701 if raceval == nil then raceval = 13 end
yellowfive@57 702 table.insert(fields, raceval)
yellowfive@57 703
yellowfive@57 704 -- faction, default to alliance if we can't read it for some reason
yellowfive@57 705 raceval = Amr.FactionIds[data.Faction]
yellowfive@57 706 if raceval == nil then raceval = 1 end
yellowfive@57 707 table.insert(fields, raceval)
yellowfive@57 708
yellowfive@124 709 table.insert(fields, data.Level)
yellowfive@185 710
yellowfive@57 711 local profs = {}
yellowfive@57 712 local noprofs = true
yellowfive@57 713 if data.Professions then
yellowfive@57 714 for k, v in pairs(data.Professions) do
yellowfive@57 715 local profval = Amr.ProfessionIds[k]
yellowfive@57 716 if profval ~= nil then
yellowfive@57 717 noprofs = false
yellowfive@57 718 table.insert(profs, profval .. ":" .. v)
yellowfive@57 719 end
yellowfive@57 720 end
yellowfive@57 721 end
yellowfive@57 722
yellowfive@57 723 if noprofs then
yellowfive@57 724 table.insert(profs, "0:0")
yellowfive@57 725 end
yellowfive@57 726
yellowfive@57 727 table.insert(fields, table.concat(profs, ","))
yellowfive@57 728
yellowfive@57 729 -- export specs
yellowfive@57 730 table.insert(fields, data.ActiveSpec)
yellowfive@81 731 for spec = 1, 4 do
yellowfive@57 732 if data.Specs[spec] and (complete or spec == data.ActiveSpec) then
yellowfive@57 733 table.insert(fields, ".s" .. spec) -- indicates the start of a spec block
yellowfive@81 734 table.insert(fields, data.Specs[spec])
yellowfive@165 735 table.insert(fields, data.Talents[spec] or "")
yellowfive@185 736 table.insert(fields, data.ActiveSoulbinds and data.ActiveSoulbinds[spec] or "0")
yellowfive@185 737
yellowfive@185 738 --[[
yellowfive@165 739 local essences = {}
yellowfive@165 740 if data.Essences and data.Essences[spec] then
yellowfive@165 741 for i, ess in ipairs(data.Essences[spec]) do
yellowfive@165 742 table.insert(essences, table.concat(ess, "."))
yellowfive@165 743 end
yellowfive@165 744 end
yellowfive@165 745 table.insert(fields, table.concat(essences, "_"))
yellowfive@185 746 ]]
yellowfive@57 747 end
yellowfive@57 748 end
yellowfive@57 749
yellowfive@57 750 -- export equipped gear
yellowfive@57 751 if data.Equipped then
yellowfive@81 752 for spec = 1, 4 do
yellowfive@57 753 if data.Equipped[spec] and (complete or spec == data.ActiveSpec) then
yellowfive@57 754 table.insert(fields, ".q" .. spec) -- indicates the start of an equipped gear block
yellowfive@57 755
yellowfive@57 756 local itemObjects = {}
yellowfive@124 757 for k, itemData in pairs(data.Equipped[spec]) do
yellowfive@57 758 itemData.slot = k
yellowfive@57 759 table.insert(itemObjects, itemData)
yellowfive@57 760 end
yellowfive@57 761
yellowfive@57 762 appendItemsToExport(fields, itemObjects)
yellowfive@57 763 end
yellowfive@57 764 end
yellowfive@57 765 end
yellowfive@165 766
yellowfive@185 767 -- export soulbind tree info
yellowfive@185 768 if data.Soulbinds then
yellowfive@185 769 table.insert(fields, ".sol")
yellowfive@185 770 for soulbindId, soulbindData in pairs(data.Soulbinds) do
yellowfive@185 771 table.insert(fields, string.format("u.%s.%s", soulbindId, soulbindData.UnlockedTier))
yellowfive@185 772 for tier, node in pairs(soulbindData.Nodes) do
yellowfive@185 773 table.insert(fields, table.concat(node, "."))
yellowfive@185 774 end
yellowfive@185 775 end
yellowfive@185 776 end
yellowfive@185 777
yellowfive@185 778 -- export unlocked conduits
yellowfive@185 779 if data.UnlockedConduits then
yellowfive@185 780 table.insert(fields, ".con")
yellowfive@185 781 for i, conduit in ipairs(data.UnlockedConduits) do
yellowfive@185 782 table.insert(fields, table.concat(conduit, "."))
yellowfive@185 783 end
yellowfive@185 784 end
yellowfive@185 785
yellowfive@185 786 --[[
yellowfive@165 787 -- export unlocked essences
yellowfive@165 788 if data.UnlockedEssences then
yellowfive@165 789 table.insert(fields, ".ess")
yellowfive@165 790 for i, ess in ipairs(data.UnlockedEssences) do
yellowfive@165 791 table.insert(fields, table.concat(ess, "_"))
yellowfive@165 792 end
yellowfive@165 793 end
yellowfive@185 794 ]]
yellowfive@185 795
yellowfive@124 796 -- if doing a complete export, include bank/bag items too
yellowfive@124 797 if complete then
yellowfive@124 798
yellowfive@57 799 local itemObjects = {}
yellowfive@57 800 if data.BagItems then
yellowfive@124 801 for i, itemData in ipairs(data.BagItems) do
yellowfive@124 802 if itemData then
yellowfive@57 803 table.insert(itemObjects, itemData)
yellowfive@57 804 end
yellowfive@57 805 end
yellowfive@57 806 end
yellowfive@127 807 if data.BankItems then
yellowfive@124 808 for i, itemData in ipairs(data.BankItems) do
yellowfive@127 809 if itemData then
yellowfive@57 810 table.insert(itemObjects, itemData)
yellowfive@57 811 end
yellowfive@57 812 end
yellowfive@124 813 end
yellowfive@124 814
yellowfive@57 815 table.insert(fields, ".inv")
yellowfive@57 816 appendItemsToExport(fields, itemObjects)
yellowfive@57 817 end
yellowfive@57 818
yellowfive@57 819 return "$" .. table.concat(fields, ";") .. "$"
yellowfive@57 820
yellowfive@57 821 end
yellowfive@57 822
yellowfive@165 823 --[[
yellowfive@57 824 -- Shortcut for the common use case: serialize the player's currently active setup with no extras.
yellowfive@57 825 function Amr:SerializePlayer()
yellowfive@57 826 local data = self:GetPlayerData()
yellowfive@57 827 return self:SerializePlayerData(data)
yellowfive@57 828 end
yellowfive@165 829 ]]
yellowfive@57 830
yellowfive@81 831 --[[
yellowfive@57 832 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 833 -- Character Snapshots
yellowfive@81 834 -- This feature snapshots a player's gear/talents/artifact when entering combat. It is enabled by default. Consumers
yellowfive@57 835 -- of this library can create a setting to enable/disable it as desired per a user setting.
yellowfive@57 836 --
yellowfive@57 837 -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that
yellowfive@57 838 -- your addon settings stay in sync with any other addon that may also be trying to control the enabled state.
yellowfive@57 839 --
yellowfive@57 840 -- Note that if a user has the main AMR addon installed, it will always enable snapshotting, and override any attempt
yellowfive@57 841 -- to disable it by immediately re-enabling it and thus re-triggering AMR_SNAPSHOT_STATE_CHANGED.
yellowfive@57 842 ----------------------------------------------------------------------------------------------------------------------
yellowfive@57 843 Amr._snapshotEnabled = true
yellowfive@57 844
yellowfive@57 845 -- Enable snapshotting of character data when entering combat. Sends this player's character data to anyone logging with the AskMrRobot addon.
yellowfive@57 846 function Amr:EnableSnapshots()
yellowfive@57 847 self._snapshotEnabled = true
yellowfive@57 848 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 849 end
yellowfive@57 850
yellowfive@57 851 -- Disable snapshotting of character data when entering combat.
yellowfive@57 852 function Amr:DisableSnapshots()
yellowfive@57 853 self._snapshotEnabled = false
yellowfive@57 854 self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled)
yellowfive@57 855 end
yellowfive@57 856
yellowfive@57 857 function Amr:IsSnapshotEnabled()
yellowfive@57 858 return self._snapshotEnabled
yellowfive@57 859 end
yellowfive@57 860
yellowfive@57 861
yellowfive@57 862 function Amr:PLAYER_REGEN_DISABLED()
yellowfive@57 863 --function Amr:GARRISON_MISSION_NPC_OPENED()
yellowfive@57 864
yellowfive@57 865 -- send data about this character when a player enters combat in a supported zone
yellowfive@57 866 if self._snapshotEnabled and Amr.IsSupportedInstance() then
yellowfive@57 867 local t = time()
yellowfive@57 868 local player = self:GetPlayerData()
yellowfive@57 869 local msg = self:SerializePlayerData(player)
yellowfive@57 870 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 871
yellowfive@57 872 self:SendCommMessage(Amr.ChatPrefix, msg, "RAID")
yellowfive@57 873 end
yellowfive@57 874 end
yellowfive@57 875
yellowfive@57 876 Amr:RegisterEvent("PLAYER_REGEN_DISABLED")
yellowfive@81 877 --Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table
yellowfive@122 878 ]]