--[[ ************************************************************************ Scanner.lua Vendor/trainer scanning for Ackis Recipe List. ************************************************************************ File date: @file-date-iso@ File hash: @file-abbreviated-hash@ Project hash: @project-abbreviated-hash@ Project version: @project-version@ ************************************************************************ Please see http://www.wowace.com/addons/arl/ for more information. ************************************************************************ This source code is released under All Rights Reserved. ************************************************************************ --- Vendor/trainer scanning for Ackis Recipe List. @class file @name Scanner.lua @release 2.0 ************************************************************************ ]]-- ------------------------------------------------------------------------------- -- Upvalued Lua API. ------------------------------------------------------------------------------- local _G = getfenv(0) -- Functions local ipairs, pairs = _G.ipairs, _G.pairs local tonumber, tostring = _G.tonumber, _G.tostring local type = _G.type -- Libraries local bit = _G.bit local string = _G.string local table = _G.table ------------------------------------------------------------------------------- -- AddOn namespace. ------------------------------------------------------------------------------- local FOLDER_NAME, private = ... local LibStub = _G.LibStub local addon = LibStub("AceAddon-3.0"):GetAddon(private.addon_name) local L = LibStub("AceLocale-3.0"):GetLocale(private.addon_name) ------------------------------------------------------------------------------- -- Constants ------------------------------------------------------------------------------- local F = private.filter_flags local A = private.acquire_types local NO_ROLE_FLAG -- Populated at the end of the file. ------------------------------------------------------------------------------- -- Functions/methods ------------------------------------------------------------------------------- local function LoadAllRecipes() local recipe_list = private.recipe_list if addon.db.profile.autoloaddb then -- Make sure the lookup lists are loaded as well, since they are no longer automatically loaded in addon:OnEnable(). if addon.InitializeLookups then addon:InitializeLookups() end for index, prof in pairs(private.professions) do addon:InitializeProfession(prof) end end return recipe_list end ------------------------------------------------------------------------------- -- Creates a reverse lookup for a recipe list ------------------------------------------------------------------------------- local GetReverseLookup do local reverse_lookup = {} function GetReverseLookup(recipe_list) if not recipe_list then addon:Print(L["DATAMINER_NODB_ERROR"]) return end table.wipe(reverse_lookup) for i in pairs(recipe_list) do --if t[recipe_list[i].name] then addon:Print("Dupe: " .. i) end reverse_lookup[recipe_list[i].name] = i end return reverse_lookup end end ------------------------------------------------------------------------------- -- Tooltip for data-mining. ------------------------------------------------------------------------------- local ARLDatamineTT = _G.CreateFrame("GameTooltip", "ARLDatamineTT", _G.UIParent, "GameTooltipTemplate") do -- Tables used in addon:ScanTrainerData local scanned_recipes, scanned_items, output = {}, {}, {} local missing_spell_ids, extra_spell_ids, fixed_item_spell_ids = {}, {}, {} local mismatched_levels, mismatched_levels_unconfirmed = {}, {} local itemless_spells = {} --- Function to compare which recipes are available from a trainer and compare with the internal ARL database. -- @name AckisRecipeList:ScanTrainerData -- @param autoscan True when autoscan is enabled in preferences, it will surpress output letting you know when a scan has occured. -- @return Does a comparison of the information in your internal ARL database, and those items which are available on the trainer. -- Compares the acquire information of the ARL database with what is available on the trainer. function addon:ScanTrainerData(autoscan) if not _G.UnitExists("target") or _G.UnitIsPlayer("target") or _G.UnitIsEnemy("player", "target") then if not autoscan then self:Print(L["DATAMINER_TRAINER_NOTTARGETTED"]) end return end if not _G.IsTradeskillTrainer() then if not autoscan then self:Print(L["DATAMINER_SKILLLEVEL_ERROR"]) end return end local recipe_list = LoadAllRecipes() -- Get internal database if not recipe_list then self:Print(L["DATAMINER_NODB_ERROR"]) return end -- Get the initial trainer filters local avail = _G.GetTrainerServiceTypeFilter("available") local unavail = _G.GetTrainerServiceTypeFilter("unavailable") local used = _G.GetTrainerServiceTypeFilter("used") -- Clear the trainer filters _G.SetTrainerServiceTypeFilter("available", 1) _G.SetTrainerServiceTypeFilter("unavailable", 1) _G.SetTrainerServiceTypeFilter("used", 1) if _G.GetNumTrainerServices() == 0 then self:Print("Warning: Trainer is bugged, reporting 0 trainer items.") end table.wipe(scanned_items) table.wipe(scanned_recipes) local trainer_profession = _G.GetTrainerServiceSkillLine(1) for index = 1, _G.GetNumTrainerServices(), 1 do local item_name = _G.GetTrainerServiceInfo(index) local item_id = private.ItemLinkToID(_G.GetTrainerServiceItemLink(index)) local _, skill_level = _G.GetTrainerServiceSkillReq(index) if not skill_level then skill_level = 0 end if item_id then scanned_items[item_id] = skill_level else scanned_recipes[item_name] = skill_level end end table.wipe(missing_spell_ids) table.wipe(extra_spell_ids) table.wipe(fixed_item_spell_ids) table.wipe(mismatched_levels) table.wipe(mismatched_levels_unconfirmed) table.wipe(output) -- Dump out trainer info local trainer_id = tonumber(_G.UnitGUID("target"):sub(-12,-9), 16) local trainer_name = _G.UnitName("target") for spell_id, recipe in pairs(recipe_list) do if trainer_profession == recipe.profession then local train_data = recipe.acquire_data[A.TRAINER] local matching_trainer = false if train_data then for id_num in pairs(train_data) do if id_num == trainer_id then matching_trainer = true break end end end local matching_item = scanned_items[recipe:CraftedItemID()] local matching_recipe = scanned_recipes[recipe.name] if recipe:CraftedItemID() then matching_recipe = nil end local trainer_entry = private.trainer_list[trainer_id] local trainer_x, trainer_y = _G.GetPlayerMapPosition("player") trainer_x = ("%.2f"):format(trainer_x * 100) trainer_y = ("%.2f"):format(trainer_y * 100) if trainer_entry and trainer_entry.coord_x ~= trainer_x or trainer_entry.coord_y ~= trainer_y then table.insert(output, ("%s appears to have different coordinates (%s, %s) than those in the database (%s, %s) - a trainer dump for %s will fix this."):format(trainer_name, trainer_entry.coord_x, trainer_entry.coord_y, trainer_x, trainer_y, trainer_profession)) trainer_entry.coord_x = trainer_x trainer_entry.coord_y = trainer_y elseif not trainer_entry then table.insert(output, ("%s was not found in the trainer list - a trainer dump for %s will fix this."):format(trainer_name, trainer_profession)) _G.SetMapToCurrentZone() -- Make sure were are looking at the right zone private:AddTrainer(trainer_id, trainer_name, _G.GetRealZoneText(), trainer_x, trainer_y, private.Player.faction) end if matching_item or matching_recipe then if not matching_trainer then table.insert(missing_spell_ids, spell_id) recipe:AddTrainer(trainer_id) if not recipe:HasFilter("common1", "TRAINER") then recipe:AddFilters(F.TRAINER) if matching_item then table.insert(output, ("Added trainer flag to recipe with spell ID %d. (matching crafted item ID %d)"):format(spell_id, recipe:CraftedItemID())) elseif matching_recipe then table.insert(output, ("Added trainer flag to recipe with spell ID %d. (matching recipe name \"%s\")"):format(spell_id, recipe.name)) end end end local recipe_skill = recipe:SkillLevels() if matching_item and matching_item ~= recipe_skill then table.insert(mismatched_levels, spell_id) elseif matching_recipe and matching_recipe ~= recipe_skill then table.insert(mismatched_levels_unconfirmed, spell_id) end elseif matching_trainer then table.wipe(itemless_spells) if not recipe:CraftedItemID() then for item_id in pairs(scanned_items) do if recipe.name == _G.GetItemInfo(item_id) then recipe:SetCraftedItemID(item_id) itemless_spells[spell_id] = true end end end if itemless_spells[spell_id] then table.insert(fixed_item_spell_ids, spell_id) else table.insert(extra_spell_ids, spell_id) end end end end local found_missing = #missing_spell_ids > 0 local found_extra = #extra_spell_ids > 0 local found_fixed_item = #fixed_item_spell_ids > 0 local found_wrong_level = #mismatched_levels > 0 local found_unconfirmed_level = #mismatched_levels_unconfirmed > 0 if found_missing then table.insert(output, "\nTrainer is missing from the following entries:") table.sort(missing_spell_ids) for index in ipairs(missing_spell_ids) do local spell_id = missing_spell_ids[index] table.insert(output, ("%d (%s)"):format(spell_id, recipe_list[spell_id].name)) end end if found_extra then table.insert(output, "\nRecipes which are wrongly assigned to the trainer:") table.sort(extra_spell_ids) for index in ipairs(extra_spell_ids) do local spell_id = extra_spell_ids[index] local recipe = recipe_list[spell_id] local crafted_item = recipe:CraftedItemID() if crafted_item then table.insert(output, ("%d (%s) - Crafted item ID set to %d (%s)"):format(spell_id, recipe.name, crafted_item, _G.GetItemInfo(crafted_item) or _G.UNKNOWN)) else table.insert(output, ("%d (%s)"):format(spell_id, recipe.name)) end end end if found_fixed_item then table.insert(output, "\nRecipes which had no crafted item ID, but will once a dump is performed:") table.sort(fixed_item_spell_ids) for index in ipairs(fixed_item_spell_ids) do local spell_id = fixed_item_spell_ids[index] table.insert(output, ("%d (%s)"):format(spell_id, recipe_list[spell_id].name)) end end if found_wrong_level then table.insert(output, "\nRecipes which had an incorrect skill level, but will not once a dump is performed:") table.sort(mismatched_levels) for index in ipairs(mismatched_levels) do local spell_id = mismatched_levels[index] local recipe = recipe_list[spell_id] local recipe_skill = recipe:SkillLevels() local corrected_skill = scanned_items[recipe:CraftedItemID()] table.insert(output, ("%d (%s): Corrected skill level from %d to %d."):format(spell_id, recipe.name, recipe_skill, corrected_skill)) recipe:SetSkillLevels(corrected_skill) end end if found_unconfirmed_level then table.insert(output, "\nRecipes with possible incorrect skill levels - unable to confirm:") table.sort(mismatched_levels_unconfirmed) for index in ipairs(mismatched_levels_unconfirmed) do local spell_id = mismatched_levels_unconfirmed[index] local recipe = recipe_list[spell_id] local recipe_skill = recipe:SkillLevels() table.insert(output, ("%d (%s): Skill set to %d; trainer reports %d."):format(spell_id, recipe.name, recipe_skill, scanned_recipes[recipe.name])) end end if #output > 0 then table.insert(output, 1, ("ARL Version: %s"):format(self.version)) table.insert(output, 2, L["DATAMINER_TRAINER_INFO"]:format(trainer_name, trainer_id)) if found_extra and trainer_profession == private.professions.Engineering then table.insert(output, "\nSome goggles may be listed as extra. These goggles ONLY show up for the classes who can make them, so they may be false positives.") end self:DisplayTextDump(nil, nil, table.concat(output, "\n")) end -- Reset the filters to what they were before _G.SetTrainerServiceTypeFilter("available", avail or 0) _G.SetTrainerServiceTypeFilter("unavailable", unavail or 0) _G.SetTrainerServiceTypeFilter("used", used or 0) end end -- do --- Generates tradeskill links for all professions so you can scan them for completeness. -- @name AckisRecipeList:GenerateLinks -- @return Generates tradeskill links with all recipes. Used for testing to see if a recipe is missing from the database or not. function addon:GenerateLinks() -- This code adopted from Gnomish Yellow Pages with permission local guid = _G.UnitGUID("player") local playerGUID = guid:gsub("0x0+","") -- Listing of all tradeskill professions local tradelist = {51304, 51300, 51313, 51306, 45363, 51311, 51302, 51309, 51296, 45542} --[[ local encodingLength = floor((#recipeList+5) / 6) local encodedString = ("/"):rep(encodingLength) ]]-- local bitmap = {} bitmap[45542] = "8bffAA" -- First Aid 100% 17/17 recipes --bitmap[51296] = "2/6///7///9f///7//////////g///B" -- Cooking 98.2% 166/169 recipes bitmap[51296] = "GEgAGAjmFAwBAxAzfex//DgAgPA6HtgAI8lC" --bitmap[51306] = "4/////////////3nFA+///9+/P7//f//n//9dgdJgHA87/3f/TolD" -- Engineering (53) bitmap[51306] = "4/////////////3nFA+///9+/P7//f//n//9dgdJgHA87/3f/Tol3B" -- Engineering bitmap[51302] = "e+//ff////d//u//v//n+vv7/+ujr7/9////bg/+////f344//dA4I8j/X//B4/vu5/////////////////nvA8/M8/D" -- Leatherworking 85.4% 451/528 recipes bitmap[51304] = "2//v//////f////3//v///////5//////////9/////v" -- Alchemy 100% 251/251 recipes bitmap[51300] = "2//////f7fM//f/f53/+//7///ze8c6q/f9///P2/////m4BAA2XIG+dFA8//PC////4//z//////////Pwvy//H" -- Blacksmithing 82.9% 413/498 recipes bitmap[51309] = "4//+/76J7//v7/ve+/XR9fLb2f3nN3vLc6vS0+Hf8/XG7/VAIh9B+/f6/////3/////9/f1wCB" -- Tailoring 83.37% 341/409 recipes bitmap[51311] = "8//3////fzj//u//////v/7///9///33////////Pw////////////////////////3///////////////////////////D" -- Jewelcrafting 97.5% 540/554 recipes bitmap[45363] = "g////7////3///////////////////////////////////////////////////f///////////f" --Inscription 99.77% 439/440 recipes bitmap[51313] = "4//////////7///////////w//+//9/n7///////3f//////ZsD" -- Enchanting 94.3% 279/296 recipes for i in pairs(tradelist) do local tradeName = _G.GetSpellInfo(tradelist[i]) local tradelink = {} tradelink[1] = "|cffffd000|Htrade:" tradelink[2] = tradelist[i] tradelink[3] = ":450:450:" tradelink[4] = playerGUID tradelink[5] = ":" tradelink[6] = bitmap[tradelist[i]] tradelink[7] = "|h[" tradelink[8] = tradeName tradelink[9] = "]|h|r" if (bitmap[tradelist[i]]) then self:Print(table.concat(tradelink, "")) else self:Print("There currently is not a generated tradeskill link for: " .. tradeName) end -- /script DEFAULT_CHAT_FRAME:AddMessage(GetTradeSkillListLink():gsub("\124","\124\124")) end end do local ORDERED_PROFESSIONS = private.ordered_professions local recipe_list = {} local output = {} local function Sort_AscID(a, b) local reca, recb = private.recipe_list[a], private.recipe_list[b] return reca.spell_id < recb.spell_id end local function SortRecipesByID() local sorted_recipes = addon.sorted_recipes table.wipe(sorted_recipes) for n, v in pairs(recipe_list) do table.insert(sorted_recipes, n) end table.sort(sorted_recipes, Sort_AscID) end ------------------------------------------------------------------------------- --- Scans the items in the specified profession ------------------------------------------------------------------------------- local function ProfessionScan(prof_name) local master_list = LoadAllRecipes() if not master_list then addon:Print(L["DATAMINER_NODB_ERROR"]) return end table.wipe(recipe_list) if prof_name == private.professions["Smelting"]:lower() then prof_name = private.mining_name:lower() end for i in pairs(master_list) do local prof = master_list[i].profession:lower() if prof and prof == prof_name then recipe_list[i] = master_list[i] end end SortRecipesByID() table.wipe(output) -- Parse the entire recipe database for index, id in ipairs(addon.sorted_recipes) do local ttscantext = addon:TooltipScanRecipe(id, false, true) if ttscantext and ttscantext ~= "" then table.insert(output, ttscantext) end end if #output == 0 then addon:Debug("ProfessionScan(): output is empty.") end addon:DisplayTextDump(nil, nil, table.concat(output, "\n")) ARLDatamineTT:Hide() end --- Parses all recipes for a specified profession, scanning their tool tips. -- @name AckisRecipeList:ScanProfession -- @usage AckisRecipeList:ScanProfession("first aid") -- @param prof_name The profession name or the spell ID of it, which you wish to scan. -- @return Recipes in the given profession have their tooltips scanned. function addon:ScanProfession(prof_name) if type(prof_name) == "number" then prof_name = _G.GetSpellInfo(prof_name) end local found = false prof_name = prof_name:lower() local scan_all = prof_name == "all" if not scan_all then for idx, name in ipairs(ORDERED_PROFESSIONS) do if prof_name == name:lower() then found = true break end end if not found then self:Print(L["DATAMINER_NODB_ERROR"]) return end ProfessionScan(prof_name) else for idx, name in ipairs(ORDERED_PROFESSIONS) do ProfessionScan(name:lower()) end end end local function RecipeDump(id, single) local recipe = private.recipe_list[id or 1] if single and not recipe then addon:Print("Invalid recipe ID.") return end recipe:Dump(output) end ------------------------------------------------------------------------------- --- Dumps the items in the specified profession ------------------------------------------------------------------------------- local function ProfessionDump(prof_name) local master_list = LoadAllRecipes() if not master_list then addon:Print(L["DATAMINE_NODB_ERROR"]) return end table.wipe(recipe_list) if prof_name == private.professions["Smelting"]:lower() then prof_name = private.mining_name:lower() end for i in pairs(master_list) do local prof = master_list[i].profession:lower() if prof and prof == prof_name then recipe_list[i] = master_list[i] end end SortRecipesByID() table.wipe(output) for index, id in ipairs(addon.sorted_recipes) do RecipeDump(id, false) end addon:DisplayTextDump(nil, nil, table.concat(output, "\n")) end function addon:DumpRecipe(id_num) table.wipe(output) RecipeDump(id_num, true) addon:DisplayTextDump(nil, nil, table.concat(output, "\n")) end function addon:DumpProfession(prof_name) if type(prof_name) == "number" then prof_name = _G.GetSpellInfo(prof_name) end local found = false prof_name = prof_name:lower() local scan_all = prof_name == "all" if not scan_all then for idx, name in ipairs(ORDERED_PROFESSIONS) do if prof_name == name:lower() then found = true break end end if not found then self:Print(L["DATAMINER_NODB_ERROR"]) return end ProfessionDump(prof_name) else for idx, name in ipairs(ORDERED_PROFESSIONS) do ProfessionDump(name:lower()) end end end ------------------------------------------------------------------------------- --- Dumps the trainers for the specified recipe/profession ------------------------------------------------------------------------------- local trainer_registry = {} local sorted_data = {} local function ProfessionTrainerDump(prof_name) local master_list = LoadAllRecipes() if not master_list then addon:Print(L["DATAMINE_NODB_ERROR"]) return end table.wipe(recipe_list) if prof_name == private.professions["Smelting"]:lower() then prof_name = private.mining_name:lower() end for i in pairs(master_list) do local prof = master_list[i].profession:lower() if prof and prof == prof_name then recipe_list[i] = master_list[i] end end SortRecipesByID() table.wipe(output) table.wipe(trainer_registry) table.wipe(sorted_data) for index, spell_id in ipairs(addon.sorted_recipes) do private.recipe_list[spell_id]:DumpTrainers(trainer_registry) end for identifier in pairs(trainer_registry) do table.insert(sorted_data, identifier) end table.sort(sorted_data) for index, identifier in ipairs(sorted_data) do local trainer = private.trainer_list[identifier] local trainer_name if trainer then if trainer.spell_id then table.insert(output, ("self:AddTrainer(%s, %s, \"%s\", %s, %s, \"%s\")"):format(identifier, trainer.spell_id, trainer.location, trainer.coord_x, trainer.coord_y, trainer.faction)) else table.insert(output, ("self:AddTrainer(%s, \"%s\", \"%s\", %s, %s, \"%s\")"):format(identifier, trainer.name:gsub("\"", "\\\""), trainer.location, trainer.coord_x, trainer.coord_y, trainer.faction)) end end end addon:DisplayTextDump(nil, nil, table.concat(output, "\n")) end function addon:DumpTrainers(prof_name) if type(prof_name) == "number" then prof_name = _G.GetSpellInfo(prof_name) end prof_name = prof_name:lower() local scan_all = prof_name == "all" if scan_all then for idx, name in ipairs(ORDERED_PROFESSIONS) do ProfessionTrainerDump(name:lower()) end else local found = false for idx, name in ipairs(ORDERED_PROFESSIONS) do if prof_name == name:lower() then found = true break end end if not found then self:Print(L["DATAMINER_NODB_ERROR"]) return end ProfessionTrainerDump(prof_name) end end end -- do local RECIPE_TYPES = { -- JC ["design: "] = true, -- LW or Tailoring ["pattern: "] = true, -- Alchemy or Cooking ["recipe: "] = true, -- BS ["plans: "] = true, -- Enchanting ["formula: "] = true, -- Engineering ["schematic: "] = true, -- First Aid ["manual: "] = true, ["alchemy: "] = true, ["blacksmithing: "] = true, ["cooking: "] = true, ["enchanting: "] = true, ["engineering: "] = true, ["first aid: "] = true, ["inscription: "] = true, ["jewelcrafting: "] = true, ["leatherworking: "] = true, ["tailoring: "] = true, } --- Scans the items on a vendor, determining which recipes are available if any and compares it with the database entries -- @name AckisRecipeList:ScanVendor -- @usage AckisRecipeList:ScanVendor() -- @return Obtains all the vendor information on tradeskill recipes and attempts to compare the current vendor with the internal database do local output = {} local RECIPE_ITEM_TO_SPELL_MAP function addon:ScanVendor() if not _G.UnitExists("target") or _G.UnitIsPlayer("target") or _G.UnitIsEnemy("player", "target") then self:Print(L["DATAMINER_VENDOR_NOTTARGETTED"]) return end local recipe_list = LoadAllRecipes() -- Get internal database if not recipe_list then self:Print(L["DATAMINER_NODB_ERROR"]) return end local vendor_name = _G.UnitName("target") local vendor_id = tonumber(_G.UnitGUID("target"):sub(-12,-9), 16) local added_output = false table.wipe(output) table.insert(output, ("ARL Version: %s"):format(self.version)) table.insert(output, L["DATAMINER_VENDOR_INFO"]:format(vendor_name, vendor_id)) if not RECIPE_ITEM_TO_SPELL_MAP then RECIPE_ITEM_TO_SPELL_MAP = {} for spell_id, recipe in pairs(private.recipe_list) do local recipe_item_id = recipe:RecipeItemID() if recipe_item_id then RECIPE_ITEM_TO_SPELL_MAP[recipe_item_id] = spell_id elseif recipe:HasFilter("common1", "VENDOR") or recipe:HasFilter("common1", "INSTANCE") or recipe:HasFilter("common1", "RAID") or recipe:HasFilter("common1", "MOB_DROP") or recipe:HasFilter("common1", "WORLD_DROP") then self:Debug("Recipe %d (%s) is missing a recipe item ID.", spell_id, recipe.name) end end end for index = 1, _G.GetMerchantNumItems(), 1 do local item_name, _, _, _, supply = _G.GetMerchantItemInfo(index) if item_name then local match_text = item_name:match("%a+: ") if match_text and RECIPE_TYPES[match_text:lower()] then local item_id = private.ItemLinkToID(_G.GetMerchantItemLink(index)) local spell_id = RECIPE_ITEM_TO_SPELL_MAP[item_id] if spell_id then local scanned_text = addon:TooltipScanRecipe(spell_id, true, true) if scanned_text and scanned_text ~= "" then added_output = true table.insert(output, scanned_text) end -- Check the database to see if the vendor is listed as an acquire method. local recipe = recipe_list[spell_id] local acquire_data = recipe and recipe.acquire_data local vendor_data = acquire_data and acquire_data[A.VENDOR] local rep_data = acquire_data and acquire_data[A.REPUTATION] local matching_vendor = false if vendor_data then for id_num in pairs(vendor_data) do if id_num == vendor_id then matching_vendor = true break end end elseif rep_data then for id_num, info in pairs(rep_data) do if matching_vendor then break end for rep_level, level_info in pairs(info) do for rep_vendor_id in pairs(level_info) do if rep_vendor_id == vendor_id then matching_vendor = true end end end end end local vendor_entry = private.vendor_list[vendor_id] if not vendor_entry then table.insert(output, ("%s was not found in the vendor list"):format(vendor_name)) end if matching_vendor and vendor_entry and vendor_entry.item_list then local reported_supply = vendor_entry.item_list[spell_id] if reported_supply == true and supply > -1 then recipe:AddLimitedVendor(vendor_id, supply) table.insert(output, ("Limited quantity for \"%s\" (%d) found on vendor %d - listed as unlimited quantity."):format(recipe.name, spell_id, vendor_id)) added_output = true elseif type(reported_supply) ~= "boolean" and supply == -1 then recipe:AddVendor(vendor_id) table.insert(output, ("Unlimited quantity for \"%s\" (%d) found on vendor %d - listed as limited quantity."):format(recipe.name, spell_id, vendor_id)) added_output = true end if not recipe:HasFilter("common1", "VENDOR") then recipe:AddFilters(F.VENDOR) table.insert(output, ("%d: Vendor flag was not set."):format(spell_id)) added_output = true end elseif not matching_vendor then if supply > -1 then recipe:AddLimitedVendor(vendor_id, supply) else recipe:AddVendor(vendor_id) end if not recipe:HasFilter("common1", "VENDOR") then recipe:AddFilters(F.VENDOR) table.insert(output, ("%d: Vendor flag was not set."):format(spell_id)) end added_output = true table.insert(output, ("Vendor ID missing from \"%s\" %d."):format(recipe and recipe.name or _G.UNKNOWN, spell_id)) end else for spell_id, recipe in pairs(private.recipe_list) do local recipe_type, match_text = (":"):split(item_name, 2) if recipe.name == match_text:trim() then recipe:SetRecipeItemID(item_id) end end --@debug@ added_output = true table.insert(output, ("Spell ID not found for recipe item %d (%s)"):format(item_id, item_name)) --@end-debug@ end end end end if added_output then self:DisplayTextDump(nil, nil, table.concat(output, "\n")) end ARLDatamineTT:Hide() end end -- do --- Parses all the recipes in the database, and scanning their tooltips. -- @name AckisRecipeList:TooltipScanDatabase -- @usage AckisRecipeList:TooltipScanDatabase() -- @return Entire recipe database has its tooltips scanned. do local output = {} function addon:TooltipScanDatabase() -- Get internal database local recipe_list = LoadAllRecipes() if (not recipe_list) then self:Print(L["DATAMINER_NODB_ERROR"]) return end table.wipe(output) -- Parse the entire recipe database for i in pairs(recipe_list) do local ttscantext = addon:TooltipScanRecipe(i, false, true) if (ttscantext) then table.insert(output, ttscantext) end end self:DisplayTextDump(nil, nil, table.concat(output, "\n")) end end -- do --- Parses a specific recipe in the database, and scanning its tooltip -- @name AckisRecipeList:TooltipScanRecipe -- @param spell_id The [[[http://www.wowpedia.org/SpellLink|Spell ID]]] of the recipe being added to the database -- @param is_vendor Boolean to determine if we're viewing a vendor or not -- @param is_largescan Boolean to determine if we're doing a large scan -- @return Recipe has its tooltips scanned -- Table to store scanned information. Wiped and re-used every scan. local scan_data = {} do --------------------------------------------------------------------------------------------------------- ----This table, DO_NOT_SCAN, contains itemid's that will not cache on the servers --------------------------------------------------------------------------------------------------------- local DO_NOT_SCAN = { ------------------------------------------------------------------------------- --Leatherworking ------------------------------------------------------------------------------- [35214] = true, [32434] = true, [15769] = true, [32431] = true, [32432] = true, [35215] = true, [35521] = true, [35520] = true, [35524] = true, [35517] = true, [35528] = true, [35527] = true, [35523] = true, [35549] = true, [35218] = true, [35217] = true, [35216] = true, [35546] = true, [35541] = true, [15756] = true, [15777] = true, [32433] = true, [29729] = true, [29732] = true, [32744] = true, [15773] = true, [30301] = true, [15745] = true, [35302] = true, [15774] = true, ------------------------------------------------------------------------------- --Tailoring ------------------------------------------------------------------------------- [14477] = true, [14485] = true, [30281] = true, [14478] = true, [14500] = true, [32439] = true, [14479] = true, [32447] = true, [14480] = true, [32437] = true, [14495] = true, [14505] = true, [35204] = true, [35205] = true, [35206] = true, [14473] = true, [14488] = true, [14481] = true, [35309] = true, [30280] = true, [14492] = true, [14491] = true, ------------------------------------------------------------------------------- --Jewelcrafting ------------------------------------------------------------------------------- [23130] = true, [23140] = true, [23137] = true, [23131] = true, [23148] = true, [35538] = true, [35201] = true, [35533] = true, [35200] = true, [23147] = true, [23135] = true, [35203] = true, [35198] = true, [23152] = true, [23151] = true, [23141] = true, [28596] = true, [28291] = true, [23153] = true, ------------------------------------------------------------------------------- --Alchemy ------------------------------------------------------------------------------- [22925] = true, [13480] = true, [13481] = true, [13493] = true, [35295] = true, [44568] = true, ------------------------------------------------------------------------------- --Cooking ------------------------------------------------------------------------------- [39644] = true, ------------------------------------------------------------------------------- --Blacksmithing ------------------------------------------------------------------------------- [32441] = true, [32443] = true, [12687] = true, [12714] = true, [12688] = true, [35211] = true, [35209] = true, [35210] = true, [12706] = true, [7982] = true, [12718] = true, [23621] = true, [35208] = true, [12716] = true, [23632] = true, [23633] = true, [30324] = true, [23637] = true, [31393] = true, [22221] = true, [12690] = true, [31394] = true, [31395] = true, [23630] = true, [23629] = true, [7978] = true, [41120] = true, [12717] = true, [22219] = true, [23627] = true, ------------------------------------------------------------------------------- --Engineering ------------------------------------------------------------------------------- [35196] = true, [21734] = true, [18292] = true, [21727] = true, [21735] = true, [16053] = true, [21729] = true, [16047] = true, [21730] = true, [21731] = true, [21732] = true, [4411] = true, [21733] = true, [21728] = true, [35186] = true, [18655] = true, ------------------------------------------------------------------------------- --Enchanting ------------------------------------------------------------------------------- [16222] = true, [20734] = true, [20729] = true, [20731] = true, [16246] = true, } local output = {} function addon:TooltipScanRecipe(spell_id, is_vendor, is_largescan) local recipe_list = LoadAllRecipes() if not recipe_list then self:Print(L["DATAMINER_NODB_ERROR"]) return end local recipe = recipe_list[spell_id] if not recipe then self:Debug("Spell ID %d does not exist in the database.", tonumber(spell_id)) return end local recipe_name = recipe.name local game_vers = private.game_versions[recipe.genesis] table.wipe(output) if not game_vers then table.insert(output, "No expansion information: " .. tostring(spell_id) .. " " .. recipe_name) elseif game_vers > #private.game_version_names then table.insert(output, "Expansion information too high: " .. tostring(spell_id) .. " " .. recipe_name) end local optimal = recipe.optimal_level local medium = recipe.medium_level local easy = recipe.easy_level local trivial = recipe.trivial_level local skill_level = recipe.skill_level if not optimal then table.insert(output, "No skill level information: " .. tostring(spell_id) .. " " .. recipe_name) else -- Highest level is greater than the skill of the recipe if optimal > skill_level then table.insert(output, "Skill Level Error (optimal_level > skill_level): " .. tostring(spell_id) .. " " .. recipe_name) elseif optimal < skill_level then table.insert(output, "Skill Level Error (optimal_level < skill_level): " .. tostring(spell_id) .. " " .. recipe_name) end -- Level info is messed up if optimal > medium or optimal > easy or optimal > trivial or medium > easy or medium > trivial or easy > trivial then table.insert(output, "Skill Level Error: " .. tostring(spell_id) .. " " .. recipe_name) end end local recipe_link = _G.GetSpellLink(recipe.spell_id) if not recipe_link then if recipe.profession ~= private.runeforging_name then -- Lets hide this output for runeforging. self:Printf("Missing spell_link for ID %d (%s).", spell_id, recipe_name) end return end ARLDatamineTT:SetOwner(_G.WorldFrame, "ANCHOR_NONE") _G.GameTooltip_SetDefaultAnchor(ARLDatamineTT, _G.UIParent) ARLDatamineTT:SetHyperlink(recipe_link) -- Check to see if this is a recipe tooltip. local text = _G["ARLDatamineTTTextLeft1"]:GetText():lower() local match_text = text:match("%a+: ") if not RECIPE_TYPES[match_text] and not (text:find("smelt") or text:find("sunder") or text:find("shatter")) then ARLDatamineTT:Hide() return end local reverse_lookup = GetReverseLookup(recipe_list) local recipe_item_id = recipe:RecipeItemID() table.wipe(scan_data) if recipe_item_id then if recipe:HasFilter("common1", "TRAINER") and not recipe:HasFilter("common1", "VENDOR") and not recipe:HasFilter("common1", "INSTANCE") and not recipe:HasFilter("common1", "RAID") and not recipe:HasFilter("common1", "WORLD_DROP") then table.insert(output, ("Recipe %d (%s): Has Trainer filter flag, but also has a recipe item (%d)."):format(recipe.spell_id, recipe.name, recipe_item_id)) elseif not DO_NOT_SCAN[recipe_item_id] then local item_name, item_link, item_quality = _G.GetItemInfo(recipe_item_id) if item_name then if item_quality > 0 then scan_data.quality = item_quality ARLDatamineTT:SetHyperlink(item_link) self:ScanToolTip(recipe_name, recipe_list, reverse_lookup, is_vendor) else table.insert(output, ("Recipe %d (%s): Recipe item quality is 0 (junk), which probably means it has been removed from the game."):format(recipe.spell_id, recipe.name)) end else table.insert(output, ("%s: %d"):format(recipe.name, spell_id)) if _G.Querier then table.insert(output, (" Recipe item not in cache. To fix: /iq %d"):format(recipe_item_id)) else table.insert(output, " Recipe item not in cache.") end end end elseif not recipe_item_id and not recipe:HasFilter("common1", "RETIRED") then -- We are dealing with a recipe that does not have an item to learn it from. -- Lets check the recipe flags to see if we have a data error and the item should exist if recipe:HasFilter("common1", "VENDOR") or recipe:HasFilter("common1", "INSTANCE") or recipe:HasFilter("common1", "RAID") then table.insert(output, ("Recipe %d (%s) is missing a recipe item ID."):format(spell_id, recipe.name)) elseif recipe:HasFilter("common1", "TRAINER") and recipe.quality ~= private.item_qualities["COMMON"] then table.insert(output, ("%s: %d"):format(recipe.name, spell_id)) table.insert(output, (" Wrong quality: Q.%s - should be Q.COMMON."):format(private.item_quality_names[recipe.quality])) end end ARLDatamineTT:Hide() -- Add the flag scan to the table if it's not nil local results = self:PrintScanResults() if results then table.insert(output, results) end if is_largescan then return table.concat(output, "\n") else self:Print(table.concat(output, "\n")) end end end -- do ------------------------------------------------------------------------------- -- Tooltip-scanning code ------------------------------------------------------------------------------- do local SPECIALTY_TEXT = { --["requires spellfire tailoring"] = 26797, --["requires mooncloth tailoring"] = 26798, --["requires shadoweave tailoring"] = 26801, --["requires dragonscale leatherworking"] = 10657, --["requires elemental leatherworking"] = 10659, --["requires tribal leatherworking"] = 10661, ["requires gnomish engineer"] = 20219, ["requires goblin engineer"] = 20222, --["requires armorsmith"] = 9788, --["requires master axesmith"] = 17041, --["requires master hammersmith"] = 17040, --["requires master swordsmith"] = 17039, --["requires weaponsmith"] = 9787, } local FACTION_TEXT = { ["thorium brotherhood"] = 98, ["zandalar tribe"] = 100, ["argent dawn"] = 96, ["timbermaw hold"] = 99, ["cenarion circle"] = 97, ["the aldor"] = 101, ["the consortium"] = 105, ["the scryers"] = 110, ["the sha'tar"] = 111, ["the mag'har"] = 108, ["cenarion expedition"] = 103, ["honor hold"] = 104, ["thrallmar"] = 104, ["the violet eye"] = 114, ["sporeggar"] = 113, ["kurenai"] = 108, ["keepers of time"] = 106, ["the scale of the sands"] = 109, ["lower city"] = 107, ["ashtongue deathsworn"] = 102, ["alliance vanguard"] = 131, ["valiance expedition"] = 126, ["horde expedition"] = 130, ["the taunka"] = 128, ["the hand of vengeance"] = 127, ["explorers' league"] = 125, ["the kalu'ak"] = 120, ["shattered sun offensive"] = 112, ["warsong offensive"] = 129, ["kirin tor"] = 118, ["the wyrmrest accord"] = 122, ["knights of the ebon blade"] = 117, ["frenzyheart tribe"] = 116, ["the oracles"] = 121, ["argent crusade"] = 115, ["the sons of hodir"] = 119, } local FACTION_LEVELS = { ["neutral"] = 0, ["friendly"] = 1, ["honored"] = 2, ["revered"] = 3, ["exalted"] = 4, } local CLASS_TYPES = { ["Death Knight"] = 21, ["Druid"] = 22, ["Hunter"] = 23, ["Mage"] = 24, ["Paladin"] = 25, ["Priest"] = 26, ["Shaman"] = 27, ["Rogue"] = 28, ["Warlock"] = 29, ["Warrior"] = 30, } local ORDERED_CLASS_TYPES = { [1] = "Death Knight", [2] = "Druid", [3] = "Hunter", [4] = "Mage", [5] = "Paladin", [6] = "Priest", [7] = "Shaman", [8] = "Rogue", [9] = "Warlock", [10] = "Warrior", } local ROLE_TYPES = { ["dps"] = 51, ["tank"] = 52, ["healer"] = 53, ["caster"] = 54, } local ORDERED_ROLE_TYPES = { [1] = "dps", [2] = "tank", [3] = "healer", [4] = "caster", } local ENCHANT_TO_ITEM = { ["Cloak"] = "Back", ["Ring"] = "Finger", ["2H Weapon"] = "Two-Hand", } local ITEM_TYPES = { -- Armor types ["Cloth"] = 56, ["Leather"] = 57, ["Mail"] = 58, ["Plate"] = 59, ["Back"] = 60, ["Trinket"] = 61, ["Finger"] = 62, ["Neck"] = 63, ["Shield"] = 64, -- Weapon types ["One-Hand"] = 66, ["Two-Hand"] = 67, ["Axe"] = 68, ["Sword"] = 69, ["Mace"] = 70, ["Polearm"] = 71, ["Dagger"] = 72, ["Staff"] = 73, ["Wand"] = 74, ["Thrown"] = 75, ["Bow"] = 76, ["CrossBow"] = 77, ["Fist Weapon"] = 79, ["Gun"] = 80, } local ORDERED_ITEM_TYPES = { -- Armor types [1] = "Cloth", [2] = "Leather", [3] = "Mail", [4] = "Plate", [5] = "Back", [6] = "Trinket", [7] = "Finger", [8] = "Neck", [9] = "Shield", -- Weapon types [11] = "One-Hand", [12] = "Two-Hand", [13] = "Axe", [14] = "Sword", [15] = "Mace", [16] = "Polearm", [17] = "Dagger", [18] = "Staff", [19] = "Wand", [20] = "Thrown", [21] = "Bow", [22] = "CrossBow", [23] = "Fist Weapon", [24] = "Gun", } --- Parses the mining tooltip for certain keywords, comparing them with the database flags -- @name AckisRecipeList:ScanToolTip -- @param name The name of the recipe -- @param recipe_list Recipe database -- @param reverse_lookup Reverse lookup database -- @param is_vendor Boolean to indicate if we're scanning a vendor -- @return Scans a tooltip, and outputs the missing or extra filter flags function addon:ScanToolTip(name, recipe_list, reverse_lookup, is_vendor) scan_data.match_name = name scan_data.recipe_list = recipe_list scan_data.reverse_lookup = reverse_lookup scan_data.is_vendor = is_vendor -- Parse all the lines of the tooltip for i = 1, ARLDatamineTT:NumLines(), 1 do local text_l = _G["ARLDatamineTTTextLeft" .. i]:GetText() local text_r = _G["ARLDatamineTTTextRight" .. i]:GetText() local text if text_r then text = ("%s %s"):format(text_l, text_r) else text = text_l end text = text:lower() -- Check for recipe/item binding -- The recipe binding is within the first few lines of the tooltip always if text:match("binds when picked up") then if (i < 3) then scan_data.recipe_bop = true else scan_data.item_bop = true end end -- Recipe Specialities if SPECIALTY_TEXT[text] then scan_data.specialty = SPECIALTY_TEXT[text] end -- Recipe Reputations local rep, replevel = text_l:match("Requires (.+) %- (.+)") if rep and replevel and FACTION_TEXT[rep] then scan_data.repid = FACTION_TEXT[rep] scan_data.repidlevel = FACTION_LEVELS[replevel] end -- Flag so that we don't bother checking for classes if we're sure of the class -- AKA +spell hit == caster DPS only no matter what other stats are on it -- Saves processing cycles and it won't cause the flags to be overwritten if a non-specific stat is found after scan_data.verifiedclass = false if not scan_data.verifiedclass then -- Certain stats can be considered for a specific role (aka spell hit == caster dps). if text:match("strength") and not text:match("strength of the clefthoof") and not text:match("set:") then scan_data.dps = true elseif text:match("agility") then scan_data.dps = true elseif text:match("spirit") or text:match("intellect") then scan_data.caster = true scan_data.healer = true elseif text:match("spell power") then scan_data.caster = true scan_data.healer = true elseif text:match("spell crit") then scan_data.caster = true scan_data.healer = true elseif text:match("spell hit") then scan_data.caster = true scan_data.verifiedclass = true elseif text:match("spell penetration") then scan_data.caster = true scan_data.verifiedclass = true elseif text:match("mana per 5 sec.") or text:match("mana every 5 seconds") then scan_data.caster = true scan_data.healer = true elseif text:match("attack power") then scan_data.dps = true elseif text:match("expertise") then scan_data.dps = true scan_data.tank = true elseif text:match("melee crit") then scan_data.dps = true elseif text:match("critical hit") then scan_data.dps = true elseif text:match("weapon damage") then scan_data.dps = true elseif text:match("ranged crit") then scan_data.dps = true scan_data.verifiedclass = true elseif text:match("melee haste") then scan_data.dps = true elseif text:match("ranged haste") then scan_data.dps = true scan_data.verifiedclass = true elseif text:match("melee hit") then scan_data.dps = true elseif text:match("ranged hit") then scan_data.dps = true scan_data.verifiedclass = true elseif text:match("armor pen") then scan_data.dps = true elseif text:match("feral attack power") then scan_data.tank = true scan_data.dps = true elseif text:match("defense") and not text:match("defenseless") then scan_data.tank = true scan_data.verifiedclass = true elseif text:match("block") then scan_data.tank = true scan_data.verifiedclass = true elseif text:match("parry") then scan_data.tank = true scan_data.verifiedclass = true elseif text:match("dodge") and not text:match("set:") then scan_data.tank = true scan_data.verifiedclass = true end end -- Classes local class_type = text_l:match("Classes: (.+)") if class_type then for idx, class in ipairs(ORDERED_CLASS_TYPES) do if class_type:match(class) then scan_data[class] = true scan_data.found_class = true end end end -- Armor types if ITEM_TYPES[text_l] then scan_data[text_l] = true elseif text_l == "Held In Off-hand" or text_l == "Off Hand" or text_l == "Main Hand" then -- Special cases. scan_data["One-Hand"] = true end if text_r and ITEM_TYPES[text_r] then scan_data[text_r] = true end -- Enchantment voodoo local ench_type, _ = text_l:match("Enchant (.+) %- (.+)") if ench_type then if ITEM_TYPES[ench_type] then scan_data[ench_type] = true elseif ITEM_TYPES[ENCHANT_TO_ITEM[ench_type]] then scan_data[ENCHANT_TO_ITEM[ench_type]] = true elseif ench_type == "Weapon" then -- Special case. scan_data["One-Hand"] = true scan_data["Two-Hand"] = true end end end -- for end -- Flag data for printing. Wiped and re-used. local missing_flags = {} local extra_flags = {} local output = {} local ACQUIRE_TO_FILTER_MAP = { [A.MOB_DROP] = F.MOB_DROP, [A.QUEST] = F.QUEST, [A.SEASONAL] = F.SEASONAL, [A.WORLD_DROP] = F.WORLD_DROP, } local FILTER_TO_ACQUIRE_MAP local OBTAIN_FILTERS = { ["TRAINER"] = true, ["VENDOR"] = true, ["INSTANCE"] = true, ["RAID"] = true, ["SEASONAL"] = true, ["QUEST"] = true, ["PVP"] = true, ["WORLD_DROP"] = true, ["MOB_DROP"] = true, ["DISC"] = true, } --- Prints out the results of the tooltip scan. -- @name AckisRecipeList:PrintScanResults function addon:PrintScanResults() if not scan_data.match_name then return end -- Parse the recipe database until we get a match on the name local recipe_name = scan_data.match_name:gsub("%a+%?: ","") local spell_id = scan_data.reverse_lookup[recipe_name] if not spell_id then self:Print(recipe_name .. " has no reverse lookup") return end local recipe = scan_data.recipe_list[spell_id] local acquire_data = recipe["acquire_data"] local FS = private.filter_strings local flag_format = "F.%s" table.wipe(missing_flags) table.wipe(extra_flags) table.wipe(output) -- If we're a vendor scan, do some extra checks if scan_data.is_vendor then -- Check to see if the vendor flag is set if not recipe:HasFilter("common1", "VENDOR") then table.insert(missing_flags, flag_format:format(FS[F.VENDOR])) end -- Check to see if we're in a PVP zone local subzone_text = _G.GetSubZoneText() if (subzone_text == "Wintergrasp Fortress" or subzone_text == "Halaa") and not recipe:HasFilter("common1", "PVP") then table.insert(missing_flags, flag_format:format(FS[F.PVP])) elseif recipe:HasFilter("common1", "PVP") and not (subzone_text == "Wintergrasp Fortress" or subzone_text == "Halaa") then table.insert(extra_flags, flag_format:format(FS[F.PVP])) end end -- -- If we've picked up at least one class flag if scan_data.found_class then for k, v in ipairs(ORDERED_CLASS_TYPES) do if scan_data[v] and not recipe:HasFilter("class1", FS[CLASS_TYPES[v]]) then table.insert(missing_flags, flag_format:format(FS[CLASS_TYPES[v]])) elseif not scan_data[v] and recipe:HasFilter("class1", FS[CLASS_TYPES[v]]) then table.insert(extra_flags, flag_format:format(FS[CLASS_TYPES[v]])) end end end if scan_data.item_bop and not recipe:HasFilter("common1", "IBOP") then table.insert(missing_flags, flag_format:format(FS[F.IBOP])) if recipe:HasFilter("common1", "IBOE") then table.insert(extra_flags, flag_format:format(FS[F.IBOE])) end if recipe:HasFilter("common1", "IBOA") then table.insert(extra_flags, flag_format:format(FS[F.IBOA])) end elseif not recipe:HasFilter("common1", "IBOE") and not scan_data.item_bop then table.insert(missing_flags, flag_format:format(FS[F.IBOE])) if recipe:HasFilter("common1", "IBOP") then table.insert(extra_flags, flag_format:format(FS[F.IBOP])) end if recipe:HasFilter("common1", "IBOA") then table.insert(extra_flags, flag_format:format(FS[F.IBOA])) end end if scan_data.recipe_bop and not recipe:HasFilter("common1", "RBOP") then table.insert(missing_flags, flag_format:format(FS[F.RBOP])) if recipe:HasFilter("common1", "RBOE") then table.insert(extra_flags, flag_format:format(FS[F.RBOE])) end if recipe:HasFilter("common1", "RBOA") then table.insert(extra_flags, flag_format:format(FS[F.RBOA])) end elseif not recipe:HasFilter("common1", "TRAINER") and not recipe:HasFilter("common1", "RBOE") and not scan_data.recipe_bop then table.insert(missing_flags, flag_format:format(FS[F.RBOE])) if recipe:HasFilter("common1", "RBOP") then table.insert(extra_flags, flag_format:format(FS[F.RBOP])) end if recipe:HasFilter("common1", "RBOA") then table.insert(extra_flags, flag_format:format(FS[F.RBOA])) end end for k, v in ipairs(ORDERED_ROLE_TYPES) do local role_string = FS[ROLE_TYPES[v]] if scan_data[v] and not recipe:HasFilter("common1", role_string) then table.insert(missing_flags, flag_format:format(role_string)) elseif not scan_data[v] and recipe:HasFilter("common1", role_string) then table.insert(extra_flags, flag_format:format(role_string)) end end for k, v in ipairs(ORDERED_ITEM_TYPES) do if scan_data[v] and not recipe:HasFilter("item1", FS[ITEM_TYPES[v]]) then table.insert(missing_flags, flag_format:format(FS[ITEM_TYPES[v]])) elseif not scan_data[v] and recipe:HasFilter("item1", FS[ITEM_TYPES[v]]) then table.insert(extra_flags, flag_format:format(FS[ITEM_TYPES[v]])) end end -- Reputations local repid = scan_data.repid local found_problem = false if repid and not recipe:HasFilter("reputation1", FS[repid]) and not recipe:HasFilter("reputation2", FS[repid]) then table.insert(missing_flags, repid) local rep_data = acquire_data[A.REPUTATION] if rep_data then for rep_id, rep_info in pairs(rep_data) do for rep_level, level_info in pairs(rep_info) do if rep_level ~= scan_data.repidlevel then table.insert(output, " Wrong reputation level.") end end end end end -- Make sure the recipe's filter flags match with its acquire types. if not FILTER_TO_ACQUIRE_MAP then FILTER_TO_ACQUIRE_MAP = {} for k, v in pairs(ACQUIRE_TO_FILTER_MAP) do FILTER_TO_ACQUIRE_MAP[v] = k end end for acquire_type in pairs(acquire_data) do local flag = ACQUIRE_TO_FILTER_MAP[acquire_type] if flag and not recipe:HasFilter("common1", FS[flag]) then table.insert(missing_flags, flag_format:format(FS[flag])) end end if (acquire_data[A.VENDOR] or acquire_data[A.REPUTATION]) and not recipe:HasFilter("common1", "VENDOR") then table.insert(missing_flags, flag_format:format(FS[F.VENDOR])) end if recipe:HasFilter("common1", "VENDOR") and not (acquire_data[A.VENDOR] or acquire_data[A.REPUTATION]) then table.insert(extra_flags, flag_format:format(FS[F.VENDOR])) end if acquire_data[A.TRAINER] and not recipe:HasFilter("common1", "TRAINER") then table.insert(missing_flags, flag_format:format(FS[F.TRAINER])) end if recipe:HasFilter("common1", "TRAINER") and not acquire_data[A.TRAINER] and not acquire_data[A.CUSTOM] then table.insert(extra_flags, flag_format:format(FS[F.TRAINER])) end for flag, acquire_type in pairs(FILTER_TO_ACQUIRE_MAP) do if recipe:HasFilter("common1", FS[flag]) and not acquire_data[acquire_type] then table.insert(extra_flags, flag_format:format(FS[flag])) end end if #missing_flags > 0 or #extra_flags > 0 then found_problem = true -- Add a string of the missing flag numbers if #missing_flags > 0 then table.insert(output, " Missing flags: " .. table.concat(missing_flags, ", ")) end -- Add a string of the extra flag numbers if #extra_flags > 0 then table.insert(output, " Extra flags: " .. table.concat(extra_flags, ", ")) end local found_type = false for k, v in ipairs(ORDERED_ITEM_TYPES) do if scan_data[v] then found_type = true break end end if not found_type then table.insert(output, " Missing: item type flag") end end -- Check to see if we have a horde/alliance flag, all recipes must have one of these if not recipe:HasFilter("common1", "ALLIANCE") and not recipe:HasFilter("common1", "HORDE") then found_problem = true table.insert(output, " Horde or Alliance not selected.") end -- Check to see if we have an obtain method flag, all recipes must have at least one of these if not recipe:HasFilter("common1", "RETIRED") then local matching_filter = false for filter in pairs(OBTAIN_FILTERS) do if recipe:HasFilter("common1", filter) then matching_filter = true break end end if not matching_filter then found_problem = true table.insert(output, " No obtain flag.") end end -- Check for recipe binding information, all recipes must have one of these if not recipe:HasFilter("common1", "RBOE") and not recipe:HasFilter("common1", "RBOP") and not recipe:HasFilter("common1", "RBOA") then found_problem = true table.insert(output, " No recipe binding information.") end -- Check for item binding information, all recipes must have one of these if not recipe:HasFilter("common1", "IBOE") and not recipe:HasFilter("common1", "IBOP") and not recipe:HasFilter("common1", "IBOA") then found_problem = true table.insert(output, " No item binding information.") end -- We need to code this better. Some items (aka bags) won't have a role at all. -- Check for player role flags if not scan_data.tank and not scan_data.healer and not scan_data.caster and not scan_data.dps and not NO_ROLE_FLAG[spell_id] then found_problem = true table.insert(output, " No player role flag.") end if scan_data.specialty then if not recipe.specialty then found_problem = true table.insert(output, (" Missing Specialty: %s"):format(scan_data.specialty)) elseif recipe.specialty ~= scan_data.specialty then table.insert(output, (" Wrong Specialty: %s - should be %s "):format(recipe.specialty,scan_data.specialty)) end elseif recipe.specialty then found_problem = true table.insert(output, (" Extra Specialty: %s"):format(recipe.specialty)) end if scan_data.quality and scan_data.quality ~= recipe.quality then local QS = private.item_quality_names found_problem = true table.insert(output, (" Wrong quality: Q.%s - should be Q.%s."):format(QS[recipe.quality], QS[scan_data.quality])) end if found_problem then table.insert(output, 1, ("%s: <a href=\"http://www.wowhead.com/?spell=%d\">%d</a>"):format(recipe_name,spell_id,spell_id)) return table.concat(output, "\n") else return end end end ------------------------------------------------------------------------------- -- Look up table of spell IDs for recipes which do not have a player flag -- BASICALLY A TEMPORARY STORAGE FOR IDS, SO WE CAN SEE CLEANER SCANS AND WHAT NOT, -- WE'LL GO BACK HERE LATER DOWN THE ROAD. ------------------------------------------------------------------------------- NO_ROLE_FLAG = { -------------------------------------------------------------------------------------------- -----ASSORTED CRAP -------------------------------------------------------------------------------------------- [30344] = true, [30341] = true, [32814] = true, [23066] = true, [26421] = true, [36955] = true, [19788] = true, [23129] = true, [26422] = true, [12715] = true, [12899] = true, [56459] = true, [30307] = true, [26423] = true, [3918] = true, [23067] = true, [30308] = true, [3953] = true, [15255] = true, [26424] = true, [30548] = true, [44157] = true, [56462] = true, [19567] = true, [30552] = true, [23068] = true, [44155] = true, [28327] = true, [26425] = true, [56461] = true, [68067] = true, [15633] = true, [23096] = true, [12589] = true, [26442] = true, [26426] = true, [67920] = true, [9273] = true, [3926] = true, [6458] = true, [19793] = true, [55252] = true, [9271] = true, [26427] = true, [26443] = true, [30551] = true, [23486] = true, [3922] = true, [3924] = true, [12590] = true, [3928] = true, [3942] = true, [26428] = true, [3952] = true, [22704] = true, [12902] = true, [30569] = true, [15628] = true, [12895] = true, [21940] = true, [56349] = true, [12584] = true, [56477] = true, [30348] = true, [26416] = true, [53281] = true, [23507] = true, [12075] = true, [12079] = true, [26746] = true, [56000] = true, ----------------------------------------------------------------------------------------- ---JEWELCRAFTING ----------------------------------------------------------------------------------------- [55401] = true, [53995] = true, [66432] = true, [25255] = true, [66497] = true, [53996] = true, [56074] = true, [56202] = true, [66434] = true, [32801] = true, [28948] = true, [53997] = true, [39963] = true, [32866] = true, [66501] = true, [39452] = true, [53934] = true, [66502] = true, [55405] = true, [58954] = true, [56205] = true, [37855] = true, [38175] = true, [25305] = true, [25321] = true, [66505] = true, [54000] = true, [39742] = true, [56206] = true, [66506] = true, [32869] = true, [54001] = true, [56079] = true, [32259] = true, [32870] = true, [56208] = true, [32807] = true, [32871] = true, [26873] = true, [31061] = true, [32872] = true, [42591] = true, [46779] = true, [56530] = true, [42592] = true, [31062] = true, [56083] = true, [32874] = true, [56531] = true, [31063] = true, [56085] = true, [38503] = true, [56086] = true, [28938] = true, [38504] = true, [31064] = true, [31096] = true, [55384] = true, [25278] = true, [68253] = true, [43493] = true, [26925] = true, [31065] = true, [46403] = true, [53852] = true, [53916] = true, [39722] = true, [25615] = true, [28924] = true, [26926] = true, [53853] = true, [36526] = true, [31098] = true, [55388] = true, [46405] = true, [53854] = true, [39724] = true, [55389] = true, [26927] = true, [31051] = true, [53919] = true, [39725] = true, [55390] = true, [53856] = true, [53920] = true, [53952] = true, [26880] = true, [53857] = true, [31052] = true, [34069] = true, [25490] = true, [55393] = true, [39451] = true, [53956] = true, [44794] = true, [36524] = true, [55395] = true, [38068] = true, [28944] = true, [53957] = true, [31066] = true, [31082] = true, [66428] = true, [26909] = true, [55396] = true, [31097] = true, [25284] = true, [56084] = true, [62941] = true, [66431] = true, [53894] = true, [53958] = true, [58146] = true, [31099] = true, [47053] = true, [31067] = true, [28917] = true, [26903] = true, [36525] = true, [66503] = true, [56197] = true, [47054] = true, [53961] = true, [39715] = true, [31092] = true, [53960] = true, [31077] = true, [31101] = true, [39719] = true, [55399] = true, [31113] = true, [32808] = true, [53917] = true, [63743] = true, [39720] = true, [39721] = true, [42590] = true, [58149] = true, [56199] = true, [47056] = true, [28950] = true, [47280] = true, [32809] = true, [31072] = true, [25318] = true, [66429] = true, [62242] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ---COOKING --------------------------------------------------------------------------------------- [62050] = true, [22761] = true, [62051] = true, [8607] = true, [18238] = true, [6413] = true, [6417] = true, [42296] = true, [45557] = true, [6501] = true, [45558] = true, [18239] = true, [7752] = true, [7828] = true, [45560] = true, [64358] = true, [57421] = true, [45561] = true, [13028] = true, [2543] = true, [2545] = true, [25659] = true, [58512] = true, [45565] = true, [42305] = true, [45566] = true, [62350] = true, [7753] = true, [45695] = true, [9513] = true, [18244] = true, [20626] = true, [45569] = true, [43779] = true, [18245] = true, [45571] = true, [18246] = true, [37836] = true, [57433] = true, [20916] = true, [58521] = true, [18247] = true, [57435] = true, [7754] = true, [53056] = true, [58523] = true, [57437] = true, [57438] = true, [58525] = true, [45570] = true, [2538] = true, [2540] = true, [2548] = true, [33290] = true, [45562] = true, [15906] = true, [18241] = true, [45559] = true, [45551] = true, [57443] = true, [58527] = true, [43758] = true, [58528] = true, [8238] = true, [7751] = true, [7755] = true, [43761] = true, [7827] = true, [45552] = true, [45553] = true, [66038] = true, [66034] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ---BLACKSMITHING --------------------------------------------------------------------------------------- [10015] = true, [55202] = true, [16726] = true, [24399] = true, [15293] = true, [32655] = true, [3320] = true, [40033] = true, [34545] = true, [61010] = true, [16639] = true, [3115] = true, [3116] = true, [3117] = true, [16648] = true, [40034] = true, [9920] = true, [9928] = true, [29558] = true, [55839] = true, [9964] = true, [7818] = true, [55732] = true, [29694] = true, [3326] = true, [9950] = true, [59405] = true, [12260] = true, [16983] = true, [16991] = true, [23650] = true, [16640] = true, [2741] = true, [3513] = true, [3497] = true, [8768] = true, [19666] = true, [2662] = true, [2663] = true, [20201] = true, [16664] = true, [15294] = true, [3496] = true, [16645] = true, [29569] = true, [2737] = true, [14380] = true, [16992] = true, [36125] = true, [19667] = true, [22757] = true, [16665] = true, [59406] = true, [2738] = true, [2739] = true, [2740] = true, [24913] = true, [9933] = true, [20873] = true, [36126] = true, [40035] = true, [8880] = true, [23636] = true, [3292] = true, [3293] = true, [21161] = true, [32656] = true, [19668] = true, [9921] = true, [7222] = true, [3319] = true, [15295] = true, [20874] = true, [20890] = true, [3337] = true, [34546] = true, [16970] = true, [16978] = true, [61008] = true, [7224] = true, [11454] = true, [19669] = true, [55656] = true, [16667] = true, [16731] = true, [61009] = true, [9926] = true, [16984] = true, [21913] = true, [40036] = true, [9974] = true, [32657] = true, [32285] = true, [23638] = true, [10011] = true, [36131] = true, [27830] = true, [32284] = true, [16732] = true, [15292] = true, [15296] = true, [38478] = true, [20876] = true, [38475] = true, [23653] = true, [3325] = true, [14379] = true, [29729] = true, [2661] = true, [29728] = true, [16655] = true, [16993] = true, [3491] = true, [3494] = true, [3501] = true, [9959] = true, [9983] = true, [36262] = true, [10003] = true, [10007] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ----INSCRIPTION --------------------------------------------------------------------------------------- [58315] = true, [58331] = true, [58347] = true, [56974] = true, [56990] = true, [64256] = true, [64304] = true, [57166] = true, [57709] = true, [57214] = true, [57230] = true, [50603] = true, [57262] = true, [59338] = true, [58332] = true, [56959] = true, [56975] = true, [56991] = true, [57007] = true, [57023] = true, [53462] = true, [64257] = true, [64273] = true, [64289] = true, [57151] = true, [57167] = true, [57183] = true, [57710] = true, [57215] = true, [57247] = true, [50620] = true, [58301] = true, [59339] = true, [58333] = true, [59387] = true, [52840] = true, [59499] = true, [56960] = true, [56976] = true, [57008] = true, [57024] = true, [64258] = true, [64274] = true, [57168] = true, [57184] = true, [61288] = true, [57216] = true, [57232] = true, [58286] = true, [58302] = true, [59340] = true, [48114] = true, [64051] = true, [59484] = true, [59500] = true, [56961] = true, [56977] = true, [57009] = true, [57025] = true, [61177] = true, [64259] = true, [57121] = true, [64291] = true, [57153] = true, [57169] = true, [57185] = true, [57712] = true, [57217] = true, [57233] = true, [58287] = true, [58303] = true, [58319] = true, [56946] = true, [56978] = true, [56994] = true, [57010] = true, [57026] = true, [64260] = true, [57122] = true, [57154] = true, [57170] = true, [57186] = true, [57713] = true, [57218] = true, [57234] = true, [57266] = true, [59326] = true, [58320] = true, [58336] = true, [59486] = true, [59502] = true, [56963] = true, [56995] = true, [57011] = true, [57027] = true, [64261] = true, [64277] = true, [57155] = true, [57187] = true, [57714] = true, [57219] = true, [57235] = true, [58289] = true, [58305] = true, [58321] = true, [58337] = true, [69385] = true, [57243] = true, [64314] = true, [64313] = true, [56958] = true, [59487] = true, [59503] = true, [58288] = true, [56980] = true, [56996] = true, [57012] = true, [57028] = true, [59501] = true, [56948] = true, [57264] = true, [64246] = true, [64262] = true, [57124] = true, [64294] = true, [57156] = true, [57172] = true, [57188] = true, [57715] = true, [57220] = true, [57236] = true, [57268] = true, [58306] = true, [58322] = true, [58338] = true, [48247] = true, [57133] = true, [57270] = true, [57131] = true, [57129] = true, [50604] = true, [59488] = true, [59504] = true, [56965] = true, [56981] = true, [56997] = true, [57013] = true, [57029] = true, [64280] = true, [57114] = true, [64286] = true, [58299] = true, [57125] = true, [64295] = true, [64311] = true, [57164] = true, [57189] = true, [57716] = true, [57221] = true, [57237] = true, [57269] = true, [58307] = true, [58323] = true, [58339] = true, [64307] = true, [57119] = true, [57246] = true, [68166] = true, [64282] = true, [57250] = true, [59489] = true, [56957] = true, [56982] = true, [56998] = true, [57014] = true, [57117] = true, [64305] = true, [61677] = true, [52843] = true, [57126] = true, [64296] = true, [57158] = true, [64310] = true, [57190] = true, [56956] = true, [57238] = true, [60336] = true, [58308] = true, [58324] = true, [58340] = true, [64315] = true, [56949] = true, [56950] = true, [64308] = true, [56955] = true, [59490] = true, [59315] = true, [56983] = true, [56999] = true, [64267] = true, [57031] = true, [56954] = true, [64276] = true, [57249] = true, [64249] = true, [56953] = true, [57127] = true, [64297] = true, [57159] = true, [64275] = true, [57191] = true, [57207] = true, [57223] = true, [57239] = true, [50612] = true, [60337] = true, [57208] = true, [58325] = true, [48121] = true, [56952] = true, [56947] = true, [56951] = true, [64268] = true, [57123] = true, [59475] = true, [59491] = true, [56968] = true, [56984] = true, [57000] = true, [48248] = true, [58565] = true, [58317] = true, [64266] = true, [57252] = true, [64250] = true, [57112] = true, [57128] = true, [64298] = true, [57160] = true, [58316] = true, [57703] = true, [57719] = true, [57224] = true, [57240] = true, [57272] = true, [58310] = true, [58326] = true, [58342] = true, [57201] = true, [56945] = true, [57200] = true, [56944] = true, [57711] = true, [56985] = true, [57001] = true, [57199] = true, [57033] = true, [56943] = true, [57198] = true, [64251] = true, [57113] = true, [64283] = true, [64299] = true, [57161] = true, [57197] = true, [57704] = true, [57209] = true, [57225] = true, [57241] = true, [50614] = true, [57273] = true, [58311] = true, [58327] = true, [58343] = true, [52738] = true, [57196] = true, [64278] = true, [57195] = true, [58318] = true, [64284] = true, [57006] = true, [64316] = true, [56986] = true, [57002] = true, [59560] = true, [57034] = true, [58341] = true, [57192] = true, [64252] = true, [67600] = true, [57130] = true, [64300] = true, [57162] = true, [57244] = true, [57194] = true, [57210] = true, [57226] = true, [50599] = true, [57258] = true, [58296] = true, [58312] = true, [58328] = true, [58344] = true, [52739] = true, [58298] = true, [57274] = true, [57265] = true, [57251] = true, [59478] = true, [56971] = true, [56987] = true, [57003] = true, [57019] = true, [57035] = true, [57259] = true, [57263] = true, [57271] = true, [64253] = true, [57115] = true, [64285] = true, [57242] = true, [57163] = true, [57120] = true, [57706] = true, [57211] = true, [57227] = true, [50600] = true, [50616] = true, [58297] = true, [58313] = true, [58329] = true, [58345] = true, [50602] = true, [57248] = true, [59559] = true, [57157] = true, [57257] = true, [64309] = true, [64317] = true, [56972] = true, [56988] = true, [57004] = true, [57020] = true, [57036] = true, [62162] = true, [57030] = true, [64254] = true, [57116] = true, [57132] = true, [64302] = true, [64318] = true, [64270] = true, [57707] = true, [57212] = true, [57228] = true, [50601] = true, [57260] = true, [57276] = true, [58314] = true, [58330] = true, [58346] = true, [45382] = true, [57267] = true, [57275] = true, [64312] = true, [64279] = true, [50598] = true, [59480] = true, [59496] = true, [56973] = true, [56989] = true, [57005] = true, [57021] = true, [50619] = true, [50618] = true, [65245] = true, [50617] = true, [64255] = true, [64271] = true, [64287] = true, [64303] = true, [57165] = true, [57181] = true, [57708] = true, [57213] = true, [57229] = true, [57245] = true, [57261] = true, [57277] = true, [57253] = true, [71015] = true, [71101] = true, [71102] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ----ENCHANTING --------------------------------------------------------------------------------------- [46578] = true, [25125] = true, [13612] = true, [13620] = true, [13628] = true, [13640] = true, [13644] = true, [13648] = true, [59625] = true, [13700] = true, [17181] = true, [27920] = true, [27960] = true, [28016] = true, [45765] = true, [14810] = true, [63746] = true, [13836] = true, [20008] = true, [20032] = true, [13868] = true, [13421] = true, [13948] = true, [27905] = true, [27945] = true, [13501] = true, [7786] = true, [7788] = true, [13529] = true, [32664] = true, [44506] = true, [25127] = true, [20025] = true, [20033] = true, [13617] = true, [64579] = true, [60619] = true, [13657] = true, [13693] = true, [27914] = true, [44555] = true, [27954] = true, [27962] = true, [14807] = true, [32665] = true, [7421] = true, [13817] = true, [25072] = true, [44492] = true, [44524] = true, [7443] = true, [44556] = true, [25128] = true, [20026] = true, [7457] = true, [13378] = true, [34001] = true, [13917] = true, [13933] = true, [13937] = true, [13941] = true, [44621] = true, [28003] = true, [28027] = true, [13522] = true, [13538] = true, [25081] = true, [44494] = true, [20011] = true, [25129] = true, [44590] = true, [47672] = true, [20051] = true, [13622] = true, [13626] = true, [69412] = true, [60623] = true, [44383] = true, [13698] = true, [13702] = true, [23804] = true, [44623] = true, [28004] = true, [28028] = true, [14293] = true, [32667] = true, [13822] = true, [25082] = true, [44576] = true, [20028] = true, [20036] = true, [13890] = true, [13898] = true, [60609] = true, [59619] = true, [47898] = true, [7745] = true, [27957] = true, [13503] = true, [34006] = true, [7793] = true, [7795] = true, [59636] = true, [25083] = true, [20029] = true, [13631] = true, [13655] = true, [7861] = true, [7863] = true, [62256] = true, [13695] = true, [13858] = true, [44483] = true, [27968] = true, [7857] = true, [27967] = true, [42615] = true, [27958] = true, [42613] = true, [25084] = true, [20017] = true, [33992] = true, [34008] = true, [13663] = true, [60653] = true, [44528] = true, [14809] = true, [27961] = true, [47901] = true, [7418] = true, [7420] = true, [44488] = true, [7426] = true, [44616] = true, [13841] = true, [44596] = true, [17180] = true, [7454] = true, [33993] = true, [34009] = true, [27950] = true, [20031] = true, [13915] = true, [47900] = true, [13943] = true, [13947] = true, [13945] = true, [13464] = true, [27927] = true, [7771] = true, [20014] = true, [20030] = true, [27947] = true, [33994] = true, [28022] = true, [60692] = true, [25130] = true, [34005] = true, [13794] = true, [7748] = true, [44588] = true, [15596] = true, [7776] = true, [44584] = true, [13607] = true, [13653] = true, [20020] = true, [70524] = true, [71692] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ---TAILORING --------------------------------------------------------------------------------------- [12055] = true, [12059] = true, [12071] = true, [46131] = true, [23664] = true, [31433] = true, [31441] = true, [7624] = true, [26403] = true, [24902] = true, [22866] = true, [56001] = true, [18411] = true, [18419] = true, [18451] = true, [28208] = true, [6686] = true, [8766] = true, [23665] = true, [8786] = true, [26747] = true, [56002] = true, [26763] = true, [26779] = true, [31434] = true, [31450] = true, [60969] = true, [28480] = true, [22867] = true, [56003] = true, [18404] = true, [18412] = true, [18420] = true, [12044] = true, [63924] = true, [12056] = true, [12064] = true, [12080] = true, [12084] = true, [23666] = true, [56004] = true, [26780] = true, [31459] = true, [7892] = true, [60971] = true, [28481] = true, [22868] = true, [20848] = true, [56005] = true, [18405] = true, [19435] = true, [26086] = true, [18437] = true, [18453] = true, [27659] = true, [28210] = true, [40021] = true, [23667] = true, [26773] = true, [26781] = true, [26759] = true, [8774] = true, [31460] = true, [24903] = true, [3755] = true, [37882] = true, [3757] = true, [3758] = true, [50194] = true, [18446] = true, [6692] = true, [28482] = true, [22869] = true, [18439] = true, [20849] = true, [18440] = true, [2389] = true, [56007] = true, [18455] = true, [6521] = true, [18445] = true, [18406] = true, [18414] = true, [18422] = true, [12045] = true, [36318] = true, [18454] = true, [27660] = true, [24091] = true, [12065] = true, [12069] = true, [12077] = true, [12081] = true, [12085] = true, [27724] = true, [12093] = true, [40060] = true, [26087] = true, [26749] = true, [31373] = true, [26750] = true, [26782] = true, [28205] = true, [28209] = true, [36686] = true, [3813] = true, [2394] = true, [7629] = true, [18438] = true, [7633] = true, [40023] = true, [49677] = true, [26407] = true, [18452] = true, [7643] = true, [22870] = true, [26755] = true, [22902] = true, [3869] = true, [55993] = true, [8789] = true, [3839] = true, [3841] = true, [3844] = true, [18407] = true, [18415] = true, [3847] = true, [3848] = true, [3851] = true, [3852] = true, [24092] = true, [3854] = true, [3856] = true, [3857] = true, [8760] = true, [27725] = true, [8776] = true, [8780] = true, [8784] = true, [3865] = true, [55994] = true, [3868] = true, [26783] = true, [3871] = true, [3872] = true, [3873] = true, [31438] = true, [22759] = true, [12091] = true, [8804] = true, [8772] = true, [18560] = true, [18447] = true, [44950] = true, [60993] = true, [55899] = true, [55898] = true, [55995] = true, [2385] = true, [21945] = true, [6688] = true, [12066] = true, [18416] = true, [8465] = true, [12046] = true, [18448] = true, [3915] = true, [24093] = true, [8489] = true, [2386] = true, [12078] = true, [12082] = true, [60994] = true, [55900] = true, [2392] = true, [2393] = true, [23662] = true, [2396] = true, [55996] = true, [2399] = true, [26784] = true, [2402] = true, [2403] = true, [2406] = true, [31431] = true, [3864] = true, [8782] = true, [37884] = true, [37883] = true, [26751] = true, [50647] = true, [8802] = true, [8467] = true, [7893] = true, [40020] = true, [41208] = true, [22813] = true, [41207] = true, [37873] = true, [2387] = true, [55997] = true, [26085] = true, [8799] = true, [27658] = true, [8483] = true, [3914] = true, [18401] = true, [56006] = true, [18441] = true, [18449] = true, [28207] = true, [55769] = true, [18413] = true, [18421] = true, [46129] = true, [2963] = true, [2964] = true, [12061] = true, [12086] = true, [23663] = true, [26745] = true, [55998] = true, [3863] = true, [8764] = true, [31440] = true, [31448] = true, [12089] = true, [6695] = true, [40024] = true, [60990] = true, [50644] = true, [8793] = true, [46130] = true, [24901] = true, [3870] = true, [31437] = true, [44958] = true, [55999] = true, [3845] = true, [6693] = true, [3866] = true, [18456] = true, [18410] = true, [18418] = true, [2397] = true, [18434] = true, [18450] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ----LEATHERWORKING --------------------------------------------------------------------------------------- [9198] = true, [23704] = true, [60996] = true, [35549] = true, [22921] = true, [50958] = true, [10509] = true, [10525] = true, [19053] = true, [19085] = true, [19101] = true, [2149] = true, [36349] = true, [2153] = true, [60997] = true, [2159] = true, [2163] = true, [2165] = true, [35582] = true, [50959] = true, [46132] = true, [23705] = true, [60998] = true, [22922] = true, [46133] = true, [24121] = true, [19054] = true, [19070] = true, [19086] = true, [19102] = true, [32465] = true, [32481] = true, [22331] = true, [35520] = true, [35584] = true, [9207] = true, [6661] = true, [46134] = true, [23706] = true, [61000] = true, [35521] = true, [35585] = true, [50962] = true, [10518] = true, [39997] = true, [24122] = true, [19055] = true, [19087] = true, [19103] = true, [32466] = true, [32482] = true, [36353] = true, [35522] = true, [35554] = true, [50963] = true, [44953] = true, [46136] = true, [23707] = true, [61002] = true, [35523] = true, [35555] = true, [35587] = true, [50964] = true, [46137] = true, [24123] = true, [19072] = true, [19104] = true, [9064] = true, [9072] = true, [69386] = true, [32499] = true, [36355] = true, [7133] = true, [35524] = true, [35588] = true, [7153] = true, [9208] = true, [46138] = true, [23708] = true, [69388] = true, [35525] = true, [35557] = true, [10487] = true, [10511] = true, [46139] = true, [24124] = true, [19073] = true, [19089] = true, [32468] = true, [32500] = true, [10647] = true, [28472] = true, [35526] = true, [35558] = true, [45117] = true, [60622] = true, [23709] = true, [35527] = true, [3774] = true, [3778] = true, [3780] = true, [40003] = true, [24125] = true, [19058] = true, [19074] = true, [32485] = true, [32501] = true, [36359] = true, [3816] = true, [3818] = true, [28473] = true, [35528] = true, [35560] = true, [9193] = true, [40004] = true, [23710] = true, [8322] = true, [35529] = true, [35561] = true, [50970] = true, [2881] = true, [10520] = true, [40005] = true, [10544] = true, [10552] = true, [10560] = true, [19091] = true, [19107] = true, [32454] = true, [32502] = true, [28474] = true, [35530] = true, [50971] = true, [62448] = true, [7953] = true, [41157] = true, [36074] = true, [24846] = true, [35531] = true, [22928] = true, [28219] = true, [41158] = true, [36075] = true, [32487] = true, [32503] = true, [7126] = true, [9146] = true, [35532] = true, [35564] = true, [9194] = true, [9202] = true, [24655] = true, [36076] = true, [24847] = true, [35533] = true, [5244] = true, [10529] = true, [28220] = true, [19093] = true, [32456] = true, [52733] = true, [2158] = true, [2160] = true, [2162] = true, [2166] = true, [24848] = true, [35535] = true, [35567] = true, [28221] = true, [19094] = true, [44359] = true, [57690] = true, [32457] = true, [32473] = true, [32489] = true, [9147] = true, [35536] = true, [35568] = true, [9195] = true, [41163] = true, [6703] = true, [24849] = true, [20853] = true, [10482] = true, [10490] = true, [28222] = true, [10546] = true, [10554] = true, [41164] = true, [10570] = true, [19095] = true, [57692] = true, [32458] = true, [10650] = true, [35538] = true, [42731] = true, [24850] = true, [20854] = true, [44970] = true, [28223] = true, [19048] = true, [19064] = true, [19080] = true, [57694] = true, [9148] = true, [35540] = true, [35572] = true, [24851] = true, [20855] = true, [45100] = true, [10531] = true, [28224] = true, [57696] = true, [10619] = true, [19081] = true, [22727] = true, [35539] = true, [19079] = true, [10566] = true, [19065] = true, [10574] = true, [19097] = true, [60647] = true, [3753] = true, [9065] = true, [44343] = true, [3763] = true, [3767] = true, [3773] = true, [3775] = true, [3777] = true, [3779] = true, [32455] = true, [24940] = true, [19050] = true, [19082] = true, [23190] = true, [32461] = true, [60999] = true, [3817] = true, [44344] = true, [9149] = true, [35544] = true, [9197] = true, [19077] = true, [9060] = true, [19076] = true, [35573] = true, [40001] = true, [19066] = true, [19090] = true, [19098] = true, [22711] = true, [57699] = true, [9059] = true, [32480] = true, [64661] = true, [10562] = true, [40002] = true, [50936] = true, [6705] = true, [9058] = true, [24703] = true, [22923] = true, [60645] = true, [35577] = true, [2152] = true, [10556] = true, [10572] = true, [44770] = true, [36077] = true, [19059] = true, [19075] = true, [35580] = true, [20648] = true, [2169] = true, [60643] = true, [19088] = true, [6702] = true, [6704] = true, [35543] = true, [7149] = true, [36078] = true, [36079] = true, [23703] = true, [35534] = true, [57701] = true, [46135] = true, [19047] = true, [20649] = true, [40006] = true, [3765] = true, [41156] = true, [3771] = true, [50956] = true, [19061] = true, [20650] = true, [32467] = true, [35537] = true, [22926] = true, [4096] = true, [19062] = true, [44768] = true, [35576] = true, [19063] = true, [9062] = true, [9070] = true, [32495] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- -----ALCHEMY --------------------------------------------------------------------------------------- [53898] = true, [28579] = true, [17566] = true, [53771] = true, [7256] = true, [53899] = true, [11452] = true, [11460] = true, [11468] = true, [3170] = true, [3172] = true, [3174] = true, [3176] = true, [53836] = true, [53900] = true, [28564] = true, [28580] = true, [54220] = true, [17551] = true, [53773] = true, [53837] = true, [62213] = true, [54221] = true, [53774] = true, [53838] = true, [6617] = true, [53902] = true, [28581] = true, [2331] = true, [2335] = true, [2337] = true, [53775] = true, [53839] = true, [11453] = true, [7836] = true, [24266] = true, [41500] = true, [53904] = true, [53936] = true, [28566] = true, [28582] = true, [3452] = true, [3454] = true, [33732] = true, [17553] = true, [22732] = true, [53777] = true, [53905] = true, [53937] = true, [33733] = true, [62409] = true, [25146] = true, [41502] = true, [53938] = true, [28551] = true, [28567] = true, [28583] = true, [62410] = true, [7181] = true, [17570] = true, [17634] = true, [53779] = true, [7257] = true, [53939] = true, [11478] = true, [17187] = true, [53812] = true, [28552] = true, [28568] = true, [28584] = true, [60366] = true, [57425] = true, [17635] = true, [53781] = true, [60367] = true, [4508] = true, [53782] = true, [24365] = true, [6618] = true, [53942] = true, [28569] = true, [28585] = true, [29688] = true, [57427] = true, [17556] = true, [17572] = true, [17636] = true, [53783] = true, [11479] = true, [7837] = true, [7841] = true, [53784] = true, [24366] = true, [53848] = true, [28554] = true, [28570] = true, [28586] = true, [58868] = true, [33741] = true, [24367] = true, [28555] = true, [28571] = true, [17574] = true, [17638] = true, [7258] = true, [11448] = true, [11456] = true, [11464] = true, [11480] = true, [3171] = true, [3173] = true, [3175] = true, [24368] = true, [28572] = true, [28588] = true, [45061] = true, [66658] = true, [17559] = true, [17575] = true, [66659] = true, [66660] = true, [63732] = true, [28573] = true, [66662] = true, [17560] = true, [2330] = true, [2332] = true, [2334] = true, [66663] = true, [11457] = true, [11465] = true, [11473] = true, [66664] = true, [3449] = true, [3451] = true, [3453] = true, [17561] = true, [17577] = true, [28543] = true, [28575] = true, [7179] = true, [17562] = true, [17578] = true, [7255] = true, [7259] = true, [11458] = true, [11466] = true, [38962] = true, [53776] = true, [12609] = true, [53780] = true, [60893] = true, [58871] = true, [3448] = true, [3450] = true, [3447] = true, [4942] = true, [28576] = true, [15833] = true, [17576] = true, [41503] = true, [60350] = true, [41501] = true, [17563] = true, [42736] = true, [54213] = true, [41458] = true, [6624] = true, [39636] = true, [28577] = true, [32765] = true, [38070] = true, [17564] = true, [17580] = true, [53895] = true, [11451] = true, [39637] = true, [28546] = true, [28562] = true, [28578] = true, [60354] = true, [32766] = true, [17565] = true, [22808] = true, [39639] = true, [60355] = true, [56519] = true, --------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ----ENGINEERING --------------------------------------------------------------------------------------- [23081] = true, [12586] = true, [12594] = true, [12622] = true, [30347] = true, [44391] = true, [30547] = true, [56464] = true, [12754] = true, [12758] = true, [23489] = true, [23082] = true, [8243] = true, [9269] = true, [30316] = true, [30332] = true, [12906] = true, [8339] = true, [26420] = true, [56514] = true, [39971] = true, [12599] = true, [12603] = true, [12607] = true, [43676] = true, [12619] = true, [54998] = true, [30349] = true, [56468] = true, [12755] = true, [12759] = true, [63750] = true, [39973] = true, [56465] = true, [56469] = true, [30318] = true, [30334] = true, [55016] = true, [12903] = true, [54793] = true, [19795] = true, [8895] = true, [19819] = true, [30558] = true, [39895] = true, [23069] = true, [23077] = true, [12596] = true, [30303] = true, [56471] = true, [12624] = true, [19796] = true, [56472] = true, [12760] = true, [63770] = true, [23070] = true, [23078] = true, [61471] = true, [30304] = true, [30312] = true, [12908] = true, [12597] = true, [3930] = true, [24356] = true, [67839] = true, [67326] = true, [56474] = true, [30560] = true, [30568] = true, [30314] = true, [3929] = true, [56475] = true, [19830] = true, [3946] = true, [54736] = true, [30563] = true, [24357] = true, [30337] = true, [12591] = true, [19814] = true, [60866] = true, [12716] = true, [23071] = true, [23079] = true, [12585] = true, [3923] = true, [3925] = true, [30305] = true, [12621] = true, [30329] = true, [3931] = true, [3932] = true, [3933] = true, [3936] = true, [3937] = true, [3938] = true, [3939] = true, [3941] = true, [56473] = true, [3944] = true, [3945] = true, [3947] = true, [54999] = true, [3949] = true, [3950] = true, [26417] = true, [12717] = true, [3954] = true, [3955] = true, [3957] = true, [3958] = true, [3961] = true, [3962] = true, [3963] = true, [3965] = true, [3966] = true, [3967] = true, [3968] = true, [3969] = true, [3971] = true, [3972] = true, [3973] = true, [7430] = true, [3977] = true, [3978] = true, [3979] = true, [19790] = true, [23080] = true, [12620] = true, [30309] = true, [30346] = true, [12905] = true, [41307] = true, [8334] = true, [56460] = true, [56476] = true, [12718] = true, [26418] = true, [19791] = true, [3960] = true, [19815] = true, [13240] = true, [19831] = true, [30570] = true, [19799] = true, [30306] = true, [30310] = true, [26011] = true, [36954] = true, [3919] = true, [3920] = true, [19800] = true, [12617] = true, [30311] = true, [56463] = true, [72952] = true, [72953] = true, --------------------------------------------------------------------------------------- }