yellowfive@57: -- AskMrRobot-Serializer will serialize and communicate character data between users. yellowfive@57: yellowfive@181: local MAJOR, MINOR = "AskMrRobot-Serializer", 85 yellowfive@57: local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR) yellowfive@57: yellowfive@57: if not Amr then return end -- already loaded by something else yellowfive@57: yellowfive@57: -- event and comm used for player snapshotting on entering combat yellowfive@57: LibStub("AceEvent-3.0"):Embed(Amr) yellowfive@57: LibStub("AceComm-3.0"):Embed(Amr) yellowfive@57: yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: -- Constants yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: yellowfive@57: -- prefix used for communicating gear snapshots created by the AMR serializer yellowfive@57: Amr.ChatPrefix = "_AMRS" yellowfive@57: yellowfive@57: -- map of region ids to AMR region names yellowfive@57: Amr.RegionNames = { yellowfive@57: [1] = "US", yellowfive@57: [2] = "KR", yellowfive@57: [3] = "EU", yellowfive@57: [4] = "TW", yellowfive@57: [5] = "CN" yellowfive@57: } yellowfive@57: yellowfive@57: -- map of the skillLine returned by profession API to the AMR profession name yellowfive@57: Amr.ProfessionSkillLineToName = { yellowfive@57: [794] = "Archaeology", yellowfive@57: [171] = "Alchemy", yellowfive@57: [164] = "Blacksmithing", yellowfive@57: [185] = "Cooking", yellowfive@57: [333] = "Enchanting", yellowfive@57: [202] = "Engineering", yellowfive@57: [129] = "First Aid", yellowfive@57: [356] = "Fishing", yellowfive@57: [182] = "Herbalism", yellowfive@57: [773] = "Inscription", yellowfive@57: [755] = "Jewelcrafting", yellowfive@57: [165] = "Leatherworking", yellowfive@57: [186] = "Mining", yellowfive@57: [393] = "Skinning", yellowfive@57: [197] = "Tailoring" yellowfive@57: } yellowfive@57: yellowfive@57: -- all slot IDs that we care about, ordered in AMR standard display order yellowfive@57: Amr.SlotIds = { 16, 17, 1, 2, 3, 15, 5, 9, 10, 6, 7, 8, 11, 12, 13, 14 } yellowfive@57: yellowfive@57: Amr.SpecIds = { yellowfive@57: [250] = 1, -- DeathKnightBlood yellowfive@57: [251] = 2, -- DeathKnightFrost yellowfive@57: [252] = 3, -- DeathKnightUnholy yellowfive@81: [577] = 4, -- DemonHunterHavoc yellowfive@81: [581] = 5, -- DemonHunterVengeance yellowfive@81: [102] = 6, -- DruidBalance yellowfive@81: [103] = 7, -- DruidFeral yellowfive@81: [104] = 8, -- DruidGuardian yellowfive@81: [105] = 9, -- DruidRestoration yellowfive@81: [253] = 10, -- HunterBeastMastery yellowfive@81: [254] = 11, -- HunterMarksmanship yellowfive@81: [255] = 12, -- HunterSurvival yellowfive@81: [62] = 13, -- MageArcane yellowfive@81: [63] = 14, -- MageFire yellowfive@81: [64] = 15, -- MageFrost yellowfive@81: [268] = 16, -- MonkBrewmaster yellowfive@81: [270] = 17, -- MonkMistweaver yellowfive@81: [269] = 18, -- MonkWindwalker yellowfive@81: [65] = 19, -- PaladinHoly yellowfive@81: [66] = 20, -- PaladinProtection yellowfive@81: [70] = 21, -- PaladinRetribution yellowfive@81: [256] = 22, -- PriestDiscipline yellowfive@81: [257] = 23, -- PriestHoly yellowfive@81: [258] = 24, -- PriestShadow yellowfive@81: [259] = 25, -- RogueAssassination yellowfive@81: [260] = 26, -- RogueOutlaw yellowfive@81: [261] = 27, -- RogueSubtlety yellowfive@81: [262] = 28, -- ShamanElemental yellowfive@81: [263] = 29, -- ShamanEnhancement yellowfive@81: [264] = 30, -- ShamanRestoration yellowfive@81: [265] = 31, -- WarlockAffliction yellowfive@81: [266] = 32, -- WarlockDemonology yellowfive@81: [267] = 33, -- WarlockDestruction yellowfive@81: [71] = 34, -- WarriorArms yellowfive@81: [72] = 35, -- WarriorFury yellowfive@81: [73] = 36 -- WarriorProtection yellowfive@57: } yellowfive@57: yellowfive@57: Amr.ClassIds = { yellowfive@57: ["NONE"] = 0, yellowfive@57: ["DEATHKNIGHT"] = 1, yellowfive@81: ["DEMONHUNTER"] = 2, yellowfive@81: ["DRUID"] = 3, yellowfive@81: ["HUNTER"] = 4, yellowfive@81: ["MAGE"] = 5, yellowfive@81: ["MONK"] = 6, yellowfive@81: ["PALADIN"] = 7, yellowfive@81: ["PRIEST"] = 8, yellowfive@81: ["ROGUE"] = 9, yellowfive@81: ["SHAMAN"] = 10, yellowfive@81: ["WARLOCK"] = 11, yellowfive@81: ["WARRIOR"] = 12, yellowfive@57: } yellowfive@57: yellowfive@57: Amr.ProfessionIds = { yellowfive@57: ["None"] = 0, yellowfive@57: ["Mining"] = 1, yellowfive@57: ["Skinning"] = 2, yellowfive@57: ["Herbalism"] = 3, yellowfive@57: ["Enchanting"] = 4, yellowfive@57: ["Jewelcrafting"] = 5, yellowfive@57: ["Engineering"] = 6, yellowfive@57: ["Blacksmithing"] = 7, yellowfive@57: ["Leatherworking"] = 8, yellowfive@57: ["Inscription"] = 9, yellowfive@57: ["Tailoring"] = 10, yellowfive@57: ["Alchemy"] = 11, yellowfive@57: ["Fishing"] = 12, yellowfive@57: ["Cooking"] = 13, yellowfive@57: ["First Aid"] = 14, yellowfive@57: ["Archaeology"] = 15 yellowfive@57: } yellowfive@57: yellowfive@57: Amr.RaceIds = { yellowfive@57: ["None"] = 0, yellowfive@57: ["BloodElf"] = 1, yellowfive@57: ["Draenei"] = 2, yellowfive@57: ["Dwarf"] = 3, yellowfive@57: ["Gnome"] = 4, yellowfive@57: ["Human"] = 5, yellowfive@57: ["NightElf"] = 6, yellowfive@57: ["Orc"] = 7, yellowfive@57: ["Tauren"] = 8, yellowfive@57: ["Troll"] = 9, yellowfive@57: ["Scourge"] = 10, yellowfive@57: ["Undead"] = 10, yellowfive@57: ["Goblin"] = 11, yellowfive@57: ["Worgen"] = 12, yellowfive@120: ["Pandaren"] = 13, yellowfive@120: ["Nightborne"] = 14, yellowfive@120: ["HighmountainTauren"] = 15, yellowfive@120: ["VoidElf"] = 16, yellowfive@135: ["LightforgedDraenei"] = 17, yellowfive@135: ["DarkIronDwarf"] = 18, yellowfive@155: ["MagharOrc"] = 19, yellowfive@155: ["ZandalariTroll"] = 20, yellowfive@173: ["KulTiran"] = 21, yellowfive@173: ["Vulpera"] = 22, yellowfive@173: ["Mechagnome"] = 23 yellowfive@57: } yellowfive@57: yellowfive@57: Amr.FactionIds = { yellowfive@57: ["None"] = 0, yellowfive@57: ["Alliance"] = 1, yellowfive@57: ["Horde"] = 2 yellowfive@57: } yellowfive@57: yellowfive@57: Amr.InstanceIds = { yellowfive@153: Uldir = 1861, yellowfive@155: Dazar = 2070, yellowfive@167: Storms = 2096, yellowfive@175: Palace = 2164, yellowfive@175: Nyalotha = 2217 yellowfive@57: } yellowfive@57: yellowfive@57: -- instances that AskMrRobot currently supports logging for yellowfive@57: Amr.SupportedInstanceIds = { yellowfive@153: [1861] = true, yellowfive@155: [2070] = true, yellowfive@167: [2096] = true, yellowfive@175: [2164] = true, yellowfive@175: [2217] = true yellowfive@57: } yellowfive@57: yellowfive@57: yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: -- Public Utility Methods yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: yellowfive@81: local function readBonusIdList(parts, first, last) yellowfive@124: local ret = {} yellowfive@81: for i = first, last do yellowfive@81: table.insert(ret, tonumber(parts[i])) yellowfive@81: end yellowfive@81: table.sort(ret) yellowfive@81: return ret yellowfive@81: end yellowfive@81: yellowfive@124: -- 1 2 3 4 5 6 7 8 9 10 11 12 yellowfive@124: -- itemId:ench:gem1 :gem2 :gem3 :gem4:suf:uid:lvl:spec:flags :instdiffid:numbonusIDs:bonusIDs1...n :varies:?:relic bonus ids yellowfive@124: --|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: -- yellowfive@57: -- get an object with all of the parts of the item link format that we care about yellowfive@57: function Amr.ParseItemLink(itemLink) yellowfive@57: if not itemLink then return nil end yellowfive@57: yellowfive@57: local str = string.match(itemLink, "|Hitem:([\-%d:]+)|") yellowfive@57: if not str then return nil end yellowfive@57: yellowfive@57: local parts = { strsplit(":", str) } yellowfive@57: yellowfive@124: local item = {} yellowfive@124: item.link = itemLink yellowfive@81: item.id = tonumber(parts[1]) or 0 yellowfive@81: item.enchantId = tonumber(parts[2]) or 0 yellowfive@81: item.gemIds = { tonumber(parts[3]) or 0, tonumber(parts[4]) or 0, tonumber(parts[5]) or 0, tonumber(parts[6]) or 0 } yellowfive@81: item.suffixId = math.abs(tonumber(parts[7]) or 0) -- convert suffix to positive number, that's what we use in our code yellowfive@81: -- part 8 is some unique ID... we never really used it yellowfive@81: -- part 9 is current player level yellowfive@81: -- part 10 is player spec yellowfive@81: local upgradeIdType = tonumber(parts[11]) or 0 -- part 11 indicates what kind of upgrade ID is just after the bonus IDs yellowfive@81: -- part 12 is instance difficulty id yellowfive@57: yellowfive@81: local numBonuses = tonumber(parts[13]) or 0 yellowfive@81: local offset = numBonuses yellowfive@81: if numBonuses > 0 then yellowfive@81: item.bonusIds = readBonusIdList(parts, 14, 13 + numBonuses) yellowfive@57: end yellowfive@69: yellowfive@81: item.upgradeId = 0 yellowfive@81: item.level = 0 yellowfive@81: yellowfive@124: -- the next part after bonus IDs depends on the upgrade id type yellowfive@81: if upgradeIdType == 4 then yellowfive@81: item.upgradeId = tonumber(parts[14 + offset]) or 0 yellowfive@81: elseif upgradeIdType == 512 then yellowfive@81: item.level = tonumber(parts[14 + offset]) or 0 yellowfive@124: elseif #parts > 16 + offset then yellowfive@124: -- check for relic info yellowfive@124: item.relicBonusIds = { nil, nil, nil } yellowfive@124: numBonuses = tonumber(parts[16 + offset]) yellowfive@124: if numBonuses then yellowfive@124: if numBonuses > 0 then yellowfive@124: item.relicBonusIds[1] = readBonusIdList(parts, 17 + offset, 16 + offset + numBonuses) yellowfive@124: end yellowfive@124: yellowfive@129: offset = offset + numBonuses yellowfive@124: if #parts > 17 + offset then yellowfive@124: numBonuses = tonumber(parts[17 + offset]) yellowfive@129: if numBonuses then yellowfive@129: if numBonuses > 0 then yellowfive@129: item.relicBonusIds[2] = readBonusIdList(parts, 18 + offset, 17 + offset + numBonuses) yellowfive@129: end yellowfive@129: yellowfive@129: offset= offset + numBonuses yellowfive@129: if #parts > 18 + offset then yellowfive@129: numBonuses = tonumber(parts[18 + offset]) yellowfive@129: if numBonuses then yellowfive@129: if numBonuses > 0 then yellowfive@129: item.relicBonusIds[3] = readBonusIdList(parts, 19 + offset, 18 + offset + numBonuses) yellowfive@129: end yellowfive@129: end yellowfive@129: end yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@69: end yellowfive@81: yellowfive@57: return item yellowfive@57: end yellowfive@57: yellowfive@135: local AZERITE_EMPOWERED_BONUS_ID = 4775 yellowfive@135: yellowfive@135: function Amr.GetItemUniqueId(item, noUpgrade, noAzeriteEmpoweredBonusId) yellowfive@81: if not item then return "" end yellowfive@81: local ret = item.id .. "" yellowfive@81: if item.bonusIds then yellowfive@135: for i = 1, #item.bonusIds do yellowfive@135: if not noAzeriteEmpoweredBonusId or item.bonusIds[i] ~= AZERITE_EMPOWERED_BONUS_ID then yellowfive@135: ret = ret .. "b" .. item.bonusIds[i] yellowfive@135: end yellowfive@81: end yellowfive@81: end yellowfive@81: if item.suffixId ~= 0 then yellowfive@81: ret = ret .. "s" .. item.suffixId yellowfive@81: end yellowfive@81: if not noUpgrade and item.upgradeId ~= 0 then yellowfive@81: ret = ret .. "u" .. item.upgradeId yellowfive@81: end yellowfive@81: if item.level ~= 0 then yellowfive@81: ret = ret .. "v" .. item.level yellowfive@81: end yellowfive@81: return ret yellowfive@81: end yellowfive@81: yellowfive@57: -- returns true if this is an instance that AskMrRobot supports for logging yellowfive@57: function Amr.IsSupportedInstanceId(instanceMapID) yellowfive@57: if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then yellowfive@57: return true yellowfive@57: else yellowfive@57: return false yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- returns true if currently in a supported instance for logging yellowfive@57: function Amr.IsSupportedInstance() yellowfive@133: local _, _, _, _, _, _, _, instanceMapID = GetInstanceInfo() yellowfive@57: return Amr.IsSupportedInstanceId(instanceMapID) yellowfive@57: end yellowfive@57: yellowfive@133: --[[ yellowfive@81: -- scanning tooltip b/c for some odd reason the api has no way to get basic item properties... yellowfive@81: -- so you have to generate a fake item tooltip and search for pre-defined strings in the display text yellowfive@81: local _scanTt yellowfive@81: function Amr.GetScanningTooltip() yellowfive@81: if not _scanTt then yellowfive@81: _scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate") yellowfive@81: _scanTt:SetOwner(UIParent, "ANCHOR_NONE") yellowfive@81: end yellowfive@81: return _scanTt yellowfive@81: end yellowfive@81: yellowfive@81: -- 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: function Amr.GetItemTooltip(bagId, slotId, link) yellowfive@81: local tt = Amr.GetScanningTooltip() yellowfive@81: tt:ClearLines() yellowfive@81: if bagId then yellowfive@81: tt:SetBagItem(bagId, slotId) yellowfive@81: elseif slotId then yellowfive@81: tt:SetInventoryItem("player", slotId) yellowfive@81: else yellowfive@81: tt:SetHyperlink(link) yellowfive@81: end yellowfive@81: return tt yellowfive@81: end yellowfive@133: ]] yellowfive@81: yellowfive@133: --[[ yellowfive@124: function Amr.GetItemLevel(bagId, slotId, link) yellowfive@81: local itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)") yellowfive@81: local tt = Amr.GetItemTooltip(bagId, slotId, link) yellowfive@81: yellowfive@81: local regions = { tt:GetRegions() } yellowfive@81: for i, region in ipairs(regions) do yellowfive@81: if region and region:GetObjectType() == "FontString" then yellowfive@81: local text = region:GetText() yellowfive@81: if text then yellowfive@81: ilvl = tonumber(text:match(itemLevelPattern)) yellowfive@81: if ilvl then yellowfive@81: return ilvl yellowfive@81: end yellowfive@81: end yellowfive@81: end yellowfive@81: end yellowfive@81: yellowfive@81: -- 0 means we couldn't find it for whatever reason yellowfive@81: return 0 yellowfive@81: end yellowfive@133: ]] yellowfive@81: yellowfive@57: yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: -- Character Reading yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: yellowfive@57: local function readProfessionInfo(prof, ret) yellowfive@57: if prof then yellowfive@133: local _, _, skillLevel, _, _, _, skillLine = GetProfessionInfo(prof); yellowfive@57: if Amr.ProfessionSkillLineToName[skillLine] ~= nil then yellowfive@57: ret.Professions[Amr.ProfessionSkillLineToName[skillLine]] = skillLevel; yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@124: -- get specs yellowfive@81: local function readSpecs(ret) yellowfive@57: yellowfive@81: for pos = 1, 4 do yellowfive@57: -- spec, convert game spec id to one of our spec ids yellowfive@81: local specId = GetSpecializationInfo(pos) yellowfive@57: if specId then yellowfive@81: ret.Specs[pos] = Amr.SpecIds[specId] yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@124: local function dump(o) yellowfive@124: if type(o) == 'table' then yellowfive@124: local s = '{ ' yellowfive@124: for k,v in pairs(o) do yellowfive@124: if type(k) ~= 'number' then k = '"'..k..'"' end yellowfive@124: s = s .. '['..k..'] = ' .. dump(v) .. ',' yellowfive@124: end yellowfive@124: return s .. '} ' yellowfive@124: else yellowfive@124: return tostring(o) yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- read azerite powers on the item in loc and put it on itemData yellowfive@124: function Amr.ReadAzeritePowers(loc) yellowfive@124: local ret = {} yellowfive@124: local hasSome = false yellowfive@124: yellowfive@124: local tiers = C_AzeriteEmpoweredItem.GetAllTierInfo(loc) yellowfive@124: for tier, tierInfo in ipairs(tiers) do yellowfive@124: for _, power in ipairs(tierInfo.azeritePowerIDs) do yellowfive@124: if C_AzeriteEmpoweredItem.IsPowerSelected(loc, power) then yellowfive@124: local powerInfo = C_AzeriteEmpoweredItem.GetPowerInfo(power) yellowfive@124: table.insert(ret, powerInfo.spellID) yellowfive@124: hasSome = true yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: if hasSome then yellowfive@124: return ret yellowfive@124: else yellowfive@124: return nil yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@57: -- get currently equipped items, store with currently active spec yellowfive@57: local function readEquippedItems(ret) yellowfive@124: local equippedItems = {}; yellowfive@124: local loc = ItemLocation.CreateEmpty() yellowfive@57: for slotNum = 1, #Amr.SlotIds do yellowfive@57: local slotId = Amr.SlotIds[slotNum] yellowfive@57: local itemLink = GetInventoryItemLink("player", slotId) yellowfive@57: if itemLink then yellowfive@124: local itemData = Amr.ParseItemLink(itemLink) yellowfive@124: if itemData then yellowfive@124: -- see if this is an azerite item and read azerite power ids yellowfive@124: loc:SetEquipmentSlot(slotId) yellowfive@124: if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then yellowfive@124: local powers = Amr.ReadAzeritePowers(loc) yellowfive@124: if powers then yellowfive@124: itemData.azerite = powers yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: equippedItems[slotId] = itemData yellowfive@124: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- store last-seen equipped gear for each spec yellowfive@81: ret.Equipped[GetSpecialization()] = equippedItems yellowfive@57: end yellowfive@57: yellowfive@124: local function readHeartOfAzerothLevel(ret) yellowfive@124: local azeriteItemLocation = C_AzeriteItem.FindActiveAzeriteItem(); yellowfive@124: if azeriteItemLocation then yellowfive@124: local azeriteItem = Item:CreateFromItemLocation(azeriteItemLocation); yellowfive@124: ret.HeartOfAzerothLevel = C_AzeriteItem.GetPowerLevel(azeriteItemLocation) yellowfive@124: else yellowfive@124: ret.HeartOfAzerothLevel = 0 yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- Get just the player's currently equipped gear yellowfive@124: function Amr:GetEquipped() yellowfive@124: local ret= {} yellowfive@124: ret.Equipped = {} yellowfive@124: readEquippedItems(ret) yellowfive@124: return ret yellowfive@124: end yellowfive@124: yellowfive@57: -- Get all data about the player as an object, includes: yellowfive@57: -- serializer version yellowfive@57: -- region/realm/name yellowfive@57: -- guild yellowfive@57: -- race yellowfive@57: -- faction yellowfive@57: -- level yellowfive@57: -- professions yellowfive@81: -- spec/talent for all specs yellowfive@57: -- equipped gear for the current spec yellowfive@57: -- yellowfive@81: function Amr:GetPlayerData() yellowfive@57: yellowfive@57: local ret = {} yellowfive@57: yellowfive@57: ret.Region = Amr.RegionNames[GetCurrentRegion()] yellowfive@57: ret.Realm = GetRealmName() yellowfive@57: ret.Name = UnitName("player") yellowfive@57: ret.Guild = GetGuildInfo("player") yellowfive@81: ret.ActiveSpec = GetSpecialization() yellowfive@57: ret.Level = UnitLevel("player"); yellowfive@124: readHeartOfAzerothLevel(ret) yellowfive@124: yellowfive@133: local _, clsEn = UnitClass("player") yellowfive@57: ret.Class = clsEn; yellowfive@57: yellowfive@133: local _, raceEn = UnitRace("player") yellowfive@57: ret.Race = raceEn; yellowfive@57: ret.Faction = UnitFactionGroup("player") yellowfive@57: yellowfive@57: ret.Professions = {}; yellowfive@57: local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions(); yellowfive@57: readProfessionInfo(prof1, ret) yellowfive@57: readProfessionInfo(prof2, ret) yellowfive@57: readProfessionInfo(archaeology, ret) yellowfive@57: readProfessionInfo(fishing, ret) yellowfive@57: readProfessionInfo(cooking, ret) yellowfive@57: readProfessionInfo(firstAid, ret) yellowfive@57: yellowfive@57: ret.Specs = {} yellowfive@57: ret.Talents = {} yellowfive@81: readSpecs(ret) yellowfive@165: yellowfive@165: -- these get updated later, since need to cache info for inactive specs yellowfive@165: ret.UnlockedEssences = {} yellowfive@165: ret.Essences = {} yellowfive@81: yellowfive@124: ret.Equipped = {} yellowfive@57: readEquippedItems(ret) yellowfive@57: yellowfive@57: return ret yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: -- Serialization yellowfive@57: ---------------------------------------------------------------------------------------- yellowfive@57: yellowfive@57: local function toCompressedNumberList(list) yellowfive@57: -- ensure the values are numbers, sorted from lowest to highest yellowfive@57: local nums = {} yellowfive@57: for i, v in ipairs(list) do yellowfive@57: table.insert(nums, tonumber(v)) yellowfive@57: end yellowfive@57: table.sort(nums) yellowfive@57: yellowfive@57: local ret = {} yellowfive@57: local prev = 0 yellowfive@57: for i, v in ipairs(nums) do yellowfive@57: local diff = v - prev yellowfive@57: table.insert(ret, diff) yellowfive@57: prev = v yellowfive@57: end yellowfive@57: yellowfive@57: return table.concat(ret, ",") yellowfive@57: end yellowfive@57: yellowfive@57: -- make this utility publicly available yellowfive@57: function Amr:ToCompressedNumberList(list) yellowfive@57: return toCompressedNumberList(list) yellowfive@57: end yellowfive@57: yellowfive@57: -- appends a list of items to the export yellowfive@57: local function appendItemsToExport(fields, itemObjects) yellowfive@57: yellowfive@57: -- sort by item id so we can compress it more easily yellowfive@57: table.sort(itemObjects, function(a, b) return a.id < b.id end) yellowfive@57: yellowfive@57: -- append to the export string yellowfive@57: local prevItemId = 0 yellowfive@57: local prevGemId = 0 yellowfive@57: local prevEnchantId = 0 yellowfive@57: local prevUpgradeId = 0 yellowfive@57: local prevBonusId = 0 yellowfive@81: local prevLevel = 0 yellowfive@124: local prevAzeriteId = 0 yellowfive@124: local prevRelicBonusId = 0 yellowfive@57: for i, itemData in ipairs(itemObjects) do yellowfive@57: local itemParts = {} yellowfive@57: yellowfive@57: table.insert(itemParts, itemData.id - prevItemId) yellowfive@57: prevItemId = itemData.id yellowfive@57: yellowfive@57: if itemData.slot ~= nil then table.insert(itemParts, "s" .. itemData.slot) end yellowfive@124: --if itemData.suffixId ~= 0 then table.insert(itemParts, "f" .. itemData.suffixId) end yellowfive@57: if itemData.upgradeId ~= 0 then yellowfive@57: table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId)) yellowfive@57: prevUpgradeId = itemData.upgradeId yellowfive@57: end yellowfive@81: if itemData.level ~= 0 then yellowfive@81: table.insert(itemParts, "v" .. (itemData.level - prevLevel)) yellowfive@81: prevLevel = itemData.level yellowfive@81: end yellowfive@57: if itemData.bonusIds then yellowfive@57: for bIndex, bValue in ipairs(itemData.bonusIds) do yellowfive@57: table.insert(itemParts, "b" .. (bValue - prevBonusId)) yellowfive@57: prevBonusId = bValue yellowfive@57: end yellowfive@124: end yellowfive@124: yellowfive@124: if itemData.azerite then yellowfive@124: for aIndex, aValue in ipairs(itemData.azerite) do yellowfive@124: table.insert(itemParts, "a" .. (aValue - prevAzeriteId)) yellowfive@124: prevAzeriteId = aValue yellowfive@124: end yellowfive@124: end yellowfive@81: yellowfive@81: if itemData.gemIds[1] ~= 0 then yellowfive@81: table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId)) yellowfive@81: prevGemId = itemData.gemIds[1] yellowfive@81: end yellowfive@81: if itemData.gemIds[2] ~= 0 then yellowfive@81: table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId)) yellowfive@81: prevGemId = itemData.gemIds[2] yellowfive@81: end yellowfive@81: if itemData.gemIds[3] ~= 0 then yellowfive@81: table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId)) yellowfive@81: prevGemId = itemData.gemIds[3] yellowfive@124: end yellowfive@81: yellowfive@57: if itemData.enchantId ~= 0 then yellowfive@57: table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId)) yellowfive@57: prevEnchantId = itemData.enchantId yellowfive@57: end yellowfive@124: yellowfive@124: if itemData.relicBonusIds and itemData.relicBonusIds[1] ~= nil then yellowfive@124: for bIndex, bValue in ipairs(itemData.relicBonusIds[1]) do yellowfive@124: table.insert(itemParts, "p" .. (bValue - prevRelicBonusId)) yellowfive@124: prevRelicBonusId = bValue yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: if itemData.relicBonusIds and itemData.relicBonusIds[2] ~= nil then yellowfive@124: for bIndex, bValue in ipairs(itemData.relicBonusIds[2]) do yellowfive@124: table.insert(itemParts, "q" .. (bValue - prevRelicBonusId)) yellowfive@124: prevRelicBonusId = bValue yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: if itemData.relicBonusIds and itemData.relicBonusIds[3] ~= nil then yellowfive@124: for bIndex, bValue in ipairs(itemData.relicBonusIds[3]) do yellowfive@124: table.insert(itemParts, "r" .. (bValue - prevRelicBonusId)) yellowfive@124: prevRelicBonusId = bValue yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@57: table.insert(fields, table.concat(itemParts, "")) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- Serialize just the identity portion of a player (region/realm/name) in the same format used by the full serialization yellowfive@57: function Amr:SerializePlayerIdentity(data) yellowfive@57: local fields = {} yellowfive@57: table.insert(fields, MINOR) yellowfive@57: table.insert(fields, data.Region) yellowfive@57: table.insert(fields, data.Realm) yellowfive@57: table.insert(fields, data.Name) yellowfive@57: return "$" .. table.concat(fields, ";") .. "$" yellowfive@57: end yellowfive@57: yellowfive@57: -- Serialize player data gathered by GetPlayerData. This can be augmented with extra data if desired (augmenting used mainly by AskMrRobot addon). yellowfive@57: -- Pass complete = true to do a complete export of this extra information, otherwise it is ignored. yellowfive@57: -- Extra data can include: yellowfive@57: -- equipped gear for the player's inactive spec, slot id to item link dictionary yellowfive@57: -- Reputations yellowfive@57: -- BagItems, BankItems, VoidItems, lists of item links yellowfive@57: -- yellowfive@57: function Amr:SerializePlayerData(data, complete) yellowfive@57: yellowfive@57: local fields = {} yellowfive@57: yellowfive@57: -- compressed string uses a fixed order rather than inserting identifiers yellowfive@57: table.insert(fields, MINOR) yellowfive@57: table.insert(fields, data.Region) yellowfive@57: table.insert(fields, data.Realm) yellowfive@57: table.insert(fields, data.Name) yellowfive@57: yellowfive@57: -- guild name yellowfive@57: if data.Guild == nil then yellowfive@57: table.insert(fields, "") yellowfive@57: else yellowfive@57: table.insert(fields, data.Guild) yellowfive@57: end yellowfive@57: yellowfive@57: -- race, default to pandaren if we can't read it for some reason yellowfive@57: local raceval = Amr.RaceIds[data.Race] yellowfive@57: if raceval == nil then raceval = 13 end yellowfive@57: table.insert(fields, raceval) yellowfive@57: yellowfive@57: -- faction, default to alliance if we can't read it for some reason yellowfive@57: raceval = Amr.FactionIds[data.Faction] yellowfive@57: if raceval == nil then raceval = 1 end yellowfive@57: table.insert(fields, raceval) yellowfive@57: yellowfive@124: table.insert(fields, data.Level) yellowfive@124: table.insert(fields, data.HeartOfAzerothLevel) yellowfive@57: yellowfive@57: local profs = {} yellowfive@57: local noprofs = true yellowfive@57: if data.Professions then yellowfive@57: for k, v in pairs(data.Professions) do yellowfive@57: local profval = Amr.ProfessionIds[k] yellowfive@57: if profval ~= nil then yellowfive@57: noprofs = false yellowfive@57: table.insert(profs, profval .. ":" .. v) yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: if noprofs then yellowfive@57: table.insert(profs, "0:0") yellowfive@57: end yellowfive@57: yellowfive@57: table.insert(fields, table.concat(profs, ",")) yellowfive@57: yellowfive@57: -- export specs yellowfive@57: table.insert(fields, data.ActiveSpec) yellowfive@81: for spec = 1, 4 do yellowfive@57: if data.Specs[spec] and (complete or spec == data.ActiveSpec) then yellowfive@57: table.insert(fields, ".s" .. spec) -- indicates the start of a spec block yellowfive@81: table.insert(fields, data.Specs[spec]) yellowfive@165: table.insert(fields, data.Talents[spec] or "") yellowfive@165: yellowfive@165: local essences = {} yellowfive@165: if data.Essences and data.Essences[spec] then yellowfive@165: for i, ess in ipairs(data.Essences[spec]) do yellowfive@165: table.insert(essences, table.concat(ess, ".")) yellowfive@165: end yellowfive@165: end yellowfive@165: table.insert(fields, table.concat(essences, "_")) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- export equipped gear yellowfive@57: if data.Equipped then yellowfive@81: for spec = 1, 4 do yellowfive@57: if data.Equipped[spec] and (complete or spec == data.ActiveSpec) then yellowfive@57: table.insert(fields, ".q" .. spec) -- indicates the start of an equipped gear block yellowfive@57: yellowfive@57: local itemObjects = {} yellowfive@124: for k, itemData in pairs(data.Equipped[spec]) do yellowfive@57: itemData.slot = k yellowfive@57: table.insert(itemObjects, itemData) yellowfive@57: end yellowfive@57: yellowfive@57: appendItemsToExport(fields, itemObjects) yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@165: yellowfive@165: -- export unlocked essences yellowfive@165: if data.UnlockedEssences then yellowfive@165: table.insert(fields, ".ess") yellowfive@165: for i, ess in ipairs(data.UnlockedEssences) do yellowfive@165: table.insert(fields, table.concat(ess, "_")) yellowfive@165: end yellowfive@165: end yellowfive@57: yellowfive@124: -- if doing a complete export, include bank/bag items too yellowfive@124: if complete then yellowfive@124: yellowfive@57: local itemObjects = {} yellowfive@57: if data.BagItems then yellowfive@124: for i, itemData in ipairs(data.BagItems) do yellowfive@124: if itemData then yellowfive@57: table.insert(itemObjects, itemData) yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@127: if data.BankItems then yellowfive@124: for i, itemData in ipairs(data.BankItems) do yellowfive@127: if itemData then yellowfive@57: table.insert(itemObjects, itemData) yellowfive@57: end yellowfive@57: end yellowfive@124: end yellowfive@124: yellowfive@57: table.insert(fields, ".inv") yellowfive@57: appendItemsToExport(fields, itemObjects) yellowfive@57: end yellowfive@57: yellowfive@57: return "$" .. table.concat(fields, ";") .. "$" yellowfive@57: yellowfive@57: end yellowfive@57: yellowfive@165: --[[ yellowfive@57: -- Shortcut for the common use case: serialize the player's currently active setup with no extras. yellowfive@57: function Amr:SerializePlayer() yellowfive@57: local data = self:GetPlayerData() yellowfive@57: return self:SerializePlayerData(data) yellowfive@57: end yellowfive@165: ]] yellowfive@57: yellowfive@81: --[[ yellowfive@57: ---------------------------------------------------------------------------------------------------------------------- yellowfive@57: -- Character Snapshots yellowfive@81: -- This feature snapshots a player's gear/talents/artifact when entering combat. It is enabled by default. Consumers yellowfive@57: -- of this library can create a setting to enable/disable it as desired per a user setting. yellowfive@57: -- yellowfive@57: -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that yellowfive@57: -- your addon settings stay in sync with any other addon that may also be trying to control the enabled state. yellowfive@57: -- yellowfive@57: -- Note that if a user has the main AMR addon installed, it will always enable snapshotting, and override any attempt yellowfive@57: -- to disable it by immediately re-enabling it and thus re-triggering AMR_SNAPSHOT_STATE_CHANGED. yellowfive@57: ---------------------------------------------------------------------------------------------------------------------- yellowfive@57: Amr._snapshotEnabled = true yellowfive@57: yellowfive@57: -- Enable snapshotting of character data when entering combat. Sends this player's character data to anyone logging with the AskMrRobot addon. yellowfive@57: function Amr:EnableSnapshots() yellowfive@57: self._snapshotEnabled = true yellowfive@57: self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled) yellowfive@57: end yellowfive@57: yellowfive@57: -- Disable snapshotting of character data when entering combat. yellowfive@57: function Amr:DisableSnapshots() yellowfive@57: self._snapshotEnabled = false yellowfive@57: self:SendMessage("AMR_SNAPSHOT_STATE_CHANGED", self._snapshotEnabled) yellowfive@57: end yellowfive@57: yellowfive@57: function Amr:IsSnapshotEnabled() yellowfive@57: return self._snapshotEnabled yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: function Amr:PLAYER_REGEN_DISABLED() yellowfive@57: --function Amr:GARRISON_MISSION_NPC_OPENED() yellowfive@57: yellowfive@57: -- send data about this character when a player enters combat in a supported zone yellowfive@57: if self._snapshotEnabled and Amr.IsSupportedInstance() then yellowfive@57: local t = time() yellowfive@57: local player = self:GetPlayerData() yellowfive@57: local msg = self:SerializePlayerData(player) yellowfive@57: 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: yellowfive@57: self:SendCommMessage(Amr.ChatPrefix, msg, "RAID") yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: Amr:RegisterEvent("PLAYER_REGEN_DISABLED") yellowfive@81: --Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table yellowfive@122: ]]