annotate AskMrRobot-Serializer/AskMrRobot-Serializer.lua @ 131:318c93e0cd6b v61

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