mirror of
https://github.com/MarkuruThunderhoof/RecipeRadar.git
synced 2026-09-22 07:26:56 +00:00
629 lines
17 KiB
Lua
629 lines
17 KiB
Lua
|
|
-- RecipeRadar.lua: main event code and general utility functions
|
|
-- $Id: RecipeRadar.lua 464 2006-09-18 18:05:57Z jnmiller $
|
|
|
|
function RecipeRadar_OnLoad()
|
|
|
|
tinsert(UISpecialFrames, "RecipeRadarFrame")
|
|
UIPanelWindows["RecipeRadarFrame"] = nil
|
|
|
|
SLASH_RecipeRadar1 = "/rrdr"
|
|
SLASH_RecipeRadar2 = "/reciperadar"
|
|
SlashCmdList["RecipeRadar"] = RecipeRadar_SlashHandler
|
|
|
|
this:RegisterEvent("VARIABLES_LOADED")
|
|
this:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
|
this:RegisterEvent("MINIMAP_UPDATE_ZOOM")
|
|
this:RegisterEvent("WORLD_MAP_UPDATE")
|
|
this:RegisterEvent("CRAFT_SHOW")
|
|
this:RegisterEvent("TRADE_SKILL_SHOW")
|
|
this:RegisterEvent("CHAT_MSG_SKILL")
|
|
this:RegisterEvent("CHAT_MSG_SYSTEM")
|
|
this:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
|
|
-- RecipeRadar_Print("Recipe Radar loaded!")
|
|
|
|
end
|
|
|
|
function RecipeRadar_OnEvent()
|
|
|
|
if (event == "VARIABLES_LOADED") then
|
|
|
|
RecipeRadar_Globals_Init()
|
|
RecipeRadar_MinimapButton_Init()
|
|
RecipeRadar_Availability_Init()
|
|
RecipeRadar_Maps_Init()
|
|
RecipeRadar_SkillDB_Init()
|
|
RecipeRadar_MainFrame_Init()
|
|
RecipeRadar_DropDowns_Init()
|
|
RecipeRadar_SelectFirstRecipe()
|
|
|
|
elseif (event == "ZONE_CHANGED_NEW_AREA") then
|
|
|
|
if (RecipeRadar_RegionHasLearnableRecipe(GetRealZoneText())) then
|
|
RecipeRadar_MinimapButton_Flash()
|
|
end
|
|
RecipeRadar_Minimap_OnNewRegion()
|
|
if (RecipeRadar_Options.AutoSelectRegion) then
|
|
RecipeRadar_NewRegion(GetRealZoneText())
|
|
end
|
|
|
|
elseif (event == "ADDON_LOADED" and arg1 == "RecipeRadar") then
|
|
|
|
--
|
|
|
|
elseif (event == "PLAYER_ENTERING_WORLD") then
|
|
|
|
if (RecipeRadar_Filters.Teams["Horde"] == nil) then
|
|
-- this should be the first run of the addon, so set team filters
|
|
RecipeRadar_Filters.Teams[UnitFactionGroup("player")] = false
|
|
RecipeRadar_Filters.Teams
|
|
[RecipeRadar_GetOpposingFaction("player")] = true
|
|
end
|
|
|
|
elseif (event == "MINIMAP_UPDATE_ZOOM") then
|
|
|
|
RecipeRadar_Minimap_SetIndoorness()
|
|
|
|
elseif (event == "WORLD_MAP_UPDATE") then
|
|
|
|
RecipeRadar_Worldmap_UpdateButtons()
|
|
|
|
elseif (event == "CRAFT_SHOW") then
|
|
|
|
RecipeRadar_SkillDB_Refresh("craft")
|
|
|
|
elseif (event == "TRADE_SKILL_SHOW") then
|
|
|
|
RecipeRadar_SkillDB_Refresh("trade")
|
|
|
|
elseif (event == "CHAT_MSG_SKILL") then
|
|
|
|
-- arg1 = eg. "Your skill in Cooking has increased to 221."
|
|
RecipeRadar_ParseChatMessage(arg1)
|
|
|
|
elseif (event == "CHAT_MSG_SYSTEM") then
|
|
|
|
-- arg1 = eg. "You have learned how to create a new item: Bristle Whisker Catfish."
|
|
RecipeRadar_ParseChatMessage(arg1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function RecipeRadar_OnShow()
|
|
|
|
--PlaySound("igCharacterInfoOpen")
|
|
|
|
end
|
|
|
|
function RecipeRadar_OnHide()
|
|
|
|
PlaySound("igCharacterInfoClose")
|
|
RecipeRadarFrame:StopMovingOrSizing()
|
|
|
|
end
|
|
|
|
function RecipeRadar_SlashHandler(msg)
|
|
|
|
local _, _, cmd, arg1, arg2 = string.find(msg, "(%w+)%s*(%w*)%s*(%w*)")
|
|
|
|
if (cmd == "debug") then
|
|
|
|
if (arg1 == "vendor") then
|
|
RecipeRadar_Options.PositionDebug = true
|
|
end
|
|
|
|
if (arg1 == "skills") then
|
|
RecipeRadar_SkillDB_Print(arg2)
|
|
end
|
|
|
|
if (arg1 == "clear") then
|
|
RecipeRadar_SkillDB_Clear(arg2)
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if (cmd == "radar") then
|
|
|
|
local region = RecipeRadar_RegionData[GetRealZoneText()]
|
|
if (not region) then return end
|
|
|
|
for _, vendor in region.Vendors do
|
|
|
|
RecipeRadar_Print(RecipeRadar_GetColoredTeamString(
|
|
vendor.Team, vendor.Name) .. " (" ..
|
|
string.format("%d", vendor.Coordinates[1].x * 100) .. ", " ..
|
|
string.format("%d", vendor.Coordinates[1].y * 100) .. "):")
|
|
|
|
table.sort(vendor.Recipes, RecipeRadar_CompareSkills)
|
|
table.foreach(vendor.Recipes, RecipeRadar_PrintRecipe)
|
|
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
RecipeRadar_Toggle()
|
|
|
|
end
|
|
|
|
-- Function to be called on selecting a new region to initialize the
|
|
-- main frame and any active filters.
|
|
function RecipeRadar_NewRegion(region)
|
|
|
|
if (not region or region == "") then return end
|
|
|
|
RecipeRadar_Options.CurrentRegion = region
|
|
RecipeRadar_Options.CurrentContinent = RecipeRadar_GetCurrentContinent()
|
|
RecipeRadar_InitVendorFilters()
|
|
RecipeRadar_SelectFirstRecipe()
|
|
RecipeRadar_FrameUpdate()
|
|
|
|
end
|
|
|
|
-- Returns whether the given region contains at least one recipe that
|
|
-- is learnable by the player or (optionally) alts
|
|
function RecipeRadar_RegionHasLearnableRecipe(region_name)
|
|
|
|
local region_data = RecipeRadar_RegionData[region_name]
|
|
if (not region_data) then return false end
|
|
|
|
for _, vendor in region_data.Vendors do
|
|
for _, recipe in vendor.Recipes do
|
|
if (RecipeRadar_Availability_IsLearnable(recipe)) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Given a table of recipes, returns the number of recipes that are
|
|
-- learnable by the player or (optionally) alts.
|
|
function RecipeRadar_CountLearnableRecipes(recipes)
|
|
|
|
if (not recipes) then return 0 end
|
|
|
|
local total = 0
|
|
|
|
for _, recipe in recipes do
|
|
if (RecipeRadar_Availability_IsLearnable(recipe)) then
|
|
total = total + 1
|
|
end
|
|
end
|
|
|
|
return total
|
|
|
|
end
|
|
|
|
-- Returns the index of the current region's continent, or 0 if the
|
|
-- player is not in a known map zone (eg. she's in an instance).
|
|
function RecipeRadar_GetCurrentContinent()
|
|
|
|
local region = RecipeRadar_RegionData[RecipeRadar_Options.CurrentRegion]
|
|
|
|
-- this mess derives the current continent from the current region
|
|
if (region) then
|
|
return region.Continent
|
|
else
|
|
-- region is not one with recipes, so we search for it
|
|
for cont_id, _ in { GetMapContinents() } do
|
|
for _, zone in { GetMapZones(cont_id) } do
|
|
if (RecipeRadar_Options.CurrentRegion == zone) then
|
|
return cont_id
|
|
end
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
end
|
|
|
|
-- Converts a format string to one that LUA can understand. I've
|
|
-- shamelessly stolen this from Innerfire in order to (I hope) prevent
|
|
-- the combat chatlog parsing errors.
|
|
function RecipeRadar_ConvertFormat(fmt)
|
|
|
|
-- remove non-English format params like "%1$s"
|
|
local find = string.gsub(fmt, "%%%d%$", "%%")
|
|
|
|
-- remove any special characters with escaped versions
|
|
find = string.gsub(find, "([%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
|
|
|
|
-- finally replace standard "%s" and "%d"
|
|
find = string.gsub(find, "%%s", "(.+)")
|
|
find = string.gsub(find, "%%d", "(%%d+)")
|
|
return find
|
|
|
|
end
|
|
|
|
-- Parses the input for a couple of known message formats to gain information
|
|
-- about changes in the character's skills.
|
|
function RecipeRadar_ParseChatMessage(msg)
|
|
|
|
-- messages of the form 'Your skill in %s has increased to %d.'
|
|
local start, _, prof, rank =
|
|
string.find(msg, RecipeRadar_ConvertFormat(ERR_SKILL_UP_SI))
|
|
if (start) then
|
|
RecipeRadar_SkillDB_SetRank(prof, rank)
|
|
end
|
|
|
|
-- messages of the form 'You have unlearned %s.'
|
|
start, _, prof =
|
|
string.find(msg, RecipeRadar_ConvertFormat(ERR_SPELL_UNLEARNED_S))
|
|
if (prof) then
|
|
RecipeRadar_SkillDB_GetPlayerProfessions()[prof] = nil
|
|
end
|
|
|
|
end
|
|
|
|
-- Foreach function that prints a recipe link and its cost.
|
|
function RecipeRadar_PrintRecipe(index, recipe)
|
|
|
|
RecipeRadar_Print(" " .. RecipeRadar_GetHyperlinkByID(recipe.ID) ..
|
|
", " .. RecipeRadar_GetGSCString(recipe.Cost))
|
|
|
|
end
|
|
|
|
-- Returns a string describing an item link from an item ID.
|
|
function RecipeRadar_GetHyperlinkByID(id)
|
|
|
|
local name, link, color = RecipeRadar_GetSafeItemInfo(id)
|
|
return RecipeRadar_GetHyperlinkByParts(name, link, color)
|
|
|
|
end
|
|
|
|
-- Returns a string describing an item link from link components.
|
|
function RecipeRadar_GetHyperlinkByParts(name, link, color)
|
|
|
|
return string.format("%s|H%s|h[%s]|h|r", color, link, name)
|
|
|
|
end
|
|
|
|
-- If an item is not in your local cache, GetItemInfo() fails. To work
|
|
-- around this, we just return 'uncached' along with the details we do know.
|
|
-- If an item is recognized by the client, we cache the name so that future
|
|
-- requests don't end up fully 'uncached'. The final return value is a
|
|
-- boolean indicating whether it was a cached ID.
|
|
function RecipeRadar_GetSafeItemInfo(id)
|
|
|
|
local name, link, rarity, _, _, _, _, _, texture = GetItemInfo(id)
|
|
|
|
-- initialize our name cache if necessary
|
|
if (not RecipeRadar_NameCache) then RecipeRadar_NameCache = { } end
|
|
|
|
if (name == nil) then
|
|
|
|
-- set returned name according to whether we've already seen the id
|
|
if (RecipeRadar_NameCache[id]) then
|
|
name = RecipeRadar_NameCache[id]
|
|
else
|
|
name = RRS("Uncached Recipe")
|
|
end
|
|
|
|
-- create our own tooltip for uncached items
|
|
return name, "item:" .. id .. ":0:0:0",
|
|
RecipeRadar_ColorToCode(RecipeRadar_Colors.UncachedRecipe),
|
|
RecipeRadar_Colors.UncachedRecipe.r,
|
|
RecipeRadar_Colors.UncachedRecipe.g,
|
|
RecipeRadar_Colors.UncachedRecipe.b,
|
|
RECIPERADAR_IMAGE_ROOT .. "Misc\\QuestionMark",
|
|
false -- boolean to indicate whether we got a real recipe
|
|
|
|
else
|
|
RecipeRadar_NameCache[id] = name -- cache names
|
|
local r, g, b, colorcode = GetItemQualityColor(rarity)
|
|
return name, link, colorcode, r, g, b, texture, true
|
|
end
|
|
|
|
end
|
|
|
|
function RecipeRadar_RegionNameToID(region_name, continent)
|
|
|
|
local index = 0
|
|
|
|
for _, zone in { GetMapZones(continent) } do
|
|
index = index + 1
|
|
if (region_name == zone) then
|
|
return index
|
|
end
|
|
end
|
|
|
|
RecipeRadar_Print("ASSERT FAILED: region '" .. region_name ..
|
|
"' not found by GetMapZones()!")
|
|
return nil
|
|
|
|
end
|
|
|
|
-- Formats the subtext that applies to a few vendors. include_parens is
|
|
-- a boolean that specifies whether the string should be in parens.
|
|
function RecipeRadar_FormatVendorNote(note, include_parens)
|
|
|
|
if (include_parens) then
|
|
return format(TEXT(PARENS_TEMPLATE), format(RRS("%s Vendor"), note))
|
|
end
|
|
return format(RRS("%s Vendor"), note)
|
|
|
|
end
|
|
|
|
-- Get gold, silver, copper from total copper.
|
|
function RecipeRadar_GetGSC(copper)
|
|
|
|
if (copper == nil) then
|
|
copper = 0
|
|
end
|
|
|
|
local g = math.floor(copper / 10000)
|
|
local s = math.floor((copper - (g * 10000)) / 100)
|
|
local c = math.floor(copper - (g * 10000) - (s * 100))
|
|
|
|
return g, s, c
|
|
|
|
end
|
|
|
|
-- Format text by color for gold, silver and copper (from EnhTooltip).
|
|
function RecipeRadar_GetGSCString(copper)
|
|
|
|
local TEXT_NONE = "0"
|
|
local GSC_GOLD = "ffd100"
|
|
local GSC_SILVER = "e6e6e6"
|
|
local GSC_COPPER = "c8602c"
|
|
local GSC_FMT = "|cff%s%d|r"
|
|
local GSC_PART = ".|cff%s%02d|r"
|
|
local GSC_NONE = "|cffa0a0a0" .. TEXT_NONE .. "|r"
|
|
|
|
local g, s, c = RecipeRadar_GetGSC(copper)
|
|
local gsc = ""
|
|
|
|
if (g > 0) then
|
|
gsc = string.format(GSC_FMT, GSC_GOLD, g)
|
|
if (s > 0) then
|
|
gsc = gsc .. string.format(GSC_PART, GSC_SILVER, s)
|
|
end
|
|
if exact then
|
|
gsc = gsc .. string.format(GSC_PART,GSC_COPPER, c)
|
|
end
|
|
elseif (s > 0) then
|
|
gsc = string.format(GSC_FMT, GSC_SILVER, s)
|
|
if (c > 0) then
|
|
gsc = gsc .. string.format(GSC_PART, GSC_COPPER, c)
|
|
end
|
|
elseif (c > 0) then
|
|
gsc = gsc .. string.format(GSC_FMT, GSC_COPPER, c)
|
|
else
|
|
gsc = GSC_NONE
|
|
end
|
|
|
|
return gsc
|
|
|
|
end
|
|
|
|
-- Returns the name of the player's opposing faction.
|
|
-- These strings shouldn't need to be localized.
|
|
function RecipeRadar_GetOpposingFaction()
|
|
|
|
if (UnitFactionGroup("player") == "Alliance") then
|
|
return "Horde"
|
|
elseif (UnitFactionGroup("player") == "Horde") then
|
|
return "Alliance"
|
|
end
|
|
return "Unknown"
|
|
|
|
end
|
|
|
|
-- Formats a string in the color specified by the key 'team'.
|
|
function RecipeRadar_GetColoredTeamString(team, s)
|
|
|
|
if (not RecipeRadar_Teams[team]) then return end
|
|
return RecipeRadar_ColorToCode(RecipeRadar_Teams[team].Color) ..
|
|
s .. FONT_COLOR_CODE_CLOSE
|
|
|
|
end
|
|
|
|
-- Returns whether the skill is an applicable profession.
|
|
function RecipeRadar_IsProfession(skill_name)
|
|
|
|
for name, _ in RecipeRadar_Professions do
|
|
if (name == skill_name) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Returns the profession under which a trade skill specialty belongs,
|
|
-- or false if skill_name is not a known specialty.
|
|
function RecipeRadar_IsProfessionSpecialty(skill_name)
|
|
|
|
for spec, prof in RecipeRadar_Specialties do
|
|
if (spec == skill_name) then
|
|
return prof
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Returns whether the supplied faction is one that Recipe Radar cares about.
|
|
function RecipeRadar_IsKnownFaction(faction)
|
|
|
|
for _, name in RecipeRadar_Factions do
|
|
if (name == faction) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Returns whether the given vendor has at least one unfiltered recipe.
|
|
function RecipeRadar_VendorHasRecipes(vendor)
|
|
|
|
for _, recipe in vendor.Recipes do
|
|
if (not RecipeRadar_IsRecipeFiltered(recipe)) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Returns whether the given recipe has at least one unfiltered location.
|
|
function RecipeRadar_RecipeHasLocations(recipe)
|
|
|
|
for _, location in recipe.Locations do
|
|
if (not location.IsFiltered) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
-- Returns a string describing the requirements to buy the given recipe
|
|
-- according to the recipe's Notes/Faction member, or nil.
|
|
function RecipeRadar_GetRecipeReqsString(recipe)
|
|
|
|
if (recipe.Notes and recipe.Notes ~= "") then
|
|
return recipe.Notes
|
|
end
|
|
if (recipe.Faction and recipe.Faction ~= "") then
|
|
return recipe.Faction .. ", " ..
|
|
getglobal("FACTION_STANDING_LABEL" .. recipe.Level)
|
|
end
|
|
return nil
|
|
|
|
end
|
|
|
|
-- Given a recipe name of the form 'Label: Name', returns 'Name'. It
|
|
-- does this by checking for a ':' and looking ahead two characters.
|
|
-- TODO: verify this with Asian/other languages.
|
|
function RecipeRadar_TrimRecipeLabel(recipe_name)
|
|
|
|
local label_end = string.find(recipe_name, ":")
|
|
if (label_end) then
|
|
return string.sub(recipe_name, label_end + 2)
|
|
end
|
|
return recipe_name
|
|
|
|
end
|
|
|
|
-- Sorts the vendors in a given region.
|
|
function RecipeRadar_SortVendors(region_data)
|
|
|
|
if (region_data.IsVendorTableSorted) then
|
|
return
|
|
end
|
|
|
|
table.sort(region_data.Vendors, RecipeRadar_CompareNames)
|
|
region_data.IsVendorTableSorted = true
|
|
|
|
end
|
|
|
|
-- Sorts the recipes on a given vendor.
|
|
function RecipeRadar_SortRecipes(container)
|
|
|
|
if (container.IsRecipeTableSorted) then
|
|
return
|
|
end
|
|
|
|
table.sort(container.Recipes, RecipeRadar_CompareSkills)
|
|
container.IsRecipeTableSorted = true
|
|
|
|
end
|
|
|
|
-- Sorts the locations on a given recipe.
|
|
function RecipeRadar_SortLocations(recipe_data)
|
|
|
|
if (recipe_data.IsLocationsTableSorted) then
|
|
return
|
|
end
|
|
|
|
table.sort(recipe_data.Locations, RecipeRadar_CompareRegions)
|
|
recipe_data.IsRecipeTableSorted = true
|
|
|
|
end
|
|
|
|
-- Sort comparison function on required skill first, then the recipe's
|
|
-- ID (to insure a deterministic order).
|
|
function RecipeRadar_CompareSkills(recipe1, recipe2)
|
|
|
|
if (recipe1.Skill == recipe2.Skill) then
|
|
return recipe1.ID < recipe2.ID
|
|
end
|
|
return recipe1.Skill < recipe2.Skill
|
|
|
|
end
|
|
|
|
-- Sort comparison function on vendor names.
|
|
function RecipeRadar_CompareNames(vendor1, vendor2)
|
|
|
|
return vendor1.Name < vendor2.Name
|
|
|
|
end
|
|
|
|
-- Sort comparison function on location names.
|
|
function RecipeRadar_CompareRegions(location1, location2)
|
|
|
|
if (location1.Region == location2.Region) then
|
|
return location1.Vendor < location2.Vendor
|
|
end
|
|
return location1.Region < location2.Region
|
|
|
|
end
|
|
|
|
-- Returns the result of the "<" operator on both major and minor portions.
|
|
function RecipeRadar_IsVersionLessThan(version1, version2)
|
|
|
|
local major1, minor1 = string.match(version1, "(%d+)\.(%d+)")
|
|
local major2, minor2 = string.match(version2, "(%d+)\.(%d+)")
|
|
|
|
if (major1 ~= major2) then
|
|
return major1+0 < major2+0
|
|
end
|
|
return minor1+0 < minor2+0
|
|
|
|
end
|
|
|
|
-- Returns a color code string converted from RGB decimals.
|
|
function RecipeRadar_ColorToCode(color)
|
|
|
|
return string.format("|cff%02x%02x%02x",
|
|
255 * color.r, 255 * color.g, 255 * color.b)
|
|
|
|
end
|
|
|
|
-- Returns the given string surrounded by the highlighted version of
|
|
-- base_color.
|
|
function RecipeRadar_HighlightString(s, base_color)
|
|
|
|
local highlight = {
|
|
r = math.min(1.0, base_color.r + RecipeRadar_Colors.Highlight),
|
|
g = math.min(1.0, base_color.g + RecipeRadar_Colors.Highlight),
|
|
b = math.min(1.0, base_color.b + RecipeRadar_Colors.Highlight), }
|
|
return RecipeRadar_ColorToCode(highlight) .. s .. FONT_COLOR_CODE_CLOSE
|
|
|
|
end
|
|
|
|
-- Basic chat frame printing function.
|
|
function RecipeRadar_Print(msg, r, g, b)
|
|
|
|
DEFAULT_CHAT_FRAME:AddMessage(msg,
|
|
(r or RecipeRadar_Colors.MainFont.r),
|
|
(g or RecipeRadar_Colors.MainFont.g),
|
|
(b or RecipeRadar_Colors.MainFont.b))
|
|
|
|
end
|