-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local PlayerProfessions = TSM.Crafting:NewPackage("PlayerProfessions") local ProfessionInfo = TSM.Include("Data.ProfessionInfo") local Database = TSM.Include("Util.Database") local Event = TSM.Include("Util.Event") local Delay = TSM.Include("Util.Delay") local TempTable = TSM.Include("Util.TempTable") local Vararg = TSM.Include("Util.Vararg") local Threading = TSM.Include("Service.Threading") local private = { playerProfessionsThread = nil, playerProfessionsThreadRunning = false, db = nil, query = nil, } local TAILORING_ES = "Sastrería" local TAILORING_SKILL_ES = "Costura" local LEATHERWORKING_ES = "Peletería" local LEATHERWORKING_SKILL_ES = "Marroquinería" local ENGINEERING_FR = "Ingénieur" local ENGINEERING_SKILL_FR = "Ingénierie" local FIRST_AID_FR = "Premiers soins" local FIRST_AID_SKILL_FR = "Secourisme" -- ============================================================================ -- Module Functions -- ============================================================================ function PlayerProfessions.OnInitialize() private.db = Database.NewSchema("PLAYER_PROFESSIONS") :AddStringField("player") :AddStringField("profession") :AddNumberField("skillId") :AddNumberField("level") :AddNumberField("maxLevel") :AddBooleanField("isSecondary") :AddIndex("player") :Commit() private.query = private.db:NewQuery() :Select("player", "profession", "skillId", "level", "maxLevel") :OrderBy("isSecondary", true) :OrderBy("level", false) :OrderBy("profession", true) private.playerProfessionsThread = Threading.New("PLAYER_PROFESSIONS", private.PlayerProfessionsThread) private.StartPlayerProfessionsThread() Event.Register("SKILL_LINES_CHANGED", private.PlayerProfessionsSkillUpdate) Event.Register("LEARNED_SPELL_IN_TAB", private.StartPlayerProfessionsThread) end function PlayerProfessions.CreateQuery() return private.db:NewQuery() end function PlayerProfessions.Iterator() return private.query:Iterator() end -- ============================================================================ -- Player Professions Thread -- ============================================================================ function private.StartPlayerProfessionsThread() if private.playerProfessionsThreadRunning then Threading.Kill(private.playerProfessionsThread) end private.playerProfessionsThreadRunning = true Threading.Start(private.playerProfessionsThread) end function private.UpdatePlayerProfessionInfo(name, skillId, level, maxLevel, isSecondary) local professionInfo = TSM.db.sync.internalData.playerProfessions[name] or {} TSM.db.sync.internalData.playerProfessions[name] = professionInfo -- preserve whether or not we've prompted to create groups and the profession link if possible local oldPrompted = professionInfo.prompted or nil local oldLink = professionInfo.link or nil wipe(professionInfo) professionInfo.skillId = skillId professionInfo.level = level professionInfo.maxLevel = maxLevel professionInfo.isSecondary = isSecondary professionInfo.prompted = oldPrompted professionInfo.link = oldLink end function private.PlayerProfessionsSkillUpdate() if TSM.IsWowClassic() then local _, _, offset, numSpells = GetSpellTabInfo(1) for i = offset + 1, offset + numSpells do local name, subName = GetSpellBookItemName(i, BOOKTYPE_SPELL) if not subName then Delay.AfterTime(0.05, private.PlayerProfessionsSkillUpdate) return end if name and subName and (ProfessionInfo.IsSubNameClassic(strtrim(subName, " ")) or name == ProfessionInfo.GetName("Smelting") or name == ProfessionInfo.GetName("Poisons") or name == LEATHERWORKING_ES or name == TAILORING_ES or name == ENGINEERING_FR or name == FIRST_AID_FR) and not TSM.UI.CraftingUI.IsProfessionIgnored(name) then local level, maxLevel = nil, nil for j = 1, GetNumSkillLines() do local skillName, _, _, skillRank, _, _, skillMaxRank = GetSkillLineInfo(j) if skillName == name then level = skillRank maxLevel = skillMaxRank break elseif name == ProfessionInfo.GetName("Smelting") and skillName == ProfessionInfo.GetName("Mining") then name = ProfessionInfo.GetName("Mining") level = skillRank maxLevel = skillMaxRank break elseif name == LEATHERWORKING_ES and skillName == LEATHERWORKING_SKILL_ES then name = LEATHERWORKING_SKILL_ES level = skillRank maxLevel = skillMaxRank break elseif name == TAILORING_ES and skillName == TAILORING_SKILL_ES then name = TAILORING_SKILL_ES level = skillRank maxLevel = skillMaxRank break elseif name == ENGINEERING_FR and skillName == ENGINEERING_SKILL_FR then name = ENGINEERING_SKILL_FR level = skillRank maxLevel = skillMaxRank break elseif name == FIRST_AID_FR and skillName == FIRST_AID_SKILL_FR then name = FIRST_AID_SKILL_FR level = skillRank maxLevel = skillMaxRank break end end if level and maxLevel and not TSM.UI.CraftingUI.IsProfessionIgnored(name) then -- exclude ignored professions private.UpdatePlayerProfessionInfo(name, -1, level, maxLevel, name == GetSpellInfo(129)) end end end else local professionIds = TempTable.Acquire(GetProfessions()) for i, id in pairs(professionIds) do -- needs to be pairs since there might be holes if id ~= 8 and id ~= 9 then -- ignore fishing and arheology local name, _, level, maxLevel, _, _, skillId = GetProfessionInfo(id) if not TSM.UI.CraftingUI.IsProfessionIgnored(name) then -- exclude ignored professions private.UpdatePlayerProfessionInfo(name, skillId, level, maxLevel, i > 2) end end end TempTable.Release(professionIds) end -- update our DB private.db:TruncateAndBulkInsertStart() for _, character in TSM.db:FactionrealmCharacterIterator() do local playerProfessions = TSM.db:Get("sync", TSM.db:GetSyncScopeKeyByCharacter(character), "internalData", "playerProfessions") if playerProfessions then for name, info in pairs(playerProfessions) do private.db:BulkInsertNewRow(character, name, info.skillId or -1, info.level, info.maxLevel, info.isSecondary) end end end private.db:BulkInsertEnd() end function private.PlayerProfessionsThread() -- get the player's tradeskills if TSM.IsWowClassic() then SpellBookFrame_UpdateSkillLineTabs() else SpellBook_UpdateProfTab() end local forgetProfession = Threading.AcquireSafeTempTable() for name in pairs(TSM.db.sync.internalData.playerProfessions) do forgetProfession[name] = true end if TSM.IsWowClassic() then local _, _, offset, numSpells = GetSpellTabInfo(1) for i = offset + 1, offset + numSpells do local name, subName = GetSpellBookItemName(i, BOOKTYPE_SPELL) if name and subName and (ProfessionInfo.IsSubNameClassic(strtrim(subName, " ")) or name == ProfessionInfo.GetName("Smelting") or name == ProfessionInfo.GetName("Poisons") or name == LEATHERWORKING_ES or name == TAILORING_ES or name == ENGINEERING_FR or name == FIRST_AID_FR) and not TSM.UI.CraftingUI.IsProfessionIgnored(name) then local level, maxLevel = nil, nil for j = 1, GetNumSkillLines() do local skillName, _, _, skillRank, _, _, skillMaxRank = GetSkillLineInfo(j) if skillName == name then level = skillRank maxLevel = skillMaxRank break elseif name == ProfessionInfo.GetName("Smelting") and skillName == ProfessionInfo.GetName("Mining") then name = ProfessionInfo.GetName("Mining") level = skillRank maxLevel = skillMaxRank break elseif name == LEATHERWORKING_ES and skillName == LEATHERWORKING_SKILL_ES then name = LEATHERWORKING_SKILL_ES level = skillRank maxLevel = skillMaxRank break elseif name == TAILORING_ES and skillName == TAILORING_SKILL_ES then name = TAILORING_SKILL_ES level = skillRank maxLevel = skillMaxRank break elseif name == ENGINEERING_FR and skillName == ENGINEERING_SKILL_FR then name = ENGINEERING_SKILL_FR level = skillRank maxLevel = skillMaxRank break elseif name == FIRST_AID_FR and skillName == FIRST_AID_SKILL_FR then name = FIRST_AID_SKILL_FR level = skillRank maxLevel = skillMaxRank break end end if level and maxLevel and not TSM.UI.CraftingUI.IsProfessionIgnored(name) then -- exclude ignored professions forgetProfession[name] = nil private.UpdatePlayerProfessionInfo(name, -1, level, maxLevel, name == GetSpellInfo(129)) end end end else Threading.WaitForFunction(GetProfessions) local professionIds = Threading.AcquireSafeTempTable(GetProfessions()) -- ignore archeology and fishing which are in the 3rd and 4th slots respectively professionIds[3] = nil professionIds[4] = nil for i, id in pairs(professionIds) do -- needs to be pairs since there might be holes local name, _, level, maxLevel, _, _, skillId = Threading.WaitForFunction(GetProfessionInfo, id) if not TSM.UI.CraftingUI.IsProfessionIgnored(name) then -- exclude ignored professions forgetProfession[name] = nil private.UpdatePlayerProfessionInfo(name, skillId, level, maxLevel, i > 2) end end Threading.ReleaseSafeTempTable(professionIds) end for name in pairs(forgetProfession) do TSM.db.sync.internalData.playerProfessions[name] = nil end Threading.ReleaseSafeTempTable(forgetProfession) -- clean up crafts which are no longer known local matUsed = Threading.AcquireSafeTempTable() local spellIds = Threading.AcquireSafeTempTable() for _, spellId in TSM.Crafting.SpellIterator() do tinsert(spellIds, spellId) end for _, spellId in ipairs(spellIds) do local playersToRemove = TempTable.Acquire() for _, player in Vararg.Iterator(TSM.Crafting.GetPlayers(spellId)) do -- check if the player still exists and still has this profession local playerProfessions = TSM.db:Get("sync", TSM.db:GetSyncScopeKeyByCharacter(player), "internalData", "playerProfessions") if not playerProfessions or not playerProfessions[TSM.Crafting.GetProfession(spellId)] then tinsert(playersToRemove, player) end end local stillExists = true if #playersToRemove > 0 then stillExists = TSM.Crafting.RemovePlayers(spellId, playersToRemove) end TempTable.Release(playersToRemove) if stillExists then for _, itemString in TSM.Crafting.MatIterator(spellId) do matUsed[itemString] = true end end Threading.Yield() end Threading.ReleaseSafeTempTable(spellIds) -- clean up mats which aren't used anymore local toRemove = TempTable.Acquire() for itemString, matInfo in pairs(TSM.db.factionrealm.internalData.mats) do -- clear out old names matInfo.name = nil if not matUsed[itemString] then tinsert(toRemove, itemString) end end Threading.ReleaseSafeTempTable(matUsed) for _, itemString in ipairs(toRemove) do TSM.db.factionrealm.internalData.mats[itemString] = nil end TempTable.Release(toRemove) -- update our DB private.db:TruncateAndBulkInsertStart() for _, character in TSM.db:FactionrealmCharacterIterator() do local playerProfessions = TSM.db:Get("sync", TSM.db:GetSyncScopeKeyByCharacter(character), "internalData", "playerProfessions") if playerProfessions then for name, info in pairs(playerProfessions) do private.db:BulkInsertNewRow(character, name, info.skillId or -1, info.level, info.maxLevel, info.isSecondary) end end end private.db:BulkInsertEnd() private.playerProfessionsThreadRunning = false end