Skip to content

Commit 2ae7717

Browse files
committed
Release 2.3.0: switch specs and talent loadouts from search
Specs for all specializations and the current spec's saved loadouts appear as Talents results; clicking one swaps to it. The active spec and loadout are marked in green. New Specialization and Loadouts toggles in the Talents filter menu control the rows (both default on).
1 parent 6deb5d0 commit 2ae7717

22 files changed

Lines changed: 274 additions & 202 deletions

.luacheckrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ read_globals = {
117117
"UnitPopup_ShowMenu", "BattlePetToolTip_ShowLink", "BattlePetTooltip",
118118
"GetUnitSpeed", "GetItemCooldown", "EJ_GetInstanceInfo", "UnitName",
119119
"GetItemInfoInstant", "GetItemStats", "GetSpecialization", "GetSpecializationInfo",
120+
"C_SpecializationInfo", "SetSpecialization",
120121
"GetNumSpecializations",
121122
"GetNumTitles", "GetTitleName", "IsTitleKnown", "GetCurrentTitle", "SetCurrentTitle",
122123
"UnitClass", "UnitGUID", "GetLootSpecialization", "DressUpItemLink", "DressUpTransmogSet",

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to EasyFind will be documented in this file.
44

55
---
66

7+
## [2.3.0] - 2026-08-14
8+
9+
### Added
10+
- **Switch specialization from search**: your specs appear as Talents results; click one to swap
11+
- **Load talent loadouts from search**: your current spec's saved loadouts load with one click, no talent window needed. The loadout and spec you are on are marked in green
12+
- **Talents filter options**: new Specialization and Loadouts toggles in the Talents filter menu control whether these rows appear
13+
14+
---
15+
716
## [2.2.1] - 2026-08-10
817

918
### Fixed

Core/Main.lua

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ local DB_DEFAULTS = {
241241
-- "all" | "recorded" | "unrecorded". All by default: statistics are a
242242
-- reference list, so narrowing is opt-in.
243243
statisticFilterMode = "all",
244+
talentShowSpecs = true,
245+
talentShowLoadouts = true,
244246
titleFilterMode = "earned",
245247
catalogQualityTier = 0, -- 0 = all crafting tiers, else keep only 1/2/3
246248
catalogTypeFilters = {}, -- per-type-bucket enable; absent/true = shown
@@ -548,7 +550,7 @@ local SUGGESTED_KEYBINDS = {
548550
-- The version whose features the What's New popup currently describes. Bump
549551
-- ONLY when the popup content is rewritten; patch releases that keep the same
550552
-- content must not re-announce it to users who already saw it.
551-
local WHATSNEW_CONTENT_VERSION = "2.2.0"
553+
local WHATSNEW_CONTENT_VERSION = "2.3.0"
552554

553555
local WHATSNEW_LINK_PREFIX = "easyfind:whatsnew:"
554556
local whatsNewHookInstalled = false
@@ -1041,6 +1043,13 @@ eventFrame:RegisterEvent("EQUIPMENT_SETS_CHANGED")
10411043
-- category dirty), or a mount collected then not searched before logout would
10421044
-- hydrate stale on the next reload. pcall: event name may vary by build.
10431045
pcall(eventFrame.RegisterEvent, eventFrame, "NEW_MOUNT_ADDED")
1046+
-- Loadout create/rename/delete and spec swaps re-dirty the talents provider
1047+
-- so spec/loadout rows never go stale. pcall: trait events are retail-only.
1048+
pcall(eventFrame.RegisterEvent, eventFrame, "TRAIT_CONFIG_CREATED")
1049+
pcall(eventFrame.RegisterEvent, eventFrame, "TRAIT_CONFIG_DELETED")
1050+
pcall(eventFrame.RegisterEvent, eventFrame, "TRAIT_CONFIG_UPDATED")
1051+
pcall(eventFrame.RegisterEvent, eventFrame, "TRAIT_CONFIG_LIST_UPDATED")
1052+
pcall(eventFrame.RegisterEvent, eventFrame, "ACTIVE_PLAYER_SPECIALIZATION_CHANGED")
10441053
if C_HousingCatalog then
10451054
eventFrame:RegisterEvent("HOUSING_STORAGE_UPDATED")
10461055
end
@@ -1081,6 +1090,7 @@ local function MaybeSnapshotBags()
10811090
ns.Database:PersistBagContents()
10821091
end
10831092

1093+
local talentRefreshTimer
10841094
local bagRefreshTimer
10851095
local spellRefreshTimer
10861096
local gearSetRefreshTimer
@@ -1181,6 +1191,16 @@ eventFrame:SetScript("OnEvent", function(self, event, arg1, arg2)
11811191
spellRefreshTimer = nil
11821192
MarkDynamicCategoryDirty("abilities")
11831193
end)
1194+
elseif event == "TRAIT_CONFIG_CREATED" or event == "TRAIT_CONFIG_DELETED"
1195+
or event == "TRAIT_CONFIG_UPDATED" or event == "TRAIT_CONFIG_LIST_UPDATED"
1196+
or event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
1197+
-- TRAIT_CONFIG_UPDATED fires in bursts while talents are edited or
1198+
-- committed; one debounced re-dirty covers the whole burst.
1199+
if talentRefreshTimer then talentRefreshTimer:Cancel() end
1200+
talentRefreshTimer = C_Timer.NewTimer(1.0, function()
1201+
talentRefreshTimer = nil
1202+
MarkDynamicCategoryDirty("talents")
1203+
end)
11841204
elseif event == "BAG_UPDATE_DELAYED" then
11851205
if bagRefreshTimer then bagRefreshTimer:Cancel() end
11861206
bagRefreshTimer = C_Timer.NewTimer(0.5, function()

Database/Main.lua

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4379,6 +4379,7 @@ function Database:PopulateDynamicTalents()
43794379
end
43804380
end
43814381
end
4382+
self:InjectTalentSwapRows()
43824383
return true
43834384
end
43844385

@@ -4588,6 +4589,156 @@ function Stored.WalkPlayerBags()
45884589
return itemMap, order, slotsSeen
45894590
end
45904591

4592+
-- Spec switching and saved talent loadouts as Talents-category rows: typing
4593+
-- "holy" or "spec" swaps specialization, a loadout name loads it. Both row
4594+
-- kinds carry stable IDs (specSetIndex / loadoutConfigID) so they dispatch
4595+
-- field-based like window commands, staying pinnable and bindable. Names and
4596+
-- icons come localized from the APIs.
4597+
local TALENT_SPEC_KW = { "spec", "specialization", "swap", "switch", "talents" }
4598+
local TALENT_LOADOUT_KW = { "loadout", "loadouts", "build", "talents", "swap", "switch" }
4599+
4600+
function Database:InjectTalentSwapRows()
4601+
local specPath = { _G["TALENTS"] or "Talents" }
4602+
local getNumSpecs = (C_SpecializationInfo and C_SpecializationInfo.GetNumSpecializations)
4603+
or GetNumSpecializations
4604+
local getSpec = (C_SpecializationInfo and C_SpecializationInfo.GetSpecialization)
4605+
or GetSpecialization
4606+
local getSpecInfo = (C_SpecializationInfo and C_SpecializationInfo.GetSpecializationInfo)
4607+
or GetSpecializationInfo
4608+
if not (getNumSpecs and getSpecInfo) then return end
4609+
4610+
local activeSpec = getSpec and getSpec()
4611+
-- Green matching the outfit rows' currently-equipped tint (0.3, 1, 0.3).
4612+
local activeLabel = "|cff4dff4d" .. L["TALENT_CURRENTLY_ACTIVE"] .. "|r"
4613+
for i = 1, getNumSpecs() or 0 do
4614+
local ok, specID, specName, _, specIcon = pcall(getSpecInfo, i)
4615+
if ok and specID and specName and specName ~= "" then
4616+
local isActive = (i == activeSpec) or nil
4617+
uiSearchData[#uiSearchData + 1] = {
4618+
name = specName,
4619+
nameLower = slower(specName),
4620+
icon = specIcon,
4621+
category = "Talent",
4622+
path = specPath,
4623+
keywords = TALENT_SPEC_KW,
4624+
keywordsLower = TALENT_SPEC_KW,
4625+
specSetIndex = i,
4626+
specIsActive = isActive,
4627+
searchCommandDesc = isActive and activeLabel
4628+
or L["TALENT_SPEC_SUBTEXT"],
4629+
}
4630+
end
4631+
end
4632+
4633+
if not (C_ClassTalents and C_ClassTalents.GetConfigIDsBySpecID
4634+
and C_Traits and C_Traits.GetConfigInfo) then
4635+
return
4636+
end
4637+
-- ONLY the active spec's loadouts. Loading one auto-commits (measured:
4638+
-- LoadConfig starts the ~5s commit cast itself), so each row is a clean
4639+
-- one-click action. Another spec's loadout would need a spec swap plus a
4640+
-- gated second commit; the spec rows above cover that -- swap first, and
4641+
-- the new spec's loadouts appear (the spec-change event re-dirties this
4642+
-- provider).
4643+
if not activeSpec then return end
4644+
local okID, specID, specName, _, specIcon = pcall(getSpecInfo, activeSpec)
4645+
if not (okID and specID) then return end
4646+
local okCfg, configIDs = pcall(C_ClassTalents.GetConfigIDsBySpecID, specID)
4647+
if not (okCfg and type(configIDs) == "table") then return end
4648+
-- The loadout the talent UI considers selected, i.e. the one the player
4649+
-- is "in". Same record LoadTalentConfig maintains after every load.
4650+
local lastSelected
4651+
if C_ClassTalents.GetLastSelectedSavedConfigID then
4652+
local okSel, sel = pcall(C_ClassTalents.GetLastSelectedSavedConfigID, specID)
4653+
if okSel then lastSelected = sel end
4654+
end
4655+
for i = 1, #configIDs do
4656+
local loadoutID = configIDs[i]
4657+
local infoOk, info = pcall(C_Traits.GetConfigInfo, loadoutID)
4658+
if infoOk and type(info) == "table" and info.name and info.name ~= "" then
4659+
-- Keywords: the shared loadout words plus the spec's name, so
4660+
-- "frost loadout" finds it.
4661+
local kw = { slower(specName or "") }
4662+
for k = 1, #TALENT_LOADOUT_KW do kw[#kw + 1] = TALENT_LOADOUT_KW[k] end
4663+
local isActive = (loadoutID == lastSelected) or nil
4664+
uiSearchData[#uiSearchData + 1] = {
4665+
name = info.name,
4666+
nameLower = slower(info.name),
4667+
icon = specIcon,
4668+
category = "Talent",
4669+
path = specPath,
4670+
keywords = kw,
4671+
keywordsLower = kw,
4672+
loadoutConfigID = loadoutID,
4673+
loadoutIsActive = isActive,
4674+
searchCommandDesc = isActive and activeLabel
4675+
or L["TALENT_LOADOUT_SUBTEXT"],
4676+
}
4677+
end
4678+
end
4679+
end
4680+
4681+
-- LoadConfig applies the BUILD but not which saved loadout the talent UI
4682+
-- considers selected; with the frame closed nothing else updates it, so the
4683+
-- frame later shows the wrong loadout ("Default Loadout") and treats the live
4684+
-- tree as a staged diff (red Apply Changes after a reload). Blizzard's talent
4685+
-- frame pairs every load with UpdateLastSelectedSavedConfigID(specID,
4686+
-- configID); the call is VERIFIED via the getter and falls back to the
4687+
-- single-argument form, so a signature drift surfaces as a wrong dropdown at
4688+
-- worst, never silently.
4689+
local function UpdateLastSelectedLoadout(configID)
4690+
local CT = C_ClassTalents
4691+
if not (CT and CT.UpdateLastSelectedSavedConfigID) then return end
4692+
local getSpec = (C_SpecializationInfo and C_SpecializationInfo.GetSpecialization)
4693+
or GetSpecialization
4694+
local getSpecInfo = (C_SpecializationInfo and C_SpecializationInfo.GetSpecializationInfo)
4695+
or GetSpecializationInfo
4696+
local specIndex = getSpec and getSpec()
4697+
local specID = specIndex and getSpecInfo and getSpecInfo(specIndex)
4698+
local function selectedNow()
4699+
if not (CT.GetLastSelectedSavedConfigID and specID) then return nil end
4700+
local ok, id = pcall(CT.GetLastSelectedSavedConfigID, specID)
4701+
if ok then return id end
4702+
return nil
4703+
end
4704+
if specID then
4705+
pcall(CT.UpdateLastSelectedSavedConfigID, specID, configID)
4706+
local sel = selectedNow()
4707+
if sel == configID or sel == nil then return end
4708+
end
4709+
pcall(CT.UpdateLastSelectedSavedConfigID, configID)
4710+
end
4711+
4712+
local function LoadTalentConfig(configID)
4713+
if not (C_ClassTalents and C_ClassTalents.LoadConfig) then return false end
4714+
local ok = (xpcall(C_ClassTalents.LoadConfig, Utils.ErrorHandler, configID, true))
4715+
if ok then UpdateLastSelectedLoadout(configID) end
4716+
return ok
4717+
end
4718+
4719+
-- Executes a spec/loadout row. Combat is a hard veto for both APIs; the
4720+
-- localized combat error keeps the refusal visible instead of silent.
4721+
-- Loadouts are always the active spec's (rows only inject for it), and
4722+
-- LoadConfig auto-commits, so both actions are single calls.
4723+
function ns.RunTalentSwap(specIndex, loadoutConfigID)
4724+
if InCombatLockdown() then
4725+
if EasyFind and EasyFind.Print then
4726+
EasyFind:Print(_G["ERR_NOT_IN_COMBAT"] or "")
4727+
end
4728+
return false
4729+
end
4730+
if specIndex then
4731+
local setSpec = (C_SpecializationInfo and C_SpecializationInfo.SetSpecialization)
4732+
or SetSpecialization
4733+
if setSpec then
4734+
return (xpcall(setSpec, Utils.ErrorHandler, specIndex))
4735+
end
4736+
elseif loadoutConfigID then
4737+
return LoadTalentConfig(loadoutConfigID)
4738+
end
4739+
return false
4740+
end
4741+
45914742
function Database:PopulateDynamicBags()
45924743
local isRealEquipLoc = Utils.IsRealEquipLoc
45934744
local getEquipLoc = Utils.GetItemEquipLoc

EasyFind.toc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
## Title: EasyFind
33
## Notes: Search and locate UI elements and map locations with ease
44
## Author: justawower
5-
## Version: 2.2.1
5+
## Version: 2.3.0
66
## IconTexture: Interface\AddOns\EasyFind\textures\SpyglassMinimap
77
## Category: UI
88
## AddonCompartmentFunc: EasyFind_OnAddonCompartmentClick

Locales/deDE.lua

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -258,24 +258,8 @@ L["WHATSNEW_CHAT_HELLO"] = "Willkommen bei |cFF00FF00EasyFind v%s
258258
L["WHATSNEW_CHAT_HERE"] = "hier"
259259
L["WHATSNEW_CHANGELOG_LINK"] = "Vollständiges Änderungsprotokoll"
260260
L["WHATSNEW_BODY"] =
261-
"|cffFFD100\226\128\162|r |cffffffffBerufe durchsuchen (im Filtermenü aktivieren)|r\n" ..
262-
"|cffFFD100\226\128\162|r |cffffffffJeden Gegenstand im Spiel suchen (im Filtermenü aktivieren)|r\n" ..
263-
" |cff999999-|r Der Katalog ist groß, daher am besten aus lassen und über den Schnellfilter '@gen' nutzen\n" ..
264-
"|cffFFD100\226\128\162|r |cffffffffDeine Bank von überall durchsuchen (im Filtermenü aktivieren)|r\n" ..
265-
" |cff999999-|r Deine Bank und die Kriegsmeutebank, im Zustand des letzten Öffnens\n" ..
266-
"|cffFFD100\226\128\162|r |cffffffffGegenstände anderer Charaktere finden (im Filtermenü aktivieren)|r\n" ..
267-
" |cff999999-|r Jeder Twink, auf dem du dich eingeloggt hast, samt Banken\n" ..
268-
"|cffFFD100\226\128\162|r |cffffffffGegenstände per Klick oder Ziehen verlinken|r\n" ..
269-
" |cff999999-|r Lass ihn auf einem Kanal, einem Flüstern oder dem Chatfeld los\n" ..
270-
" |cff999999-|r Bsp.: Suche %s und zieh ihn mit Umschalt+Ziehen ins Chatfeld\n" ..
271-
"|cffFFD100\226\128\162|r |cffffffffNoch nicht erspielte Titel sehen (im Filtermenü aktivieren)|r\n" ..
272-
" |cff999999-|r Zeigt den Erfolg dazu, mit Alt+Klick öffnen\n" ..
273-
"|cffFFD100\226\128\162|r |cffffffffErfolgs-Tooltips beim Überfahren|r\n" ..
274-
" |cff999999-|r Kriterien, Fortschritt und Belohnungen, ohne den Erfolg zu öffnen\n" ..
275-
"|cffFFD100\226\128\162|r |cffffffffSuche nach 'Gold', 'Haltbarkeit' oder 'Gegenstandsstufe'|r\n" ..
276-
" |cff999999-|r Die Antwort erscheint direkt über den Ergebnissen. Auch Schlüsselstein, Wertung, Taschenplatz und Tempo\n" ..
277-
"|cffFFD100\226\128\162|r |cffffffffNeue Filteroptionen|r\n" ..
278-
" |cff999999-|r Ausrüstungssets nach Spezialisierung, Titel, Statistiken, Spielzeuge und Haustiere"
261+
"|cffFFD100\226\128\162|r |cffffffffSpezialisierungen und Talent-Sammlungen jetzt durchsuchbar|r\n" ..
262+
" |cff999999-|r Zum Wechseln im Ergebnis anklicken"
279263

280264
-- Context menu
281265
L["CTX_ADD_ALIAS"] = "Alias hinzufügen"
@@ -367,6 +351,10 @@ L["QUICK_FILTER"] = "Schnellfilter"
367351
L["QUICK_FILTER_TT"] = "Klicke oder drücke Rücktaste bei leerer Suche, um zu löschen."
368352
L["FILTER_TOGGLE_ALL"] = "Alle umschalten"
369353
L["FILTER_HIDE_TOOLTIPS"] = "Tooltips ausblenden"
354+
L["TALENT_SPEC_SUBTEXT"] = "Spezialisierung wechseln"
355+
L["TALENT_LOADOUT_SUBTEXT"] = "Talent-Sammlung laden"
356+
L["TALENT_CURRENTLY_ACTIVE"] = "Derzeit aktiv"
357+
L["FILTER_LOADOUTS"] = "Sammlungen"
370358
L["FILTER_STAT_RECORDED"] = "Erfasst"
371359
L["FILTER_STAT_UNRECORDED"] = "Nicht erfasst"
372360
L["FILTER_EXCLUDE_JUNK"] = "Schrott ausblenden"

Locales/enUS.lua

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -279,24 +279,8 @@ L["WHATSNEW_CHAT_HERE"] = "here"
279279
-- The body is rewritten per release. Edit this single string each version.
280280
L["WHATSNEW_CHANGELOG_LINK"] = "See full changelog"
281281
L["WHATSNEW_BODY"] =
282-
"|cffFFD100\226\128\162|r |cffffffffSearch your professions (enable in filter menu)|r\n" ..
283-
"|cffFFD100\226\128\162|r |cffffffffSearch every item in the game (enable in filter menu)|r\n" ..
284-
" |cff999999-|r The catalog is large, so recommended use is keeping it off and reaching via '@gen' quick filter\n" ..
285-
"|cffFFD100\226\128\162|r |cffffffffSearch your bank from anywhere (enable in filter menu)|r\n" ..
286-
" |cff999999-|r Your bank and the warband bank, as they were when you last opened them\n" ..
287-
"|cffFFD100\226\128\162|r |cffffffffFind items on your other characters (enable in filter menu)|r\n" ..
288-
" |cff999999-|r Every alt you have logged in on, banks included\n" ..
289-
"|cffFFD100\226\128\162|r |cffffffffClick or drag any item into chat|r\n" ..
290-
" |cff999999-|r Drop it on a channel, a whisper, or the chat box to share it\n" ..
291-
" |cff999999-|r Ex: search %s, then Shift+drag it into the chat box to link it\n" ..
292-
"|cffFFD100\226\128\162|r |cffffffffSee titles you have not earned yet (enable in filter menu)|r\n" ..
293-
" |cff999999-|r Hover for the achievement that awards it, Alt+click to open\n" ..
294-
"|cffFFD100\226\128\162|r |cffffffffAchievement tooltips on hover|r\n" ..
295-
" |cff999999-|r Criteria, progress and rewards, without opening the achievement\n" ..
296-
"|cffFFD100\226\128\162|r |cffffffffSearch 'gold', 'durability' or 'item level'|r\n" ..
297-
" |cff999999-|r The answer appears inline above your results. Also keystone, rating, bag space and speed\n" ..
298-
"|cffFFD100\226\128\162|r |cffffffffNew filter options|r\n" ..
299-
" |cff999999-|r Gear sets by spec, titles, statistics, toys and pets"
282+
"|cffFFD100\226\128\162|r |cffffffffSpecs and talent loadouts now searchable|r\n" ..
283+
" |cff999999-|r Click from results to swap"
300284

301285
-- =============================================================================
302286
-- Shared/Utils.lua -- context menu labels
@@ -396,6 +380,10 @@ L["QUICK_FILTER"] = "Quick Filter"
396380
L["QUICK_FILTER_TT"] = "Click or press Backspace on an empty search to clear."
397381
L["FILTER_TOGGLE_ALL"] = "Toggle All"
398382
L["FILTER_HIDE_TOOLTIPS"] = "Hide tooltips"
383+
L["TALENT_SPEC_SUBTEXT"] = "Switch specialization"
384+
L["TALENT_LOADOUT_SUBTEXT"] = "Load talent loadout"
385+
L["TALENT_CURRENTLY_ACTIVE"] = "Currently active"
386+
L["FILTER_LOADOUTS"] = "Loadouts"
399387
L["FILTER_STAT_RECORDED"] = "Recorded"
400388
L["FILTER_STAT_UNRECORDED"] = "Not Recorded"
401389
L["FILTER_EXCLUDE_JUNK"] = "Exclude junk"

0 commit comments

Comments
 (0)