860 lines
26 KiB
Lua
860 lines
26 KiB
Lua
--[[ *** DataStore_Crafts ***
|
|
Written by : Thaoky, EU-Marécages de Zangar
|
|
June 23rd, 2009
|
|
--]]
|
|
if not DataStore then return end
|
|
|
|
local addonName = "DataStore_Crafts"
|
|
|
|
_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceComm-3.0", "AceSerializer-3.0", "AceTimer-3.0")
|
|
|
|
local addon = _G[addonName]
|
|
|
|
local THIS_ACCOUNT = "Default"
|
|
local commPrefix = "DS_Craft"
|
|
local L = LibStub("AceLocale-3.0"):GetLocale("DataStore_Crafts")
|
|
local PT = LibStub("LibPeriodicTable-3.1")
|
|
|
|
local MSG_SEND_LOGIN = 1 -- Sends a login message, to request crafts to other players
|
|
local MSG_LOGIN_REPLY = 2 -- ..reply
|
|
local MSG_SEND_PROFESSION = 3 -- Sends a profession link, or profession id if no full link
|
|
|
|
local AddonDB_Defaults = {
|
|
global = {
|
|
Options = {
|
|
BroadcastProfs = 1, -- Broadcast professions at login or not
|
|
},
|
|
Guilds = {
|
|
['*'] = { -- ["Account.Realm.Name"]
|
|
Members = {
|
|
['*'] = { -- ["MemberName"]
|
|
lastUpdate = nil,
|
|
Version = nil,
|
|
Professions = {}, -- 3 profession links : [1] & [2] for the 2 primary professions, [3] for cooking ([4] for archaeology ? wait & see)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
Characters = {
|
|
['*'] = { -- ["Account.Realm.Name"]
|
|
lastUpdate = nil,
|
|
Professions = {
|
|
['*'] = {
|
|
FullLink = nil, -- Tradeskill link
|
|
NumCrafts = 0, -- total number of crafts for this tradeskill
|
|
Crafts = { ['*'] = nil },
|
|
Cooldowns = { ['*'] = nil }, -- list of active cooldowns
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
local SPELL_ID_ALCHEMY = 2259
|
|
local SPELL_ID_BLACKSMITHING = 3100
|
|
local SPELL_ID_ENCHANTING = 7411
|
|
local SPELL_ID_ENGINEERING = 4036
|
|
local SPELL_ID_INSCRIPTION = 45357
|
|
local SPELL_ID_JEWELCRAFTING = 25229
|
|
local SPELL_ID_LEATHERWORKING = 2108
|
|
local SPELL_ID_TAILORING = 3908
|
|
local SPELL_ID_SKINNING = 8613
|
|
local SPELL_ID_MINING = 2575
|
|
local SPELL_ID_HERBALISM = 13614
|
|
local SPELL_ID_SMELTING = 2656
|
|
local SPELL_ID_COOKING = 2550
|
|
local SPELL_ID_FIRSTAID = 3273
|
|
local SPELL_ID_FISHING = 7733
|
|
|
|
local ProfessionSpellID = {
|
|
-- GetSpellInfo with this value will return localized spell name
|
|
["Alchemy"] = SPELL_ID_ALCHEMY,
|
|
["Blacksmithing"] = SPELL_ID_BLACKSMITHING,
|
|
["Enchanting"] = SPELL_ID_ENCHANTING,
|
|
["Engineering"] = SPELL_ID_ENGINEERING,
|
|
["Inscription"] = SPELL_ID_INSCRIPTION,
|
|
["Jewelcrafting"] = SPELL_ID_JEWELCRAFTING,
|
|
["Leatherworking"] = SPELL_ID_LEATHERWORKING,
|
|
["Tailoring"] = SPELL_ID_TAILORING,
|
|
["Skinning"] = SPELL_ID_SKINNING,
|
|
["Mining"] = SPELL_ID_MINING,
|
|
["Herbalism"] = SPELL_ID_HERBALISM,
|
|
["Smelting"] = SPELL_ID_SMELTING,
|
|
|
|
["Cooking"] = SPELL_ID_COOKING,
|
|
["First Aid"] = SPELL_ID_FIRSTAID,
|
|
["Fishing"] = SPELL_ID_FISHING,
|
|
}
|
|
|
|
-- *** Utility functions ***
|
|
local function GetOption(option)
|
|
return addon.db.global.Options[option]
|
|
end
|
|
|
|
local function GetProfessionID(profession)
|
|
-- profession = localized profession name "Cooking" or "Cuisine", "Alchemy"...
|
|
-- note: we're not using a reverse lookup table because of the localization issue.
|
|
|
|
if ProfessionSpellID[profession] then
|
|
return ProfessionSpellID[profession]
|
|
end
|
|
|
|
for _, id in pairs( ProfessionSpellID ) do
|
|
if profession == GetSpellInfo(id) then -- profession found ?
|
|
ProfessionSpellID[profession] = id -- cache the result to speed up future searches
|
|
return id
|
|
end
|
|
end
|
|
end
|
|
|
|
local function GetThisGuild()
|
|
local guild = GetGuildInfo("player")
|
|
if guild then
|
|
local key = format("%s.%s.%s", THIS_ACCOUNT, GetRealmName(), guild)
|
|
return addon.db.global.Guilds[key]
|
|
end
|
|
end
|
|
|
|
local function GetVersion()
|
|
local _, version = GetBuildInfo()
|
|
return tonumber(version)
|
|
end
|
|
|
|
local function SaveVersion(sender, version)
|
|
local thisGuild = GetThisGuild()
|
|
if thisGuild and sender and version then
|
|
thisGuild.Members[sender].Version = version
|
|
end
|
|
end
|
|
|
|
local function GuildBroadcast(messageType, ...)
|
|
local serializedData = addon:Serialize(messageType, ...)
|
|
addon:SendCommMessage(commPrefix, serializedData, "GUILD")
|
|
end
|
|
|
|
local function GuildWhisper(player, messageType, ...)
|
|
if DataStore:IsGuildMemberOnline(player) then
|
|
local serializedData = addon:Serialize(messageType, ...)
|
|
addon:SendCommMessage(commPrefix, serializedData, "WHISPER", player)
|
|
end
|
|
end
|
|
|
|
local professionQueue -- queue containing the professions to send to guildmates
|
|
local professionTimer -- timer used to control the pace at which professions are place on comm channels.
|
|
|
|
local function SendProfession()
|
|
if #professionQueue == 0 then -- nothing left in the queue ? cancel the timer & exit
|
|
addon:CancelTimer(professionTimer)
|
|
professionTimer = nil
|
|
return
|
|
end
|
|
|
|
-- send the last profession found in the queue, then remove it
|
|
local profession = professionQueue[#professionQueue] -- last element
|
|
local alt = profession[1]
|
|
local data = profession[2]
|
|
local index = profession[3]
|
|
local recipient = profession[4]
|
|
-- DEFAULT_CHAT_FRAME:AddMessage(format("sending %s, %s to %s (size : %d)", alt, index, recipient or "guild", #professionQueue ))
|
|
|
|
if profession[4] then -- recipient found ?
|
|
GuildWhisper(recipient, MSG_SEND_PROFESSION, alt, data, index)
|
|
else
|
|
GuildBroadcast(MSG_SEND_PROFESSION, alt, data, index)
|
|
end
|
|
|
|
table.remove(professionQueue)
|
|
end
|
|
|
|
local function QueueCharacterProfessions(character, recipient)
|
|
local index = 1
|
|
local _, _, alt = strsplit(".", character)
|
|
|
|
for profName, profession in pairs(addon.db.global.Characters[character].Professions) do
|
|
local spellID = GetProfessionID(profName) or 0
|
|
local data = profession.FullLink or spellID
|
|
|
|
if profession.isPrimary then
|
|
table.insert(professionQueue, { alt, data, index, recipient })
|
|
index = index + 1
|
|
elseif profession.isSecondary then
|
|
if profName == GetSpellInfo(SPELL_ID_COOKING) then
|
|
table.insert(professionQueue, { alt, data, 3, recipient }) -- index = 3
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function SendAllProfessions(alts, recipient)
|
|
if GetOption("BroadcastProfs") == 0 then
|
|
return
|
|
end
|
|
|
|
professionQueue = professionQueue or {}
|
|
|
|
-- sends the professions of the current character + his alts
|
|
local character = DataStore:GetCharacter() -- this character
|
|
QueueCharacterProfessions(character, recipient)
|
|
|
|
if strlen(alts) > 0 then
|
|
for _, name in pairs( { strsplit("|", alts) }) do -- then all his alts
|
|
character = DataStore:GetCharacter(name)
|
|
if character then
|
|
QueueCharacterProfessions(character, recipient)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- reuse current timer if already available (may be the case if 2 players connect simultaneously)
|
|
professionTimer = professionTimer or addon:ScheduleRepeatingTimer(SendProfession, 0.5) -- send 1 profession every half-second, max 15 seconds for 30 professions on a realm
|
|
end
|
|
|
|
local function SaveProfession(sender, alt, data, index)
|
|
local thisGuild = GetThisGuild()
|
|
if thisGuild and sender then
|
|
local version = thisGuild.Members[sender].Version
|
|
local member = thisGuild.Members[alt]
|
|
|
|
member.Version = version
|
|
member.Professions[index] = data
|
|
member.lastUpdate = time()
|
|
end
|
|
addon:SendMessage("DATASTORE_GUILD_PROFESSION_RECEIVED", sender, alt, data, index)
|
|
end
|
|
|
|
local function ClearExpiredProfessions()
|
|
-- this function will clear all the guild profession links that were saved with a build number anterior to the current one (they're invalid after a patch anyway)
|
|
|
|
local thisGuild = GetThisGuild()
|
|
if not thisGuild then return end
|
|
|
|
local version = GetVersion()
|
|
|
|
for name, member in pairs(thisGuild.Members) do
|
|
if member.Version ~= version then
|
|
thisGuild.Members[name] = nil -- clear this member's entry if version is outdated
|
|
end
|
|
end
|
|
end
|
|
|
|
local function LocalizeProfessionSpellIDs()
|
|
-- this function adds localized entries in the ProfessionSpellID table
|
|
|
|
local localizedSpells = {} -- avoid infinite loop by storing in a temp table first
|
|
local localizedName
|
|
for englishName, spellID in pairs(ProfessionSpellID) do
|
|
localizedName = GetSpellInfo(spellID)
|
|
localizedSpells[localizedName] = spellID
|
|
end
|
|
|
|
for name, id in pairs(localizedSpells) do
|
|
ProfessionSpellID[name] = id
|
|
end
|
|
end
|
|
|
|
-- *** Scanning functions ***
|
|
|
|
local selectedTradeSkillIndex
|
|
local subClasses, subClassID
|
|
local invSlots, invSlotID
|
|
local haveMats
|
|
|
|
local function GetSubClassID()
|
|
-- The purpose of this function is to get the subClassID in a UI independant way
|
|
-- ie: without relying on UIDropDownMenu_GetSelectedID(TradeSkillSubClassDropDown), which uses a hardcoded frame name.
|
|
|
|
if GetTradeSkillSubClassFilter(0) then -- if "All Subclasses" is selected, GetTradeSkillSubClassFilter() will return 1 for all indexes, including 0
|
|
return 1 -- thus return 1 as selected id (as would be returned by UIDropDownMenu_GetSelectedID(TradeSkillSubClassDropDown))
|
|
end
|
|
|
|
local filter
|
|
for i = 1, #subClasses do
|
|
filter = GetTradeSkillSubClassFilter(i)
|
|
if filter then
|
|
return i+1 -- ex: 3rd element of the subClasses array, but 4th in the dropdown due to "All Subclasses", so return i+1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function GetInvSlotID()
|
|
-- The purpose of this function is to get the invSlotID in a UI independant way (same as GetSubClassID)
|
|
-- ie: without relying on UIDropDownMenu_GetSelectedID(TradeSkillInvSlotDropDown), which uses a hardcoded frame name.
|
|
|
|
if GetTradeSkillInvSlotFilter(0) then -- if "All Slots" is selected, GetTradeSkillInvSlotFilter() will return 1 for all indexes, including 0
|
|
return 1 -- thus return 1 as selected id (as would be returned by UIDropDownMenu_GetSelectedID(TradeSkillInvSlotDropDown))
|
|
end
|
|
|
|
local filter
|
|
for i = 1, #invSlots do
|
|
filter = GetTradeSkillInvSlotFilter(i)
|
|
if filter then
|
|
return i+1 -- ex: 3rd element of the invSlots array, but 4th in the dropdown due to "All Slots", so return i+1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function SaveActiveFilters()
|
|
selectedTradeSkillIndex = GetTradeSkillSelectionIndex()
|
|
|
|
subClasses = { GetTradeSkillSubClasses() }
|
|
invSlots = { GetTradeSkillInvSlots() }
|
|
subClassID = GetSubClassID()
|
|
invSlotID = GetInvSlotID()
|
|
|
|
-- Subclasses
|
|
SetTradeSkillSubClassFilter(0, 1, 1) -- this checks "All subclasses"
|
|
if TradeSkillSubClassDropDown then
|
|
UIDropDownMenu_SetSelectedID(TradeSkillSubClassDropDown, 1)
|
|
end
|
|
|
|
-- Inventory slots
|
|
SetTradeSkillInvSlotFilter(0, 1, 1) -- this checks "All slots"
|
|
if TradeSkillInvSlotDropDown then
|
|
UIDropDownMenu_SetSelectedID(TradeSkillInvSlotDropDown, 1)
|
|
end
|
|
|
|
-- Have Materials
|
|
if TradeSkillFrameAvailableFilterCheckButton then
|
|
haveMats = TradeSkillFrameAvailableFilterCheckButton:GetChecked() -- nil or true
|
|
TradeSkillFrameAvailableFilterCheckButton:SetChecked(false)
|
|
end
|
|
TradeSkillOnlyShowMakeable(false)
|
|
end
|
|
|
|
local function RestoreActiveFilters()
|
|
-- Subclasses
|
|
SetTradeSkillSubClassFilter(subClassID-1, 1, 1) -- this checks the previously checked value
|
|
|
|
local frame = TradeSkillSubClassDropDown
|
|
if frame then -- other addons might nil this frame (delayed load, etc..), so secure DDM calls
|
|
local text = (subClassID == 1) and ALL_SUBCLASSES or subClasses[subClassID-1]
|
|
UIDropDownMenu_SetSelectedID(frame, subClassID)
|
|
UIDropDownMenu_SetText(frame, text);
|
|
end
|
|
|
|
subClassID = nil
|
|
wipe(subClasses)
|
|
subClasses = nil
|
|
|
|
-- Inventory slots
|
|
invSlotID = invSlotID or 1
|
|
SetTradeSkillInvSlotFilter(invSlotID-1, 1, 1) -- this checks the previously checked value
|
|
|
|
frame = TradeSkillInvSlotDropDown
|
|
if frame then
|
|
local text = (invSlotID == 1) and ALL_INVENTORY_SLOTS or invSlots[invSlotID-1]
|
|
UIDropDownMenu_SetSelectedID(frame, invSlotID)
|
|
UIDropDownMenu_SetText(frame, text);
|
|
end
|
|
|
|
invSlotID = nil
|
|
wipe(invSlots)
|
|
invSlots = nil
|
|
|
|
-- Have Materials
|
|
if TradeSkillFrameAvailableFilterCheckButton then
|
|
TradeSkillFrameAvailableFilterCheckButton:SetChecked(haveMats or false)
|
|
end
|
|
TradeSkillOnlyShowMakeable(haveMats or false)
|
|
haveMats = nil
|
|
|
|
SelectTradeSkill(selectedTradeSkillIndex)
|
|
selectedTradeSkillIndex = nil
|
|
end
|
|
|
|
local headersState = {}
|
|
|
|
local function SaveHeaders()
|
|
local headerCount = 0 -- use a counter to avoid being bound to header names, which might not be unique.
|
|
|
|
for i = GetNumTradeSkills(), 1, -1 do -- 1st pass, expand all categories
|
|
local _, skillType, _, isExpanded = GetTradeSkillInfo(i)
|
|
if (skillType == "header") then
|
|
headerCount = headerCount + 1
|
|
if not isExpanded then
|
|
ExpandTradeSkillSubClass(i)
|
|
headersState[headerCount] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function RestoreHeaders()
|
|
local headerCount = 0
|
|
for i = GetNumTradeSkills(), 1, -1 do
|
|
local _, skillType = GetTradeSkillInfo(i)
|
|
if (skillType == "header") then
|
|
headerCount = headerCount + 1
|
|
if headersState[headerCount] then
|
|
CollapseTradeSkillSubClass(i)
|
|
end
|
|
end
|
|
end
|
|
wipe(headersState)
|
|
end
|
|
|
|
local function ScanCooldowns()
|
|
local tradeskillName = GetTradeSkillLine()
|
|
local char = addon.ThisCharacter
|
|
local profession = char.Professions[tradeskillName]
|
|
|
|
wipe(profession.Cooldowns)
|
|
for i = 1, GetNumTradeSkills() do
|
|
local skillName, skillType = GetTradeSkillInfo(i)
|
|
|
|
if skillType ~= "header" then
|
|
local cooldown = GetTradeSkillCooldown(i)
|
|
if cooldown then
|
|
table.insert(profession.Cooldowns, skillName .. "|" .. cooldown .. "|" .. time())
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ScanProfessionLinks()
|
|
local char = addon.ThisCharacter
|
|
|
|
if not char then return end
|
|
|
|
local old_professions = {}
|
|
-- start listing previously known professions
|
|
for skillName, v in pairs(char.Professions) do
|
|
old_professions[skillName] = 1
|
|
end
|
|
|
|
for i = GetNumSkillLines(), 1, -1 do -- 1st pass, expand all categories
|
|
local _, isHeader = GetSkillLineInfo(i)
|
|
if isHeader then
|
|
ExpandSkillHeader(i)
|
|
end
|
|
end
|
|
|
|
local category
|
|
for i = 1, GetNumSkillLines() do
|
|
local skillName, isHeader, _, skillRank, _, _, skillMaxRank = GetSkillLineInfo(i)
|
|
|
|
if isHeader then
|
|
category = skillName
|
|
else
|
|
if category and skillName then
|
|
local field
|
|
|
|
if category == L["Professions"] then
|
|
field = "isPrimary"
|
|
end
|
|
|
|
if category == L["Secondary Skills"] then
|
|
field = "isSecondary"
|
|
end
|
|
|
|
if field then
|
|
char.Professions[skillName][field] = true
|
|
|
|
-- remove a confimed Profession from the "old" list
|
|
old_professions[skillName] = nil
|
|
|
|
-- should be nil anyway for fishing, mining, etc..
|
|
local newLink = select(2, GetSpellLink(skillName))
|
|
if newLink then -- sometimes a nil value may be returned, so keep the old one if nil
|
|
char.Professions[skillName].FullLink = newLink
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- clear old professions which are no longer known
|
|
for oldProfession,_ in pairs(old_professions) do
|
|
wipe( char.Professions[oldProfession] ) -- clean the table first
|
|
char.Professions[oldProfession] = nil -- remove the entry
|
|
end
|
|
end
|
|
|
|
local SkillTypeToColor = {
|
|
["header"] = 0,
|
|
["optimal"] = 1, -- orange
|
|
["medium"] = 2, -- yellow
|
|
["easy"] = 3, -- green
|
|
["trivial"] = 4, -- grey
|
|
}
|
|
|
|
local function ScanRecipes()
|
|
local tradeskillName = GetTradeSkillLine()
|
|
if not tradeskillName or tradeskillName == "UNKNOWN" then return end -- may happen after a patch, or under extreme lag, so do not save anything to the db !
|
|
|
|
local char = addon.ThisCharacter
|
|
local profession = char.Professions[tradeskillName]
|
|
-- local crafts = profession.Crafts
|
|
-- wipe(crafts)
|
|
local crafts = {}
|
|
|
|
|
|
local NumCrafts = 0
|
|
local NumHeaders = 0
|
|
local color, craftInfo, link
|
|
|
|
for i = 1, GetNumTradeSkills() do
|
|
local skillName, skillType = GetTradeSkillInfo(i)
|
|
color = SkillTypeToColor[skillType]
|
|
|
|
if skillType == "header" then
|
|
craftInfo = skillName or ""
|
|
NumHeaders = NumHeaders + 1
|
|
else
|
|
link = GetTradeSkillRecipeLink(i)
|
|
craftInfo = tonumber(link:match("enchant:(%d+)")) -- this actually extracts the spellID
|
|
NumCrafts = NumCrafts + 1
|
|
end
|
|
crafts[i] = color .. "|" .. craftInfo
|
|
end
|
|
|
|
if NumHeaders > 0 then -- only overwrite stored info if he have seen some headers (indicator that the whole profession is in game cache and the interface is shown in its entirety)
|
|
profession.FullLink = select(2, GetSpellLink(tradeskillName))
|
|
profession.NumCrafts = NumCrafts
|
|
wipe(profession.Crafts) -- wipe old table (for memory saving)
|
|
profession.Crafts = crafts -- assign the valid new table
|
|
else
|
|
wipe(crafts) -- wipe unused table
|
|
print(format("|cffFFFF20%s|r:",addonName), "Profession not yet fully loaded.")
|
|
end
|
|
end
|
|
|
|
local function ScanTradeSkills()
|
|
SaveActiveFilters()
|
|
SaveHeaders()
|
|
ScanRecipes()
|
|
ScanCooldowns()
|
|
RestoreHeaders()
|
|
RestoreActiveFilters()
|
|
|
|
addon.ThisCharacter.lastUpdate = time()
|
|
end
|
|
|
|
-- *** Event Handlers ***
|
|
local function OnPlayerAlive()
|
|
-- print("DataStore_Crafts.lua") -- DEBUG 2025 07 21
|
|
if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard
|
|
|
|
ScanProfessionLinks()
|
|
end
|
|
|
|
local function OnTradeSkillClose()
|
|
addon:UnregisterEvent("TRADE_SKILL_CLOSE")
|
|
addon:UnregisterEvent("TRADE_SKILL_UPDATE")
|
|
addon.isOpen = nil
|
|
end
|
|
|
|
local function OnTradeSkillShow()
|
|
if IsTradeSkillLinked() then return end
|
|
|
|
addon.isOpen = true
|
|
addon:RegisterEvent("TRADE_SKILL_CLOSE", OnTradeSkillClose)
|
|
ScanTradeSkills()
|
|
end
|
|
|
|
local function OnTradeSkillUpdate()
|
|
-- The hook in DoTradeSkill will register this event so that we only update skills once.
|
|
-- unregister it before calling the update, or the event will be called recursively (due to expand/collapse)
|
|
addon:UnregisterEvent("TRADE_SKILL_UPDATE")
|
|
ScanCooldowns() -- only cooldowns need to be refreshed
|
|
end
|
|
|
|
-- this turns
|
|
-- "Your skill in %s has increased to %d."
|
|
-- into
|
|
-- "Your skill in (.+) has increased to (%d+)."
|
|
local arg1pattern, arg2pattern
|
|
if GetLocale() == "deDE" then
|
|
-- ERR_SKILL_UP_SI = "Eure Fertigkeit '%1$s' hat sich auf %2$d erhöht.";
|
|
arg1pattern = "'%%1%$s'"
|
|
arg2pattern = "%%2%$d"
|
|
else
|
|
arg1pattern = "%%s"
|
|
arg2pattern = "%%d"
|
|
end
|
|
|
|
local skillUpMsg = gsub(ERR_SKILL_UP_SI, arg1pattern, "(.+)")
|
|
skillUpMsg = gsub(skillUpMsg, arg2pattern, "(%%d+)")
|
|
|
|
local function OnChatMsgSkill(self, msg)
|
|
if msg and addon.isOpen then -- point gained while ts window is open ? rescan
|
|
local skill = msg:match(skillUpMsg)
|
|
if skill and skill == GetTradeSkillLine() then -- if we gained a skill point in the currently opened profession pane, rescan
|
|
ScanTradeSkills()
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- ** Mixins **
|
|
local function _GetProfession(character, name)
|
|
return character.Professions[name]
|
|
end
|
|
|
|
local function _GetProfessions(character)
|
|
return character.Professions
|
|
end
|
|
|
|
local function _GetProfessionInfo(profession)
|
|
-- accepts either a pointer (type == table)to the profession table, as returned by addon:GetProfession()
|
|
-- or a link (type == string)
|
|
local link
|
|
if type(profession) == "table" then
|
|
link = profession.FullLink
|
|
elseif type(profession) == "string" then
|
|
link = profession
|
|
end
|
|
|
|
if link then
|
|
local spellID, currentLevel, maxLevel = link:match("trade:(%d+):(%d+):(%d+):")
|
|
return tonumber(currentLevel), tonumber(maxLevel), tonumber(spellID)
|
|
end
|
|
end
|
|
|
|
local function _GetNumCraftLines(profession)
|
|
return #profession.Crafts
|
|
end
|
|
|
|
local function _GetCraftLineInfo(profession, index)
|
|
local craft = profession.Crafts[index]
|
|
local color, info = strsplit("|", craft)
|
|
|
|
color = tonumber(color)
|
|
if color ~= 0 then -- not a header ?
|
|
info = tonumber(info) -- it's a spellID, return a number
|
|
end
|
|
return (color == 0), color, info -- 1st return value = isHeader
|
|
end
|
|
|
|
local function _GetCraftCooldownInfo(profession, index)
|
|
local cooldown = profession.Cooldowns[index]
|
|
local name, reset, lastCheck = strsplit("|", cooldown)
|
|
|
|
reset = tonumber(reset)
|
|
lastCheck = tonumber(lastCheck)
|
|
local expiresIn = reset - (time() - lastCheck)
|
|
|
|
return name, expiresIn, reset, lastCheck
|
|
end
|
|
|
|
local function _GetNumActiveCooldowns(profession)
|
|
assert(type(profession) == "table") -- this is the pointer to a profession table, obtained through addon:GetProfession()
|
|
return #profession.Cooldowns
|
|
end
|
|
|
|
local function _ClearExpiredCooldowns(profession)
|
|
assert(type(profession) == "table") -- this is the pointer to a profession table, obtained through addon:GetProfession()
|
|
|
|
for i = #profession.Cooldowns, 1, -1 do -- from last to first, to avoid messing up indexes when removing entries
|
|
local _, expiresIn = _GetCraftCooldownInfo(profession, i)
|
|
if expiresIn <= 0 then -- already expired ? remove it
|
|
table.remove(profession.Cooldowns, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function _GetCraftInfo(spellID)
|
|
-- get the id of the item that can be crafted by this spellID
|
|
local itemID = PT:ItemInSet("-"..spellID, "Tradeskill.RecipeLinks")
|
|
local reagents
|
|
|
|
if itemID then
|
|
itemID = tonumber(itemID)
|
|
|
|
-- ex: itemID 10046 is made with reagents : "2996x2;2318x1;2320x1"
|
|
reagents = PT:ItemInSet(itemID, "TradeskillResultMats.Forward")
|
|
|
|
if itemID == -spellID then -- enchants that do not yield an item will return this, ex: enchant 7420 will return itemID -7420
|
|
itemID = nil
|
|
end
|
|
end
|
|
|
|
return itemID, reagents
|
|
end
|
|
|
|
local function _GetCraftLevels(spellID)
|
|
-- get the id of the item that can be crafted by this spellID
|
|
local itemID = PT:ItemInSet("-"..spellID, "Tradeskill.RecipeLinks")
|
|
|
|
if itemID then
|
|
itemID = tonumber(itemID)
|
|
|
|
-- ex: itemID 10046 : levels = "20/50/67/85", the item turns yellow at 50, green at 67, grey at 85
|
|
local levels = PT:ItemInSet(itemID, "TradeskillLevels")
|
|
|
|
if levels then
|
|
local orange, yellow, green, grey = strsplit("/", levels)
|
|
return tonumber(orange), tonumber(yellow), tonumber(green), tonumber(grey)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function _GetNumRecipesByColor(profession)
|
|
-- counts the number of orange, yellow, green and grey recipes.
|
|
local counts = { 0, 0, 0, 0 }
|
|
|
|
for i = 1, _GetNumCraftLines(profession) do
|
|
local isHeader, color = _GetCraftLineInfo(profession, i)
|
|
|
|
if not isHeader then
|
|
counts[color] = counts[color] + 1
|
|
end
|
|
end
|
|
return counts[1], counts[2], counts[3], counts[4] -- orange, yellow, green, grey
|
|
end
|
|
|
|
local function _IsCraftKnown(profession, spellID)
|
|
-- returns true if a given spell ID is known in the profession passed as first argument
|
|
for i = 1, _GetNumCraftLines(profession) do
|
|
local isHeader, _, info = _GetCraftLineInfo(profession, i)
|
|
|
|
if not isHeader then
|
|
if info == spellID then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function _GetGuildCrafters(guild)
|
|
return guild.Members
|
|
end
|
|
|
|
local function _GetGuildMemberProfession(guild, member, index)
|
|
local m = guild.Members[member]
|
|
local profession = m.Professions[index]
|
|
|
|
if type(profession) == "string" then
|
|
local spellID = profession:match("trade:(%d+):")
|
|
return tonumber(spellID), profession, m.lastUpdate -- return the profession spell ID + full link
|
|
elseif type(profession) == "number" then
|
|
return profession, nil, m.lastUpdate -- return the profession spell ID
|
|
end
|
|
end
|
|
|
|
local function _GetProfessionSpellID(name)
|
|
-- name can be either the english name or the localized name
|
|
return ProfessionSpellID[name]
|
|
end
|
|
|
|
local PublicMethods = {
|
|
GetProfession = _GetProfession,
|
|
GetProfessions = _GetProfessions,
|
|
GetProfessionInfo = _GetProfessionInfo,
|
|
GetNumCraftLines = _GetNumCraftLines,
|
|
GetCraftLineInfo = _GetCraftLineInfo,
|
|
GetCraftCooldownInfo = _GetCraftCooldownInfo,
|
|
GetNumActiveCooldowns = _GetNumActiveCooldowns,
|
|
ClearExpiredCooldowns = _ClearExpiredCooldowns,
|
|
GetCraftInfo = _GetCraftInfo,
|
|
GetCraftLevels = _GetCraftLevels,
|
|
GetNumRecipesByColor = _GetNumRecipesByColor,
|
|
IsCraftKnown = _IsCraftKnown,
|
|
GetGuildCrafters = _GetGuildCrafters,
|
|
GetGuildMemberProfession = _GetGuildMemberProfession,
|
|
GetProfessionSpellID = _GetProfessionSpellID,
|
|
}
|
|
|
|
-- *** Guild Comm ***
|
|
local function OnGuildAltsReceived(self, sender, alts)
|
|
if sender == UnitName("player") then -- if I receive my own list of alts in the same guild, same realm, same account..
|
|
GuildBroadcast(MSG_SEND_LOGIN, GetVersion())
|
|
addon:ScheduleTimer(SendAllProfessions, 5, alts) -- broadcast my crafts to the guild 5 seconds later, to decrease the load at startup
|
|
end
|
|
end
|
|
|
|
local GuildCommCallbacks = {
|
|
[MSG_SEND_LOGIN] = function(sender, version)
|
|
local player = UnitName("player")
|
|
if sender ~= player then -- don't send back to self
|
|
GuildWhisper(sender, MSG_LOGIN_REPLY, GetVersion())
|
|
local alts = DataStore:GetGuildMemberAlts(player) -- get my own alts
|
|
if alts then
|
|
SendAllProfessions(alts, sender) -- when another player sends me his login, reply with my own crafts
|
|
end
|
|
end
|
|
SaveVersion(sender, version)
|
|
end,
|
|
[MSG_LOGIN_REPLY] = function(sender, version)
|
|
SaveVersion(sender, version)
|
|
end,
|
|
[MSG_SEND_PROFESSION] = function(sender, alt, data, index)
|
|
SaveProfession(sender, alt, data, index)
|
|
end,
|
|
}
|
|
|
|
function addon:OnInitialize()
|
|
addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults)
|
|
|
|
DataStore:RegisterModule(addonName, addon, PublicMethods)
|
|
DataStore:SetGuildCommCallbacks(commPrefix, GuildCommCallbacks)
|
|
DataStore:SetCharacterBasedMethod("GetProfession")
|
|
DataStore:SetCharacterBasedMethod("GetProfessions")
|
|
|
|
DataStore:SetGuildBasedMethod("GetGuildCrafters")
|
|
DataStore:SetGuildBasedMethod("GetGuildMemberProfession")
|
|
|
|
addon:RegisterMessage("DATASTORE_GUILD_ALTS_RECEIVED", OnGuildAltsReceived)
|
|
addon:RegisterComm(commPrefix, DataStore:GetGuildCommHandler())
|
|
end
|
|
|
|
function addon:OnEnable()
|
|
addon:RegisterEvent("PLAYER_ALIVE", OnPlayerAlive)
|
|
addon:RegisterEvent("TRADE_SKILL_SHOW", OnTradeSkillShow)
|
|
addon:RegisterEvent("CHAT_MSG_SKILL", OnChatMsgSkill)
|
|
|
|
addon:SetupOptions()
|
|
ClearExpiredProfessions() -- automatically cleanup guild profession links that are from an older version
|
|
LocalizeProfessionSpellIDs()
|
|
end
|
|
|
|
function addon:OnDisable()
|
|
addon:UnregisterEvent("PLAYER_ALIVE")
|
|
addon:UnregisterEvent("TRADE_SKILL_SHOW")
|
|
addon:UnregisterEvent("CHAT_MSG_SKILL")
|
|
end
|
|
|
|
function addon:GetSource(searchedID)
|
|
local PT = LibStub("LibPeriodicTable-3.1")
|
|
|
|
-- Returns "Profession, level" ex: "Alchemy", "180"
|
|
local level, data = PT:ItemInSet(searchedID, "Tradeskill.Crafted")
|
|
if level and data then
|
|
local _, _, profession = strsplit(".", data) -- ex: "Tradeskill.Crafted.Inscription"
|
|
local localizedProfession
|
|
if ProfessionSpellID[profession] then
|
|
localizedProfession = GetSpellInfo(ProfessionSpellID[profession])
|
|
end
|
|
|
|
return localizedProfession or profession, level
|
|
end
|
|
end
|
|
|
|
function addon:IsTradeSkillWindowOpen()
|
|
-- note : maybe there's a function in the WoW API to test this, but I did not find it :(
|
|
return addon.isOpen
|
|
end
|
|
|
|
-- *** Hooks ***
|
|
-- todo : change the hooks, do them the Ace way
|
|
local Orig_DoTradeSkill = DoTradeSkill
|
|
|
|
function DoTradeSkill(index, repeatCount, ...) -- this hook is necessary to get cooldown information after a craft
|
|
Orig_DoTradeSkill(index, repeatCount, ...)
|
|
addon:RegisterEvent("TRADE_SKILL_UPDATE", OnTradeSkillUpdate)
|
|
end
|
|
|
|
local Orig_AbandonSkill = AbandonSkill
|
|
|
|
function AbandonSkill(index, ...)
|
|
local skillName = GetSkillLineInfo(index) -- get the name of the profession that is being abandonned
|
|
|
|
Orig_AbandonSkill(index, ...)
|
|
|
|
-- clear the list of recipes
|
|
local char = addon.ThisCharacter
|
|
wipe(char.Professions[skillName])
|
|
char.Professions[skillName] = nil
|
|
end
|