11---- ----------------------------------------------------------------------------
22-- ConsumableManager
3- -- Version: 2.7 .0
3+ -- Version: 2.8 .0
44-- Purpose: Automatically identifies and buttons the best Food, Drink, and Potions in bags.
55-- Author: Achim Finkbeiner
66--
@@ -12,6 +12,22 @@ local addonName, AddonNamespace = ...
1212local KNOWN_CONSUMABLES = AddonNamespace .KNOWN_CONSUMABLES
1313local EventFrame = CreateFrame (" Frame" , addonName )
1414
15+ -- Cache frequently used API calls for performance
16+ local GetTime = GetTime
17+ local InCombatLockdown = InCombatLockdown
18+ local GetContainerNumSlots = GetContainerNumSlots
19+ local GetContainerItemLink = GetContainerItemLink
20+ local GetContainerItemInfo = GetContainerItemInfo
21+ local GetItemInfo = GetItemInfo
22+ local GetItemCooldown = GetItemCooldown
23+ local UnitLevel = UnitLevel
24+ local GetBindingKey = GetBindingKey
25+
26+ -- Configuration constants
27+ local SCAN_THROTTLE = 0.5
28+ local CACHE_MAX_SIZE = 500
29+ local INIT_DELAY = 0.1
30+
1531--[[ Global Binding Strings
1632 These allow the WoW Keybindings menu to display readable names for our actions.
1733 Format: BINDING_NAME_CLICK [ButtonName]:[MouseButton]
@@ -57,12 +73,22 @@ local BIND_ABBREV = {
5773 [" NUMPAD" ] = " N"
5874}
5975
76+ ---- ----------------------------------------------------------------------------
77+ -- Forward Declarations
78+ ---- ----------------------------------------------------------------------------
79+ local RefreshLockState
80+ local RefreshButtonPositions
81+ local UpdateConsumableDisplay
82+ local CreateConfigMenu
83+ local InitializeButtons
84+ local DisplayAddonStatus
85+
6086---- ----------------------------------------------------------------------------
6187-- Movement & Locking Helpers
6288---- ----------------------------------------------------------------------------
6389
6490--- Updates the interactivity of frames based on the current Lock status.
65- local function RefreshLockState ()
91+ RefreshLockState = function ()
6692 if not ConsumableManagerDB then return end
6793
6894 local isLocked = ConsumableManagerDB .isLocked
@@ -83,7 +109,7 @@ local function RefreshLockState()
83109end
84110
85111--- Handles the visual arrangement of buttons (Grouped or Individual).
86- local function RefreshButtonPositions ()
112+ RefreshButtonPositions = function ()
87113 if not ConsumableManagerDB or not CONSUMABLE_TYPES .Food .button then return end
88114
89115 local buttonSize , padding = 36 , 6
123149---- ----------------------------------------------------------------------------
124150
125151--- Scans bags for the highest-level consumables and updates button attributes.
126- local function UpdateConsumableDisplay ()
152+ UpdateConsumableDisplay = function ()
127153 -- Combat Guard: Secure attributes cannot be changed while in combat
128154 if not CONSUMABLE_TYPES .Food .button or InCombatLockdown () then
129155 if InCombatLockdown () then
@@ -133,23 +159,26 @@ local function UpdateConsumableDisplay()
133159 end
134160
135161 -- Throttle updates
136- if (GetTime () - lastScanTime ) < 0.5 then
162+ local currentTime = GetTime ()
163+ if (currentTime - lastScanTime ) < SCAN_THROTTLE then
137164 isUpdatePending = true
138165 return
139166 end
140167
141- lastScanTime , isUpdatePending = GetTime () , false
168+ lastScanTime , isUpdatePending = currentTime , false
142169
170+ -- Initialize best items tracking
143171 local bestItemsFound = {}
144172 for typeKey in pairs (CONSUMABLE_TYPES ) do
145- bestItemsFound [typeKey ] = { level = - 1 , id = 0 }
173+ bestItemsFound [typeKey ] = { level = - 1 , id = 0 , count = 0 }
146174 end
147175
148176 -- Track items found in bags for cache pruning
149177 local foundItems = {}
150- local playerLevel = UnitLevel (" player" ) -- Cache player level for the loop
178+ local playerLevel = UnitLevel (" player" )
179+ local cacheSize = 0
151180
152- -- Loop through all bags (0-4) and slots
181+ -- Single pass through all bags (0-4) and slots
153182 for bag = 0 , 4 do
154183 local numSlots = GetContainerNumSlots (bag )
155184 for slot = 1 , numSlots do
@@ -161,7 +190,7 @@ local function UpdateConsumableDisplay()
161190 if itemId then
162191 foundItems [itemId ] = true
163192
164- -- Check known consumables database first
193+ -- Check known consumables database first (fastest path)
165194 local assignedType = KNOWN_CONSUMABLES [itemId ]
166195
167196 -- If not in known database, check cache
@@ -171,51 +200,63 @@ local function UpdateConsumableDisplay()
171200
172201 -- If still unknown, scan tooltip
173202 if not assignedType then
174- ItemTypeCache [itemId ] = " NONE"
175203 TooltipScanner :ClearLines ()
176204 TooltipScanner :SetBagItem (bag , slot )
205+
206+ local foundMatch = false
177207 for i = 1 , TooltipScanner :NumLines () do
178208 local lineText = _G [" CMScannerTextLeft" .. i ]:GetText ()
179209 if lineText then
180210 for typeKey , typeData in pairs (CONSUMABLE_TYPES ) do
181- -- Match the pattern (e.g., 'Restores health') and check for exclusions
182- if lineText :match (typeData .pattern ) and not (typeData .exclude and lineText :match (typeData .exclude )) then
183- ItemTypeCache [itemId ] = typeKey
184- assignedType = typeKey
185- break
211+ -- Check pattern match first (most common case)
212+ if lineText :match (typeData .pattern ) then
213+ -- Only check exclusion if pattern matched
214+ if not typeData .exclude or not lineText :match (typeData .exclude ) then
215+ ItemTypeCache [itemId ] = typeKey
216+ assignedType = typeKey
217+ foundMatch = true
218+ break
219+ end
186220 end
187221 end
222+ if foundMatch then break end
188223 end
189224 end
225+
226+ -- Mark as scanned even if no match found
227+ if not foundMatch then
228+ ItemTypeCache [itemId ] = " NONE"
229+ end
190230 end
191231
192232 -- Compare this item against the "Best" found so far for its category
193233 if assignedType and assignedType ~= " NONE" then
194234 local _ , itemCount = GetContainerItemInfo (bag , slot )
195- -- Get itemMinLevel to check usage requirements
196- local _ , _ , _ , itemLvl , itemMinLevel = GetItemInfo (itemLink )
235+
236+ -- Single GetItemInfo call - get everything we need at once
237+ local itemName , _ , _ , itemLvl , itemMinLevel , _ , _ , _ , _ , itemTexture = GetItemInfo (itemLink )
197238
198239 -- Safety checks
199240 itemLvl = itemLvl or 0
200241 itemMinLevel = itemMinLevel or 0
242+ itemCount = itemCount or 1
201243
202244 -- Proceed if player level meets item requirement
203245 if playerLevel >= itemMinLevel then
204- if itemLvl > bestItemsFound [assignedType ].level then
205- -- Only get full item info for items that beat the current best
206- local itemName , _ , _ , _ , _ , _ , _ , _ , _ , itemTexture = GetItemInfo (itemLink )
207- bestItemsFound [assignedType ] = {
208- level = itemLvl ,
209- id = itemId ,
210- name = itemName ,
211- texture = itemTexture ,
212- bag = bag ,
213- slot = slot ,
214- count = itemCount or 1
215- }
216- elseif itemLvl == bestItemsFound [assignedType ].level and itemId == bestItemsFound [assignedType ].id then
217- bestItemsFound [assignedType ].count = bestItemsFound [assignedType ].count +
218- (itemCount or 1 )
246+ local currentBest = bestItemsFound [assignedType ]
247+
248+ if itemLvl > currentBest .level then
249+ -- New best item found
250+ currentBest .level = itemLvl
251+ currentBest .id = itemId
252+ currentBest .name = itemName
253+ currentBest .texture = itemTexture
254+ currentBest .bag = bag
255+ currentBest .slot = slot
256+ currentBest .count = itemCount
257+ elseif itemLvl == currentBest .level and itemId == currentBest .id then
258+ -- Same item in different stack - add to count
259+ currentBest .count = currentBest .count + itemCount
219260 end
220261 end
221262 end
@@ -228,13 +269,22 @@ local function UpdateConsumableDisplay()
228269 for itemId in pairs (ItemTypeCache ) do
229270 if not foundItems [itemId ] then
230271 ItemTypeCache [itemId ] = nil
272+ else
273+ cacheSize = cacheSize + 1
231274 end
232275 end
233276
277+ -- Emergency cache cleanup if it grows too large
278+ if cacheSize > CACHE_MAX_SIZE then
279+ ItemTypeCache = {}
280+ end
281+
234282 -- Update Secure Attributes and Visuals
235283 for typeKey , typeData in pairs (CONSUMABLE_TYPES ) do
236- local btn , itemData = typeData .button , bestItemsFound [ typeKey ]
284+ local btn = typeData .button
237285 if btn then
286+ local itemData = bestItemsFound [typeKey ]
287+
238288 if itemData .name then
239289 -- Apply the actual item usage logic
240290 btn .icon :SetTexture (itemData .texture )
281331-- Configuration Panel (GUI)
282332---- ----------------------------------------------------------------------------
283333
284- local function CreateConfigMenu ()
334+ CreateConfigMenu = function ()
285335 -- Create a panel for the Interface Options
286336 local panel = CreateFrame (" Frame" , " ConsumableManagerConfigPanel" , UIParent )
287337 panel .name = " Consumable Manager"
376426---- ----------------------------------------------------------------------------
377427
378428--- Creates the Secure Buttons and sets up their appearance/scripts.
379- local function InitializeButtons ()
429+ InitializeButtons = function ()
380430 -- Ensure SavedVariables table exists with defaults
381431 if not ConsumableManagerDB or type (ConsumableManagerDB ) ~= " table" or next (ConsumableManagerDB ) == nil then
382432 ConsumableManagerDB = {
@@ -472,10 +522,10 @@ end
472522-- Help, Status & Commands
473523---- ----------------------------------------------------------------------------
474524
475- local function DisplayAddonStatus ()
525+ DisplayAddonStatus = function ()
476526 local function HexColor (text , hex ) return " |cff" .. hex .. text .. " |r" end
477527 local title = HexColor (" Consumable Manager" , " 00ff00" )
478- local version = HexColor (" v2.7 .0" , " 888888" )
528+ local version = HexColor (" v2.8 .0" , " 888888" )
479529
480530 print (" --------------------------------------------------" )
481531 print (title .. " " .. version )
@@ -566,7 +616,7 @@ EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
566616EventFrame :SetScript (" OnEvent" , function (_ , event , arg1 )
567617 if event == " ADDON_LOADED" and arg1 == addonName then
568618 -- Slight delay to ensure SavedVariables are fully loaded
569- C_Timer .After (0.1 , function ()
619+ C_Timer .After (INIT_DELAY , function ()
570620 InitializeButtons ()
571621 UpdateConsumableDisplay ()
572622 end )
0 commit comments