------------------------------------------------------ -- FeedOMatic.lua ------------------------------------------------------ FOM_VERSION = "11200.1"; ------------------------------------------------------ -- TODO: if you don't auto-feed, have an option to make the need to feed more noticeable (e.g., pulsing halo around the pet-happiness icon). -- constants FOM_WARNING_INTERVAL = 10; -- don't warn more than once per this many seconds MAX_QUALITY = 35 * 60 + 1; -- We store a notion of a food's "quality": its best happiness-per-tick multiplied by the pet's level as of when that tick occurred. We use "best" because a pet that's closer to "sated" (maximum happiness) will receive less happiness per tick than he would from the same food if he were hungrier. (So, a food that gives 35 happiness per tick to a level 60 pet is "better" than a food that's worth 35 happiness per tick to a level 30 pet.) Foods whose quality hasn't been observed yet are given this value when sorting, so we can prioritize the discovery of new foods' quality ratings. MAX_KEEPOPEN_SLOTS = 150; -- Configuration FOM_Config_Default = { Enabled = false; Alert = "emote"; Level = "content"; KeepOpenSlots = 8; AvoidUsefulFood = true; AvoidQuestFood = true; AvoidBonusFood = true; Fallback = false; SaveForCookingLevel = 1; PreferHigherQuality = true; Tooltip = true; }; FOM_Config = FOM_Config_Default; -- FOM_Cooking = { }; -- Has the following internal structure: -- REALM_PLAYER = { -- FOODNAME = SKILL_DIFFICULTY, -- } -- FOM_QuestFood = { }; -- Has the following internal structure: -- REALM_PLAYER = { -- FOODNAME = QUANTITY_REQUIRED, -- } -- FOM_FoodQuality = { }; -- Has the following internal structure: -- REALM_PLAYER = { -- PETNAME = { -- FOODNAME = HAPPINESS, -- } -- } -- Variables FOM_State = { }; FOM_State.InCombat = false; FOM_State.IsAFK = false; FOM_State.ShouldFeed = false; FOM_LastWarning = 0; FOM_LastFood = nil; FOM_RealmPlayer = nil; FOM_LastPetName = nil; -- Anti-freeze code borrowed from ReagentInfo (in turn, from Quest-I-On): -- keeps WoW from locking up if we try to scan the tradeskill window too fast. FOM_TradeSkillLock = { }; FOM_TradeSkillLock.Locked = false; FOM_TradeSkillLock.EventTimer = 0; FOM_TradeSkillLock.EventCooldown = 0; FOM_TradeSkillLock.EventCooldownTime = 1; -- State variable used to track required quantities of quest food when it's in more than one stack FOM_Quantity = { }; -- Remember how item IDs map to food names at runtime, but don't bloat long-term memory with it... FOM_FoodIDsToNames = {}; function FOM_FeedButton_OnClick() if (arg1 == "RightButton") then if FOM_OptionsFrame:IsVisible() then HideUIPanel(FOM_OptionsFrame); else ShowUIPanel(FOM_OptionsFrame); end else FOM_Feed(); end end function FOM_FeedButton_OnEnter() if ( PetFrameHappiness.tooltip ) then GameTooltip:SetOwner(PetFrameHappiness, "ANCHOR_RIGHT"); GameTooltip:SetText(PetFrameHappiness.tooltip); if ( PetFrameHappiness.tooltipDamage ) then GameTooltip:AddLine(PetFrameHappiness.tooltipDamage, "", 1, 1, 1); end if ( PetFrameHappiness.tooltipLoyalty ) then GameTooltip:AddLine(PetFrameHappiness.tooltipLoyalty, "", 1, 1, 1); end GameTooltip:Show(); end end function FOM_FeedButton_OnLeave() GameTooltip:Hide(); end function FOM_OnLoad() -- Register for Events this:RegisterEvent("VARIABLES_LOADED"); -- Register Slash Commands SLASH_FEEDOMATIC1 = "/feedomatic"; SLASH_FEEDOMATIC2 = "/fom"; SLASH_FEEDOMATIC3 = "/feed"; SLASH_FEEDOMATIC4 = "/petfeed"; -- Rauen's PetFeed compatibility SLASH_FEEDOMATIC5 = "/pf"; SlashCmdList["FEEDOMATIC"] = function(msg) FOM_ChatCommandHandler(msg); end -- hook functions so we can manage per-pet saved food quality data FOM_Original_PetRename = PetRename; PetRename = FOM_PetRename; FOM_Original_PetAbandon = PetAbandon; PetAbandon = FOM_PetAbandon; --GFWUtils.Debug = true; GFWUtils.Print("Fizzwidget Feed-O-Matic "..FOM_VERSION.." initialized!"); end function FOM_CheckSetup() _, realClass = UnitClass("player"); if (realClass ~= "HUNTER") then return; end if (FOM_RealmPlayer == nil) then FOM_RealmPlayer = GetCVar("realmName") .. "." .. UnitName("player"); end local currentPetName = UnitName("pet"); if (currentPetName and currentPetName ~= "" and currentPetName ~= UNKNOWNOBJECT) then FOM_LastPetName = currentPetName; end if (FOM_FoodQuality == nil) then FOM_FoodQuality = { }; end if (FOM_FoodQuality[FOM_RealmPlayer] == nil) then FOM_FoodQuality[FOM_RealmPlayer] = { }; end if (FOM_LastPetName) then if (FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName] == nil) then FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName] = { }; end end end function FOM_Tooltip(frame, name, link, source) if (FOM_Config.Tooltip and name ~= nil and UnitExists("pet")) then FOM_CheckSetup(); local itemID = FOM_IDFromLink(link); if (not FOM_IsInDiet(itemID)) then return false; end local color; local absoluteQuality = FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID]; if (absoluteQuality == nil) then color = HIGHLIGHT_FONT_COLOR; frame:AddLine(string.format(FOM_QUALITY_UNKNOWN, FOM_LastPetName), color.r, color.g, color.b); return true; else local currentQuality = absoluteQuality / UnitLevel("pet"); if (currentQuality < 0) then color = QuestDifficultyColor["trivial"]; frame:AddLine(string.format(FOM_QUALITY_UNDER, FOM_LastPetName), color.r, color.g, color.b); return true; elseif (currentQuality == 0) then color = QuestDifficultyColor["trivial"]; frame:AddLine(string.format(FOM_QUALITY_MIGHT, FOM_LastPetName), color.r, color.g, color.b); return true; elseif (currentQuality <= 8) then color = QuestDifficultyColor["standard"]; frame:AddLine(string.format(FOM_QUALITY_WILL, FOM_LastPetName), color.r, color.g, color.b); return true; elseif (currentQuality <= 17) then color = QuestDifficultyColor["difficult"]; frame:AddLine(string.format(FOM_QUALITY_LIKE, FOM_LastPetName), color.r, color.g, color.b); return true; elseif (currentQuality <= 35) then color = QuestDifficultyColor["verydifficult"]; frame:AddLine(string.format(FOM_QUALITY_LOVE, FOM_LastPetName), color.r, color.g, color.b); return true; else GFWUtils.DebugLog("Unexpected food quality level "..currentQuality); return false; end end end end function FOM_OnEvent(event, arg1) -- Save Variables if ( event == "VARIABLES_LOADED" ) then _, realClass = UnitClass("player"); if (realClass == "HUNTER") then -- monitor status for whether we're able to feed this:RegisterEvent("PET_ATTACK_START"); this:RegisterEvent("PET_ATTACK_STOP"); -- this:RegisterEvent("CHAT_MSG_SYSTEM"); -- check your pet roster when at the stables so we don't bloat SavedVariables this:RegisterEvent("PET_STABLE_SHOW"); this:RegisterEvent("PET_STABLE_UPDATE"); -- track whether foods are useful for Cooking this:RegisterEvent("TRADE_SKILL_SHOW"); this:RegisterEvent("TRADE_SKILL_UPDATE"); -- figure out what happens when we try to feed pet (gain happiness, didn't like, etc) this:RegisterEvent("CHAT_MSG_SPELL_TRADESKILLS"); this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"); this:RegisterEvent("UI_ERROR_MESSAGE"); -- Events for trying to catch when the pet needs feeding this:RegisterEvent("PET_BAR_SHOWGRID"); this:RegisterEvent("PET_BAR_UPDATE"); this:RegisterEvent("PET_UI_UPDATE"); this:RegisterEvent("UNIT_HAPPINESS"); FOM_FeedButton = CreateFrame("Button", "FOM_FeedButton", PetFrameHappiness); FOM_FeedButton:SetAllPoints(PetFrameHappiness); FOM_FeedButton:RegisterForClicks("LeftButtonUp", "RightButtonUp"); FOM_FeedButton:SetScript("OnClick", FOM_FeedButton_OnClick); FOM_FeedButton:SetScript("OnEnter", FOM_FeedButton_OnEnter); FOM_FeedButton:SetScript("OnLeave", FOM_FeedButton_OnLeave); table.insert(UISpecialFrames,"FOM_OptionsFrame"); if (FOM_Config.Level == "happy") then -- we've redefined the Level option and this setting is no loger available FOM_Config.Level = "content"; end if (FOM_Config.Tooltip) then GFWTooltip_AddCallback("GFW_FeedOMatic", FOM_Tooltip); end end return; elseif ( event == "PET_ATTACK_START" ) then -- Set Flag FOM_State.InCombat = true; return; elseif ( event == "PET_ATTACK_STOP" ) then -- Remove Flag FOM_State.InCombat = false; elseif ( event == "CHAT_MSG_SPELL_TRADESKILLS" ) then if (FOM_FEEDPET_LOG_FIRSTPERSON == nil) then FOM_FEEDPET_LOG_FIRSTPERSON = GFWUtils.FormatToPattern(FEEDPET_LOG_FIRSTPERSON); end _, _, foodName = string.find(arg1, FOM_FEEDPET_LOG_FIRSTPERSON); if (foodName and foodName ~= "") then local foodID = GFWTable.KeyOf(FOM_FoodIDsToNames, foodName); if (foodID == nil) then local bag, slot = FOM_FindSpecificFood(foodName); local foodLink = GetContainerItemLink(bag, slot); foodID = FOM_IDFromLink(foodLink); end if (foodID) then FOM_LastFood = GFWUtils.ItemLink(foodID); GFWUtils.DebugLog("Manually fed "..FOM_LastFood); end end return; elseif ( event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" ) then if (arg1 and FOM_HasFeedEffect()) then if (FOM_POWERGAIN_OTHER == nil and POWERGAINSELFOTHER) then FOM_POWERGAIN_OTHER = GFWUtils.FormatToPattern(POWERGAINSELFOTHER); end if (FOM_POWERGAIN_OTHER == nil and POWERGAIN_OTHER) then FOM_POWERGAIN_OTHER = GFWUtils.FormatToPattern(POWERGAIN_OTHER); end if (FOM_POWERGAIN_OTHER == nil) then GFWUtils.PrintOnce(GFWUtils.Red("Feed-O-Matic Error: ").. "Can't find parse pattern for pet happiness."); return; end _, _, name, amount, powerType = string.find(arg1, FOM_POWERGAIN_OTHER); local happiness; if (name == UnitName("pet") and powerType == HAPPINESS_POINTS) then happiness = tonumber(amount); else return; end if (FOM_LastFood and happiness > 0) then FOM_CheckSetup(); local itemID = FOM_IDFromLink(FOM_LastFood); local knownQuality = FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID]; local quality = happiness * UnitLevel("pet"); FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID] = math.max((knownQuality or 0), quality); FOM_LastFood = nil; end end return; elseif ( event == "UI_ERROR_MESSAGE" ) then if (arg1 and string.find(arg1, SPELL_FAILED_FOOD_LOWLEVEL)) then FOM_CheckSetup(); if not (FOM_LastFood == nil) then local itemID = FOM_IDFromLink(FOM_LastFood); FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID] = -1; FOM_LastFood = nil; if ( FOM_Config.Alert == "chat") then GFWUtils.Print(string.format(FOM_FEEDING_EAT_ANOTHER, UnitName("pet"))); elseif ( FOM_Config.Alert == "emote") then SendChatMessage(string.format(FOM_FEEDING_FEED_ANOTHER, UnitName("pet")), "EMOTE"); end return; end elseif (arg1 and string.find(arg1, SPELL_FAILED_WRONG_PET_FOOD)) then FOM_CheckSetup(); if (FOM_LastFood) then if ( FOM_Config.Alert == "chat") then GFWUtils.Print(string.format(FOM_FEEDING_EAT_ANOTHER, UnitName("pet"))); elseif ( FOM_Config.Alert == "emote") then SendChatMessage(string.format(FOM_FEEDING_FEED_ANOTHER, UnitName("pet")), "EMOTE"); end -- remove from quality tracking local itemID = FOM_IDFromLink(FOM_LastFood); FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID] = nil; -- remove from diet local dietList = {GetPetFoodTypes()}; for _, diet in dietList do if ( FOM_RemoveFood(string.lower(diet), itemID) ) then local capDiet = string.upper(string.sub(diet, 1, 1)) .. string.sub(diet, 2); -- print a nicely capitalized version GFWUtils.Print("Removed "..FOM_LastFood.." from "..GFWUtils.Hilite(capDiet).." list."); end end FOM_LastFood = nil; return; end end return; elseif (event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_UPDATE") then if (GetTradeSkillLine() ~= nil and GetTradeSkillLine() == FOM_CookingSpellName()) then if (FOM_Config.SaveForCookingLevel >= 0 and FOM_Config.SaveForCookingLevel <= 3) then -- Update Cooking reagents list so we can avoid consuming food we could skillup from. if (FOM_RealmPlayer == nil) then FOM_RealmPlayer = GetCVar("realmName") .. "." .. UnitName("player"); end if (FOM_Cooking == nil) then FOM_Cooking = { }; end if (FOM_Cooking[FOM_RealmPlayer] == nil) then FOM_Cooking[FOM_RealmPlayer] = { }; end if (FOM_Cooking ~= nil and FOM_Cooking[FOM_RealmPlayer] ~= nil and TradeSkillFrame and TradeSkillFrame:IsVisible() and not FOM_TradeSkillLock.Locked) then -- This prevents further update events from being handled if we're already processing one. -- This is done to prevent the game from freezing under certain conditions. FOM_TradeSkillLock.Locked = true; for i=1, GetNumTradeSkills() do local itemName, type, _, _ = GetTradeSkillInfo(i); if (type ~= "header") then for j=1, GetTradeSkillNumReagents(i) do local reagentLink = GetTradeSkillReagentItemLink(i, j); local itemID = FOM_IDFromLink(reagentLink); if (itemID and FOM_IsKnownFood(itemID)) then if (FOM_Cooking[FOM_RealmPlayer][itemID] == nil) then FOM_Cooking[FOM_RealmPlayer][itemID] = FOM_DifficultyToNum(type); else FOM_Cooking[FOM_RealmPlayer][itemID] = max(FOM_Cooking[FOM_RealmPlayer][itemID], FOM_DifficultyToNum(type)); end end end end end end end end return; elseif (event == "PET_STABLE_SHOW" or event == "PET_STABLE_UPDATE") then -- clean up the FOM_FoodQuality sub-tables in case we missed you abandoning a pet FOM_CheckSetup(); local stabledPetNames = {}; for petIndex = 0, 2 do local _, petName, _, _, _ = GetStablePetInfo(petIndex); if (petName) then table.insert(stabledPetNames, petName); end end local orphanedPetNames = {}; for savedPetName in FOM_FoodQuality[FOM_RealmPlayer] do if (stabledPetNames == nil) then GFWUtils.DebugLog("stabledPetNames == nil"); end if (stabledPetNames ~= nil and GFWTable.IndexOf(stabledPetNames, savedPetName) == 0) then table.insert(orphanedPetNames, savedPetName); end end for _, orphanedPet in orphanedPetNames do FOM_FoodQuality[FOM_RealmPlayer][orphanedPet] = nil; end return; elseif (FOM_Config.Level) then FOM_CheckHappiness(); end end -- Update our list of quest objectives so we can avoid consuming food we want to accumulate for a quest. function FOM_ScanQuests() FOM_QuestFood = nil; for questNum=1, GetNumQuestLogEntries() do local QText, level, questTag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(questNum); if (not isHeader) then for objectiveNum=1, GetNumQuestLeaderBoards(questNum) do local text, type, finished = GetQuestLogLeaderBoard(objectiveNum, questNum); if (text ~= nil and strlen(text) > 0) then local _, _, objectiveName, numCurrent, numRequired = string.find(text, "(.*): (%d+)/(%d+)"); if (FOM_IsKnownFood(objectiveName)) then if (FOM_QuestFood == nil) then FOM_QuestFood = { }; end if (FOM_QuestFood[FOM_RealmPlayer] == nil) then FOM_QuestFood[FOM_RealmPlayer] = { }; end if (FOM_QuestFood[FOM_RealmPlayer][objectiveName] == nil) then FOM_QuestFood[FOM_RealmPlayer][objectiveName] = tonumber(numRequired); else FOM_QuestFood[FOM_RealmPlayer][objectiveName] = max(FOM_QuestFood[FOM_RealmPlayer][objectiveName], tonumber(numRequired)); end end end end end end end function FOM_DifficultyToNum(level) if (level == "optimal" or level == "orange") then return 3; elseif (level == "medium" or level == "yellow") then return 2; elseif (level == "easy" or level == "green") then return 1; elseif (level == "trivial" or level == "gray" or level == "grey") then return 1; else -- bad input return nil; end end function FOM_OnUpdate(elapsed) _, realClass = UnitClass("player"); if (realClass ~= "HUNTER") then return; end -- If it's been more than a second since our last tradeskill update, -- we can allow the event to process again. FOM_TradeSkillLock.EventTimer = FOM_TradeSkillLock.EventTimer + elapsed; if (FOM_TradeSkillLock.Locked) then FOM_TradeSkillLock.EventCooldown = FOM_TradeSkillLock.EventCooldown + elapsed; if (FOM_TradeSkillLock.EventCooldown > FOM_TradeSkillLock.EventCooldownTime) then FOM_TradeSkillLock.EventCooldown = 0; FOM_TradeSkillLock.Locked = false; end end --GFWUtils.Debug = true; if (FOM_State.ShouldFeed and FOM_Config.IconWarning and PetFrameHappiness) then if (PetFrameHappiness:IsVisible() and PetFrameHappiness:GetAlpha() == 1) then FOM_FadeOut(); end end end function FOM_FadeOut() local fadeInfo = {}; fadeInfo.mode = "OUT"; fadeInfo.timeToFade = 0.5; fadeInfo.finishedFunc = FOM_FadeIn; UIFrameFade(PetFrameHappiness, fadeInfo); end --hack since a frame can't have a reference to itself in it function FOM_FadeIn() UIFrameFadeIn(PetFrameHappiness, 0.5); end function FOM_CanFeed() if ( not UnitExists("pet") ) then GFWUtils.DebugLog("Can't feed; pet doesn't exist."); return false; end if ( UnitHealth("pet") <= 0 ) then GFWUtils.DebugLog("Can't feed; pet is dead."); return false; end if ( UnitHealth("player") <= 0 ) then GFWUtils.DebugLog("Can't feed; I'm dead."); return false; end if ( CastingBarFrameStatusBar:IsVisible() ) then GFWUtils.DebugLog("Can't feed; casting a spell / tradeksill."); return false; end if ( UnitOnTaxi("player") ) then GFWUtils.DebugLog("Can't feed; flying."); return false; end if ( FOM_State.InCombat ) or ( PlayerFrame.inCombat ) then GFWUtils.DebugLog("Can't feed; in combat."); return false; end if ( LootFrame:IsVisible() ) then GFWUtils.DebugLog("Shouldn't feed; loot window is open."); return false; end local buff, buffIndex; local dontFeedBuffTextures = { "Interface\\Icons\\Ability_Ambush", -- NE Shadowmeld (maybe not unique buff icon?) "Interface\\Icons\\Ability_Rogue_FeignDeath", -- Feign Death "Interface\\Icons\\INV_Drink_07", -- drinking "Interface\\Icons\\INV_Misc_Fork&Knife", -- eating }; local mountTextureSubStrings = { "Ability_Mount", "INV_Misc_Foot_Kodo", }; for buffIndex=0, 15 do local buff = GetPlayerBuffTexture(buffIndex); if ( buff ~= nil) then for _, buffTexture in dontFeedBuffTextures do if ( buff == buffTexture ) then GFWUtils.DebugLog("Can't feed; currently, eating, drinking, or feigning death."); return false; end end if ( UnitLevel("player") >= 40 ) then for _, buffTexture in mountTextureSubStrings do if ( string.find(buff, buffTexture) ) then FOMTooltip:SetUnitBuff("player", buffIndex+1); local msg = FOMTooltipTextLeft1:GetText(); if (msg ~= nil) then msg = string.lower(msg); for _, mountName in FOM_MOUNT_NAME_SUBSTRINGS do if (string.find(msg, mountName)) then GFWUtils.DebugLog("Can't feed; mounted."); return false; end end end end end end end end return true; end function FOM_ChatCommandHandler(msg) if ( msg == "" ) then if FOM_OptionsFrame:IsVisible() then HideUIPanel(FOM_OptionsFrame); else ShowUIPanel(FOM_OptionsFrame); end return; end -- Check for Pet (we don't really need one for most of our chat commands, but we conveniently use its name.) if ( UnitExists("pet") ) then petName = UnitName("pet"); if (GetLocale() ~= "enUS") then if (FOM_LocaleInfo == nil) then FOM_LocaleInfo = {}; end FOM_LocaleInfo[UnitCreatureFamily("pet")] = {GetPetFoodTypes()}; end else petName = "Your pet"; end -- Print Help if ( msg == "help" ) or ( msg == "" ) then GFWUtils.Print("Fizzwidget Feed-O-Matic "..FOM_VERSION..":"); GFWUtils.Print("/feedomatic /fom "); GFWUtils.Print("- "..GFWUtils.Hilite("help").." - Print this helplist."); GFWUtils.Print("- "..GFWUtils.Hilite("status").." - Check current settings."); GFWUtils.Print("- "..GFWUtils.Hilite("reset").." - Reset to default settings."); GFWUtils.Print("- "..GFWUtils.Hilite("alert chat").." | "..GFWUtils.Hilite("emote").." | "..GFWUtils.Hilite("off").." - Alert via chat window or emote channel when feeding."); GFWUtils.Print("- "..GFWUtils.Hilite("level content").." | "..GFWUtils.Hilite("happy").." | "..GFWUtils.Hilite("off").." - Provide an extra reminder to feed your pet when happiness is below this level."); GFWUtils.Print("- "..GFWUtils.Hilite("saveforcook orange").." | "..GFWUtils.Hilite("yellow").." | "..GFWUtils.Hilite("green").." | "..GFWUtils.Hilite("gray").." | "..GFWUtils.Hilite("off").." - Avoid foods used in cooking recipes (based on their difficulty)."); GFWUtils.Print("- "..GFWUtils.Hilite("savequest on").." | "..GFWUtils.Hilite("off").." - Avoid foods you need to collect for a quest."); GFWUtils.Print("- "..GFWUtils.Hilite("savebonus on").." | "..GFWUtils.Hilite("off").." - Avoid foods which have bonus effects."); GFWUtils.Print("- "..GFWUtils.Hilite("fallback on").." | "..GFWUtils.Hilite("off").." - Fall back to foods we'd normally avoid if no other food is available."); GFWUtils.Print("- "..GFWUtils.Hilite("keepopen ").." - Set when to prefer smaller stacks of food versus evaluating food based on quality. Specify "..GFWUtils.Hilite("off").." instead of a number to always select foods by quality, or "..GFWUtils.Hilite("max").." to always prefer smaller stacks."); GFWUtils.Print("- "..GFWUtils.Hilite("quality high").." | "..GFWUtils.Hilite("low").." - Set whether to prefer foods that give your pet more happiness faster or less happiness more slowly."); GFWUtils.Print("- "..GFWUtils.Hilite("tooltip on").." | "..GFWUtils.Hilite("off").." - Identifies and rates pet foods in their tooltips."); GFWUtils.Print("- "..GFWUtils.Hilite("feed").." - Feed your pet (automatically finds an appropriate food)."); GFWUtils.Print("- "..GFWUtils.Hilite("feed ").." - Feed your pet a specific food."); GFWUtils.Print("- "..GFWUtils.Hilite("add ").." - Add food to list."); GFWUtils.Print("- "..GFWUtils.Hilite("remove ").." - Remove food from list."); GFWUtils.Print("- "..GFWUtils.Hilite("show ").." - Show food list."); return; end if ( msg == "version" ) then GFWUtils.Print("Fizzwidget Feed-O-Matic "..FOM_VERSION..":"); return; end -- Check Status if ( msg == "status" ) then if (FOM_Config.Level) then GFWUtils.Print("Feed-O-Matic will help remind you to feed your pet when he's "..GFWUtils.Hilite(FOM_Config.Level).."."); else GFWUtils.Print("Feed-O-Matic will "..GFWUtils.Hilite("not").." help remind you when to feed your pet."); end if (FOM_Config.KeepOpenSlots < MAX_KEEPOPEN_SLOTS) then if (FOM_Config.PreferHigherQuality) then GFWUtils.Print("Feed-O-Matic will prefer to use higher quality foods first."); else GFWUtils.Print("Feed-O-Matic will prefer to use lower quality foods first."); end if (FOM_Config.KeepOpenSlots == 0) then GFWUtils.Print("Feed-O-Matic will look first at food quality when determining what to feed to your pet."); else GFWUtils.Print("If fewer than "..GFWUtils.Hilite(FOM_Config.KeepOpenSlots).." spaces are open in your inventory, Feed-O-Matic will prefer smaller stacks of food regardless of quality."); end else GFWUtils.Print("Feed-O-Matic will always prefer smaller stacks of food regardless of quality."); end if (FOM_Config.Alert == "emote") then GFWUtils.Print("You will automatically emote when feeding "..petName.."."); elseif (FOM_Config.Alert == "chat") then GFWUtils.Print("Feed-O-Matic will notify you in chat when feeding "..petName.."."); else GFWUtils.Print("There will be no alert when feeding "..petName.."."); end if (FOM_Config.SaveForCookingLevel >= 0 and FOM_Config.SaveForCookingLevel <= 3) then if (FOM_Config.SaveForCookingLevel == 3) then level = "orange"; elseif (FOM_Config.SaveForCookingLevel == 2) then level = "yellow"; elseif (FOM_Config.SaveForCookingLevel == 1) then level = "green"; elseif (FOM_Config.SaveForCookingLevel == 0) then level = "gray"; end GFWUtils.Print("Feed-O-Matic will avoid foods used in "..GFWUtils.Hilite(level).." or higher Cooking recipes."); else GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they're used in Cooking."); end if (FOM_Config.AvoidQuestFood) then GFWUtils.Print("Feed-O-Matic will avoid foods you need to collect for quests."); else GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they're needed for quests."); end if (FOM_Config.AvoidBonusFood) then GFWUtils.Print("Feed-O-Matic will avoid foods that have an additional bonus effect when eaten by a player."); else GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they have bonus effects."); end if (FOM_Config.Fallback) then GFWUtils.Print("Feed-O-Matic will fall back to food it would otherwise avoid if no other food is available."); else GFWUtils.Print("Feed-O-Matic will not feed your pet if the only foods available are foods you'd prefer to avoid feeding."); end if (FOM_Config.Tooltip) then GFWUtils.Print("Adding food quality information to tooltips for foods your current pet can eat."); else GFWUtils.Print("Not adding information to item tooltips."); end return; end -- Reset Variables if ( msg == "reset" ) then FOM_Config = FOM_Config_Default; FOM_Cooking = nil; FOM_FoodQuality = nil; FOM_AddedFoods = nil; FOM_RemovedFoods = nil; FOM_QuestFood = nil; GFWUtils.Print("Feed-O-Matic configuration reset."); FOM_ChatCommandHandler("status"); return; end -- Turn automatic feeding On if ( msg == "on" ) then GFWUtils.Print("Automatic feeding is no longer available due to changes in the WoW client as of Patch 1.10."); return; end local _, _, cmd, option = string.find(msg, "(%w+) (%w+)"); -- Toggle Alert if ( cmd == "alert" ) then if (option == "emote") then FOM_Config.Alert = "emote"; GFWUtils.Print("You will automatically emote when feeding "..petName.."."); elseif (option == "chat") then FOM_Config.Alert = "chat"; GFWUtils.Print("Feed-O-Matic will notify you in chat when feeding "..petName.."."); elseif (option == "off") then FOM_Config.Alert = nil; GFWUtils.Print("There will be no alert when feeding "..petName.."."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic alert chat").." | "..GFWUtils.Hilite("emote").." | "..GFWUtils.Hilite("off")); end return; end -- Set Happiness Level if ( cmd == "level" ) then if ( option == "content" ) then FOM_Config.Level = "content"; elseif ( option == "happy" ) then FOM_Config.Level = "happy"; elseif ( option == "debug" ) then FOM_Config.Level = "debug"; else FOM_Config.Level = nil; end if (FOM_Config.Level) then GFWUtils.Print("Feed-O-Matic will help remind you to feed your pet when he's less than "..GFWUtils.Hilite(FOM_Config.Level).."."); FOM_CheckHappiness(); else GFWUtils.Print("Feed-O-Matic will "..GFWUtils.Hilite("not").." help remind you when to feed your pet."); FOM_Status.ShouldFeed = nil; end return; end -- Set Cooking recipe level if ( cmd == "saveforcook" ) then local level = option; if (level ~= nil) then local levelNum = FOM_DifficultyToNum(level); if (levelNum ~= nil) then FOM_Config.SaveForCookingLevel = levelNum; FOM_Config.AvoidUsefulFood = true; GFWUtils.Print("Feed-O-Matic will avoid foods used in "..GFWUtils.Hilite(level).." or higher Cooking recipes. You'll need to open your Cooking window for Feed-O-Matic to cache information about what recipes you know."); return; elseif (level == "off") then FOM_Config.SaveForCookingLevel = 4; if (not FOM_Config.AvoidQuestFood and not FOM_Config.Avoid9) then FOM_Config.AvoidUsefulFood = false; end GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they're used in Cooking."); return; end end GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic saveforcook orange").." | "..GFWUtils.Hilite("yellow").." | "..GFWUtils.Hilite("green").." | "..GFWUtils.Hilite("gray").." | "..GFWUtils.Hilite("off")); return; end -- Set avoiding food with bonuses if ( cmd == "savequest" ) then if (option == "on") then FOM_Config.AvoidQuestFood = true; FOM_Config.AvoidUsefulFood = true; FOM_ScanQuests(); GFWUtils.Print("Feed-O-Matic will avoid foods you need to collect for quests."); elseif (option == "off") then FOM_Config.AvoidQuestFood = true; if not (FOM_Config.SaveForCookingLevel >= 0 and FOM_Config.SaveForCookingLevel <= 3 and not FOM_Config.AvoidBonusFood) then FOM_Config.AvoidUsefulFood = false; end GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they're needed for quests."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic savequest on").." | "..GFWUtils.Hilite("off")); end return; end -- Set avoiding quest-objective food if ( cmd == "savebonus" ) then if (option == "on") then FOM_Config.AvoidBonusFood = true; FOM_Config.AvoidUsefulFood = true; GFWUtils.Print("Feed-O-Matic will avoid foods that have an additional bonus effect when eaten by a player."); elseif (option == "off") then FOM_Config.AvoidBonusFood = true; if not (FOM_Config.SaveForCookingLevel >= 0 and FOM_Config.SaveForCookingLevel <= 3 and not FOM_Config.AvoidQuestFood) then FOM_Config.AvoidUsefulFood = false; end GFWUtils.Print("Feed-O-Matic will choose foods without regard to whether they have bonus effects."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic savebonus on").." | "..GFWUtils.Hilite("off")); end return; end if ( cmd == "fallback" ) then if (option == "on") then FOM_Config.Fallback = true; GFWUtils.Print("Feed-O-Matic will fall back to food it would otherwise avoid if no other food is available."); elseif (option == "off") then FOM_Config.Fallback = false; GFWUtils.Print("Feed-O-Matic will not feed your pet if the only foods available are foods you'd prefer to avoid feeding."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic fallback on").." | "..GFWUtils.Hilite("off")); end return; end if ( cmd == "tooltip" ) then if (option == "on") then FOM_Config.Tooltip = true; GFWTooltip_AddCallback("GFW_FeedOMatic", FOM_Tooltip); GFWUtils.Print("Adding food quality information to tooltips for foods your current pet can eat."); elseif (option == "off") then FOM_Config.Tooltip = false; GFWUtils.Print("Not adding information to item tooltips."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic tooltip on").." | "..GFWUtils.Hilite("off")); end return; end -- Set quality sorting direction if ( cmd == "quality" ) then if (option == "high") then FOM_Config.PreferHigherQuality = true; GFWUtils.Print("Feed-O-Matic will prefer to use higher quality foods first."); elseif (option == "low") then FOM_Config.PreferHigherQuality = false; GFWUtils.Print("Feed-O-Matic will prefer to use lower quality foods first."); else GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic quality high").." | "..GFWUtils.Hilite("low")); end return; end -- Set inventory management threshold if ( cmd == "keepopen" ) then if (option == "off" or option == "none") then newNum = 0; elseif (option == "max") then newNum = MAX_KEEPOPEN_SLOTS; else newNum = tonumber(option); end if (newNum == nil) then GFWUtils.Print("Usage: "..GFWUtils.Hilite("/feedomatic keepopen ")); return; end FOM_Config.KeepOpenSlots = newNum; GFWUtils.Print("Feed-O-Matic will try to keep at least "..GFWUtils.Hilite(FOM_Config.KeepOpenSlots).." spaces open in your inventory when looking for food."); return; end -- Feed Pet local _, _, cmd, foodString = string.find(msg, "(%w+) *(.*)"); if ( cmd == "feed" ) then if (foodString == "") then FOM_Feed(nil); -- automatically find a food and feed it else local inputFoods = { }; for itemLink in string.gfind(foodString, "%[[%w%s:()\"'-]+%]") do local _, _, foodName = string.find(itemLink, "^%[([%w%s:()\"'-]+)%]$"); table.insert(inputFoods, foodName); end if (table.getn(inputFoods) == 0) then table.insert(inputFoods, foodString); -- if no item links, treat whole input line as one food's name end for _, food in inputFoods do FOM_Feed(food); end end return; end local _, _, cmd, diet, foodString = string.find(msg, "(%w+) (%w+) *(.*)"); if ( cmd == "add" or cmd == "remove" or cmd == "show" or cmd == "list" ) then diet = string.lower(diet); -- let's be case insensitive if ( FOM_Foods[diet] == nil and diet ~= FOM_DIET_ALL) then local usageString = "Usage: "..GFWUtils.Hilite("/feedomatic "..cmd..FOM_DIET_MEAT).." | "..GFWUtils.Hilite(FOM_DIET_FISH).." | "..GFWUtils.Hilite(FOM_DIET_BREAD).." | "..GFWUtils.Hilite(FOM_DIET_CHEESE).." | "..GFWUtils.Hilite(FOM_DIET_FRUIT).." | "..GFWUtils.Hilite(FOM_DIET_FUNGUS).." | "..GFWUtils.Hilite(FOM_DIET_BONUS) if (cmd ~= "show" and cmd ~= "list") then usageString = usageString.." ."; end GFWUtils.Print(usageString); return; end if (cmd == "show" or cmd == "list") then if ( diet == FOM_DIET_ALL ) then diets = {FOM_DIET_MEAT, FOM_DIET_FISH, FOM_DIET_BREAD, FOM_DIET_CHEESE, FOM_DIET_FRUIT, FOM_DIET_FUNGUS, FOM_DIET_BONUS}; else diets = {diet}; end for _, aDiet in diets do local capDiet = string.upper(string.sub(aDiet, 1, 1)) .. string.sub(aDiet, 2); -- print a nicely capitalized version GFWUtils.Print("Feed-O-Matic "..GFWUtils.Hilite(capDiet).." List:"); local dietFoods = FOM_Foods[aDiet]; if (FOM_AddedFoods ~= nil and FOM_AddedFoods[aDiet] ~= nil) then dietFoods = GFWTable.Merge(dietFoods, FOM_AddedFoods[aDiet]); end if (FOM_RemovedFoods ~= nil and FOM_RemovedFoods[aDiet] ~= nil) then dietFoods = GFWTable.Subtract(dietFoods, FOM_RemovedFoods[aDiet]); end table.sort(dietFoods); for _, food in dietFoods do local foodName = GetItemInfo(food); if (foodName) then if (FOM_FoodIDsToNames == nil) then FOM_FoodIDsToNames = {}; end FOM_FoodIDsToNames[food] = foodName; GFWUtils.Print(GFWUtils.Hilite(" - ")..foodName); else GFWUtils.Print(GFWUtils.Hilite(" - ").."item id "..food.." (name not available)"); end end end return; else local inputFoods = { }; for itemLink in string.gfind(foodString, "|c%x+|Hitem:%d+:%d+:%d+:%d+|h%[.-%]|h|r") do table.insert(inputFoods, itemLink); local foodID = FOM_IDFromLink(itemLink); if (foodID) then local foodName = FOM_NameFromLink(itemLink); if (FOM_FoodIDsToNames == nil) then FOM_FoodIDsToNames = {}; end FOM_FoodIDsToNames[foodID] = foodName; end end if (table.getn(inputFoods) == 0) then GFWUtils.Print("The "..GFWUtils.Hilite("/fom "..cmd).." command requires an item link; shift-click an item to insert a link."); return; end local capDiet = string.upper(string.sub(diet, 1, 1)) .. string.sub(diet, 2); -- print a nicely capitalized version if ( cmd == "add" ) then for _, food in inputFoods do local foodID = FOM_IDFromLink(food); if ( FOM_AddFood(diet, tonumber(foodID)) ) then GFWUtils.Print("Added "..food.." to "..GFWUtils.Hilite(capDiet).." list."); else GFWUtils.Print(food.." already in "..GFWUtils.Hilite(capDiet).." list."); end end if (FOM_Config.AvoidQuestFood) then FOM_ScanQuests(); -- in case any of the newly added foods are quest objectives end return; elseif (cmd == "remove" ) then for _, food in inputFoods do local foodID = FOM_IDFromLink(food); if ( FOM_RemoveFood(diet, tonumber(foodID)) ) then GFWUtils.Print("Removed "..food.." from "..GFWUtils.Hilite(capDiet).." list."); else GFWUtils.Print("Could not find "..food.." in "..GFWUtils.Hilite(capDiet).." list."); end end return; end end end -- if we got down to here, we got bad input FOM_ChatCommandHandler("help"); end -- Add a food to a list function FOM_AddFood(diet, food) if (FOM_Foods[diet] == nil) then GFWUtils.DebugLog("FOM_Foods[diet] == nil"); end if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil"); end if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil"); end if ( GFWTable.IndexOf(FOM_Foods[diet], food) == 0 ) then if (FOM_AddedFoods == nil) then FOM_AddedFoods = {}; end if (FOM_AddedFoods[diet] == nil) then FOM_AddedFoods[diet] = {}; end if ( GFWTable.IndexOf(FOM_AddedFoods[diet], food) == 0 ) then table.insert( FOM_AddedFoods[diet], food ); table.sort( FOM_AddedFoods[diet] ); if (FOM_RemovedFoods and FOM_RemovedFoods[diet] and GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ~= 0) then table.remove( FOM_RemovedFoods[diet], GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ); table.sort( FOM_RemovedFoods[diet] ); end return true; else return false; end else return false; end end -- Remove a food from a list function FOM_RemoveFood(diet, food) if (FOM_Foods[diet] == nil) then GFWUtils.DebugLog("FOM_Foods[diet] == nil"); end if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil"); end if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil"); end if ( GFWTable.IndexOf(FOM_Foods[diet], food) ~= 0 ) then if (FOM_RemovedFoods == nil) then FOM_RemovedFoods = {}; end if (FOM_RemovedFoods[diet] == nil) then FOM_RemovedFoods[diet] = {}; end if ( GFWTable.IndexOf(FOM_RemovedFoods[diet], food) == 0 ) then table.insert( FOM_RemovedFoods[diet], food ); table.sort( FOM_RemovedFoods[diet] ); if (FOM_AddedFoods and FOM_AddedFoods[diet] and GFWTable.IndexOf(FOM_AddedFoods[diet], food) ~= 0) then table.remove( FOM_AddedFoods[diet], GFWTable.IndexOf(FOM_AddedFoods[diet], food) ); table.sort( FOM_AddedFoods[diet] ); end return true; else return false; end else if (FOM_AddedFoods and FOM_AddedFoods[diet] and GFWTable.IndexOf(FOM_AddedFoods[diet], food) ~= 0) then table.remove( FOM_AddedFoods[diet], GFWTable.IndexOf(FOM_AddedFoods[diet], food) ); table.sort( FOM_AddedFoods[diet] ); return true; end return false; end end function FOM_IsBGActive() local bgNum = 1; local status; repeat status = GetBattlefieldStatus(bgNum); if (status == "active") then return true; end bgNum = bgNum + 1; until (status == nil) return false; end -- Check Happiness function FOM_CheckHappiness() -- Check for pet if not ( UnitExists("pet") ) then FOM_State.ShouldFeed = nil; return; end -- Get Pet Info local pet = UnitName("pet"); local happiness, damage, loyalty = GetPetHappiness(); -- Check No Happiness if ( happiness == 0 ) or ( happiness == nil ) then return; end local level; if ( FOM_Config.Level == "unhappy" ) then level = 1; elseif ( FOM_Config.Level == "content" ) then level = 2; elseif ( FOM_Config.Level == "happy" ) then level = 3; elseif ( FOM_Config.Level == "debug" ) then level = 4; else level = 0; end -- Check if Need Feeding if ( happiness < level + 1 ) then FOM_State.ShouldFeed = true; if (not FOM_HasFeedEffect() and GetTime() - FOM_LastWarning > FOM_WARNING_INTERVAL) then if (FOM_Config.TextWarning) then local msg; if (level - happiness == 0) then msg = FOM_PET_HUNGRY; else msg = FOM_PET_VERY_HUNGRY; end GFWUtils.Print(string.format(msg, pet)); GFWUtils.Note(string.format(msg, pet)); end FOM_PlayHungrySound(); FOM_LastWarning = GetTime(); end else FOM_State.ShouldFeed = nil; end end FOM_HungrySounds = { [BAT] = "Sound\\Creature\\FelBat\\FelBatDeath.wav", [BEAR] = "Sound\\Creature\\Bear\\mBearDeathA.wav", [BOAR] = "Sound\\Creature\\Boar\\mWildBoarAggro2.wav", [CAT] = "Sound\\Creature\\Tiger\\mTigerStand2A.wav", [CARRION_BIRD] = "Sound\\Creature\\Carrion\\mCarrionWoundCriticalA.wav", [CRAB] = "Sound\\Creature\\Crab\\CrabDeathA.wav", [CROCOLISK] = "Sound\\Creature\\Basilisk\\mBasiliskSpellCastA.wav", [GORILLA] = "Sound\\Creature\\Gorilla\\GorillaDeathA.wav", [HYENA] = "Sound\\Creature\\Hyena\\HyenaPreAggroA.wav", [OWL] = "Sound\\Creature\\OWl\\OwlPreAggro.wav", [RAPTOR] = "Sound\\Creature\\Raptor\\mRaptorWoundCriticalA.wav", [SCORPID] = "Sound\\Creature\\SilithidWasp\\mSilithidWaspStand2A.wav", [SPIDER] = "Sound\\Creature\\Tarantula\\mTarantulaFidget2a.wav", [TALLSTRIDER] = "Sound\\Creature\\TallStrider\\tallStriderPreAggroA.wav", [TURTLE] = "Sound\\Creature\\SeaTurtle\\SeaTurtleWoundCritA.wav", [WIND_SERPENT] = "Sound\\Creature\\WindSerpant\\mWindSerpantDeathA.wav", [WOLF] = "Sound\\Creature\\Wolf\\mWolfFidget2c.wav", }; function FOM_PlayHungrySound() if (FOM_Config.AudioWarning) then local type = UnitCreatureFamily("pet"); local sound = FOM_HungrySounds[type]; if (sound == nil or FOM_Config.AudioWarning == "bell") then PlaySoundFile("Sound\\Doodad\\BellTollNightElf.wav"); else PlaySoundFile(sound); end end end -- Check Feed Effect function FOM_HasFeedEffect() local i = 1; local buff; buff = UnitBuff("pet", i); while buff do if ( string.find(buff, "Ability_Hunter_BeastTraining") ) then return true; end i = i + 1; buff = UnitBuff("pet", i); end return false; end -- Feed Pet function FOM_Feed(aFood) FOM_State.ShouldFeed = false; -- Make sure we have a feedable pet if not (UnitExists("pet")) then GFWUtils.Note(FOM_ERROR_NO_PET); return; end if (UnitIsDead("pet")) then GFWUtils.Note(FOM_ERROR_PET_DEAD); return; end if (GetPetFoodTypes() == nil) then GFWUtils.Note(FOM_ERROR_NO_FEEDABLE_PET); return; end -- Assign Variable local pet = UnitName("pet"); FOM_CheckSetup(); if (FOM_LastPetName == nil or FOM_LastPetName == "") then GFWUtils.DebugLog("Can't get pet info."); return; end if (GetLocale() ~= "enUS") then if (FOM_LocaleInfo == nil) then FOM_LocaleInfo = {}; end FOM_LocaleInfo[UnitCreatureFamily("pet")] = {GetPetFoodTypes()}; end -- Look for Food local foodBag, foodItem; if (aFood ~= nil) then -- if told to feed a specific food, do so foodBag, foodItem = FOM_FindSpecificFood(aFood); if ( foodBag == nil) then -- No Food Could be Found GFWUtils.Print(string.format(FOM_ERROR_FOOD_NOT_FOUND, pet, aFood)); return; end else foodBag, foodItem = FOM_NewFindFood(); end if ( foodBag == nil) then -- No Food Could be Found GFWUtils.Print(string.format(FOM_ERROR_NO_FOOD, pet)); return; end FOM_LastFood = GetContainerItemLink(foodBag, foodItem); GFWUtils.DebugLog("Picked "..FOM_LastFood.." (bag "..foodBag..", slot "..foodItem..") for feeding."); if (FOM_Config.Debug) then -- don't actually feed anything, just show what we would choose return; end -- Actually feed the item to the pet PickupContainerItem(foodBag, foodItem); if ( CursorHasItem() ) then DropItemOnUnit("pet"); end if ( CursorHasItem() ) then PickupContainerItem(foodBag, foodItem); else FOM_State.ShouldFeed = nil; -- Alert if ( FOM_Config.Alert == "chat") then GFWUtils.Print(string.format(FOM_FEEDING_EAT, pet, GFWUtils.Hilite(FOM_LastFood))); elseif ( FOM_Config.Alert == "emote") then SendChatMessage(string.format(FOM_FEEDING_FEED, pet, FOM_LastFood).. FOM_RandomEmote(), "EMOTE"); end end end function FOM_RandomEmote() local randomEmotes = {}; if (UnitSex("pet") == 2) then randomEmotes = GFWTable.Merge(randomEmotes, FOM_Emotes["male"]); elseif (UnitSex("pet") == 3) then randomEmotes = GFWTable.Merge(randomEmotes, FOM_Emotes["female"]); end randomEmotes = GFWTable.Merge(randomEmotes, FOM_Emotes[UnitCreatureFamily("pet")]); randomEmotes = GFWTable.Merge(randomEmotes, FOM_Emotes[FOM_NameFromLink(FOM_LastFood)]); randomEmotes = GFWTable.Merge(randomEmotes, FOM_Emotes["any"]); return randomEmotes[math.random(table.getn(randomEmotes))]; end function FOM_FindSpecificFood(foodName) for bagNum = 0, 4 do if (not FOM_BagIsQuiver(bagNum) ) then -- skip bags that can't contain food local bagSize = GetContainerNumSlots(bagNum); for itemNum = 1, bagSize do itemName = FOM_GetItemName(bagNum, itemNum); if ( itemName == foodName ) then return bagNum, itemNum; end end end end return nil; end function FOM_IsTemporaryFood(itemLink) local _, _, link = string.find(itemLink, "(item:%d+:%d+:%d+:%d+)"); if (link == nil or link == "") then return false; end FOMTooltip:ClearLines(); FOMTooltip:SetHyperlink(link); if (FOMTooltipTextLeft2:GetText() == ITEM_CONJURED) then return true; else return false; end end function FOM_FlatFoodList() local foodList = {}; FOM_Quantity = { }; for bagNum = 0, 4 do if (not FOM_BagIsQuiver(bagNum) ) then -- skip bags that can't contain food for itemNum = 1, GetContainerNumSlots(bagNum) do local itemLink = GetContainerItemLink(bagNum, itemNum); if (itemLink) then local itemID = FOM_IDFromLink(itemLink); local _, itemCount = GetContainerItemInfo(bagNum, itemNum); if ( FOM_IsInDiet(itemID) ) then if (FOM_FoodIDsToNames == nil) then FOM_FoodIDsToNames = {}; end local name = FOM_NameFromLink(itemLink); FOM_FoodIDsToNames[itemID] = name; local isUseful = FOM_IsUsefulFood(itemID, itemCount); foodQuality = (FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName][itemID] or MAX_QUALITY); if (foodQuality > 0) then table.insert(foodList, {bag=bagNum, slot=itemNum, link=itemLink, count=itemCount, quality=foodQuality, useful=isUseful, temp=FOM_IsTemporaryFood(itemLink)}); end end end end end end return foodList; end function FOM_NewFindFood() FlatFoodList = FOM_FlatFoodList(); table.sort(FlatFoodList, FOM_SortCount); -- small stacks first if (FOM_NumOpenBagSlots() > FOM_Config.KeepOpenSlots) then if (FOM_Config.PreferHigherQuality) then table.sort(FlatFoodList, FOM_SortQualityDescending); -- higher quality first else table.sort(FlatFoodList, FOM_SortQualityAscending); -- lower quality first end end table.sort(FlatFoodList, FOM_SortTemporary); -- temporary foods first if (FOM_Config.AvoidUsefulFood) then table.sort(FlatFoodList, FOM_SortUseful); -- non-useful foods first end for _, foodInfo in FlatFoodList do if (foodInfo.useful and FOM_Config.AvoidUsefulFood and not FOM_Config.Fallback) then GFWUtils.DebugLog("Skipping "..foodInfo.count.."x "..foodInfo.link.."; no falling back to avoided foods."); else return foodInfo.bag, foodInfo.slot; end end return nil; end function FOM_SortTemporary(a, b) if (a.temp) then aTemp = 1; else aTemp = 0; end if (b.temp) then bTemp = 1; else bTemp = 0; end return aTemp > bTemp; end function FOM_SortCount(a, b) return a.count < b.count; end function FOM_SortUseful(a, b) if (a.useful) then aUseful = 1; else aUseful = 0; end if (b.useful) then bUseful = 1; else bUseful = 0; end return aUseful < bUseful; end function FOM_SortQualityDescending(a, b) return a.quality > b.quality; end function FOM_SortQualityAscending(a, b) return a.quality < b.quality; end function FOM_IsUsefulFood(itemID, quantity) local foodName = GetItemInfo(itemID); if (foodName == nil) then GFWUtils.DebugLog("Can't get info for item ID "..itemID..", assuming it's OK to eat."); return false; end if (FOM_Cooking and FOM_Cooking[FOM_RealmPlayer] and FOM_Cooking[FOM_RealmPlayer][itemID]) then if (FOM_Cooking[FOM_RealmPlayer][itemID] >= FOM_Config.SaveForCookingLevel) then GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is good for cooking."); return true; end end if (FOM_Config.AvoidQuestFood) then FOM_ScanQuests(); if (FOM_QuestFood ~= nil and FOM_QuestFood[FOM_RealmPlayer] ~= nil and FOM_QuestFood[FOM_RealmPlayer][foodName]) then if (FOM_Quantity[foodName] == nil) then FOM_Quantity[foodName] = quantity; else FOM_Quantity[foodName] = FOM_Quantity[foodName] + quantity; end if (FOM_Quantity[foodName] > FOM_QuestFood[FOM_RealmPlayer][foodName]) then GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; is needed for quest, but we have more than enough."); return false; else GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is needed for quest."); return true; end end end if (FOM_Config.AvoidBonusFood and FOM_IsInDiet(itemID, FOM_DIET_BONUS)) then GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; has bonus effect when eaten by player."); return true; end --GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; doesn't have other uses."); return false; end function FOM_NumOpenBagSlots() local openSlots = 0; for bagNum = 0, 4 do if (not FOM_BagIsQuiver(bagNum) ) then -- skip bags that can't contain food local bagSize = GetContainerNumSlots(bagNum); for itemNum = 1, bagSize do if (GetContainerItemInfo(bagNum, itemNum) == nil) then openSlots = openSlots + 1; end end end end return openSlots; end function FOM_IsInDiet(food, dietList) if ( dietList == nil ) then dietList = {GetPetFoodTypes()}; end if ( dietList == nil ) then return false; end if (type(dietList) ~= "table") then dietList = {dietList}; end for _, diet in dietList do diet = string.lower(diet); -- let's be case insensitive if (FOM_Foods[diet] == nil) then GFWUtils.DebugLog("FOM_Foods[diet] == nil"); end if (FOM_RemovedFoods ~= nil and FOM_RemovedFoods[diet] ~= nil and GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ~= 0) then return false; end if (FOM_AddedFoods ~= nil and FOM_AddedFoods[diet] ~= nil and GFWTable.IndexOf(FOM_AddedFoods[diet], food) ~= 0) then return true; end if (GFWTable.IndexOf(FOM_Foods[diet], food) ~= 0) then return true; end end return false; end function FOM_IsKnownFood(food) return FOM_IsInDiet(food, {FOM_DIET_MEAT, FOM_DIET_FISH, FOM_DIET_BREAD, FOM_DIET_CHEESE, FOM_DIET_FUNGUS, FOM_DIET_FRUIT}); end -- Get Item Name function FOM_GetItemName(bag, slot) local itemLink = GetContainerItemLink(bag, slot); if (itemLink) then return FOM_NameFromLink(itemLink); else return ""; end end function FOM_PetRename(newName) FOM_CheckSetup(); -- move our saved food quality data to be indexed under the new name FOM_FoodQuality[FOM_RealmPlayer][newName] = FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName]; FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName] = nil; FOM_Original_PetRename(newName); end function FOM_PetAbandon() FOM_CheckSetup(); -- delete saved food-quality data for this pet so we don't bloat SavedVariables FOM_FoodQuality[FOM_RealmPlayer][FOM_LastPetName] = nil; FOM_Original_PetAbandon(); end -- The icon for the cooking spell is unique and the same in all languages; use that to determine the localized name. function FOM_CookingSpellName() FOM_COOKING_ICON = "Interface\\Icons\\INV_Misc_Food_15"; if (FOM_COOKING_NAME == nil) then local spellName; local i = 0; repeat i = i + 1; spellName = GetSpellName(i, BOOKTYPE_SPELL); if (spellName ~= nil and GetSpellTexture(i, BOOKTYPE_SPELL) == FOM_COOKING_ICON) then FOM_COOKING_NAME = spellName; return FOM_COOKING_NAME; end until (spellName == nil); end return FOM_COOKING_NAME; end function FOM_BagIsQuiver(bagNum) local invSlotID = ContainerIDToInventoryID(bagNum); local bagLink = GetInventoryItemLink("player", invSlotID); if (bagLink == nil) then return false; end local _, _, itemID = string.find(bagLink, "item:(%d+):%d+:%d+:%d+"); if (tonumber(itemID)) then itemID = tonumber(itemID); local name, link, rarity, minLevel, type, subType, stackCount, equipLoc = GetItemInfo(itemID); if (type == "Ammo Pouch" or type == "Quiver" or subType == "Ammo Pouch" or subType == "Quiver") then return true; end if (type == FOM_AMMO_POUCH or type == FOM_QUIVER or subType == FOM_AMMO_POUCH or subType == FOM_QUIVER) then return true; end end return false; end function FOM_IDFromLink(itemLink) if (itemLink == nil) then return nil; end local _, _, itemID = string.find(itemLink, "item:(%d+):%d+:%d+:%d+"); if (tonumber(itemID)) then return tonumber(itemID); else return nil; end end function FOM_NameFromLink(itemLink) if (itemLink == nil) then return nil; end local _, _, name = string.find(itemLink, "%[(.+)%]"); return name; end function FOM_OptionsShow() FOM_VersionText:SetText("v. "..FOM_VERSION); FOM_KeepOpenSlots:SetText(FOM_Config.KeepOpenSlots); for option, text in FOM_OptionsButtonText do local button = getglobal("FOM_OptionsButton_"..option); local buttonText = getglobal("FOM_OptionsButton_"..option.."Text"); if (button and buttonText) then if (FOM_Config[option]) then button:SetChecked(true); elseif (option == "SaveForCook_All" and FOM_Config.SaveForCookingLevel <= 0) then button:SetChecked(true); elseif (option == "SaveForCook_Green" and FOM_Config.SaveForCookingLevel == 1) then button:SetChecked(true); elseif (option == "SaveForCook_Yellow" and FOM_Config.SaveForCookingLevel == 2) then button:SetChecked(true); elseif (option == "SaveForCook_Orange" and FOM_Config.SaveForCookingLevel == 3) then button:SetChecked(true); elseif (option == "SaveForCook_None" and FOM_Config.SaveForCookingLevel >= 4) then button:SetChecked(true); elseif (option == "AudioWarningBell" and FOM_Config.AudioWarning == "bell") then button:SetChecked(true); elseif (option == "AlertEmote" and FOM_Config.Alert == "emote") then button:SetChecked(true); elseif (option == "AlertChat" and FOM_Config.Alert == "chat") then button:SetChecked(true); elseif (option == "AlertNone" and not FOM_Config.Alert) then button:SetChecked(true); elseif (option == "LevelContent" and FOM_Config.Level == "content") then button:SetChecked(true); elseif (option == "LevelUnhappy" and FOM_Config.Level == "unhappy") then button:SetChecked(true); elseif (option == "LevelOff" and not FOM_Config.Level) then button:SetChecked(true); else button:SetChecked(false); end buttonText:SetText(text); end end end function FOM_OptionsClick() local button = this:GetName(); local option = string.gsub(button, "FOM_OptionsButton_", ""); if (option == "SaveForCook_All" and this:GetChecked()) then FOM_Config.SaveForCookingLevel = 0; elseif (option == "SaveForCook_Green" and this:GetChecked()) then FOM_Config.SaveForCookingLevel = 1; elseif (option == "SaveForCook_Yellow" and this:GetChecked()) then FOM_Config.SaveForCookingLevel = 2; elseif (option == "SaveForCook_Orange" and this:GetChecked()) then FOM_Config.SaveForCookingLevel = 3; elseif (option == "SaveForCook_None" and this:GetChecked()) then FOM_Config.SaveForCookingLevel = 4; elseif (option == "AudioWarningBell") then if (this:GetChecked()) then FOM_Config.AudioWarning = "bell"; else FOM_Config.AudioWarning = 1; end elseif (option == "AlertEmote" and this:GetChecked()) then FOM_Config.Alert = "emote"; elseif (option == "AlertChat" and this:GetChecked()) then FOM_Config.Alert = "chat"; elseif (option == "AlertNone" and this:GetChecked()) then FOM_Config.Alert = nil; elseif (option == "LevelContent" and this:GetChecked()) then FOM_Config.Level = "content"; elseif (option == "LevelUnhappy" and this:GetChecked()) then FOM_Config.Level = "unhappy"; elseif (option == "LevelOff" and this:GetChecked()) then FOM_Config.Level = nil; else FOM_Config[option] = this:GetChecked(); end FOM_OptionsShow(); end function FOM_KeepOpenSlots_TextChanged() FOM_Config.KeepOpenSlots = tonumber(this:GetText()) or 0; end