----------------------------------------------------------------------- -- Upvalued Lua API. ----------------------------------------------------------------------- local _G = getfenv(0) -- Functions local ipairs = _G.ipairs local pairs = _G.pairs local select = _G.select 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) local BZ = LibStub("LibBabble-Zone-3.0"):GetLookupTable() local BFAC = LibStub("LibBabble-Faction-3.0"):GetLookupTable() local A = private.acquire_types local SF = private.recipe_state_flags private.num_recipes = {} ----------------------------------------------------------------------- -- Local constants. ----------------------------------------------------------------------- local recipe_prototype = {} local recipe_meta = { __index = recipe_prototype } ---Adds a tradeskill recipe into the specified recipe database -- @name AckisRecipeList:AddRecipe -- @usage AckisRecipeList:AddRecipe(28927, 23109, V.TBC, Q.UNCOMMON) -- @param spell_id The [[http://www.wowpedia.org/SpellLink|Spell ID]] of the recipe being added to the database -- @param profession The profession ID that uses the recipe. See [[API/database-documentation]] for a listing of profession IDs -- @param genesis Game version that the recipe was first introduced in, for example, Original, BC, WoTLK, or Cata -- @param quality The quality/rarity of the recipe -- @return Resultant recipe table. function addon:AddRecipe(spell_id, profession, genesis, quality) local recipe_list = private.recipe_list if recipe_list[spell_id] then --@alpha@ self:Debug("Duplicate recipe: %d - %s (%s)", spell_id, recipe_list[spell_id].name, recipe_list[spell_id].profession) --@end-alpha@ return end local recipe = _G.setmetatable({ spell_id = spell_id, profession = _G.GetSpellInfo(profession), genesis = private.game_version_names[genesis], quality = quality, name = _G.GetSpellInfo(spell_id), flags = {}, acquire_data = {}, }, recipe_meta) if not recipe.name or recipe.name == "" then recipe.name = ("%s: %d"):format(_G.UNKNOWN, tonumber(spell_id)) self:Print(L["SpellIDCache"]:format(spell_id)) end recipe_list[spell_id] = recipe private.num_recipes[recipe.profession] = (private.num_recipes[recipe.profession] or 0) + 1 return recipe end ------------------------------------------------------------------------------- -- Recipe methods. ------------------------------------------------------------------------------- function recipe_prototype:SetRecipeItemID(item_id) self.recipe_item_id = item_id end function recipe_prototype:RecipeItemID() return self.recipe_item_id end function recipe_prototype:SetCraftedItemID(item_id) self.crafted_item_id = item_id end function recipe_prototype:CraftedItemID() return self.crafted_item_id end function recipe_prototype:SetSkillLevels(skill_level, optimal_level, medium_level, easy_level, trivial_level) self.skill_level = skill_level self.optimal_level = optimal_level or skill_level self.medium_level = medium_level or skill_level + 10 self.easy_level = easy_level or skill_level + 15 self.trivial_level = trivial_level or skill_level + 20 end function recipe_prototype:SkillLevels() return self.skill_level, self.optimal_level, self.medium_level, self.easy_level, self.trivial_level end function recipe_prototype:SetSpecialty(spell_id) self.specialty = spell_id end function recipe_prototype:Specialty() return self.specialty end function recipe_prototype:SetRequiredFaction(faction_name) self.required_faction = faction_name if faction_name and private.Player.faction ~= faction_name then self.is_ignored = true private.num_recipes[self.profession] = private.num_recipes[self.profession] - 1 end end function recipe_prototype:RequiredFaction() return self.required_faction end function recipe_prototype:HasState(state_name) return self.state and (bit.band(self.state, SF[state_name]) == SF[state_name]) or false end function recipe_prototype:AddState(state_name) if not self.state then self.state = 0 end if bit.band(self.state, SF[state_name]) == SF[state_name] then return end self.state = bit.bxor(self.state, SF[state_name]) end function recipe_prototype:RemoveState(state_name) if not self.state then return end if bit.band(self.state, SF[state_name]) ~= SF[state_name] then return end self.state = bit.bxor(self.state, SF[state_name]) if self.state == 0 then self.state = nil end end do local BITFIELD_MAP = { common1 = private.common_flags_word1, class1 = private.class_flags_word1, reputation1 = private.rep_flags_word1, reputation2 = private.rep_flags_word2, item1 = private.item_flags_word1, } function recipe_prototype:HasFilter(field_name, flag_name) local bitfield = self.flags[field_name] local bitset = BITFIELD_MAP[field_name] local value = bitset[flag_name] return bitfield and (bit.band(bitfield, value) == value) or false end end -- do-block do local SKILL_LEVEL_FORMAT = "[%d]" local SPELL_ENCHANTING = _G.GetSpellInfo(51313) function recipe_prototype:GetDisplayName() local _, _, _, quality_color = _G.GetItemQualityColor(self.quality) local recipe_name = self.name if private.ordered_professions[addon.Frame.profession] == SPELL_ENCHANTING then recipe_name = recipe_name:gsub(_G.ENSCRIBE .. " ","") end local has_faction = private.Player:HasProperRepLevel(self.acquire_data[A.REPUTATION]) local skill_level = private.current_profession_scanlevel local recipe_level = self.skill_level local diff_color if not has_faction or recipe_level > skill_level then diff_color = "impossible" elseif skill_level >= self.trivial_level then diff_color = "trivial" elseif skill_level >= self.easy_level then diff_color = "easy" elseif skill_level >= self.medium_level then diff_color = "medium" elseif skill_level >= self.optimal_level then diff_color = "optimal" else diff_color = "trivial" end local display_name = ("|c%s%s|r"):format(quality_color, recipe_name) local level_text = private.SetTextColor(private.difficulty_colors[diff_color], SKILL_LEVEL_FORMAT):format(recipe_level) if addon.db.profile.skill_view then display_name = ("%s - %s"):format(level_text, display_name) else display_name = ("%s - %s"):format(display_name, level_text) end if addon.db.profile.exclusionlist[self.spell_id] then display_name = ("** %s **"):format(display_name) end return display_name end end -- do-block function recipe_prototype:AddFilters(...) local num_flags = select('#', ...) for index = 1, num_flags, 1 do local flag = select(index, ...) local flag_name = private.filter_strings[flag] local bitfield local member_name for table_index, bits in ipairs(private.bit_flags) do if bits[flag_name] then bitfield = bits member_name = private.flag_members[table_index] end end if not bitfield or not member_name then return end if not self.flags[member_name] then self.flags[member_name] = 0 end if bit.band(self.flags[member_name], bitfield[flag_name]) == bitfield[flag_name] then return end self.flags[member_name] = bit.bxor(self.flags[member_name], bitfield[flag_name]) end end function recipe_prototype:AddAcquireData(acquire_type, type_string, unit_list, ...) local location_list = private.location_list local acquire_list = private.acquire_list self.acquire_data[acquire_type] = self.acquire_data[acquire_type] or {} local acquire = self.acquire_data[acquire_type] local num_vars = select('#', ...) local cur_var = 1 while cur_var <= num_vars do local location, affiliation local identifier = select(cur_var, ...) cur_var = cur_var + 1 -- A quantity of true means unlimited - normal vendor item. local quantity = true if type_string == "Limited Vendor" then quantity = select(cur_var, ...) cur_var = cur_var + 1 end acquire[identifier] = true if unit_list and not unit_list[identifier] then addon:Debug("Spell ID %d: %s ID %s does not exist in the database.", self.spell_id, type_string, identifier) else if not unit_list then local id_type = type(identifier) location = id_type == "string" and BZ[identifier] or nil if location then affiliation = "world_drop" else if id_type == "string" then addon:Debug("WORLD_DROP with no location: %d %s", self.spell_id, self.name) end end else local unit = unit_list[identifier] affiliation = unit.faction location = unit.location unit.item_list = unit.item_list or {} unit.item_list[self.spell_id] = quantity end end acquire_list[acquire_type] = acquire_list[acquire_type] or {} acquire_list[acquire_type].recipes = acquire_list[acquire_type].recipes or {} acquire_list[acquire_type].name = private.acquire_names[acquire_type] acquire_list[acquire_type].recipes[self.spell_id] = affiliation or true if location then location_list[location] = location_list[location] or {} location_list[location].recipes = location_list[location].recipes or {} location_list[location].name = location location_list[location].recipes[self.spell_id] = affiliation or true end end end function recipe_prototype:AddMobDrop(...) self:AddAcquireData(A.MOB_DROP, "Mob", private.mob_list, ...) end function recipe_prototype:AddTrainer(...) self:AddAcquireData(A.TRAINER, "Trainer", private.trainer_list, ...) end function recipe_prototype:AddVendor(...) self:AddAcquireData(A.VENDOR, "Vendor", private.vendor_list, ...) end function recipe_prototype:AddLimitedVendor(...) self:AddAcquireData(A.VENDOR, "Limited Vendor", private.vendor_list, ...) end function recipe_prototype:AddWorldDrop(...) self:AddAcquireData(A.WORLD_DROP, nil, nil, ...) end function recipe_prototype:AddQuest(...) self:AddAcquireData(A.QUEST, "Quest", private.quest_list, ...) end function recipe_prototype:AddAchievement(...) self:AddAcquireData(A.ACHIEVEMENT, "Achievement", nil, ...) end function recipe_prototype:AddCustom(...) self:AddAcquireData(A.CUSTOM, "Custom", private.custom_list, ...) end function recipe_prototype:AddSeason(...) self:AddAcquireData(A.SEASONAL, "Seasonal", private.seasonal_list, ...) end function recipe_prototype:AddRepVendor(faction_id, rep_level, ...) local location_list = private.location_list local acquire_list = private.acquire_list local vendor_list = private.vendor_list self.acquire_data[A.REPUTATION] = self.acquire_data[A.REPUTATION] or {} local acquire = self.acquire_data[A.REPUTATION] acquire[faction_id] = acquire[faction_id] or {} local faction = acquire[faction_id] faction[rep_level] = faction[rep_level] or {} local num_vars = select('#', ...) local cur_var = 1 while cur_var <= num_vars do local location, affiliation local vendor_id = select(cur_var, ...) cur_var = cur_var + 1 if not private.reputation_list[faction_id] then --@alpha@ self:Printf("Spell ID %d: Faction ID %d does not exist in the database.", self.spell_id, faction_id) --@end-alpha@ else if not vendor_id then --@alpha@ self:Printf("Spell ID %d: Reputation Vendor ID is nil.", self.spell_id) --@end-alpha@ elseif not vendor_list[vendor_id] then --@alpha@ self:Printf("Spell ID %d: Reputation Vendor ID %d does not exist in the database.", self.spell_id, vendor_id) --@end-alpha@ else faction[rep_level][vendor_id] = true local rep_vendor = vendor_list[vendor_id] affiliation = rep_vendor.faction location = rep_vendor.location rep_vendor.item_list = rep_vendor.item_list or {} rep_vendor.item_list[self.spell_id] = true end end acquire_list[A.REPUTATION] = acquire_list[A.REPUTATION] or {} acquire_list[A.REPUTATION].recipes = acquire_list[A.REPUTATION].recipes or {} acquire_list[A.REPUTATION].name = private.acquire_names[A.REPUTATION] acquire_list[A.REPUTATION].recipes[self.spell_id] = affiliation or true if location then location_list[location] = location_list[location] or {} location_list[location].recipes = location_list[location].recipes or {} location_list[location].name = location location_list[location].recipes[self.spell_id] = affiliation or true end end end local DUMP_FUNCTION_FORMATS = { [A.ACHIEVEMENT] = "recipe:AddAchievement(%s)", [A.CUSTOM] = "recipe:AddCustom(%s)", [A.SEASONAL] = "recipe:AddSeason(%s)", [A.TRAINER] = "recipe:AddTrainer(%s)", [A.MOB_DROP] = "recipe:AddMobDrop(%s)", [A.WORLD_DROP] = "recipe:AddWorldDrop(%s)", [A.QUEST] = "recipe:AddQuest(%s)", } local sorted_data = {} local reverse_map = {} function recipe_prototype:Dump(output) local genesis = private.game_versions[self.genesis] table.insert(output, ("-- %s -- %d"):format(self.name, self.spell_id)) table.insert(output, ("recipe = AddRecipe(%d, V.%s, Q.%s)"):format(self.spell_id, private.game_version_names[genesis], private.item_quality_names[self.quality])) if self.recipe_item_id then table.insert(output, ("recipe:SetRecipeItemID(%d)"):format(self.recipe_item_id)) end if self.crafted_item_id then table.insert(output, ("recipe:SetCraftedItemID(%d)"):format(self.crafted_item_id)) end local skill_level = self.skill_level local optimal_level = self.optimal_level local medium_level = self.medium_level local easy_level = self.easy_level local trivial_level = self.trivial_level table.insert(output, ("recipe:SetSkillLevels(%d, %d, %d, %d, %d)"):format(skill_level, optimal_level, medium_level, easy_level, trivial_level)) if self.specialty then table.insert(output, ("recipe:SetSpecialty(%d)"):format(self.specialty)) end if self.required_faction then table.insert(output, ("recipe:SetRequiredFaction(\"%s\")"):format(self.required_faction)) end local flag_string for table_index, bits in ipairs(private.bit_flags) do table.wipe(sorted_data) table.wipe(reverse_map) for flag_name, flag in pairs(bits) do local bitfield = self.flags[private.flag_members[table_index]] if bitfield and bit.band(bitfield, flag) == flag then table.insert(sorted_data, flag) reverse_map[flag] = flag_name end end table.sort(sorted_data) for index, flag in ipairs(sorted_data) do local bitfield = self.flags[private.flag_members[table_index]] if bitfield and bit.band(bitfield, flag) == flag then if not flag_string then flag_string = ("F.%s"):format(private.filter_strings[private.filter_flags[reverse_map[flag]]]) else flag_string = ("%s, F.%s"):format(flag_string, private.filter_strings[private.filter_flags[reverse_map[flag]]]) end end end end table.insert(output, ("recipe:AddFilters(%s)"):format(flag_string)) flag_string = nil for acquire_type, acquire_info in pairs(self.acquire_data) do if acquire_type == A.REPUTATION then for rep_id, rep_info in pairs(acquire_info) do local faction_string = private.faction_strings[rep_id] if not faction_string then faction_string = rep_id addon:Printf("Recipe %d (%s) - no string for faction %d", self.spell_id, self.name, rep_id) else faction_string = ("FAC.%s"):format(faction_string) end for rep_level, level_info in pairs(rep_info) do local rep_string = ("REP.%s"):format(private.rep_level_strings[rep_level or 1]) local values table.wipe(sorted_data) table.wipe(reverse_map) for id_num in pairs(level_info) do table.insert(sorted_data, id_num) end table.sort(sorted_data) for index, vendor_id in ipairs(sorted_data) do if values then values = ("%s, %d"):format(values, vendor_id) else values = vendor_id end end table.insert(output, ("recipe:AddRepVendor(%s, %s, %s)"):format(faction_string, rep_string, values)) end end elseif acquire_type == A.VENDOR then local values local limited_values table.wipe(sorted_data) table.wipe(reverse_map) for id_num in pairs(acquire_info) do table.insert(sorted_data, id_num) end table.sort(sorted_data) for index, identifier in ipairs(sorted_data) do local saved_id if type(identifier) == "string" then saved_id = ("\"%s\""):format(identifier) else saved_id = identifier end local vendor = private.vendor_list[identifier] local quantity = vendor.item_list[self.spell_id] if type(quantity) == "number" then if limited_values then limited_values = ("%s, %s, %d"):format(limited_values, saved_id, quantity) else limited_values = ("%s, %d"):format(saved_id, quantity) end else if values then values = ("%s, %s"):format(values, saved_id) else values = saved_id end end end if values then table.insert(output, ("recipe:AddVendor(%s)"):format(values)) end if limited_values then table.insert(output, ("recipe:AddLimitedVendor(%s)"):format(limited_values)) end elseif DUMP_FUNCTION_FORMATS[acquire_type] then local values table.wipe(sorted_data) table.wipe(reverse_map) for id_num in pairs(acquire_info) do table.insert(sorted_data, id_num) end table.sort(sorted_data) for index, identifier in ipairs(sorted_data) do local saved_id if type(identifier) == "string" then saved_id = ("\"%s\""):format(identifier) else saved_id = identifier end if values then values = ("%s, %s"):format(values, saved_id) else values = saved_id end end table.insert(output, (DUMP_FUNCTION_FORMATS[acquire_type]):format(values)) else for identifier in pairs(acquire_info) do local saved_id if type(identifier) == "string" then saved_id = ("\"%s\""):format(identifier) else saved_id = identifier end if flag_string then flag_string = ("%s, A.%s, %s"):format(flag_string, private.acquire_strings[acquire_type], saved_id) else flag_string = ("A.%s, %s"):format(private.acquire_strings[acquire_type], saved_id) end end end end if flag_string then table.insert(output, ("recipe:AddAcquireData(%s)"):format(flag_string)) end table.insert(output, "") end function recipe_prototype:DumpTrainers(registry) local trainer_data = self.acquire_data[A.TRAINER] if not trainer_data then return end for identifier in pairs(trainer_data) do registry[identifier] = true end end --- Public API function for retrieving specific information about a recipe. -- @name AckisRecipeList:GetRecipeData -- @usage AckisRecipeList:GetRecipeData(28972, "profession") -- @param spell_id The [[http://www.wowpedia.org/SpellLink|Spell ID]] of the recipe being queried. -- @param data Which member of the recipe table is being queried. -- @return Variable, depending upon which member of the recipe table is queried. function addon:GetRecipeData(spell_id, data) local recipe = private.recipe_list[spell_id] return recipe and recipe[data] or nil end