From 7ae03935253b326b853af07af580fbbece5c51b8 Mon Sep 17 00:00:00 2001 From: Brendon <9218035+phuze@users.noreply.github.com> Date: Wed, 30 Oct 2024 06:22:13 -0400 Subject: [PATCH] Support for the Heartseeking Health Injector tinker (#62) * add support for the Heartseeking Health Injector tinker | update priorities | improved config handling * fix tooltip --- Core/DB.lua | 1 + FrameXML/InterfaceOptionsFrame.lua | 143 +++++++++++++++++++++-------- code.lua | 118 ++++++++++++++++++++---- 3 files changed, 202 insertions(+), 60 deletions(-) diff --git a/Core/DB.lua b/Core/DB.lua index 1a97816..236a3ba 100644 --- a/Core/DB.lua +++ b/Core/DB.lua @@ -6,6 +6,7 @@ ham.defaults = { witheringPotion = false, witheringDreamsPotion = false, cavedwellerDelight = true, + heartseekingInjector = false, activatedSpells = { ham.crimsonVialSpell, ham.renewal, ham.exhilaration, ham.fortitudeOfTheBear, ham.bitterImmunity, ham.desperatePrayer, ham.healingElixir, ham.giftOfTheNaaruDK, ham.giftOfTheNaaruHunter, ham.giftOfTheNaaruMage, ham.giftOfTheNaaruMageWarlock, ham.giftOfTheNaaruMonk, ham.giftOfTheNaaruPaladin, ham.giftOfTheNaaruPriest, ham diff --git a/FrameXML/InterfaceOptionsFrame.lua b/FrameXML/InterfaceOptionsFrame.lua index c8c9380..bd6666f 100644 --- a/FrameXML/InterfaceOptionsFrame.lua +++ b/FrameXML/InterfaceOptionsFrame.lua @@ -20,6 +20,20 @@ local positionx = 0 local currentPrioTitle = nil local lastStaticElement = nil +function ham.settingsFrame:updateConfig(option, value) + if ham.options[option] ~= nil then + ham.options[option] = value -- Update in-memory + HAMDB[option] = value -- Persist to DB + else + print("Invalid option: " .. tostring(option)) + end + -- Rebuild the macro and update priority frame + ham.checkTinker() + ham.updateHeals() + ham.updateMacro() + self:updatePrio() +end + function ham.settingsFrame:OnEvent(event, addOnName) if addOnName == "AutoPotion" then if event == "ADDON_LOADED" then @@ -43,7 +57,7 @@ ham.settingsFrame:RegisterEvent("PLAYER_LOGIN") ham.settingsFrame:RegisterEvent("ADDON_LOADED") ham.settingsFrame:SetScript("OnEvent", ham.settingsFrame.OnEvent) -function ham.settingsFrame:createPrioFrame(id, iconTexture, positionx, isSpell) +function ham.settingsFrame:createPrioFrame(id, iconTexture, positionx, isSpell, isTinker) local icon = CreateFrame("Frame", nil, self.content, UIParent) icon:SetFrameStrata("MEDIUM") icon:SetWidth(ICON_SIZE) @@ -52,6 +66,8 @@ function ham.settingsFrame:createPrioFrame(id, iconTexture, positionx, isSpell) GameTooltip:SetOwner(icon, "ANCHOR_TOPRIGHT") if isSpell == true then GameTooltip:SetSpellByID(id) + elseif isTinker then + GameTooltip:SetInventoryItem("player", id) else GameTooltip:SetItemByID(id) end @@ -87,6 +103,7 @@ function ham.settingsFrame:updatePrio() frame:Hide() end + -- Add spells to priority frames if next(ham.spellIDs) ~= nil then for i, id in ipairs(ham.spellIDs) do local iconTexture, originalIconTexture @@ -113,16 +130,34 @@ function ham.settingsFrame:updatePrio() currentFrame.texture = currentTexture currentFrame:Show() else - self:createPrioFrame(id, iconTexture, positionx, true) + self:createPrioFrame(id, iconTexture, positionx, true, false) positionx = positionx + (ICON_SIZE + (ICON_SIZE / 2)) end spellCounter = spellCounter + 1 end end + + -- Add items to priority frames if next(ham.itemIdList) ~= nil then for i, id in ipairs(ham.itemIdList) do - local itemName, itemLink, itemQuality, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, iconTexture, sellPrice, classID, subclassID, bindType, expansionID, setID, isCraftingReagent = - C_Item.GetItemInfo(id) + + local entry + local iconTexture + local isTinker = false + + -- if the entry is a gear slot (ie: tinker) + if type(id) == "string" and id:match("^slot:") then + local slot = assert(tonumber(id:sub(6)), "Invalid slot number") + entry = GetInventoryItemID("player", slot) + iconTexture = GetInventoryItemTexture("player", slot) + isTinker = true + -- otherwise its a normal item id + else + local _, _, _, _, _, _, _, _, _, tmpTexture = C_Item.GetItemInfo(id) + entry = id + iconTexture = tmpTexture + end + local currentFrame = prioFrames[i + spellCounter] local currentTexture = prioTextures[i + spellCounter] @@ -131,7 +166,11 @@ function ham.settingsFrame:updatePrio() currentFrame:SetScript("OnLeave", nil) currentFrame:HookScript("OnEnter", function(_, btn, down) GameTooltip:SetOwner(currentFrame, "ANCHOR_TOPRIGHT") - GameTooltip:SetItemByID(id) + if isTinker then + GameTooltip:SetInventoryItem("player", ham.tinkerSlot) + else + GameTooltip:SetItemByID(id) + end GameTooltip:Show() end) currentFrame:HookScript("OnLeave", function(_, btn, down) @@ -142,7 +181,7 @@ function ham.settingsFrame:updatePrio() currentFrame.texture = currentTexture currentFrame:Show() else - self:createPrioFrame(id, iconTexture, positionx, false) + self:createPrioFrame(entry, iconTexture, positionx, false, isTinker) positionx = positionx + (ICON_SIZE + (ICON_SIZE / 2)) end itemCounter = itemCounter + 1 @@ -189,9 +228,18 @@ function ham.settingsFrame:InitializeOptions() local stopCastButton = CreateFrame("CheckButton", nil, self.content, "InterfaceOptionsCheckButtonTemplate") stopCastButton:SetPoint("TOPLEFT", behaviourTitle, 0, -PADDING) ---@diagnostic disable-next-line: undefined-field - stopCastButton.Text:SetText("Include /stopcasting in the macro (reload after changing)") + stopCastButton.Text:SetText("Include /stopcasting in the macro") stopCastButton:HookScript("OnClick", function(_, btn, down) - HAMDB.stopCast = stopCastButton:GetChecked() + ham.settingsFrame:updateConfig("stopCast", stopCastButton:GetChecked()) + end) + stopCastButton:HookScript("OnEnter", function(_, btn, down) + ---@diagnostic disable-next-line: param-type-mismatch + GameTooltip:SetOwner(stopCastButton, "ANCHOR_TOPRIGHT") + GameTooltip:SetText("Useful for casters.") + GameTooltip:Show() + end) + stopCastButton:HookScript("OnLeave", function(_, btn, down) + GameTooltip:Hide() end) stopCastButton:SetChecked(HAMDB.stopCast) lastStaticElement = stopCastButton @@ -202,7 +250,7 @@ function ham.settingsFrame:InitializeOptions() ---@diagnostic disable-next-line: undefined-field cdResetButton.Text:SetText("Includes the shortest Cooldown in the reset Condition of Castsequence. !!USE CAREFULLY!!") cdResetButton:HookScript("OnClick", function(_, btn, down) - HAMDB.cdReset = cdResetButton:GetChecked() + ham.settingsFrame:updateConfig("cdReset", cdResetButton:GetChecked()) end) cdResetButton:SetChecked(HAMDB.cdReset) lastStaticElement = cdResetButton @@ -211,18 +259,14 @@ function ham.settingsFrame:InitializeOptions() local raidStoneButton = CreateFrame("CheckButton", nil, self.content, "InterfaceOptionsCheckButtonTemplate") raidStoneButton:SetPoint("TOPLEFT", lastStaticElement, 0, -PADDING) ---@diagnostic disable-next-line: undefined-field - raidStoneButton.Text:SetText("Low Priority Healthstones (Instance only)") + raidStoneButton.Text:SetText("Low Priority Healthstones") raidStoneButton:HookScript("OnClick", function(_, btn, down) - HAMDB.raidStone = raidStoneButton:GetChecked() - ham.updateHeals() - ham.updateMacro() - self:updatePrio() + ham.settingsFrame:updateConfig("raidStone", raidStoneButton:GetChecked()) end) raidStoneButton:HookScript("OnEnter", function(_, btn, down) ---@diagnostic disable-next-line: param-type-mismatch GameTooltip:SetOwner(raidStoneButton, "ANCHOR_TOPRIGHT") - GameTooltip:SetText( - "This option will prioritize health potions over a healthstone while in a Instance") + GameTooltip:SetText("Prioritize health potions over a healthstone.") GameTooltip:Show() end) raidStoneButton:HookScript("OnLeave", function(_, btn, down) @@ -236,6 +280,7 @@ function ham.settingsFrame:InitializeOptions() local witheringPotionButton = nil local witheringDreamsPotionButton = nil local cavedwellerDelightButton = nil + local heartseekingButton = nil if isRetail then local itemsTitle = self.content:CreateFontString("ARTWORK", nil, "GameFontNormalHuge") itemsTitle:SetPoint("TOPLEFT", lastStaticElement, 0, -PADDING_CATERGORY) @@ -247,10 +292,7 @@ function ham.settingsFrame:InitializeOptions() ---@diagnostic disable-next-line: undefined-field witheringPotionButton.Text:SetText("Potion of Withering Vitality") witheringPotionButton:HookScript("OnClick", function(_, btn, down) - HAMDB.witheringPotion = witheringPotionButton:GetChecked() - ham.updateHeals() - ham.updateMacro() - self:updatePrio() + ham.settingsFrame:updateConfig("witheringPotion", witheringPotionButton:GetChecked()) end) witheringPotionButton:HookScript("OnEnter", function(_, btn, down) ---@diagnostic disable-next-line: param-type-mismatch @@ -264,16 +306,12 @@ function ham.settingsFrame:InitializeOptions() witheringPotionButton:SetChecked(HAMDB.witheringPotion) ---Withering Dreams Potion--- - witheringDreamsPotionButton = CreateFrame("CheckButton", nil, self.content, - "InterfaceOptionsCheckButtonTemplate") + witheringDreamsPotionButton = CreateFrame("CheckButton", nil, self.content, "InterfaceOptionsCheckButtonTemplate") witheringDreamsPotionButton:SetPoint("TOPLEFT", itemsTitle, 220, -PADDING) ---@diagnostic disable-next-line: undefined-field witheringDreamsPotionButton.Text:SetText("Potion of Withering Dreams") witheringDreamsPotionButton:HookScript("OnClick", function(_, btn, down) - HAMDB.witheringDreamsPotion = witheringDreamsPotionButton:GetChecked() - ham.updateHeals() - ham.updateMacro() - self:updatePrio() + ham.settingsFrame:updateConfig("witheringDreamsPotion", witheringDreamsPotionButton:GetChecked()) end) witheringDreamsPotionButton:HookScript("OnEnter", function(_, btn, down) ---@diagnostic disable-next-line: param-type-mismatch @@ -286,17 +324,13 @@ function ham.settingsFrame:InitializeOptions() end) witheringDreamsPotionButton:SetChecked(HAMDB.witheringDreamsPotion) - ---Cavedwellers Deligth--- - cavedwellerDelightButton = CreateFrame("CheckButton", nil, self.content, - "InterfaceOptionsCheckButtonTemplate") + ---Cavedwellers Delight--- + cavedwellerDelightButton = CreateFrame("CheckButton", nil, self.content, "InterfaceOptionsCheckButtonTemplate") cavedwellerDelightButton:SetPoint("TOPLEFT", itemsTitle, 440, -PADDING) ---@diagnostic disable-next-line: undefined-field cavedwellerDelightButton.Text:SetText("Cavedweller's Delight") cavedwellerDelightButton:HookScript("OnClick", function(_, btn, down) - HAMDB.cavedwellerDelight = cavedwellerDelightButton:GetChecked() - ham.updateHeals() - ham.updateMacro() - self:updatePrio() + ham.settingsFrame:updateConfig("cavedwellerDelight", cavedwellerDelightButton:GetChecked()) end) cavedwellerDelightButton:HookScript("OnEnter", function(_, btn, down) ---@diagnostic disable-next-line: param-type-mismatch @@ -309,8 +343,28 @@ function ham.settingsFrame:InitializeOptions() end) cavedwellerDelightButton:SetChecked(HAMDB.cavedwellerDelight) + ---Heartseeking Health Injector--- + heartseekingButton = CreateFrame("CheckButton", nil, self.content, "InterfaceOptionsCheckButtonTemplate") + heartseekingButton:SetPoint("TOPLEFT", itemsTitle, 0, -60) + ---@diagnostic disable-next-line: undefined-field + heartseekingButton.Text:SetText("Heartseeking Health Injector (tinker)") + heartseekingButton:HookScript("OnClick", function(_, btn, down) + ham.settingsFrame:updateConfig("heartseekingInjector", heartseekingButton:GetChecked()) + end) + heartseekingButton:HookScript("OnEnter", function(_, btn, down) + ---@diagnostic disable-next-line: param-type-mismatch + if ham.tinkerSlot then + GameTooltip:SetOwner(heartseekingButton, "ANCHOR_TOPRIGHT") + GameTooltip:SetInventoryItem("player", ham.tinkerSlot) + GameTooltip:Show() + end + end) + heartseekingButton:HookScript("OnLeave", function(_, btn, down) + GameTooltip:Hide() + end) + heartseekingButton:SetChecked(HAMDB.heartseekingInjector) - lastStaticElement = witheringPotionButton ---MAYBE witheringDreamsPotionButton + lastStaticElement = heartseekingButton end @@ -410,10 +464,19 @@ SLASH_HAM3 = "/ap" SLASH_HAM4 = "/autopotion" SlashCmdList.HAM = function(msg, editBox) - if InterfaceOptions_AddCategory then - InterfaceOptionsFrame_OpenToCategory(addonName) - else - local settingsCategoryID = _G[addonName].categoryID - Settings.OpenToCategory(settingsCategoryID) - end + -- Check if the message contains "debug" + if msg and msg:trim():lower() == "debug" then + ham.debug = not ham.debug + ham.checkTinker() + print("|cffb48ef9AutoPotion:|r Debug mode is now " .. (ham.debug and "enabled" or "disabled")) + return + end + + -- Open settings if no "debug" keyword was passed + if InterfaceOptions_AddCategory then + InterfaceOptionsFrame_OpenToCategory(addonName) + else + local settingsCategoryID = _G[addonName].categoryID + Settings.OpenToCategory(settingsCategoryID) + end end diff --git a/code.lua b/code.lua index 8754907..d1a1f88 100755 --- a/code.lua +++ b/code.lua @@ -5,9 +5,30 @@ local isClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) local isWrath = (WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC) local isCata = (WOW_PROJECT_ID == WOW_PROJECT_CATACLYSM_CLASSIC) +-- Configuration options +-- Use in-memory options as HAMDB updates are not persisted instantly. +-- We'll also use lazy initialization to prevent early access issues. +setmetatable(ham, { + __index = function(t, k) + if k == "options" then + t.options = { + cdReset = HAMDB.cdReset or false, + stopCast = HAMDB.stopCast or false, + raidStone = HAMDB.raidStone or false, + witheringPotion = HAMDB.witheringPotion or false, + witheringDreamsPotion = HAMDB.witheringDreamsPotion or false, + cavedwellerDelight = HAMDB.cavedwellerDelight or true, + heartseekingInjector = HAMDB.heartseekingInjector or false, + } + return t.options + end + end +}) + +ham.debug = false +ham.tinkerSlot = nil ham.myPlayer = ham.Player.new() -local debug = false local spellsMacroString = '' local itemsMacroString = '' local macroStr = '' @@ -26,13 +47,25 @@ local megaMacro = { loaded = false, -- is the addon loaded? } +-- Slots to check for tinkers +-- As of 2024-10-27, only Head, Wrist and Guns can have tinkers. +local tinkerSlots = { + 1, -- Head + 9, -- Wrist + 18, -- Gun + 14, -- Trinket 2 (for debugging) +} + local function log(message) - if debug then + if ham.debug then print("|cffb48ef9AutoPotion:|r " .. message) end end local function addPlayerHealingItemIfAvailable() + if isRetail and ham.options.heartseekingInjector and ham.tinkerSlot then + table.insert(ham.itemIdList, "slot:" .. ham.tinkerSlot) + end for i, value in ipairs(ham.myPlayer.getHealingItems()) do if value.getCount() > 0 then table.insert(ham.itemIdList, value.getId()) @@ -56,7 +89,7 @@ local function addHealthstoneIfAvailable() end if ham.demonicHealthstone.getCount() > 0 then table.insert(ham.itemIdList, ham.demonicHealthstone.getId()) - if HAMDB.cdReset then + if ham.options.cdReset then if shortestCD == nil then shortestCD = 60 end @@ -86,21 +119,27 @@ function ham.updateHeals() ham.itemIdList = {} ham.spellIDs = ham.myPlayer.getHealingSpells() + -- Priority 1: Add player items, including tinkers addPlayerHealingItemIfAvailable() - -- lower the priority of healthstones in insatanced content if selected - if HAMDB.raidStone and IsInInstance() then - addPotIfAvailable() - if HAMDB.cavedwellerDelight then - addPotIfAvailable(true) - end - addHealthstoneIfAvailable() - else + -- Priority 2: Add Healthstone if NOT set to lower priority + if not ham.options.raidStone then addHealthstoneIfAvailable() + end + + -- Priority 3: Add Health Pots if available, and Heartseeking is NOT available or enabled + if not ham.options.heartseekingInjector or not ham.tinkerSlot then addPotIfAvailable() - if HAMDB.cavedwellerDelight then - addPotIfAvailable(true) - end + end + + -- Priority 4: Add Cavedweller's Delight if enabled + if ham.options.cavedwellerDelight then + addPotIfAvailable(true) + end + + -- Priority 5: Add Healthstone if set to lower priority + if ham.options.raidStone then + addHealthstoneIfAvailable() end end @@ -116,7 +155,7 @@ local function createMacroIfMissing() end local function setShortestSpellCD(newSpell) - if HAMDB.cdReset then + if ham.options.cdReset then local cd cd = GetSpellBaseCooldown(newSpell) / 1000 if shortestCD == nil then @@ -129,7 +168,7 @@ local function setShortestSpellCD(newSpell) end local function setResetType() - if HAMDB.cdReset == true and shortestCD ~= nil then + if ham.options.cdReset == true and shortestCD ~= nil then resetType = "combat/" .. shortestCD else resetType = "combat" @@ -166,10 +205,18 @@ end local function buildItemMacroString() if next(ham.itemIdList) ~= nil then for i, name in ipairs(ham.itemIdList) do + local entry + -- Check if the entry starts with "slot:" and extract the slot number + if type(name) == "string" and name:match("^slot:") then + entry = name:sub(6) -- Extract everything after "slot:" + else + entry = "item:" .. tostring(name) -- Default to item ID formatting + end + -- Add the entry to the macro string if i == 1 then - itemsMacroString = "item:" .. name; + itemsMacroString = entry else - itemsMacroString = itemsMacroString .. ", " .. "item:" .. name; + itemsMacroString = itemsMacroString .. ", " .. entry end end end @@ -219,10 +266,35 @@ local function checkMegaMacroAddon() end end +-- check if player has the engineering tinker: Heartseeking Health Injector +function ham.checkTinker() + if not isRetail then return end + ham.tinkerSlot = nil -- always reset + for _, slot in ipairs(tinkerSlots) do + local itemID = GetInventoryItemID("player", slot) + if itemID then + local spellName, _ = C_Item.GetItemSpell(itemID) + if spellName then + -- note: i'm not an engineer, so i use a trinket with a use effect for debugging. + -- this is why the "Phylactery" reference exists if debugging is enabled --- phuze. + if ham.debug then + if spellName:find("Phylactery") or spellName:find("Heartseeking") then + ham.tinkerSlot = slot + end + else + if spellName:find("Heartseeking") then + ham.tinkerSlot = slot + end + end + end + end + end +end + function ham.updateMacro() if next(ham.itemIdList) == nil and next(ham.spellIDs) == nil then macroStr = "#showtooltip" - if HAMDB.stopCast then + if ham.options.stopCast then macroStr = macroStr .. "\n /stopcasting \n" end else @@ -231,7 +303,7 @@ function ham.updateMacro() buildSpellMacroString() setResetType() macroStr = "#showtooltip \n" - if HAMDB.stopCast then + if ham.options.stopCast then macroStr = macroStr .. "/stopcasting \n" end macroStr = macroStr .. "/castsequence reset=" .. resetType .. " " @@ -291,6 +363,7 @@ local function MakeMacro() -- safe to update macro combatRetry = 0 + ham.checkTinker() ham.updateHeals() ham.updateMacro() ham.settingsFrame:updatePrio() @@ -313,6 +386,7 @@ local updateFrame = CreateFrame("Frame") updateFrame:RegisterEvent("ADDON_LOADED") updateFrame:RegisterEvent("BAG_UPDATE") updateFrame:RegisterEvent("PLAYER_ENTERING_WORLD") +updateFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") if isClassic == false then updateFrame:RegisterEvent("TRAIT_CONFIG_UPDATED") end @@ -346,5 +420,9 @@ updateFrame:SetScript("OnEvent", function(self, event, arg1, ...) elseif isClassic == false and event == "TRAIT_CONFIG_UPDATED" then log("event: TRAIT_CONFIG_UPDATED") MakeMacro() + -- when player changes equipment + elseif event == "PLAYER_EQUIPMENT_CHANGED" then + log("event: PLAYER_EQUIPMENT_CHANGED") + MakeMacro() end end)