From 5c61d302c96cac4dfb03a5ae85edc0a5ee8a822e Mon Sep 17 00:00:00 2001 From: Hekili Date: Sun, 22 Sep 2024 06:31:41 -1000 Subject: [PATCH] Potions and general fixes. --- Classes.lua | 224 ++++++++++++++++++---------- Core.lua | 16 +- Options.lua | 28 +++- TheWarWithin/HunterBeastMastery.lua | 4 +- TheWarWithin/HunterMarksmanship.lua | 3 +- TheWarWithin/MageFire.lua | 4 + TheWarWithin/MageFrost.lua | 10 +- TheWarWithin/PaladinProtection.lua | 3 +- TheWarWithin/PaladinRetribution.lua | 5 +- TheWarWithin/ShamanElemental.lua | 1 + UI.lua | 31 +++- 11 files changed, 236 insertions(+), 93 deletions(-) diff --git a/Classes.lua b/Classes.lua index 0a287b5df..97d1126a5 100644 --- a/Classes.lua +++ b/Classes.lua @@ -594,20 +594,22 @@ local HekiliSpecMixin = { end end, + + RegisterPotion = function( self, potion, data ) self.potions[ potion ] = data data.key = potion - if data.copy then - if type( data.copy ) == "table" then - for _, key in ipairs( data.copy ) do + if data.items then + if type( data.items ) == "table" then + for _, key in ipairs( data.items ) do self.potions[ key ] = data CommitKey( key ) end else - self.potions[ data.copy ] = data - CommitKey( data.copy ) + self.potions[ data.items ] = data + CommitKey( data.items ) end end @@ -2318,11 +2320,22 @@ all:RegisterAuras( { }, } ) - do -- Dragonflight Potions -- There are multiple items for each potion, and there are also Toxic potions that people may not want to use. - local df_potions = { + local exp_potions = { + { + name = "tempered_potion", + items = { 212971, 212970, 212969, 212265, 212264, 212263 } + }, + { + name = "potion_of_unwavering_focus", + items = { 212965, 212964, 212963, 212259, 212258, 212257 } + }, + { + name = "frontline_potion", + items = { 212968, 212967, 212966, 212262, 212261, 212260 } + }, { name = "elemental_potion_of_ultimate_power", items = { 191914, 191913, 191912, 191383, 191382, 191381 } @@ -2331,35 +2344,104 @@ do name = "elemental_potion_of_power", items = { 191907, 191906, 191905, 191389, 191388, 191387 } }, + } - all:RegisterAuras( { - elemental_potion_of_ultimate_power = { - id = 371028, - duration = 30, - max_stack = 1 - }, - elemental_potion_of_power = { - id = 371024, - duration = 30, - max_stack = 1 - }, - potion = { - alias = { "elemental_potion_of_ultimate_power", "elemental_potion_of_power" }, - aliasMode = "first", - aliasType = "buff", - duration = 30 - } - } ) + local first_potion, first_potion_key + local potion_items = {} - local function getValidPotion() - for _, potion in ipairs( df_potions ) do + all:RegisterHook( "reset_precast", function () + wipe( potion_items ) + for _, potion in ipairs( exp_potions ) do for _, item in ipairs( potion.items ) do - if GetItemCount( item, false ) > 0 then return item, potion.name end + if GetItemCount( item, false ) > 0 then + potion_items[ potion.name ] = item + break + end end end - end + end ) + + all:RegisterAura( "potion", { + alias = {}, + aliasMode = "first", + aliasType = "buff", + duration = 30 + } ) + + local GetItemInfo = C_Item.GetItemInfo + + for _, potion in ipairs( exp_potions ) do + local potionItem = Item:CreateFromItemID( potion.items[ #potion.items ] ) + + potionItem:ContinueOnItemLoad( function() + all:RegisterAbility( potion.name, { + name = potionItem:GetItemName(), + listName = potionItem:GetItemLink(), + cast = 0, + cooldown = 300, + gcd = "off", + + startsCombat = false, + toggle = "potions", + + item = function () + return potion_items[ potion.name ] or potion.items[ #potion.items ] + end, + items = potion.items, + bagItem = true, + texture = potionItem:GetItemIcon(), + + handler = function () + applyBuff( potion.name ) + end, + } ) + + class.potions[ potion.name ] = { + name = potionItem:GetItemName(), + link = potionItem:GetItemLink(), + item = potion.items[ #potion.items ] + } + + class.potionList[ potion.name ] = "|T" .. potionItem:GetItemIcon() .. ":0|t |cff00ccff[" .. potionItem:GetItemName() .. "]|r" + + for i, item in ipairs( potion.items ) do + if not first_potion then + first_potion_key = potion.name + first_potion = item + end + + local each_potion = Item:CreateFromItemID( item ) + + if not each_potion:IsItemEmpty() then + each_potion:ContinueOnItemLoad( function() + local _, spell = GetItemSpell( item ) + if not spell then Hekili:Error( "No spell found for item %d.", item ) return false end + + if not all.auras[ potion.name ] then + all:RegisterAura( potion.name, { + id = spell, + duration = 30, + max_stack = 1, + copy = { spell } + } ) + + class.auras[ spell ] = all.auras[ potion.name ] + else + insert( all.auras[ potion.name ].copy, spell ) + all.auras[ spell ] = all.auras[ potion.name ] + class.auras[ spell ] = all.auras[ potion.name ] + end + + return true + end ) + else + Hekili:Error( "Item %d is empty.", item ) + end + end + end ) + end all:RegisterAbility( "potion", { name = "Potion", @@ -2371,41 +2453,35 @@ do startsCombat = false, toggle = "potions", - item = function () - return getValidPotion() + consumable = function() return state.args.potion or settings.potion or first_potion_key or "elemental_potion_of_power" end, + item = function() + if state.args.potion and class.abilities[ state.args.potion ] then return class.abilities[ state.args.potion ].item end + if settings.potion and class.abilities[ settings.potion ] then return class.abilities[ settings.potion ].item end + if first_potion and class.abilities[ first_potion ] then return class.abilities[ first_potion ].item end + return class.abilities.elemental_potion_of_power.item end, bagItem = true, - timeToReady = function () - local item = getValidPotion() - - if item then - local start, dur = GetItemCooldown( item ) - return max( 0, start + dur - query_time ) - end - - return 3600 - end, - handler = function () - local item, effect = getValidPotion() + local use = all.abilities.potion + use = use and use.consumable - if item and effect then - applyBuff( effect ) + if use ~= "global_cooldown" then + class.abilities[ use ].handler() + setCooldown( use, action[ use ].cooldown ) end end, usable = function () - if getValidPotion() ~= nil then return true end - return false, "no valid potions found in inventory" + return potion_items[ all.abilities.potion.item ], "no valid potions found in inventory" end, + + copy = "potion_default" } ) end - - local gotn_classes = { WARRIOR = 28880, MONK = 121093, @@ -2761,12 +2837,8 @@ all:RegisterAbilities( { }, healthstone = { - name = function () return ( GetItemInfo( 5512 ) ) or "Healthstone" end, - listName = function () - local _, link, _, _, _, _, _, _, _, tex = GetItemInfo( 5512 ) - if link and tex then return "|T" .. tex .. ":0|t " .. link end - return "|cff00ccff[Healthstone]|r" - end, + name = "Healthstone", + listName = "|T538745:0|t |cff00ccff[Healthstone]|r", cast = 0, cooldown = function () return time > 0 and 3600 or 60 end, gcd = "off", @@ -6023,7 +6095,7 @@ end ns.addHook = function( hook, func ) - class.hooks[ hook ] = func + insert( class.hooks[ hook ], func ) end @@ -6031,22 +6103,18 @@ do local inProgress = {} ns.callHook = function( hook, ... ) - if class.hooks[ hook ] and not inProgress[ hook ] then - local a1, a2, a3, a4, a5 - - inProgress[ hook ] = true - for _, hook in ipairs( class.hooks[ hook ] ) do - a1, a2, a3, a4, a5 = hook ( ... ) - end - inProgress[ hook ] = nil + if not class.hooks[ hook ] or inProgress[ hook ] then return ... end + local a1, a2, a3, a4, a5 = ... - if a1 ~= nil then - return a1, a2, a3, a4, a5 - else - return ... - end + inProgress[ hook ] = true + for _, h in ipairs( class.hooks[ hook ] ) do + a1, a2, a3, a4, a5 = h( a1, a2, a3, a4, a5 ) end + inProgress[ hook ] = nil + if ( a1 ~= nil or a2 ~= nil or a3 ~= nil or a4 ~= nil or a5 ~= nil ) then + return a1, a2, a3, a4, a5 + end return ... end end @@ -6279,6 +6347,7 @@ function Hekili:SpecializationChanged() wipe( class.auras ) wipe( class.abilities ) + wipe( class.hooks ) wipe( class.talents ) wipe( class.pvptalents ) wipe( class.powers ) @@ -6392,14 +6461,18 @@ function Hekili:SpecializationChanged() class.pvptalents[ talent ] = id end - class.hooks = spec.hooks or {} - --[[ for name, func in pairs( spec.hooks ) do - class.hooks[ name ] = func - end ]] - class.variables = spec.variables - class.potionList.default = "|cFFFFD100Default|r" + class.potionList.default = "|T967533:0|t |cFFFFD100Default|r" + end + + if specID == currentID or specID == 0 then + for event, hooks in pairs( spec.hooks ) do + for _, hook in ipairs( hooks ) do + class.hooks[ event ] = class.hooks[ event ] or {} + insert( class.hooks[ event ], hook ) + end + end end for res, model in pairs( spec.resources ) do @@ -6408,6 +6481,7 @@ function Hekili:SpecializationChanged() state[ res ] = model.state end end + if rawget( state, "runes" ) then state.rune = state.runes end for k, v in pairs( spec.auras ) do diff --git a/Core.lua b/Core.lua index a6c323d4d..16f63bb60 100644 --- a/Core.lua +++ b/Core.lua @@ -791,6 +791,16 @@ function Hekili:GetPredictionFromAPL( dispName, packName, listName, slot, action end elseif action == "main_hand" and class.abilities[ action ].key ~= action and not Hekili:IsItemScripted( action, true ) then action = class.abilities[ action ].key + ability = class.abilities[ action ] + state.this_action = action + entryReplaced = true + elseif action == "potion" then + local usePotion = entry.potion or spec.potion + if not usePotion or not class.abilities[ usePotion ] then usePotion = class.specs[ specID ].options.potion end + if not usePotion or not class.abilities[ usePotion ] then usePotion = "elemental_potion_of_power" end + + action = class.abilities[ usePotion ] and class.abilities[ usePotion ].key or "elemental_potion_of_power" + ability = class.abilities[ action ] state.this_action = action entryReplaced = true end @@ -1097,7 +1107,7 @@ function Hekili:GetPredictionFromAPL( dispName, packName, listName, slot, action Timer:Track("Post Recheck") if aScriptPass then - if action == "potion" then + --[[ if action == "potion" then local item = class.abilities.potion.item slot.scriptType = "simc" @@ -1130,9 +1140,9 @@ function Hekili:GetPredictionFromAPL( dispName, packName, listName, slot, action -- slot.indicator = ( entry.Indicator and entry.Indicator ~= "none" ) and entry.Indicator state.selection_time = state.delay - state.selected_action = rAction + state.selected_action = rAction ]] - elseif action == "wait" then + if action == "wait" then local sec = state.args.sec or 0.5 if sec <= 0 then diff --git a/Options.lua b/Options.lua index 20e83e89f..5ffe720ba 100644 --- a/Options.lua +++ b/Options.lua @@ -157,7 +157,15 @@ local oneTimeFixes = { havoc.version = 20240727 end end, - } + + resetPotionsIfBroke = function( p ) + for _, spec in pairs( p.specs ) do + if spec.potion ~= "default" and not class.potionList[ spec.potion ] then spec.potion = "default" end + end + + p.runOnce.resetPotionsIfBroke = nil + end +} function Hekili:RunOneTimeFixes() @@ -5328,6 +5336,20 @@ do width = 0.15, }, + potion = { + type = "select", + name = "Potion", + desc = "Unless otherwise specified in the priority, the selected potion will be recommended.", + order = 1.2, + width = 3, + values = class.potionList, + get = function() + local p = self.DB.profile.specs[ id ].potion or class.specs[ id ].options.potion or "default" + if not class.potionList[ p ] then p = "default" end + return p + end, + }, + blankLine1 = { type = 'description', name = '', @@ -7391,7 +7413,7 @@ do end, }, - --[[ potion = { + potion = { type = "select", name = "Potion", order = 3.2, @@ -7402,7 +7424,7 @@ do return e.action ~= "potion" end, width = 1.5, - }, ]] + }, sec = { type = "input", diff --git a/TheWarWithin/HunterBeastMastery.lua b/TheWarWithin/HunterBeastMastery.lua index 694554e08..1a19a796f 100644 --- a/TheWarWithin/HunterBeastMastery.lua +++ b/TheWarWithin/HunterBeastMastery.lua @@ -2432,8 +2432,8 @@ spec:RegisterOptions( { spec:RegisterSetting( "barbed_shot_grace_period", 1, { name = strformat( "%s Grace Period", Hekili:GetSpellLinkWithTexture( spec.abilities.barbed_shot.id ) ), - desc = strformat( "If set above zero, %s's cooldown will be reduced by this number of global cooldowns. This feature helps to ensure that you maintain %s stacks by recommending %s earlier", - Hekili:GetSpellLinkWithTexture( spec.abilities.barbed_shot.id ), Hekili:GetSpellLinkWithTexture( spec.auras.frenzy.id ), spec.abiliities.barbed_shot.name ), + desc = strformat( "If set above zero, %s's cooldown will be reduced by this number of global cooldowns. This feature helps to ensure that you maintain %s stacks by recommending %s with time remaining on %s.", + Hekili:GetSpellLinkWithTexture( spec.abilities.barbed_shot.id ), Hekili:GetSpellLinkWithTexture( spec.auras.frenzy.id ), spec.abilities.barbed_shot.name, spec.auras.frenzy.name ), icon = 2058007, iconCoords = { 0.1, 0.9, 0.1, 0.9 }, type = "range", diff --git a/TheWarWithin/HunterMarksmanship.lua b/TheWarWithin/HunterMarksmanship.lua index c474e4319..499e7d9e9 100644 --- a/TheWarWithin/HunterMarksmanship.lua +++ b/TheWarWithin/HunterMarksmanship.lua @@ -1525,7 +1525,7 @@ spec:RegisterOptions( { local beastMastery = class.specs[ 253 ] spec:RegisterSetting( "pet_healing", 0, { - name = strformat( "%s Below Health %", Hekili:GetSpellLinkWithTexture( beastMastery.abilities.mend_pet.id ) ), + name = strformat( "%s Below Health %%", Hekili:GetSpellLinkWithTexture( beastMastery.abilities.mend_pet.id ) ), desc = strformat( "If set above zero, %s may be recommended when your pet falls below this health percentage. Setting to |cFFFFD1000|r disables this feature.", Hekili:GetSpellLinkWithTexture( beastMastery.abilities.mend_pet.id ) ), icon = 132179, iconCoords = { 0.1, 0.9, 0.1, 0.9 }, @@ -1549,6 +1549,7 @@ spec:RegisterSetting( "trueshot_rapid_fire", true, { Hekili:GetSpellLinkWithTexture( spec.abilities.aimed_shot.id ), Hekili:GetSpellLinkWithTexture( spec.abilities.trueshot.id ), Hekili:GetSpellLinkWithTexture( spec.abilities.rapid_fire.id ), + spec.abilities.rapid_fire.name, spec.abilities.aimed_shot.name, Hekili:GetSpellLinkWithTexture( spec.talents.deathblow[ 2 ] ), Hekili:GetSpellLinkWithTexture( spec.talents.surging_shots[ 2 ] ) ), diff --git a/TheWarWithin/MageFire.lua b/TheWarWithin/MageFire.lua index 3fda52575..e087f5c19 100644 --- a/TheWarWithin/MageFire.lua +++ b/TheWarWithin/MageFire.lua @@ -1405,6 +1405,10 @@ spec:RegisterAbilities( { end if talent.from_the_ashes.enabled then reduceCooldown( "phoenix_flames", 1 ) end + if talent.frostfire_bolt.enabled then + applyDebuff( "target", "frostfire_bolt" ) + end + if set_bonus.tier30_4pc > 0 and debuff.charring_embers.up then if buff.calefaction.stack == 19 then removeBuff( "calefaction" ) diff --git a/TheWarWithin/MageFrost.lua b/TheWarWithin/MageFrost.lua index 6bbe9c807..bd409750f 100644 --- a/TheWarWithin/MageFrost.lua +++ b/TheWarWithin/MageFrost.lua @@ -1118,6 +1118,8 @@ spec:RegisterAbilities( { handler = function () addStack( "icicles" ) + if action.frostbolt.cast_time > 0 then removeStack( "ice_floes" ) end + if buff.frostfire_empowerment.up then applyBuff( "frost_mastery", nil, 6 ) if talent.excess_frost.enabled then applyBuff( "excess_frost" ) end @@ -1134,7 +1136,6 @@ spec:RegisterAbilities( { addStack( "deaths_chill", buff.icy_veins.remains, 1 ) end - removeStack( "ice_floes" ) if buff.cold_front_ready.up then spec.abilities.frozen_orb.handler() @@ -1164,10 +1165,11 @@ spec:RegisterAbilities( { impact = function () applyDebuff( "target", "chilled" ) - --[[ if debuff.winters_chill.stack > 0 and action.frostbolt.lastCast > action.flurry.lastCast then - removeDebuffStack( "target", "winters_chill" ) - end ]] removeDebuffStack( "target", "winters_chill" ) + + if talent.frostfire_bolt.enabled then + applyDebuff( "target", "frostfire_bolt" ) + end end, copy = { 116, 228597, "frostfire_bolt", 431044 } diff --git a/TheWarWithin/PaladinProtection.lua b/TheWarWithin/PaladinProtection.lua index d595e6432..4d689e3b5 100644 --- a/TheWarWithin/PaladinProtection.lua +++ b/TheWarWithin/PaladinProtection.lua @@ -909,7 +909,8 @@ spec:RegisterHook( "reset_precast", function () else applyBuff( "holy_bulwark_ready" ) end end - if talent.lights_guidance.enabled and IsActiveSpell( 427453 ) then + if talent.lights_guidance.enabled and ( IsActiveSpell( 427453 ) or IsActiveSpell( 429826 ) ) then + applyBuff( "hammer_of_light_free" ) applyBuff( "hammer_of_light_ready" ) end diff --git a/TheWarWithin/PaladinRetribution.lua b/TheWarWithin/PaladinRetribution.lua index f8e14f6fe..8581e79c2 100644 --- a/TheWarWithin/PaladinRetribution.lua +++ b/TheWarWithin/PaladinRetribution.lua @@ -1114,7 +1114,8 @@ spec:RegisterHook( "reset_precast", function () applyBuff( "templar_strikes" ) end - if IsActiveSpell( 427453 ) then + if IsActiveSpell( 429826 ) then + applyBuff( "hammer_of_light_free" ) applyBuff( "hammer_of_light_ready", 12 - ( query_time - action.wake_of_ashes.lastCast ) ) end @@ -1765,7 +1766,7 @@ spec:RegisterAbilities( { gcd = "spell", spend = function() - if buff.divine_purpose.up then return 0 end + if buff.divine_purpose.up or buff.hammer_of_light_free.up then return 0 end return state.spec.protection and 3 or 5 end, spendType = 'holy_power', diff --git a/TheWarWithin/ShamanElemental.lua b/TheWarWithin/ShamanElemental.lua index d0f7fa2d3..797f7e932 100644 --- a/TheWarWithin/ShamanElemental.lua +++ b/TheWarWithin/ShamanElemental.lua @@ -1697,6 +1697,7 @@ spec:RegisterAbilities( { end, usable = function () + if state.spec.restoration then return end return max( cooldown.fire_elemental.true_remains, cooldown.storm_elemental.true_remains ) > 0, "DPS elementals must be on CD first" end, diff --git a/UI.lua b/UI.lua index 1e7b50afb..66b40e7ab 100644 --- a/UI.lua +++ b/UI.lua @@ -512,8 +512,7 @@ do text = "Potions", func = function() Hekili:FireToggle( "potions" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.potions.value end, - }, - + } } local specsParsed = false @@ -564,6 +563,34 @@ do hidden = function () return Hekili.State.spec.id ~= i end, } ) + local potionMenu = { + text = "|T967533:0|t Preferred Potion", + tooltipTitle = "|T967533:0|t Preferred Potion", + tooltipText = "Select the potion you would like to use when the |cFFFFD100Potions|r toggle is enabled.", + tooltipOnButton = true, + hasArrow = true, + menuList = {}, + notCheckable = true, + hidden = function () return Hekili.State.spec.id ~= i end, + } + + for k, v in orderedPairs( class.potionList ) do + insert( potionMenu.menuList, { + text = v, + func = function () + Hekili.DB.profile.specs[ Hekili.State.spec.id ].potion = k + for _, display in pairs( Hekili.DisplayPool ) do + display:OnEvent( "HEKILI_MENU" ) + end + end, + checked = function () + return Hekili.DB.profile.specs[ Hekili.State.spec.id ].potion == k + end, + } ) + end + + insert( menuData, potionMenu ) + -- Check for Toggles. for n, setting in pairs( spec.settings ) do if setting.info and ( not setting.info.arg or setting.info.arg() ) then