Skip to content

Instantly share code, notes, and snippets.

@seriallos
Created May 1, 2017 07:33
Show Gist options
  • Save seriallos/42fcb69ad5b6ae0db8d2bc8359851abb to your computer and use it in GitHub Desktop.
Save seriallos/42fcb69ad5b6ae0db8d2bc8359851abb to your computer and use it in GitHub Desktop.
Add gear from bags to /simc output
local _, Simulationcraft = ...
Simulationcraft = LibStub("AceAddon-3.0"):NewAddon(Simulationcraft, "Simulationcraft", "AceConsole-3.0", "AceEvent-3.0")
local OFFSET_ITEM_ID = 1
local OFFSET_ENCHANT_ID = 2
local OFFSET_GEM_ID_1 = 3
local OFFSET_GEM_ID_2 = 4
local OFFSET_GEM_ID_3 = 5
local OFFSET_GEM_ID_4 = 6
local OFFSET_SUFFIX_ID = 7
local OFFSET_FLAGS = 11
local OFFSET_BONUS_ID = 13
local OFFSET_UPGRADE_ID = 14 -- Flags = 0x4
-- Artifact stuff (adapted from LibArtifactData [https://www.wowace.com/addons/libartifactdata-1-0/], thanks!)
local ArtifactUI = _G.C_ArtifactUI
local HasArtifactEquipped = _G.HasArtifactEquipped
local SocketInventoryItem = _G.SocketInventoryItem
local Timer = _G.C_Timer
-- load stuff from extras.lua
local upgradeTable = Simulationcraft.upgradeTable
local slotNames = Simulationcraft.slotNames
local simcSlotNames = Simulationcraft.simcSlotNames
local equipSlotMap = Simulationcraft.equipSlotMap
local specNames = Simulationcraft.SpecNames
local profNames = Simulationcraft.ProfNames
local regionString = Simulationcraft.RegionString
local artifactTable = Simulationcraft.ArtifactTable
-- Most of the guts of this addon were based on a variety of other ones, including
-- Statslog, AskMrRobot, and BonusScanner. And a bunch of hacking around with AceGUI.
-- Many thanks to the authors of those addons, and to reia for fixing my awful amateur
-- coding mistakes regarding objects and namespaces.
function Simulationcraft:OnInitialize()
Simulationcraft:RegisterChatCommand('simc', 'PrintSimcProfile')
end
function Simulationcraft:OnEnable()
SimulationcraftTooltip:SetOwner(_G["UIParent"],"ANCHOR_NONE")
end
function Simulationcraft:OnDisable()
end
-- SimC tokenize function
local function tokenize(str)
str = str or ""
-- convert to lowercase and remove spaces
str = string.lower(str)
str = string.gsub(str, ' ', '_')
-- keep stuff we want, dumpster everything else
local s = ""
for i=1,str:len() do
-- keep digits 0-9
if str:byte(i) >= 48 and str:byte(i) <= 57 then
s = s .. str:sub(i,i)
-- keep lowercase letters
elseif str:byte(i) >= 97 and str:byte(i) <= 122 then
s = s .. str:sub(i,i)
-- keep %, +, ., _
elseif str:byte(i)==37 or str:byte(i)==43 or str:byte(i)==46 or str:byte(i)==95 then
s = s .. str:sub(i,i)
end
end
-- strip trailing spaces
if string.sub(s, s:len())=='_' then
s = string.sub(s, 0, s:len()-1)
end
return s
end
-- method for constructing the talent string
local function CreateSimcTalentString()
local talentInfo = {}
local maxTiers = 7
local maxColumns = 3
for tier = 1, maxTiers do
for column = 1, maxColumns do
local talentID, name, iconTexture, selected, available = GetTalentInfo(tier, column, GetActiveSpecGroup())
if selected then
talentInfo[tier] = column
end
end
end
local str = 'talents='
for i = 1, maxTiers do
if talentInfo[i] then
str = str .. talentInfo[i]
else
str = str .. '0'
end
end
return str
end
-- function that translates between the game's role values and ours
local function translateRole(spec_id, str)
local spec_role = Simulationcraft.RoleTable[spec_id]
if spec_role ~= nil then
return spec_role
end
if str == 'TANK' then
return 'tank'
elseif str == 'DAMAGER' then
return 'attack'
elseif str == 'HEALER' then
return 'heal'
else
return ''
end
end
-- ================= Artifact Information =======================
local function IsArtifactFrameOpen()
local ArtifactFrame = _G.ArtifactFrame
return ArtifactFrame and ArtifactFrame:IsShown() or false
end
function Simulationcraft:GetArtifactString()
if not HasArtifactEquipped() then
return nil
end
local artifactFrameOpen = IsArtifactFrameOpen()
if not artifactFrameOpen then
SocketInventoryItem(INVSLOT_MAINHAND)
end
local item_id = select(1, ArtifactUI.GetArtifactInfo())
if item_id == nil or item_id == 0 then
return nil
end
local artifact_id = self.ArtifactTable[item_id]
if artifact_id == nil then
return nil
end
-- Note, relics are handled by the item string
local str = 'artifact=' .. artifact_id .. ':0:0:0:0'
local powers = ArtifactUI.GetPowers()
for i = 1, #powers do
local power_id = powers[i]
local power_info = ArtifactUI.GetPowerInfo(power_id)
if power_info ~= nil and power_info.currentRank > 0 and power_info.currentRank - power_info.bonusRanks > 0 then
str = str .. ':' .. power_id .. ':' .. (power_info.currentRank - power_info.bonusRanks)
end
end
if not artifactFrameOpen then
HideUIPanel(ArtifactFrame)
end
return str
end
-- =================== Item Information =========================
local function GetItemStringFromItemLink(slotNum, itemLink)
local itemString = string.match(itemLink, "item:([%-?%d:]+)")
local itemSplit = {}
local simcItemOptions = {}
-- Split data into a table
for v in string.gmatch(itemString, "(%d*:?)") do
if v == ":" then
itemSplit[#itemSplit + 1] = 0
else
itemSplit[#itemSplit + 1] = string.gsub(v, ':', '')
end
end
-- Item id
local itemId = itemSplit[OFFSET_ITEM_ID]
simcItemOptions[#simcItemOptions + 1] = ',id=' .. itemId
-- Enchant
if tonumber(itemSplit[OFFSET_ENCHANT_ID]) > 0 then
simcItemOptions[#simcItemOptions + 1] = 'enchant_id=' .. itemSplit[OFFSET_ENCHANT_ID]
end
-- New style item suffix, old suffix style not supported
if tonumber(itemSplit[OFFSET_SUFFIX_ID]) ~= 0 then
simcItemOptions[#simcItemOptions + 1] = 'suffix=' .. itemSplit[OFFSET_SUFFIX_ID]
end
local flags = tonumber(itemSplit[OFFSET_FLAGS])
local bonuses = {}
for index=1, tonumber(itemSplit[OFFSET_BONUS_ID]) do
bonuses[#bonuses + 1] = itemSplit[OFFSET_BONUS_ID + index]
end
if #bonuses > 0 then
simcItemOptions[#simcItemOptions + 1] = 'bonus_id=' .. table.concat(bonuses, '/')
end
local rest_offset = OFFSET_BONUS_ID + #bonuses + 1
-- Upgrade level
if bit.band(flags, 0x4) == 0x4 then
local upgrade_id = tonumber(itemSplit[rest_offset])
if self.upgradeTable[upgrade_id] ~= nil and self.upgradeTable[upgrade_id] > 0 then
simcItemOptions[#simcItemOptions + 1] = 'upgrade=' .. self.upgradeTable[upgrade_id]
end
rest_offset = rest_offset + 1
end
-- Artifacts use this
if bit.band(flags, 0x100) == 0x100 then
rest_offset = rest_offset + 1 -- An unknown field
-- 7.2 added a new field to the item string if additional trait ranks are attained
-- for the artifact.
if bit.band(flags, 0x1000000) == 0x1000000 then
rest_offset = rest_offset + 1
end
local relic_str = ''
while rest_offset < #itemSplit do
local n_bonus_ids = tonumber(itemSplit[rest_offset])
rest_offset = rest_offset + 1
if n_bonus_ids == 0 then
relic_str = relic_str .. 0
else
for rbid = 1, n_bonus_ids do
relic_str = relic_str .. itemSplit[rest_offset]
if rbid < n_bonus_ids then
relic_str = relic_str .. ':'
end
rest_offset = rest_offset + 1
end
end
if rest_offset < #itemSplit then
relic_str = relic_str .. '/'
end
end
if relic_str ~= '' then
simcItemOptions[#simcItemOptions + 1] = 'relic_id=' .. relic_str
end
end
-- Some leveling quest items seem to use this, it'll include the drop level of the item
if bit.band(flags, 0x200) == 0x200 then
simcItemOptions[#simcItemOptions + 1] = 'drop_level=' .. itemSplit[rest_offset]
rest_offset = rest_offset + 1
end
-- Gems
local gems = {}
for i=1, 4 do -- hardcoded here to just grab all 4 sockets
local _,gemLink = GetItemGem(itemLink, i)
if gemLink then
local gemDetail = string.match(gemLink, "item[%-?%d:]+")
gems[#gems + 1] = string.match(gemDetail, "item:(%d+):" )
elseif bit.band(flags, 0x100) == 0x100 then
gems[#gems + 1] = "0"
end
end
if #gems > 0 then
simcItemOptions[#simcItemOptions + 1] = 'gem_id=' .. table.concat(gems, '/')
end
return simcSlotNames[slotNum] .. "=" .. table.concat(simcItemOptions, ',')
end
function Simulationcraft:GetItemStrings()
local items = {}
for slotNum=1, #slotNames do
local slotId = GetInventorySlotInfo(slotNames[slotNum])
local itemLink = GetInventoryItemLink('player', slotId)
-- if we don't have an item link, we don't care
if itemLink then
items[slotNum] = GetItemStringFromItemLink(slotNum, itemLink)
end
end
return items
end
function Simulationcraft:GetBagItemStrings()
local bagItems = {}
for bagIndex=0, NUM_BAG_SLOTS do
for bagSlotIndex=0, GetContainerNumSlots(bagIndex) do
local itemLink = GetContainerItemLink(bagIndex, bagSlotIndex)
if itemLink then
local name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture, vendorPrice = GetItemInfo(itemLink)
-- find all equippable, non-artifact items
if IsEquippableItem(itemLink) and (class == 'Armor' or class == 'Weapon') and quality ~= 6 then
bagItems[#bagItems + 1] = {
string = GetItemStringFromItemLink(equipSlotMap[equipSlot], itemLink),
name = name .. ' (' .. iLevel .. ')'
}
end
end
end
end
return bagItems
end
-- This is the workhorse function that constructs the profile
function Simulationcraft:PrintSimcProfile()
-- Basic player info
local playerName = UnitName('player')
local _, playerClass = UnitClass('player')
local playerLevel = UnitLevel('player')
local playerRealm = GetRealmName()
local playerRegion = regionString[GetCurrentRegion()]
-- Race info
local _, playerRace = UnitRace('player')
-- fix some races to match SimC format
if playerRace == 'BloodElf' then
playerRace = 'Blood Elf'
elseif playerRace == 'NightElf' then
playerRace = 'Night Elf'
elseif playerRace == 'Scourge' then --lulz
playerRace = 'Undead'
end
-- Spec info
local role, globalSpecID
local specId = GetSpecialization()
if specId then
globalSpecID,_,_,_,_,role = GetSpecializationInfo(specId)
end
local playerSpec = specNames[ globalSpecID ]
-- Professions
local pid1, pid2 = GetProfessions()
local firstProf, firstProfRank, secondProf, secondProfRank, profOneId, profTwoId
if pid1 then
_,_,firstProfRank,_,_,_,profOneId = GetProfessionInfo(pid1)
end
if pid2 then
secondProf,_,secondProfRank,_,_,_,profTwoId = GetProfessionInfo(pid2)
end
firstProf = profNames[ profOneId ]
secondProf = profNames[ profTwoId ]
local playerProfessions = ''
if pid1 or pid2 then
playerProfessions = 'professions='
if pid1 then
playerProfessions = playerProfessions..tokenize(firstProf)..'='..tostring(firstProfRank)..'/'
end
if pid2 then
playerProfessions = playerProfessions..tokenize(secondProf)..'='..tostring(secondProfRank)
end
else
playerProfessions = ''
end
-- Construct SimC-compatible strings from the basic information
local player = tokenize(playerClass) .. '="' .. playerName .. '"'
playerLevel = 'level=' .. playerLevel
playerRace = 'race=' .. tokenize(playerRace)
playerRole = 'role=' .. translateRole(globalSpecID, role)
playerSpec = 'spec=' .. tokenize(playerSpec)
playerRealm = 'server=' .. tokenize(playerRealm)
playerRegion = 'region=' .. tokenize(playerRegion)
-- Talents are more involved - method to handle them
local playerTalents = CreateSimcTalentString()
local playerArtifact = self:GetArtifactString()
-- Build the output string for the player (not including gear)
local simulationcraftProfile = player .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerLevel .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerRace .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerRegion .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerRealm .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerRole .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerProfessions .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerTalents .. '\n'
simulationcraftProfile = simulationcraftProfile .. playerSpec .. '\n'
if playerArtifact ~= nil then
simulationcraftProfile = simulationcraftProfile .. playerArtifact .. '\n'
end
simulationcraftProfile = simulationcraftProfile .. '\n'
-- Method that gets gear information
local items = Simulationcraft:GetItemStrings()
-- output gear
for slotNum=1, #slotNames do
if items[slotNum] then
simulationcraftProfile = simulationcraftProfile .. items[slotNum] .. '\n'
end
end
simulationcraftProfile = simulationcraftProfile .. '\n'
-- output gear from bags
local bagItems = Simulationcraft:GetBagItemStrings()
simulationcraftProfile = simulationcraftProfile .. '### Item strings from bags' .. '\n'
for i=1, #bagItems do
simulationcraftProfile = simulationcraftProfile .. '# ' .. bagItems[i].name .. '\n'
simulationcraftProfile = simulationcraftProfile .. '# ' .. bagItems[i].string .. '\n'
end
-- sanity checks - if there's anything that makes the output completely invalid, punt!
if specId==nil then
simulationcraftProfile = "Error: You need to pick a spec!"
end
-- show the appropriate frames
SimcCopyFrame:Show()
SimcCopyFrameScroll:Show()
SimcCopyFrameScrollText:Show()
SimcCopyFrameScrollText:SetText(simulationcraftProfile)
SimcCopyFrameScrollText:HighlightText()
end
local _, Simulationcraft = ...
-- Artifact lookup
Simulationcraft.ArtifactTable = {
-- Death Knight
[128402] = 15,
[128292] = 12,
[128403] = 16,
-- Demon Hunter
[127829] = 3,
[128832] = 60,
-- Druid
[128858] = 59,
[128860] = 58,
[128821] = 57,
[128306] = 13,
-- Hunter
[128861] = 56,
[128826] = 55,
[128808] = 34,
-- Mage
[127857] = 4,
[128820] = 54,
[128862] = 53,
-- Monk
[128938] = 52,
[128937] = 51,
[128940] = 50,
-- Paladin
[128823] = 48,
[128866] = 49,
[120978] = 2,
-- Priest
[128868] = 46,
[128825] = 45,
[128827] = 47,
-- Rogue
[128870] = 43,
[128872] = 44,
[128476] = 17,
-- Shaman
[128935] = 40,
[128819] = 41,
[128911] = 32,
-- Warlock
[128942] = 39,
[128943] = 37,
[128941] = 38,
-- Warrior
[128910] = 36,
[128908] = 35,
[128289] = 11
}
Simulationcraft.RoleTable = {
-- Death Knight
[250] = 'tank',
[251] = 'attack',
[252] = 'attack',
-- Demon Hunter
[577] = 'attack',
[581] = 'tank',
-- Druid
[102] = 'spell',
[103] = 'attack',
[104] = 'tank',
[105] = 'heal',
-- Hunter
[253] = 'attack',
[254] = 'attack',
[255] = 'attack',
-- Mage
[62] = 'spell',
[63] = 'spell',
[64] = 'spell',
-- Monk
[268] = 'tank',
[269] = 'attack',
[270] = 'hybrid',
-- Paladin
[65] = 'heal',
[66] = 'tank',
[70] = 'attack',
-- Priest
[256] = 'spell',
[257] = 'heal',
[258] = 'spell',
-- Rogue
[259] = 'attack',
[260] = 'attack',
[261] = 'attack',
-- Shaman
[262] = 'spell',
[263] = 'attack',
[264] = 'heal',
-- Warlock
[265] = 'spell',
[266] = 'spell',
[267] = 'spell',
-- Warrior
[71] = 'attack',
[72] = 'attack',
[73] = 'attack'
}
-- regionID lookup
Simulationcraft.RegionString = {
[1] = 'us',
[2] = 'kr',
[3] = 'eu',
[4] = 'tw',
[5] = 'cn'
}
-- non-localized profession names from ids
Simulationcraft.ProfNames = {
[129] = 'First Aid',
[164] = 'Blacksmithing',
[165] = 'Leatherworking',
[171] = 'Alchemy',
[182] = 'Herbalism',
[184] = 'Cooking',
[186] = 'Mining',
[197] = 'Tailoring',
[202] = 'Engineering',
[333] = 'Enchanting',
[356] = 'Fishing',
[393] = 'Skinning',
[755] = 'Jewelcrafting',
[773] = 'Inscription',
[794] = 'Archaeology'
}
-- non-localized spec names from spec ids
Simulationcraft.SpecNames = {
-- Death Knight
[250] = 'Blood',
[251] = 'Frost',
[252] = 'Unholy',
-- Demon Hunter
[577] = 'Havoc',
[581] = 'Vengeance',
-- Druid
[102] = 'Balance',
[103] = 'Feral',
[104] = 'Guardian',
[105] = 'Restoration',
-- Hunter
[253] = 'Beast Mastery',
[254] = 'Marksmanship',
[255] = 'Survival',
-- Mage
[62] = 'Arcane',
[63] = 'Fire',
[64] = 'Frost',
-- Monk
[268] = 'Brewmaster',
[269] = 'Windwalker',
[270] = 'Mistweaver',
-- Paladin
[65] = 'Holy',
[66] = 'Protection',
[70] = 'Retribution',
-- Priest
[256] = 'Discipline',
[257] = 'Holy',
[258] = 'Shadow',
-- Rogue
[259] = 'Assassination',
[260] = 'Outlaw',
[261] = 'Subtlety',
-- Shaman
[262] = 'Elemental',
[263] = 'Enhancement',
[264] = 'Restoration',
-- Warlock
[265] = 'Affliction',
[266] = 'Demonology',
[267] = 'Destruction',
-- Warrior
[71] = 'Arms',
[72] = 'Fury',
[73] = 'Protection'
}
-- slot name conversion stuff
Simulationcraft.slotNames = {"HeadSlot", "NeckSlot", "ShoulderSlot", "BackSlot", "ChestSlot", "ShirtSlot", "TabardSlot", "WristSlot", "HandsSlot", "WaistSlot", "LegsSlot", "FeetSlot", "Finger0Slot", "Finger1Slot", "Trinket0Slot", "Trinket1Slot", "MainHandSlot", "SecondaryHandSlot", "AmmoSlot" };
Simulationcraft.simcSlotNames = {'head','neck','shoulder','back','chest','shirt','tabard','wrist','hands','waist','legs','feet','finger1','finger2','trinket1','trinket2','main_hand','off_hand','ammo'}
-- map equipSlot from GetItemInfo to simcSlotName index
Simulationcraft.equipSlotMap = {
INVTYPE_HEAD = 1,
INVTYPE_NECK = 2,
INVTYPE_SHOULDER = 3,
INVTYPE_CLOAK = 4,
INVTYPE_CHEST = 5,
INVTYPE_ROBE = 5,
INVTYPE_BODY = 5,
INVTYPE_SHIRT = 6,
INVTYPE_TABARD = 7,
INVTYPE_WRIST = 8,
INVTYPE_HAND = 9,
INVTYPE_WAIST = 10,
INVTYPE_LEGS = 11,
INVTYPE_FEET = 12,
INVTYPE_FINGER = 13,
-- skip finger 2
INVTYPE_TRINKET = 15,
-- skip trinket 2
INVTYPE_MAINHAND = 17,
INVTYPE_OFFHAND = 18,
INVTYPE_AMMO = 19
}
-- table for conversion to upgrade level, stolen from AMR (<3)
Simulationcraft.upgradeTable = {
[0] = 0,
[1] = 1, -- 1/1 -> 8
[373] = 1, -- 1/2 -> 4
[374] = 2, -- 2/2 -> 8
[375] = 1, -- 1/3 -> 4
[376] = 2, -- 2/3 -> 4
[377] = 3, -- 3/3 -> 4
[378] = 1, -- 1/1 -> 7
[379] = 1, -- 1/2 -> 4
[380] = 2, -- 2/2 -> 4
[445] = 0, -- 0/2 -> 0
[446] = 1, -- 1/2 -> 4
[447] = 2, -- 2/2 -> 8
[451] = 0, -- 0/1 -> 0
[452] = 1, -- 1/1 -> 8
[453] = 0, -- 0/2 -> 0
[454] = 1, -- 1/2 -> 4
[455] = 2, -- 2/2 -> 8
[456] = 0, -- 0/1 -> 0
[457] = 1, -- 1/1 -> 8
[458] = 0, -- 0/4 -> 0
[459] = 1, -- 1/4 -> 4
[460] = 2, -- 2/4 -> 8
[461] = 3, -- 3/4 -> 12
[462] = 4, -- 4/4 -> 16
[465] = 0, -- 0/2 -> 0
[466] = 1, -- 1/2 -> 4
[467] = 2, -- 2/2 -> 8
[468] = 0, -- 0/4 -> 0
[469] = 1, -- 1/4 -> 4
[470] = 2, -- 2/4 -> 8
[471] = 3, -- 3/4 -> 12
[472] = 4, -- 4/4 -> 16
[476] = 0, -- ? -> 0
[479] = 0, -- ? -> 0
[491] = 0, -- ? -> 0
[492] = 1, -- ? -> 0
[493] = 2, -- ? -> 0
[494] = 0,
[495] = 1,
[496] = 2,
[497] = 3,
[498] = 4,
[504] = 3,
[505] = 4,
-- WOW-20726patch6.2.3_Retail
[529] = 0, -- 0/2 -> 0
[530] = 1, -- 1/2 -> 5
[531] = 2 -- 2/2 -> 10
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment