From 1a28d95b871173e7ccb24e137cd1bf848304fd25 Mon Sep 17 00:00:00 2001 From: DuvelCorp Date: Sun, 1 Mar 2026 09:10:22 +0100 Subject: [PATCH] v1.0.5 --- .gitignore | 1 - CHANGELOG.md | 55 +++++ MetaHunt.toc | 2 +- api/constants.lua | 2 +- api/core-feed-tracking.lua | 9 +- api/core-framework.lua | 94 +++++---- api/core-scan-beasttraining.lua | 34 ++++ api/core-scan-spellbookpet.lua | 31 ++- api/core-scan-stablemaster.lua | 105 ++++++++-- api/core.lua | 57 +++++- api/map-markers.lua | 134 ++++++++---- api/minimap-button.lua | 6 +- api/options-autobuy.lua | 5 +- api/options-chronometer.lua | 13 +- api/options-zbuttons.lua | 18 +- api/version-check.lua | 168 ++++++++++++++- modules/autobuy/engine.lua | 72 ++++++- modules/autobuy/module.lua | 45 ++-- modules/chronometer/data-hunter.lua | 3 +- modules/chronometer/engine.lua | 24 ++- modules/chronometer/module.lua | 2 +- modules/feedomatic/module.lua | 83 ++++---- modules/smartammo/engine.lua | 3 +- modules/smartammo/module.lua | 2 +- modules/tooltips/module.lua | 133 +++++++++++- modules/zhunter/ZHunterMod.lua | 3 +- modules/zhunter/module.lua | 55 ++++- modules/zhunter/zButtonAmmo.lua | 283 ++++++++++++++++++++++---- modules/zhunter/zButtonAspect.lua | 102 ++++++++-- modules/zhunter/zButtonCompanions.lua | 32 +++ modules/zhunter/zButtonMounts.lua | 32 +++ modules/zhunter/zButtonPet.lua | 101 +++++++-- modules/zhunter/zButtonRanged.lua | 49 ++++- modules/zhunter/zButtonToys.lua | 32 +++ modules/zhunter/zButtonTrack.lua | 188 ++++++++++++++--- modules/zhunter/zButtonTrap.lua | 18 +- 36 files changed, 1671 insertions(+), 325 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.gitignore b/.gitignore index 0af11ef..e3d5407 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ tools/ LOC*.md LOCALIZATION*.md -CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a23ccbb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +All notable changes to MetaHunt will be documented in this file. + +## [1.0.5] - 2026-03-01 + +### Fixed +- Multiple changes to the addon to reduce its memory and CPU time footprint to the strict minimum possible. +- Restored FeedOMatic pet hunger notifications so hungry/very hungry warnings are emitted reliably again when enabled. +- Fixed Chronometer `Wing Clip` tracking by adding a safe fallback timer path for the missing trigger case. +- Fixed Chronometer `Quick Shots` bar icon by mapping the event to the correct icon texture. +- Fixed module enabled/disabled persistence to be truly per-character (`MTH_CharSavedVariables.moduleStates`) instead of account-shared. +- Fixed AutoBuy persistence fallback by removing undeclared `MTH_AutoBuy_Saved` paths and using module stores/transient runtime fallback only. +- Consolidated FeedOMatic legacy persistence to a single module-backed store (`modules.feedomatic.legacy`) with globals bound to that source. +- Switched Chronometer profile persistence to per-character module storage with one-time migration from existing account profile data. +- Switched Tooltips module options persistence to per-character module storage with one-time migration from existing account settings. + +## [1.0.4] - 2026-02-27 + +### Added +- Added new zButton bar `zRanged` that tracks all ranged weapons in bags and allows to quickly swap them. +- Added structured localization architecture with dedicated locale packs under `locales/` and loader wiring through `init/localization.xml`. +- Added a dedicated `Messages` options panel with per-message toggles. +- Added Smart Ammo option `Enable Weapon-Swap Auto Ammo` to instantly equip best bullets/arrows when switching ranged weapon type. +- Added two new Hunter Book pages in the main tab bar: `Projectiles` and `Ammo Bags` + +### Changed +- Reworked core localization runtime to support explicit locale fallback rules (`enUS`, `deDE`, `esES`, `ptBR`, `ruRU`, `zhCN`; no `frFR` target). +- Updated Hunter Book NPC finder and map vendor markers to use localized NPC names by NPC ID. +- Updated Hunter Book, item search/sort, AutoBuy item labels, and drop map markers to use localized item names by item ID. + + +### Fixed +- Fixed version metadata consistency so all first-party module descriptors report the same release version as the main addon. +- Fixed SmartAmmo junk-shot detection by including `Baited Shot` and `Tranquilizing Shot` in `MTH_AMMO_JUNKSHOT_SET`. +- Fixed zButtons options side effect where opening Mounts/Companions/Toys options could implicitly enable those buttons. +- Normalized zButtons checkbox defaults (`enabled`, `tooltip`, `hideonclick`, `parent.hide`, `parent.circle`, `showammoname`) so toggle states are always explicit and never nil. +- Fixed Beast Lore ability dropdown to exclude trainer-only abilities and list beast-learned abilities only. +- Removed junk `TBD` ability value from Wind Serpent dataset entry (`Venomflayer Serpent`) so it no longer appears in Beast Lore filters. +- Fixed zhunter child-button visibility restore so collapsed bars no longer hard-hide children (`ztrack` / `ztrap` expansion regression). +- Fixed pet tame metadata fallback so existing pets are not assigned current zone/time unless a real tame attempt is pending. +- Fixed Smart Ammo options copy mismatch by clarifying junk-shot swap behavior and restoring ammo target as previous equipped ammo. +- Fixed Smart Ammo default state so junk-shot swaps are enabled when no saved value exists. +- Fixed Beast Training startup prompt behavior so low-level hunters no longer receive training-scan warnings before level threshold. +- Restored hidden `/mth err` slash command behavior for debug-frame access. +- Fixed `zammo` children refresh on bag/inventory updates so newly acquired ammo types appear without `/reload`. +- Preserved `zammo` out-of-stock behavior so previously shown ammo entries still remain visible with zero/out state. +- Fixed intermittent `ztrack`/`ztrap` right-click expand/collapse issue after fresh login by preventing duplicate child-frame recreation during startup. +- Fixed `zpet` spell resolution after trainer updates by using robust spellbook lookup and immediate refresh on `LEARNED_SPELL_IN_TAB`. + + +## [1.0.3] - 2026-02-24 + +### Added +- Initial published release. diff --git a/MetaHunt.toc b/MetaHunt.toc index 009c157..9c30691 100644 --- a/MetaHunt.toc +++ b/MetaHunt.toc @@ -1,5 +1,5 @@ ## Interface: 11200 -## Version: 1.0.4 +## Version: 1.0.5 ## Title: MetaHunt - |cff00ff00Hunter ## Author: Metasploit and his Copilot ;) ## Notes: Unified addon suite for huntards making old addons compatible with TurtleWoW, and adding an arsenal of never seen Hunter's utilities. diff --git a/api/constants.lua b/api/constants.lua index a9081e5..bebfd71 100644 --- a/api/constants.lua +++ b/api/constants.lua @@ -1,5 +1,5 @@ MTH_CONST = MTH_CONST or {} -MTH_CONST.version = MTH_CONST.version or "1.0.4" +MTH_CONST.version = MTH_CONST.version or "1.0.5" MTH_CONST.WEAPON_TYPES = { BOWS = "Bows", diff --git a/api/core-feed-tracking.lua b/api/core-feed-tracking.lua index c19923a..08e2126 100644 --- a/api/core-feed-tracking.lua +++ b/api/core-feed-tracking.lua @@ -526,6 +526,7 @@ local function MTH_FEED_CoreDropItemOnUnitHook(unit) end if target == "pet" and CursorHasItem and CursorHasItem() then + MTH_FEED_EnsureTrackerFrame() local itemId, itemName, itemLink, itemIcon = MTH_FEED_GetCursorItemSnapshot() local pet = MTH_FEED_GetCurrentPetContext() if not itemId then @@ -608,7 +609,6 @@ function MTH_FEED_DebugStatus() end function MTH_FEED_ReinstallCoreTracking() - MTH_FEED_EnsureTrackerFrame() local ok = MTH_FEED_InstallCoreHooks() return ok and true or false end @@ -624,8 +624,12 @@ local function MTH_FEED_EnsureBootstrapFrame() frame:RegisterEvent("VARIABLES_LOADED") frame:RegisterEvent("PLAYER_LOGIN") frame:RegisterEvent("PLAYER_ENTERING_WORLD") - frame:SetScript("OnEvent", function() + frame:SetScript("OnEvent", function(self) MTH_FEED_ReinstallCoreTracking() + if self and self.UnregisterAllEvents then + self:UnregisterAllEvents() + self:SetScript("OnEvent", nil) + end end) MTH_FEED_BootstrapFrame = frame return frame @@ -1542,6 +1546,5 @@ function MTH_FEED_DebugDumpCurrentPet(limit) return true end -MTH_FEED_EnsureTrackerFrame() MTH_FEED_InstallCoreHooks() MTH_FEED_EnsureBootstrapFrame() diff --git a/api/core-framework.lua b/api/core-framework.lua index ec278f4..5b0b8cd 100644 --- a/api/core-framework.lua +++ b/api/core-framework.lua @@ -1,5 +1,5 @@ MTH = MTH or { - version = "1.0.4", + version = "1.0.5", name = "MetaHunt", modules = {}, config = {}, @@ -424,9 +424,37 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled) local resolvedName = MTH_NormalizeModuleName(name) local candidates = MTH_ModuleNameCandidates(name) + if type(MTH_CharSavedVariables) ~= "table" then + MTH_CharSavedVariables = {} + end + if type(MTH_CharSavedVariables.moduleStates) ~= "table" then + MTH_CharSavedVariables.moduleStates = {} + end + if type(MTH_CharSavedVariables.modules) ~= "table" then + MTH_CharSavedVariables.modules = {} + end + if type(MTH_SavedVariables) ~= "table" then - MTH_ModuleStateDebug("read " .. tostring(name) .. " no-savedvars fallback-default=" .. tostring(defaultEnabled and true or false)) - return defaultEnabled and true or false + MTH_SavedVariables = {} + end + if type(MTH_SavedVariables.moduleStates) ~= "table" then + MTH_SavedVariables.moduleStates = {} + end + if type(MTH_SavedVariables.modules) ~= "table" then + MTH_SavedVariables.modules = {} + end + + if type(MTH_CharSavedVariables.moduleStates) == "table" + then + for i = 1, table.getn(candidates) do + local key = candidates[i] + if MTH_CharSavedVariables.moduleStates[key] ~= nil then + local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false + MTH_ModuleStateDebug("read " .. tostring(name) .. " from char.moduleStates." .. tostring(key) .. "=" .. tostring(resolved)) + MTH_CharSavedVariables.moduleStates[resolvedName] = resolved + return resolved + end + end end if type(MTH_SavedVariables.moduleStates) == "table" then @@ -435,25 +463,11 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled) if MTH_SavedVariables.moduleStates[key] ~= nil then local resolved = MTH_SavedVariables.moduleStates[key] and true or false MTH_ModuleStateDebug("read " .. tostring(name) .. " from moduleStates." .. tostring(key) .. "=" .. tostring(resolved)) - MTH_SavedVariables.moduleStates[resolvedName] = resolved - return resolved - end - end - end - - if type(MTH_CharSavedVariables) == "table" - and type(MTH_CharSavedVariables.moduleStates) == "table" - then - for i = 1, table.getn(candidates) do - local key = candidates[i] - if MTH_CharSavedVariables.moduleStates[key] ~= nil then - local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false - MTH_ModuleStateDebug("read " .. tostring(name) .. " from char.moduleStates." .. tostring(key) .. "=" .. tostring(resolved)) - if type(MTH_SavedVariables.moduleStates) ~= "table" then - MTH_SavedVariables.moduleStates = {} - end - MTH_SavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved + if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then + MTH_CharSavedVariables.modules[resolvedName] = {} + end + MTH_CharSavedVariables.modules[resolvedName].enabled = resolved return resolved end end @@ -465,9 +479,11 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled) if type(MTH_SavedVariables.modules[key]) == "table" and MTH_SavedVariables.modules[key].enabled ~= nil then local resolved = MTH_SavedVariables.modules[key].enabled and true or false MTH_ModuleStateDebug("read " .. tostring(name) .. " from modules." .. tostring(key) .. ".enabled=" .. tostring(resolved)) - if type(MTH_SavedVariables.modules[resolvedName]) ~= "table" then - MTH_SavedVariables.modules[resolvedName] = MTH_SavedVariables.modules[key] + MTH_CharSavedVariables.moduleStates[resolvedName] = resolved + if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then + MTH_CharSavedVariables.modules[resolvedName] = {} end + MTH_CharSavedVariables.modules[resolvedName].enabled = resolved return resolved end end @@ -478,16 +494,11 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled) if type(MTH_SavedVariables[key]) == "table" and MTH_SavedVariables[key].enabled ~= nil then local resolved = MTH_SavedVariables[key].enabled and true or false MTH_ModuleStateDebug("read " .. tostring(name) .. " from legacy-top-level." .. tostring(key) .. ".enabled=" .. tostring(resolved)) - if type(MTH_SavedVariables.modules) ~= "table" then - MTH_SavedVariables.modules = {} + MTH_CharSavedVariables.moduleStates[resolvedName] = resolved + if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then + MTH_CharSavedVariables.modules[resolvedName] = {} end - if type(MTH_SavedVariables.modules[resolvedName]) ~= "table" then - MTH_SavedVariables.modules[resolvedName] = MTH_SavedVariables[key] - end - if type(MTH_SavedVariables.moduleStates) ~= "table" then - MTH_SavedVariables.moduleStates = {} - end - MTH_SavedVariables.moduleStates[resolvedName] = resolved + MTH_CharSavedVariables.modules[resolvedName].enabled = resolved return resolved end end @@ -506,27 +517,22 @@ local function MTH_SetPersistedModuleEnabled(name, enabled) if type(MTH_SavedVariables) ~= "table" then MTH_SavedVariables = {} end - if type(MTH_SavedVariables.moduleStates) ~= "table" then - MTH_SavedVariables.moduleStates = {} - end - if type(MTH_SavedVariables.modules) ~= "table" then - MTH_SavedVariables.modules = {} - end - if type(MTH_SavedVariables.modules[name]) ~= "table" then - MTH_SavedVariables.modules[name] = {} - end if type(MTH_CharSavedVariables) ~= "table" then MTH_CharSavedVariables = {} end if type(MTH_CharSavedVariables.moduleStates) ~= "table" then MTH_CharSavedVariables.moduleStates = {} end + if type(MTH_CharSavedVariables.modules) ~= "table" then + MTH_CharSavedVariables.modules = {} + end + if type(MTH_CharSavedVariables.modules[name]) ~= "table" then + MTH_CharSavedVariables.modules[name] = {} + end enabled = enabled and true or false - MTH_SavedVariables.moduleStates[name] = enabled - MTH_SavedVariables.modules[name].enabled = enabled - MTH_SavedVariables[name] = MTH_SavedVariables.modules[name] MTH_CharSavedVariables.moduleStates[name] = enabled + MTH_CharSavedVariables.modules[name].enabled = enabled MTH_ModuleStateDebug("write " .. tostring(name) .. " enabled=" .. tostring(enabled)) end diff --git a/api/core-scan-beasttraining.lua b/api/core-scan-beasttraining.lua index f29b5a7..63eb591 100644 --- a/api/core-scan-beasttraining.lua +++ b/api/core-scan-beasttraining.lua @@ -20,6 +20,7 @@ end local MTH_PT_ApplyScan local MTH_PT_AbilityHasPositiveRanks local MTH_PT_PromoteUnrankedKnownAliases +local MTH_PT_BootstrapFrame = nil local MTH_PT_VERBOSE_SCAN_LOGS = false @@ -846,6 +847,38 @@ function MTH_PT_InitService() end) end +function MTH_PT_InitBootstrap() + if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("trainscan-bootstrap") then + return + end + if MTH_PetTrainingFrame or MTH_PT_BootstrapFrame then + return + end + + local frame = CreateFrame("Frame") + if not frame then + return + end + frame:RegisterEvent("PET_TRAINING_SHOW") + frame:RegisterEvent("CRAFT_SHOW") + frame:SetScript("OnEvent", function(self, evt) + evt = evt or event + if evt ~= "PET_TRAINING_SHOW" and evt ~= "CRAFT_SHOW" then + return + end + MTH_PT_InitService() + if type(MTH_PT_OnEvent) == "function" then + MTH_PT_OnEvent(MTH_PetTrainingFrame, evt) + end + if self and self.UnregisterAllEvents then + self:UnregisterAllEvents() + self:SetScript("OnEvent", nil) + end + MTH_PT_BootstrapFrame = nil + end) + MTH_PT_BootstrapFrame = frame +end + function MTH_PT_ShutdownService(_reason) if not MTH_PetTrainingFrame then return @@ -929,3 +962,4 @@ end MTH_TR_InitService = MTH_PT_InitService MTH_TR_ShutdownService = MTH_PT_ShutdownService +MTH_TR_InitBootstrap = MTH_PT_InitBootstrap diff --git a/api/core-scan-spellbookpet.lua b/api/core-scan-spellbookpet.lua index 73b39b1..2cbc67b 100644 --- a/api/core-scan-spellbookpet.lua +++ b/api/core-scan-spellbookpet.lua @@ -7,6 +7,7 @@ local MTH_PS_LastScanAt = 0 local MTH_PS_VERBOSE_LOGS = false local MTH_PS_ForceScanUntil = 0 local MTH_PS_ForceScanBudget = 0 +local MTH_PS_EventThrottle = {} local function MTH_PS_ClearForceWindow() MTH_PS_ForceScanUntil = 0 @@ -26,9 +27,25 @@ end local function MTH_PS_IsFollowupEventTrigger(trigger) local t = tostring(trigger or "") return t == "event:UNIT_PET" + or t == "UNIT_PET" or t == "event:PET_BAR_UPDATE" + or t == "PET_BAR_UPDATE" or t == "event:SPELLS_CHANGED" + or t == "SPELLS_CHANGED" or t == "event:LEARNED_SPELL_IN_TAB" + or t == "LEARNED_SPELL_IN_TAB" +end + +local function MTH_PS_ShouldHandleEvent(evt, minIntervalSeconds) + local now = tonumber(GetTime and GetTime() or time()) or 0 + local key = tostring(evt or "") + local minInterval = tonumber(minIntervalSeconds) or 0 + local last = tonumber(MTH_PS_EventThrottle[key] or 0) or 0 + if minInterval > 0 and (now - last) < minInterval then + return false + end + MTH_PS_EventThrottle[key] = now + return true end local function MTH_PS_IsPriorityTrigger(trigger) @@ -237,7 +254,19 @@ local function MTH_PS_OnEvent(_, evt, eventArg1) end if evt == "PLAYER_ENTERING_WORLD" or evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" or evt == "SPELLS_CHANGED" or evt == "LEARNED_SPELL_IN_TAB" then - MTH_PSP_RequestScan("event:" .. tostring(evt), 1) + local minInterval = 1 + if evt == "PET_BAR_UPDATE" then + minInterval = 2 + elseif evt == "SPELLS_CHANGED" then + minInterval = 2.5 + elseif evt == "LEARNED_SPELL_IN_TAB" then + minInterval = 0.75 + elseif evt == "UNIT_PET" then + minInterval = 0.5 + end + if MTH_PS_ShouldHandleEvent(evt, minInterval) then + MTH_PSP_RequestScan(evt, 1) + end end end diff --git a/api/core-scan-stablemaster.lua b/api/core-scan-stablemaster.lua index 1cad6a7..770ab30 100644 --- a/api/core-scan-stablemaster.lua +++ b/api/core-scan-stablemaster.lua @@ -8,6 +8,7 @@ local MTH_PETS_TRACE_RUNAWAY = false MTH_PETS_TRACE_CONSISTENCY = false local MTH_StableFrame = nil +local MTH_ST_BootstrapFrame = nil local MTH_ST_LastAutoScanAt = 0 local MTH_ST_LastUnitPetHadPet = false local MTH_PETS_LastTameAttempt = nil @@ -15,6 +16,8 @@ local MTH_PETS_LiveState = nil local MTH_PETS_LiveStateSeq = 0 local MTH_PETS_LiveStateSubscribers = {} local MTH_PETS_LiveStateEventName = "MTH_PET_LIVE_STATE_CHANGED" +local MTH_PETS_EventThrottle = {} +local MTH_PETS_TRACE_TAME = false MTH_PETS_CoreOriginal_PetRename = MTH_PETS_CoreOriginal_PetRename or nil @@ -61,6 +64,17 @@ local function MTH_PETS_RequestPetSpellScan(reason) end end +local function MTH_PETS_ShouldHandleThrottledEvent(key, minIntervalSeconds) + local now = (type(GetTime) == "function" and GetTime()) or (type(time) == "function" and time()) or 0 + local threshold = tonumber(minIntervalSeconds) or 0 + local last = tonumber(MTH_PETS_EventThrottle[key] or 0) or 0 + if threshold > 0 and (now - last) < threshold then + return false + end + MTH_PETS_EventThrottle[key] = now + return true +end + local function MTH_PETS_CopyLiveState(state) if type(state) ~= "table" then return nil @@ -146,7 +160,10 @@ local function MTH_PETS_NotifyLiveSubscribers(state, source) end function MTH_PETS_EmitLiveState(source, force) - local emittedSource = tostring(source or "stablemaster") + local emittedSource = source or "stablemaster" + if type(emittedSource) ~= "string" then + emittedSource = tostring(emittedSource) + end local nextState = MTH_PETS_BuildLiveState(emittedSource) local changed = force and true or (not MTH_PETS_AreLiveStatesEqual(MTH_PETS_LiveState, nextState)) if not changed then @@ -2784,35 +2801,45 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) local pets = MTH_PETS_GetRootStore() MTH_PETS_MaybePromptStableVisit(pets) MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:PLAYER_ENTERING_WORLD", true) + MTH_PETS_EmitLiveState("PLAYER_ENTERING_WORLD", true) return end if evt == "SPELLCAST_CHANNEL_START" then - MTH_PETS_RecordTameAttempt("event:" .. tostring(evt), eventArg1) + MTH_PETS_RecordTameAttempt(evt, eventArg1) return end if evt == "SPELLCAST_STOP" or evt == "SPELLCAST_CHANNEL_STOP" or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED" then - MTH_PETS_LogTame("Spell terminal evt=" .. tostring(evt) .. " spell='" .. tostring(eventArg1 or "") .. "' pending=" .. tostring(type(MTH_PETS_LastTameAttempt) == "table")) + if MTH_PETS_TRACE_TAME then + MTH_PETS_LogTame("Spell terminal evt=" .. tostring(evt) .. " spell='" .. tostring(eventArg1 or "") .. "' pending=" .. tostring(type(MTH_PETS_LastTameAttempt) == "table")) + end local terminalIsTame = MTH_PETS_IsTameBeastSpellName(eventArg1) if not terminalIsTame and type(MTH_PETS_LastTameAttempt) == "table" then terminalIsTame = true - MTH_PETS_LogTame("Treating terminal event as tame because pending attempt exists") + if MTH_PETS_TRACE_TAME then + MTH_PETS_LogTame("Treating terminal event as tame because pending attempt exists") + end end if not terminalIsTame then local fallbackName, fallbackSource = MTH_PETS_GetCurrentPlayerCastOrChannelName() if fallbackName and MTH_PETS_IsTameBeastSpellName(fallbackName) then terminalIsTame = true - MTH_PETS_LogTame("Resolved terminal spell via " .. tostring(fallbackSource) .. " => '" .. tostring(fallbackName) .. "'") + if MTH_PETS_TRACE_TAME then + MTH_PETS_LogTame("Resolved terminal spell via " .. tostring(fallbackSource) .. " => '" .. tostring(fallbackName) .. "'") + end end end if terminalIsTame then if evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED" then - MTH_PETS_LogTame("Tame ended with failure/interruption; clearing pending attempt") + if MTH_PETS_TRACE_TAME then + MTH_PETS_LogTame("Tame ended with failure/interruption; clearing pending attempt") + end MTH_PETS_LastTameAttempt = nil elseif evt == "SPELLCAST_CHANNEL_STOP" or evt == "SPELLCAST_STOP" then - MTH_PETS_LogTame("Tame channel ended; awaiting UNIT_PET to confirm acquire") + if MTH_PETS_TRACE_TAME then + MTH_PETS_LogTame("Tame channel ended; awaiting UNIT_PET to confirm acquire") + end end end return @@ -2822,14 +2849,17 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) if eventArg1 ~= "player" then return end - MTH_PETS_HandleUnitPetTransition("event:UNIT_PET") - MTH_PETS_EmitLiveState("event:UNIT_PET") + MTH_PETS_HandleUnitPetTransition("UNIT_PET") + MTH_PETS_EmitLiveState("UNIT_PET") return end if evt == "PET_BAR_UPDATE" then + if not MTH_PETS_ShouldHandleThrottledEvent("PET_BAR_UPDATE", 0.5) then + return + end MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:PET_BAR_UPDATE") + MTH_PETS_EmitLiveState("PET_BAR_UPDATE") return end @@ -2837,14 +2867,20 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) if eventArg1 and eventArg1 ~= "pet" then return end + if not MTH_PETS_ShouldHandleThrottledEvent("UNIT_HAPPINESS", 0.75) then + return + end MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:UNIT_HAPPINESS") + MTH_PETS_EmitLiveState("UNIT_HAPPINESS") return end if evt == "CHAT_MSG_COMBAT_XP_GAIN" or evt == "PLAYER_XP_UPDATE" then + if not MTH_PETS_ShouldHandleThrottledEvent("XP_UPDATE", 1.5) then + return + end MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:" .. tostring(evt)) + MTH_PETS_EmitLiveState(evt) return end @@ -2852,28 +2888,28 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) local matchedRunaway = MTH_PETS_IsRunawaySystemMessage(eventArg1) MTH_PETS_TraceRunawayEvent(evt, eventArg1, matchedRunaway) if matchedRunaway then - MTH_PETS_RecordPetRunaway("event:" .. tostring(evt), eventArg1) + MTH_PETS_RecordPetRunaway(evt, eventArg1) MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:" .. tostring(evt)) + MTH_PETS_EmitLiveState(evt) end return end if evt == "PET_STABLE_SHOW" then - MTH_ST_RunScanThrottled("event:" .. tostring(evt), 1) + MTH_ST_RunScanThrottled(evt, 1) return end if evt == "PET_STABLE_UPDATE" then - MTH_ST_Scan("event:PET_STABLE_UPDATE") + MTH_ST_Scan(evt) MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:PET_STABLE_UPDATE") + MTH_PETS_EmitLiveState(evt) return end if evt == "PET_STABLE_CLOSED" then MTH_PETS_RefreshCurrentPet() - MTH_PETS_EmitLiveState("event:PET_STABLE_CLOSED") + MTH_PETS_EmitLiveState(evt) return end end @@ -2912,6 +2948,37 @@ function MTH_ST_InitService() end end +function MTH_ST_InitBootstrap() + if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("stablescan-bootstrap") then + return + end + if MTH_StableFrame or MTH_ST_BootstrapFrame then + return + end + + local frame = CreateFrame("Frame") + if not frame then + return + end + frame:RegisterEvent("PET_STABLE_SHOW") + frame:SetScript("OnEvent", function(self, evt) + evt = evt or event + if evt ~= "PET_STABLE_SHOW" then + return + end + MTH_ST_InitService() + if type(MTH_ST_Scan) == "function" then + MTH_ST_Scan("bootstrap:PET_STABLE_SHOW") + end + if self and self.UnregisterAllEvents then + self:UnregisterAllEvents() + self:SetScript("OnEvent", nil) + end + MTH_ST_BootstrapFrame = nil + end) + MTH_ST_BootstrapFrame = frame +end + function MTH_ST_ShutdownService(_reason) if not MTH_StableFrame then return diff --git a/api/core.lua b/api/core.lua index ffadf0a..57debf7 100644 --- a/api/core.lua +++ b/api/core.lua @@ -129,6 +129,49 @@ local function MTH_CommandOptions() end end +local function MTH_FormatAgeSeconds(seconds) + local value = tonumber(seconds) or 0 + if value < 0 then value = 0 end + if value < 60 then + return tostring(math.floor(value)) .. "s" + end + if value < 3600 then + return tostring(math.floor(value / 60)) .. "m" + end + if value < 86400 then + return tostring(math.floor(value / 3600)) .. "h" + end + return tostring(math.floor(value / 86400)) .. "d" +end + +local function MTH_CommandPeers() + if not (MTH and MTH.VersionCheck and type(MTH.VersionCheck.GetTrackedPeers) == "function") then + MTH:Print("Peer tracker is not available yet") + return + end + + local peers = MTH.VersionCheck:GetTrackedPeers() + local count = table.getn(peers) + if count <= 0 then + MTH:Print("No MetaHunt broadcasters seen yet on LFT") + return + end + + MTH:Print("MetaHunt broadcasters seen on LFT: " .. tostring(count)) + local now = (type(time) == "function" and time()) or (type(GetTime) == "function" and math.floor(GetTime())) or 0 + local maxRows = math.min(count, 20) + for i = 1, maxRows do + local peer = peers[i] + if type(peer) == "table" then + local age = MTH_FormatAgeSeconds((now or 0) - (tonumber(peer.lastSeenAt) or 0)) + MTH:Print("- " .. tostring(peer.name or "?") .. " v" .. tostring(peer.versionText or "?") .. " (seen " .. tostring(age) .. " ago)") + end + end + if count > maxRows then + MTH:Print("... and " .. tostring(count - maxRows) .. " more") + end +end + local function MTH_CommandPetSpellScanFallback() local getSpellName = (type(getglobal) == "function" and getglobal("GetSpellName")) or (_G and _G["GetSpellName"]) local getSpellTexture = (type(getglobal) == "function" and getglobal("GetSpellTexture")) or (_G and _G["GetSpellTexture"]) @@ -245,6 +288,8 @@ function SlashCmdList.MTH(msg, editbox) MTH_CommandOptions() elseif lowerMsg == "book" or lowerMsg == "hunterbook" then MTH_CommandBook() + elseif lowerMsg == "peers" or lowerMsg == "who" then + MTH_CommandPeers() elseif lowerMsg == "err" then if MTH_DebugFrame and type(MTH_DebugFrame.Toggle) == "function" then MTH_DebugFrame:Toggle() @@ -259,13 +304,13 @@ end -- Initialize on load MTH:InitSavedVariables() -if type(MTH_ST_InitService) == "function" then - MTH_ST_InitService() +if type(MTH_ST_InitBootstrap) == "function" then + MTH_ST_InitBootstrap() end -if type(MTH_TR_InitService) == "function" then - MTH_TR_InitService() -elseif type(MTH_PT_InitService) == "function" then - MTH_PT_InitService() +if type(MTH_TR_InitBootstrap) == "function" then + MTH_TR_InitBootstrap() +elseif type(MTH_PT_InitBootstrap) == "function" then + MTH_PT_InitBootstrap() end if type(MTH_PS_InitService) == "function" then MTH_PS_InitService() diff --git a/api/map-markers.lua b/api/map-markers.lua index c486b09..dd3b615 100644 --- a/api/map-markers.lua +++ b/api/map-markers.lua @@ -2,7 +2,7 @@ MTH_Map = { enabled = true, showWorld = true, showMinimap = true, - activeSource = "beasts", + activeSource = "focus", providers = {}, nodesByZone = {}, worldPins = {}, @@ -21,6 +21,10 @@ MTH_Map = { lastWorldReason = "n/a", lastMiniReason = "n/a", nodeRevision = 0, + minimapForceRefreshInterval = 1.0, + verboseMiniReasons = false, + minimapTickActive = 0.20, + minimapTickIdle = 0.75, } local MTH_MAP_MINIMAP_ZOOM = { @@ -468,7 +472,18 @@ local function MTH_Map_ClearPendingZoneOpen() MTH_Map.pendingZoneOpenNextAttemptAt = 0 end +function MTH_Map:EnsureInitialized() + if self.controller then + return true + end + self:Init() + return self.controller ~= nil +end + function MTH_Map:SetSource(key) + if not self:EnsureInitialized() then + return false + end if not self.providers[key] then return false end @@ -672,6 +687,9 @@ function MTH_Map:SetEnabled(enabled) if not self.enabled then self:HideAllPins() else + if not self:EnsureInitialized() then + return + end self:UpdateWorldMap() self:UpdateMinimap() end @@ -682,6 +700,9 @@ function MTH_Map:SetWorldEnabled(enabled) if not self.showWorld then self:HideWorldPins() else + if not self:EnsureInitialized() then + return + end self:UpdateWorldMap() end end @@ -691,6 +712,9 @@ function MTH_Map:SetMinimapEnabled(enabled) if not self.showMinimap then self:HideMinimapPins() else + if not self:EnsureInitialized() then + return + end self:UpdateMinimap() end end @@ -952,22 +976,21 @@ function MTH_Map:UpdateMinimap() end local mapId = self:GetCurrentMapID() - if not mapId or not self.nodesByZone[mapId] then + local nodes = mapId and self.nodesByZone[mapId] or nil + if not mapId or not nodes then self.lastMiniReason = "no-mapid-or-nodes" self._lastMiniState = nil self:HideMinimapPins() return end - - local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) - local mapSize = minimapSizes and minimapSizes[mapId] - if not mapSize then - self.lastMiniReason = "missing-minimap-size" + if table.getn(nodes) <= 0 then + self.lastMiniReason = "no-nodes-in-zone" self._lastMiniState = nil self:HideMinimapPins() return end + local now = GetTime() or 0 local xPlayer, yPlayer = GetPlayerMapPosition("player") if not xPlayer or not yPlayer or (xPlayer == 0 and yPlayer == 0) then self.lastMiniReason = "player-position-unavailable" @@ -980,10 +1003,33 @@ function MTH_Map:UpdateMinimap() yPlayer = yPlayer * 100 local zoom = Minimap:GetZoom() + local miniState = self._lastMiniState + if miniState + and miniState.mapId == mapId + and miniState.zoom == zoom + and miniState.nodeRevision == (self.nodeRevision or 0) + then + local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0)) + local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0)) + if dx < 0.05 and dy < 0.05 and now < (miniState.nextForceAt or 0) then + self.lastMiniReason = self.verboseMiniReasons and "throttled-small-move" or "throttle" + return + end + end + + local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) + local mapSize = minimapSizes and minimapSizes[mapId] + if not mapSize then + self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size" + self._lastMiniState = nil + self:HideMinimapPins() + return + end + local indoor = MTH_Map_MinimapIndoorState() local zoomSet = MTH_MAP_MINIMAP_ZOOM[indoor] if not zoomSet then - self.lastMiniReason = "missing-zoomset" + self.lastMiniReason = self.verboseMiniReasons and "missing-zoomset" or "missing-zoomset" self._lastMiniState = nil self:HideMinimapPins() return @@ -991,7 +1037,7 @@ function MTH_Map:UpdateMinimap() local mapZoom = zoomSet[zoom] if not mapZoom then - self.lastMiniReason = "missing-mapzoom" + self.lastMiniReason = self.verboseMiniReasons and "missing-mapzoom" or "missing-zoom" self._lastMiniState = nil self:HideMinimapPins() return @@ -1000,33 +1046,16 @@ function MTH_Map:UpdateMinimap() local mapWidth = mapSize[1] local mapHeight = mapSize[2] if not mapWidth or not mapHeight or mapWidth == 0 or mapHeight == 0 then - self.lastMiniReason = "invalid-map-size" + self.lastMiniReason = self.verboseMiniReasons and "invalid-map-size" or "invalid-size" self._lastMiniState = nil self:HideMinimapPins() return end - local miniState = self._lastMiniState - if miniState - and miniState.mapId == mapId - and miniState.zoom == zoom - and miniState.indoor == indoor - and miniState.nodeRevision == (self.nodeRevision or 0) - then - local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0)) - local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0)) - if dx < 0.05 and dy < 0.05 then - self.lastMiniReason = "throttled-small-move" - return - end - end - local xScale = mapZoom / mapWidth local yScale = mapZoom / mapHeight local xDraw = Minimap:GetWidth() / xScale / 100 local yDraw = Minimap:GetHeight() / yScale / 100 - - local nodes = self.nodesByZone[mapId] local shown = 0 for i = 1, table.getn(nodes) do @@ -1057,16 +1086,21 @@ function MTH_Map:UpdateMinimap() if self.minimapPins[i] then self.minimapPins[i]:Hide() end end - self._lastMiniState = { - mapId = mapId, - zoom = zoom, - indoor = indoor, - xPlayer = xPlayer, - yPlayer = yPlayer, - nodeRevision = self.nodeRevision or 0, - } + local lastState = self._lastMiniState or {} + lastState.mapId = mapId + lastState.zoom = zoom + lastState.indoor = indoor + lastState.xPlayer = xPlayer + lastState.yPlayer = yPlayer + lastState.nodeRevision = self.nodeRevision or 0 + lastState.nextForceAt = now + (tonumber(self.minimapForceRefreshInterval) or 1.0) + self._lastMiniState = lastState - self.lastMiniReason = "ok(map=" .. tostring(mapId) .. ",shown=" .. tostring(shown) .. ")" + if self.verboseMiniReasons then + self.lastMiniReason = "ok(map=" .. tostring(mapId) .. ",shown=" .. tostring(shown) .. ")" + else + self.lastMiniReason = "ok" + end end function MTH_Map:RegisterDefaultProviders() @@ -1202,9 +1236,12 @@ function MTH_Map:RegisterDefaultProviders() end function MTH_Map:Init() + if self.controller then + return + end self:BuildZoneLookup() self:RegisterDefaultProviders() - self:RebuildNodes() + self.nodesByZone = self.nodesByZone or {} if not self.controller then self.controller = CreateFrame("Frame", "MTHMapController", UIParent) @@ -1230,7 +1267,6 @@ function MTH_Map:Init() if not frame then return end local now = GetTime() or 0 if (frame._mthTick or 0) > now then return end - frame._mthTick = now + 0.12 if MTH_Map.pendingZoneOpenId and MTH_Map.pendingZoneOpenAttempts and MTH_Map.pendingZoneOpenAttempts > 0 then if (MTH_Map.pendingZoneOpenNextAttemptAt or 0) <= now then @@ -1259,12 +1295,28 @@ function MTH_Map:Init() end if MTH_Map.enabled and MTH_Map.showMinimap then - MTH_Map:UpdateMinimap() + local mapId = MTH_Map:GetCurrentMapID() + if not mapId and (frame._mthMapRecoverAt or 0) <= now then + frame._mthMapRecoverAt = now + 2.0 + if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then + SetMapToCurrentZone() + mapId = MTH_Map:GetCurrentMapID() + end + end + local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[mapId] or nil + local hasNodes = nodes and table.getn(nodes) > 0 + frame._mthTick = now + (hasNodes and (tonumber(MTH_Map.minimapTickActive) or 0.20) or (tonumber(MTH_Map.minimapTickIdle) or 0.75)) + if hasNodes then + MTH_Map:UpdateMinimap() + elseif (frame._mthNoNodeTick or 0) <= now then + frame._mthNoNodeTick = now + 1.0 + MTH_Map:UpdateMinimap() + end + else + frame._mthTick = now + (tonumber(MTH_Map.minimapTickIdle) or 0.75) end end) end MTH_Map_Log("Map system initialized") end - -MTH_Map:Init() diff --git a/api/minimap-button.lua b/api/minimap-button.lua index bb5bc2c..f58092b 100644 --- a/api/minimap-button.lua +++ b/api/minimap-button.lua @@ -169,10 +169,14 @@ end local loader = CreateFrame("Frame", "MTH_MinimapButtonLoader") loader:RegisterEvent("ADDON_LOADED") -loader:SetScript("OnEvent", function(_, eventName, addonName) +loader:SetScript("OnEvent", function(self, eventName, addonName) eventName = eventName or event addonName = addonName or arg1 if eventName == "ADDON_LOADED" and addonName == "MetaHunt" then MTH_MinimapButton:Initialize() + if self and self.UnregisterAllEvents then + self:UnregisterAllEvents() + self:SetScript("OnEvent", nil) + end end end) diff --git a/api/options-autobuy.lua b/api/options-autobuy.lua index 69849bd..6f8e148 100644 --- a/api/options-autobuy.lua +++ b/api/options-autobuy.lua @@ -44,6 +44,8 @@ local MTH_AB_STATE = { }, } +local MTH_AB_TRANSIENT_STORE = {} + local function MTH_AB_SetCheckedNoClick(check, checked) if not check then return end MTH_AB_STATE.syncing = true @@ -60,8 +62,7 @@ local function MTH_AB_GetStore() if engine and engine.EnsureDefaults then return engine:EnsureDefaults() end - MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {} - return MTH_AutoBuy_Saved + return MTH_AB_TRANSIENT_STORE end local function MTH_AB_NormalizeRule(rule) diff --git a/api/options-chronometer.lua b/api/options-chronometer.lua index 08f0c75..33e81c2 100644 --- a/api/options-chronometer.lua +++ b/api/options-chronometer.lua @@ -98,11 +98,20 @@ local function MTH_CHRON_CopyTable(source) end local function MTH_CHRON_GetProfile() - if not (MTH and MTH.GetModuleSavedVariables) then + if not (MTH and MTH.GetModuleCharSavedVariables) then return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS) end - local store = MTH:GetModuleSavedVariables("chronometer") + local store = MTH:GetModuleCharSavedVariables("chronometer") + if type(store) ~= "table" then + return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS) + end + if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then + local accountStore = MTH:GetModuleSavedVariables("chronometer") + if type(accountStore) == "table" and type(accountStore.profile) == "table" then + store.profile = MTH_CHRON_CopyTable(accountStore.profile) + end + end if type(store.profile) ~= "table" then store.profile = {} end diff --git a/api/options-zbuttons.lua b/api/options-zbuttons.lua index e4166e7..cf663cc 100644 --- a/api/options-zbuttons.lua +++ b/api/options-zbuttons.lua @@ -1114,10 +1114,26 @@ function MTH_SetupGeneralOptions() local tooltipsSection = ensureSection("MetaHuntGeneralTooltipsBox", "Tooltips", topY, 356, "right") if tooltipsSection then - local tooltipsStore = MTH and MTH.GetModuleSavedVariables and MTH:GetModuleSavedVariables("tooltips") + local tooltipsStore = MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("tooltips") if type(tooltipsStore) ~= "table" then tooltipsStore = {} end + if MTH and MTH.GetModuleSavedVariables and next(tooltipsStore) == nil then + local accountStore = MTH:GetModuleSavedVariables("tooltips") + if type(accountStore) == "table" and next(accountStore) ~= nil then + for key, value in pairs(accountStore) do + if type(value) == "table" then + local copied = {} + for subKey, subValue in pairs(value) do + copied[subKey] = subValue + end + tooltipsStore[key] = copied + else + tooltipsStore[key] = value + end + end + end + end if tooltipsStore.beastTooltips == nil then tooltipsStore.beastTooltips = true end diff --git a/api/version-check.lua b/api/version-check.lua index 85c19d6..b44ef0c 100644 --- a/api/version-check.lua +++ b/api/version-check.lua @@ -8,6 +8,8 @@ VC.nextPublishAt = nil VC.joinAt = nil VC.notified = false VC._invalidVersionWarned = false +VC.maxPersistedPeers = 200 +VC.seenPeers = VC.seenPeers or {} local function VC_GetTimeNow() if type(time) == "function" then @@ -63,6 +65,158 @@ local function VC_IsOurChannel(name) return type(name) == "string" and string.lower(name) == string.lower(VC.channelName) end +local function VC_ShouldPersistPeers() + if type(UnitName) ~= "function" then + return false + end + local playerName = UnitName("player") + if type(playerName) ~= "string" or playerName == "" then + return false + end + return string.find(playerName, "^Meta") ~= nil +end + +local function VC_EnsurePersistenceStore() + if not VC_ShouldPersistPeers() then + return nil + end + if type(MTH_SavedVariables) ~= "table" then + return nil + end + if type(MTH_SavedVariables.versionCheck) ~= "table" then + MTH_SavedVariables.versionCheck = {} + end + local store = MTH_SavedVariables.versionCheck + if type(store.seenPeers) ~= "table" then + store.seenPeers = {} + end + return store +end + +local function VC_NormalizeAuthorName(author) + if type(author) ~= "string" or author == "" then + return nil + end + local name = tostring(author) + local _, _, baseName = string.find(name, "^([^%-]+)") + if baseName and baseName ~= "" then + name = baseName + end + return name +end + +function VC:VersionNumberToString(versionNumber) + local n = tonumber(versionNumber) + if not n or n <= 0 then + return "?" + end + local major = math.floor(n / 1000000) + local remMajor = n - (major * 1000000) + local minor = math.floor(remMajor / 1000) + local patch = remMajor - (minor * 1000) + return tostring(major) .. "." .. tostring(minor) .. "." .. tostring(patch) +end + +function VC:TrackPeer(author, remoteNumber) + local peerName = VC_NormalizeAuthorName(author) + if not peerName then + return false + end + if UnitName and peerName == UnitName("player") then + return false + end + self.seenPeers = self.seenPeers or {} + self.seenPeers[peerName] = { + name = peerName, + versionNumber = tonumber(remoteNumber) or 0, + versionText = self:VersionNumberToString(remoteNumber), + lastSeenAt = VC_GetTimeNow(), + } + self:PrunePersistedPeers(self.maxPersistedPeers) + return true +end + +function VC:PrunePersistedPeers(limit) + local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200 + if maxCount <= 0 then + maxCount = 200 + end + if type(self.seenPeers) ~= "table" then + return + end + + local names = {} + for name, peer in pairs(self.seenPeers) do + if type(name) == "string" and type(peer) == "table" then + table.insert(names, name) + end + end + if table.getn(names) <= maxCount then + return + end + + table.sort(names, function(a, b) + local pa = self.seenPeers[a] or {} + local pb = self.seenPeers[b] or {} + local ta = tonumber(pa.lastSeenAt) or 0 + local tb = tonumber(pb.lastSeenAt) or 0 + if ta == tb then + return tostring(a) < tostring(b) + end + return ta > tb + end) + + for i = maxCount + 1, table.getn(names) do + self.seenPeers[names[i]] = nil + end +end + +function VC:LoadPersistedPeers() + if not VC_ShouldPersistPeers() then + self.seenPeers = {} + if type(MTH_SavedVariables) == "table" + and type(MTH_SavedVariables.versionCheck) == "table" + and type(MTH_SavedVariables.versionCheck.seenPeers) == "table" then + MTH_SavedVariables.versionCheck.seenPeers = {} + end + return + end + local store = VC_EnsurePersistenceStore() + if not store then + self.seenPeers = self.seenPeers or {} + return + end + if type(store.seenPeers) ~= "table" then + store.seenPeers = {} + end + self.seenPeers = store.seenPeers + self:PrunePersistedPeers(self.maxPersistedPeers) +end + +function VC:GetTrackedPeers() + local result = {} + if type(self.seenPeers) ~= "table" then + return result + end + for _, peer in pairs(self.seenPeers) do + if type(peer) == "table" and peer.name then + table.insert(result, { + name = tostring(peer.name), + versionNumber = tonumber(peer.versionNumber) or 0, + versionText = tostring(peer.versionText or self:VersionNumberToString(peer.versionNumber)), + lastSeenAt = tonumber(peer.lastSeenAt) or 0, + }) + end + end + table.sort(result, function(a, b) + if (a.lastSeenAt or 0) == (b.lastSeenAt or 0) then + return tostring(a.name or "") < tostring(b.name or "") + end + return (a.lastSeenAt or 0) > (b.lastSeenAt or 0) + end) + return result +end + function VC:ResetPublishDelay() self.nextPublishAt = VC_GetTimeNow() + math.random(10, 20) end @@ -96,13 +250,11 @@ function VC:TryPublish() end function VC:HandleRemoteMessage(message, author, channelLabel) - if self.notified then - return - end if not message or message == "" then return end - if author and UnitName and author == UnitName("player") then + local normalizedAuthor = VC_NormalizeAuthorName(author) + if normalizedAuthor and UnitName and normalizedAuthor == UnitName("player") then return end local channelName = string.gsub(tostring(channelLabel or ""), "^%d+%.%s*", "") @@ -121,6 +273,12 @@ function VC:HandleRemoteMessage(message, author, channelLabel) if not remoteNumber or remoteNumber <= 0 then return end + + self:TrackPeer(normalizedAuthor or author, remoteNumber) + + if self.notified then + return + end local localVersionNumber = self:GetLocalVersionNumber() if not localVersionNumber or localVersionNumber <= 0 then return @@ -136,7 +294,6 @@ function VC:HandleRemoteMessage(message, author, channelLabel) end if VC.frame then - VC.frame:UnregisterEvent("CHAT_MSG_CHANNEL") VC.frame:SetScript("OnUpdate", nil) end end @@ -149,6 +306,7 @@ if not VC.frame then frame:RegisterEvent("CHAT_MSG_CHANNEL") frame:SetScript("OnEvent", function() if event == "PLAYER_ENTERING_WORLD" then + VC:LoadPersistedPeers() VC.joinAt = GetTime() + 6 VC:ResetPublishDelay() elseif event == "CHAT_MSG_CHANNEL" then diff --git a/modules/autobuy/engine.lua b/modules/autobuy/engine.lua index 079783b..eda313b 100644 --- a/modules/autobuy/engine.lua +++ b/modules/autobuy/engine.lua @@ -9,6 +9,10 @@ local Engine = MTH_AutoBuyEngine local AB_AMMO_BY_NAME = nil local AB_FOOD_DIET_BY_ITEM_ID = nil +local function AB_Trace(message) + return +end + local function AB_ParseItemIdFromLink(link) if type(link) ~= "string" then return nil @@ -589,15 +593,40 @@ local function AB_EnsureRuleList(bucket) return bucket end +local function AB_DeepCopy(value) + if type(value) ~= "table" then + return value + end + local copy = {} + for key, entry in pairs(value) do + copy[key] = AB_DeepCopy(entry) + end + return copy +end + function Engine:GetConfigStore() - if MTH and MTH.GetModuleSavedVariables then - local store = MTH:GetModuleSavedVariables("autobuy") - if type(store) == "table" then - return store + if MTH and MTH.GetModuleCharSavedVariables then + local charStore = MTH:GetModuleCharSavedVariables("autobuy") + if type(charStore) == "table" then + if MTH.GetModuleSavedVariables then + local accountStore = MTH:GetModuleSavedVariables("autobuy") + if type(accountStore) == "table" and next(charStore) == nil and next(accountStore) ~= nil then + for key, value in pairs(accountStore) do + charStore[key] = AB_DeepCopy(value) + end + end + end + return charStore end end - MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {} - return MTH_AutoBuy_Saved + if MTH and MTH.GetModuleSavedVariables then + local accountStore = MTH:GetModuleSavedVariables("autobuy") + if type(accountStore) == "table" then + return accountStore + end + end + self._transientStore = self._transientStore or {} + return self._transientStore end function Engine:EnsureDefaults() @@ -1151,6 +1180,7 @@ end function Engine:ExecutePlan(plan) if type(plan) ~= "table" or type(plan.actions) ~= "table" then + AB_Trace("ExecutePlan skipped: invalid plan table") return 0, 0 end local executedActions = 0 @@ -1159,6 +1189,10 @@ function Engine:ExecutePlan(plan) if type(action) == "table" and action.index then local stacksToBuy = math.floor(tonumber(action.quantityStacks) or 0) if stacksToBuy > 0 then + AB_Trace("ExecutePlan action index=" .. tostring(action.index) + .. " itemId=" .. tostring(action.itemId) + .. " stacks=" .. tostring(stacksToBuy) + .. " reason=" .. tostring(plan.reason or "")) for i = 1, stacksToBuy do BuyMerchantItem(action.index) end @@ -1167,6 +1201,7 @@ function Engine:ExecutePlan(plan) end end end + AB_Trace("ExecutePlan done actions=" .. tostring(executedActions) .. " stacks=" .. tostring(requestedStacks) .. " reason=" .. tostring(plan.reason or "")) return executedActions, requestedStacks end @@ -1229,24 +1264,42 @@ local function AB_AnnouncePurchases(actions) end function Engine:OnMerchantEvent(eventName) + AB_Trace("OnMerchantEvent start event=" .. tostring(eventName) + .. " sessionExecuted=" .. tostring(self._merchantSessionExecuted and true or false) + .. " engineEnabled=" .. tostring(self:IsEnabled() and true or false)) + if eventName == "MERCHANT_SHOW" then self._merchantSessionExecuted = false + AB_Trace("MERCHANT_SHOW resets session execution flag") elseif eventName == "MERCHANT_UPDATE" and self._merchantSessionExecuted then + AB_Trace("MERCHANT_UPDATE ignored: already executed this merchant session") return end if not self:IsEnabled() then + AB_Trace("event ignored: autobuy module disabled") return end local snapshot = self:ScanMerchant() + AB_Trace("snapshot rows=" .. tostring(snapshot and snapshot.count or 0)) local foodPlan = self:BuildFoodPlan(snapshot) local ammoPlan = self:BuildAmmoPlan(snapshot) + AB_Trace("foodPlan reason=" .. tostring(foodPlan.reason) .. " actions=" .. tostring(table.getn(foodPlan.actions or {}))) + for i = 1, table.getn(foodPlan.debugLines or {}) do + AB_Trace("food#" .. tostring(i) .. " " .. tostring(foodPlan.debugLines[i])) + end + AB_Trace("ammoPlan reason=" .. tostring(ammoPlan.reason) .. " actions=" .. tostring(table.getn(ammoPlan.actions or {}))) + for i = 1, table.getn(ammoPlan.debugLines or {}) do + AB_Trace("ammo#" .. tostring(i) .. " " .. tostring(ammoPlan.debugLines[i])) + end + local totalRequestedStacks = 0 if table.getn(foodPlan.actions or {}) > 0 then local _, requestedStacks = self:ExecutePlan(foodPlan) totalRequestedStacks = totalRequestedStacks + requestedStacks + AB_Trace("food plan executed requestedStacks=" .. tostring(requestedStacks)) if requestedStacks > 0 then AB_AnnouncePurchases(foodPlan.actions) end @@ -1255,14 +1308,19 @@ function Engine:OnMerchantEvent(eventName) if table.getn(ammoPlan.actions or {}) > 0 then local _, requestedStacks = self:ExecutePlan(ammoPlan) totalRequestedStacks = totalRequestedStacks + requestedStacks + AB_Trace("ammo plan executed requestedStacks=" .. tostring(requestedStacks)) if requestedStacks > 0 then - AB_MoveAmmoStacksToSpecialBags(300) + local moved = AB_MoveAmmoStacksToSpecialBags(300) + AB_Trace("ammo restack moved=" .. tostring(moved)) AB_AnnouncePurchases(ammoPlan.actions) end end if totalRequestedStacks > 0 then self._merchantSessionExecuted = true + AB_Trace("session marked executed requestedStacks=" .. tostring(totalRequestedStacks)) + else + AB_Trace("no purchases requested in this event") end end diff --git a/modules/autobuy/module.lua b/modules/autobuy/module.lua index a7f4b44..d76ee60 100644 --- a/modules/autobuy/module.lua +++ b/modules/autobuy/module.lua @@ -6,7 +6,7 @@ local MTH_AutoBuy = { name = "autobuy", enabled = false, - version = "1.0.4", + version = "1.0.5", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", @@ -39,7 +39,6 @@ local function AB_EnsureBridgeFrame(moduleRef) return nil end - frame:RegisterEvent("VARIABLES_LOADED") frame:RegisterEvent("MERCHANT_SHOW") frame:RegisterEvent("MERCHANT_UPDATE") frame:RegisterEvent("MERCHANT_CLOSED") @@ -51,14 +50,6 @@ local function AB_EnsureBridgeFrame(moduleRef) return end - if eventName == "VARIABLES_LOADED" then - engine:Init() - local store = engine:EnsureDefaults() - AB_Trace("bridge VARIABLES_LOADED store.enabled=" .. tostring(store and store.enabled) - .. " projectiles.enabled=" .. tostring(store and store.projectiles and store.projectiles.enabled)) - return - end - if eventName == "MERCHANT_SHOW" or eventName == "MERCHANT_UPDATE" then AB_Trace("bridge " .. tostring(eventName) .. " -> engine") engine:OnMerchantEvent(eventName) @@ -75,6 +66,34 @@ local function AB_EnsureBridgeFrame(moduleRef) return frame end +local function AB_SetBridgeActive(active) + local frame = MTH_AutoBuy._bridgeFrame + if active then + if not frame then + frame = AB_EnsureBridgeFrame(MTH_AutoBuy) + end + if frame then + frame:RegisterEvent("MERCHANT_SHOW") + frame:RegisterEvent("MERCHANT_UPDATE") + frame:RegisterEvent("MERCHANT_CLOSED") + MTH_AutoBuy._bridgeActive = true + AB_Trace("bridge active=true") + else + MTH_AutoBuy._bridgeActive = false + AB_Trace("bridge active=false (frame missing)") + end + return + end + + if frame then + frame:UnregisterEvent("MERCHANT_SHOW") + frame:UnregisterEvent("MERCHANT_UPDATE") + frame:UnregisterEvent("MERCHANT_CLOSED") + end + MTH_AutoBuy._bridgeActive = false + AB_Trace("bridge active=false") +end + AB_Engine = function() return MTH_AutoBuyEngine end @@ -87,8 +106,8 @@ function MTH_AutoBuy:init() return end engine:Init() - AB_EnsureBridgeFrame(self) self.initialized = true + AB_SetBridgeActive(self.enabled and true or false) AB_Trace("init done") if self.enabled then AB_Log("module initialized", "debug") @@ -106,7 +125,7 @@ function MTH_AutoBuy:setEnabled(enabled) end local store = engine:EnsureDefaults() store.enabled = enabled and true or false - AB_EnsureBridgeFrame(self) + AB_SetBridgeActive(store.enabled and true or false) AB_Trace("setEnabled store.enabled=" .. tostring(store.enabled)) end @@ -125,7 +144,7 @@ function MTH_AutoBuy:onEvent(event) if event == "VARIABLES_LOADED" then engine:Init() - AB_EnsureBridgeFrame(self) + AB_SetBridgeActive(self.enabled and true or false) AB_Log("onEvent VARIABLES_LOADED -> engine init", "debug") local store = engine:EnsureDefaults() AB_Trace("VARIABLES_LOADED module.enabled=" .. tostring(self.enabled) diff --git a/modules/chronometer/data-hunter.lua b/modules/chronometer/data-hunter.lua index 3515a0a..188327d 100644 --- a/modules/chronometer/data-hunter.lua +++ b/modules/chronometer/data-hunter.lua @@ -45,7 +45,8 @@ function MTH_ChronometerHunterInstallData(engine) engine:AddTimer(engine.EVENT, BS["Improved Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] }) engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "YELLOW", xn = BS["Wing Clip"] }) engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" }) - engine:AddTimer(engine.EVENT, BS["Quick Shots"], 12, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Ability_Hunter_Quickshot" }) + engine:AddTimer(engine.EVENT, BS["Quick Shots"], 12, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Ability_Warrior_InnerRage" }) + engine:AddTimer(engine.EVENT, BS["Wing Clip"], 10, 1, 0, 1, { a = 1, cr = "YELLOW" }) engine:AddTimer(engine.EVENT, BS["Scorpid Poison"], 10, 1, 0, 0, { gr = 3, cr = "PURPLE", a = 1, dn = 1 }) engine:AddTimer(engine.EVENT, BS["Devilsaur Fury"], 20, 0, 1, 1, { a = 1, cr = "PURPLE", xn = BS["Devilsaur Eye"], tx = "Interface\\Icons\\Ability_Hunter_Pet_Raptor" }) engine:AddTimer(engine.EVENT, BS["Restless Strength"], 20, 0, 1, 1, { a = 1, cr = "PURPLE", xn = BS["Zandalarian Hero Medallion"], tx = "Interface\\Icons\\INV_Jewelry_Necklace_13" }) diff --git a/modules/chronometer/engine.lua b/modules/chronometer/engine.lua index 3993952..4abdee2 100644 --- a/modules/chronometer/engine.lua +++ b/modules/chronometer/engine.lua @@ -143,12 +143,32 @@ local function shallowCopy(source) return dest end +local function deepCopyValue(value) + if type(value) ~= "table" then + return value + end + local copy = {} + for key, entry in pairs(value) do + copy[key] = deepCopyValue(entry) + end + return copy +end + local function ensureProfile() - if not MTH or not MTH.GetModuleSavedVariables then + if not MTH or not MTH.GetModuleCharSavedVariables then return shallowCopy(DEFAULTS) end - local store = MTH:GetModuleSavedVariables("chronometer") + local store = MTH:GetModuleCharSavedVariables("chronometer") + if type(store) ~= "table" then + return shallowCopy(DEFAULTS) + end + if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then + local accountStore = MTH:GetModuleSavedVariables("chronometer") + if type(accountStore) == "table" and type(accountStore.profile) == "table" then + store.profile = deepCopyValue(accountStore.profile) + end + end if type(store.profile) ~= "table" then store.profile = {} end diff --git a/modules/chronometer/module.lua b/modules/chronometer/module.lua index c0d5614..c73de47 100644 --- a/modules/chronometer/module.lua +++ b/modules/chronometer/module.lua @@ -1,7 +1,7 @@ local MTH_ChronometerModule = { name = "chronometer", enabled = true, - version = "1.0.4", + version = "1.0.5", events = {}, initialized = false, } diff --git a/modules/feedomatic/module.lua b/modules/feedomatic/module.lua index ce588f8..175741f 100644 --- a/modules/feedomatic/module.lua +++ b/modules/feedomatic/module.lua @@ -6,7 +6,7 @@ local MTH_FeedOMatic = { name = "feedomatic", enabled = false, - version = "1.0.4", + version = "1.0.5", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", @@ -137,47 +137,31 @@ local function MTH_FeedOMatic_SyncSavedVariables() moduleStore.legacy = {} end - if type(FOM_Config) == "table" then - moduleStore.legacy.FOM_Config = FOM_Config - elseif type(moduleStore.legacy.FOM_Config) == "table" then - FOM_Config = moduleStore.legacy.FOM_Config + local function bindLegacyTable(globalName) + local legacyValue = moduleStore.legacy[globalName] + local globalValue = _G and _G[globalName] or nil + + if type(legacyValue) ~= "table" then + if type(globalValue) == "table" then + legacyValue = globalValue + else + legacyValue = {} + end + moduleStore.legacy[globalName] = legacyValue + end + + if _G then + _G[globalName] = legacyValue + end end - if type(FOM_FoodQuality) == "table" then - moduleStore.legacy.FOM_FoodQuality = FOM_FoodQuality - elseif type(moduleStore.legacy.FOM_FoodQuality) == "table" then - FOM_FoodQuality = moduleStore.legacy.FOM_FoodQuality - end - - if type(FOM_AddedFoods) == "table" then - moduleStore.legacy.FOM_AddedFoods = FOM_AddedFoods - elseif type(moduleStore.legacy.FOM_AddedFoods) == "table" then - FOM_AddedFoods = moduleStore.legacy.FOM_AddedFoods - end - - if type(FOM_RemovedFoods) == "table" then - moduleStore.legacy.FOM_RemovedFoods = FOM_RemovedFoods - elseif type(moduleStore.legacy.FOM_RemovedFoods) == "table" then - FOM_RemovedFoods = moduleStore.legacy.FOM_RemovedFoods - end - - if type(FOM_Cooking) == "table" then - moduleStore.legacy.FOM_Cooking = FOM_Cooking - elseif type(moduleStore.legacy.FOM_Cooking) == "table" then - FOM_Cooking = moduleStore.legacy.FOM_Cooking - end - - if type(FOM_QuestFood) == "table" then - moduleStore.legacy.FOM_QuestFood = FOM_QuestFood - elseif type(moduleStore.legacy.FOM_QuestFood) == "table" then - FOM_QuestFood = moduleStore.legacy.FOM_QuestFood - end - - if type(FOM_LocaleInfo) == "table" then - moduleStore.legacy.FOM_LocaleInfo = FOM_LocaleInfo - elseif type(moduleStore.legacy.FOM_LocaleInfo) == "table" then - FOM_LocaleInfo = moduleStore.legacy.FOM_LocaleInfo - end + bindLegacyTable("FOM_Config") + bindLegacyTable("FOM_FoodQuality") + bindLegacyTable("FOM_AddedFoods") + bindLegacyTable("FOM_RemovedFoods") + bindLegacyTable("FOM_Cooking") + bindLegacyTable("FOM_QuestFood") + bindLegacyTable("FOM_LocaleInfo") end local function MTH_FeedOMatic_Log(message, severity) @@ -231,6 +215,14 @@ local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent) end local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled) + if not enabled then + if MTH_FeedOMatic_MerchantProbeFrame then + MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_SHOW") + MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_UPDATE") + end + return + end + if not MTH_FeedOMatic_MerchantProbeFrame then MTH_FeedOMatic_MerchantProbeFrame = CreateFrame("Frame", "MTH_FeedOMatic_MerchantProbeFrame") MTH_FeedOMatic_MerchantProbeFrame:SetScript("OnEvent", function() @@ -242,14 +234,9 @@ local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled) end) end - if enabled then - MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_SHOW") - MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_UPDATE") - MTH_FeedOMatic_Log("merchant probe enabled", "debug") - else - MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_SHOW") - MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_UPDATE") - end + MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_SHOW") + MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_UPDATE") + MTH_FeedOMatic_Log("merchant probe enabled", "debug") end function MTH_FeedOMatic:init() diff --git a/modules/smartammo/engine.lua b/modules/smartammo/engine.lua index 76696cc..db23be0 100644 --- a/modules/smartammo/engine.lua +++ b/modules/smartammo/engine.lua @@ -155,7 +155,8 @@ MTH_SA_List = {} MTH_SA_LastCheck = 0 -MTH_SA_Tooltip = CreateFrame("GameTooltip", "MTH_SA_Tooltip", nil, "GameTooltipTemplate") +MTH_SA_Tooltip = CreateFrame("GameTooltip", "MTH_SA_Scan", nil, "GameTooltipTemplate") +MTH_SA_TooltipTextLeft1 = getglobal("MTH_SA_ScanTextLeft1") MTH_SA_Tooltip:Hide() local _, class = UnitClass("player") diff --git a/modules/smartammo/module.lua b/modules/smartammo/module.lua index a8e1f26..6405e04 100644 --- a/modules/smartammo/module.lua +++ b/modules/smartammo/module.lua @@ -7,7 +7,7 @@ MTH_SA_MANAGED_HOOKS = true local MTH_SmartAmmo = { name = "smartammo", enabled = true, - version = "1.0.4", + version = "1.0.5", events = { "VARIABLES_LOADED", "PLAYER_ENTERING_WORLD", diff --git a/modules/tooltips/module.lua b/modules/tooltips/module.lua index 704e671..47fd8ff 100644 --- a/modules/tooltips/module.lua +++ b/modules/tooltips/module.lua @@ -6,7 +6,7 @@ local MTH_Tooltips = { name = "tooltips", enabled = true, - version = "1.0.4", + version = "1.0.5", events = { "UPDATE_MOUSEOVER_UNIT", "UNIT_NAME_UPDATE", @@ -34,14 +34,33 @@ local MTH_Tooltips = { injectFrame = nil, pollFrame = nil, pollElapsed = 0, + hintElapsed = 0, lastMouseoverKey = "", } +local function MTH_TT_CopyValue(value) + if type(value) ~= "table" then + return value + end + local copy = {} + for key, entry in pairs(value) do + copy[key] = MTH_TT_CopyValue(entry) + end + return copy +end local function MTH_TT_GetOptionsStore() - if MTH and MTH.GetModuleSavedVariables then - local store = MTH:GetModuleSavedVariables("tooltips") + if MTH and MTH.GetModuleCharSavedVariables then + local store = MTH:GetModuleCharSavedVariables("tooltips") if type(store) == "table" then + if MTH.GetModuleSavedVariables and next(store) == nil then + local accountStore = MTH:GetModuleSavedVariables("tooltips") + if type(accountStore) == "table" and next(accountStore) ~= nil then + for key, value in pairs(accountStore) do + store[key] = MTH_TT_CopyValue(value) + end + end + end if store.beastTooltips == nil then store.beastTooltips = true end @@ -1165,16 +1184,21 @@ local function MTH_TT_EnsureInjectFrame() end local frame = CreateFrame("Frame", "MTH_TooltipsInjectFrame") frame.pending = false - frame:SetScript("OnUpdate", function(self) + frame.onUpdate = function(self) self = self or this - if not self or not self.pending then + if not self then + return + end + if not self.pending then + self:SetScript("OnUpdate", nil) return end self.pending = false + self:SetScript("OnUpdate", nil) if MTH_Tooltips and MTH_Tooltips.enabled then MTH_TT_AddTooltip("mouseover") end - end) + end MTH_Tooltips.injectFrame = frame return frame end @@ -1183,6 +1207,9 @@ local function MTH_TT_RequestInject() local frame = MTH_TT_EnsureInjectFrame() if frame then frame.pending = true + if not frame:GetScript("OnUpdate") then + frame:SetScript("OnUpdate", frame.onUpdate) + end end end @@ -1197,14 +1224,18 @@ local function MTH_TT_EnsurePollFrame() return end - if GameTooltip and GameTooltip.IsShown and GameTooltip:IsShown() then - MTH_TT_AddOwnPetTooltipHint() - if not MTH_TT_HasExistingNotLearnedLine() then - MTH_TT_AddPetActionNotLearnedHint() + elapsed = elapsed or arg1 + MTH_Tooltips.hintElapsed = (MTH_Tooltips.hintElapsed or 0) + (elapsed or 0) + if MTH_Tooltips.hintElapsed >= 0.25 then + MTH_Tooltips.hintElapsed = 0 + if GameTooltip and GameTooltip.IsShown and GameTooltip:IsShown() then + MTH_TT_AddOwnPetTooltipHint() + if not MTH_TT_HasExistingNotLearnedLine() then + MTH_TT_AddPetActionNotLearnedHint() + end end end - elapsed = elapsed or arg1 MTH_Tooltips.pollElapsed = (MTH_Tooltips.pollElapsed or 0) + (elapsed or 0) if MTH_Tooltips.pollElapsed < 0.15 then return @@ -1242,6 +1273,75 @@ local function MTH_TT_EnsurePollFrame() return frame end +local function MTH_TT_SetRuntimeActive(active) + local injectFrame = MTH_Tooltips.injectFrame + if injectFrame then + if not active then + injectFrame.pending = false + injectFrame:SetScript("OnUpdate", nil) + end + end + + local pollFrame = MTH_Tooltips.pollFrame + if pollFrame then + if active then + pollFrame:SetScript("OnUpdate", pollFrame:GetScript("OnUpdate") or function(_, elapsed) + if not (MTH_Tooltips and MTH_Tooltips.enabled) then + return + end + + elapsed = elapsed or arg1 + MTH_Tooltips.hintElapsed = (MTH_Tooltips.hintElapsed or 0) + (elapsed or 0) + if MTH_Tooltips.hintElapsed >= 0.25 then + MTH_Tooltips.hintElapsed = 0 + if GameTooltip and GameTooltip.IsShown and GameTooltip:IsShown() then + MTH_TT_AddOwnPetTooltipHint() + if not MTH_TT_HasExistingNotLearnedLine() then + MTH_TT_AddPetActionNotLearnedHint() + end + end + end + + MTH_Tooltips.pollElapsed = (MTH_Tooltips.pollElapsed or 0) + (elapsed or 0) + if MTH_Tooltips.pollElapsed < 0.15 then + return + end + MTH_Tooltips.pollElapsed = 0 + + local state = MTH_Tooltips.debugState + state.pollTicks = (state.pollTicks or 0) + 1 + + if not UnitExists("mouseover") then + MTH_Tooltips.lastMouseoverKey = "" + return + end + + local name = UnitName("mouseover") + if not name or name == "" then + return + end + local lvl = UnitLevel and UnitLevel("mouseover") or 0 + local key = tostring(name) .. "#" .. tostring(lvl) + + if key ~= MTH_Tooltips.lastMouseoverKey then + MTH_Tooltips.lastMouseoverKey = key + state.pollTriggers = (state.pollTriggers or 0) + 1 + MTH_TT_RequestInject() + return + end + + if not MTH_TT_HasExistingTooltipLine() then + MTH_TT_RequestInject() + end + end) + else + MTH_Tooltips.pollElapsed = 0 + MTH_Tooltips.hintElapsed = 0 + pollFrame:SetScript("OnUpdate", nil) + end + end +end + local function MTH_TT_InstallTooltipHook() if MTH_Tooltips.hookInstalled then return @@ -1260,9 +1360,15 @@ local function MTH_TT_InstallTooltipHook() MTH_TT_RequestInject() end) GameTooltip:HookScript("OnTooltipSetItem", function() + if not (MTH_Tooltips and MTH_Tooltips.enabled) then + return + end MTH_TT_AddFoodTooltipHint() end) GameTooltip:HookScript("OnTooltipSetSpell", function() + if not (MTH_Tooltips and MTH_Tooltips.enabled) then + return + end MTH_TT_AddPetActionNotLearnedHint() end) end) @@ -1284,6 +1390,7 @@ function MTH_Tooltips:init() MTH_TT_InstallTooltipHook() MTH_TT_EnsureInjectFrame() MTH_TT_EnsurePollFrame() + MTH_TT_SetRuntimeActive(self.enabled) MTH_TT_Log("module init complete") end @@ -1307,6 +1414,9 @@ function MTH_Tooltips:setEnabled(enabled) elseif self.enabled then MTH_TT_EnsurePollFrame() MTH_TT_EnsureInjectFrame() + MTH_TT_SetRuntimeActive(true) + else + MTH_TT_SetRuntimeActive(false) end end @@ -1357,6 +1467,7 @@ end function MTH_Tooltips:cleanup() self.enabled = false + MTH_TT_SetRuntimeActive(false) end function MTH_Tooltips:GetDebugSnapshot() diff --git a/modules/zhunter/ZHunterMod.lua b/modules/zhunter/ZHunterMod.lua index 6f4396e..803dcb4 100644 --- a/modules/zhunter/ZHunterMod.lua +++ b/modules/zhunter/ZHunterMod.lua @@ -11,7 +11,8 @@ BINDING_HEADER_ZMountsHeader = "ZMounts Buttons" BINDING_HEADER_ZCompanionsHeader = "ZCompanions Buttons" BINDING_HEADER_ZToysHeader = "ZToys Buttons" -ZHunterModTooltip = CreateFrame("GameTooltip", "ZHunterModTooltip", nil, "GameTooltipTemplate") +ZHunterModTooltip = CreateFrame("GameTooltip", "ZHunterModScan", nil, "GameTooltipTemplate") +ZHunterModTooltipTextLeft1 = getglobal("ZHunterModScanTextLeft1") function MTH_ZH_IsModuleEnabled() if not (MTH and MTH.IsModuleEnabled) then diff --git a/modules/zhunter/module.lua b/modules/zhunter/module.lua index bce490d..b704cb2 100644 --- a/modules/zhunter/module.lua +++ b/modules/zhunter/module.lua @@ -8,7 +8,7 @@ MTH_ZH_MANAGED_HOOKS = true local MTH_ZHunter = { name = "zhunter", enabled = true, - version = "1.0.4", + version = "1.0.5", events = { "VARIABLES_LOADED", "PLAYER_ENTERING_WORLD", @@ -288,6 +288,57 @@ MTH_ZH_SetAuxFramesVisible = function(visible) return end +local function MTH_ZH_SetAdjustmentHandlersActive(active) + local frameSpecs = { + { frame = "zButtonAspectAdjustment", handler = "zButtonAspectAdjustment_OnEvent", getter = "zButtonAspect_GetSaved" }, + { frame = "zButtonTrackAdjustment", handler = "zButtonTrackAdjustment_OnEvent", getter = "zButtonTrack_GetSaved" }, + { frame = "zButtonTrapAdjustment", handler = "zButtonTrapAdjustment_OnEvent", getter = "zButtonTrap_GetSaved" }, + { frame = "zButtonRangedAdjustment", handler = "zButtonRangedAdjustment_OnEvent", getter = "zButtonRanged_GetSaved" }, + { frame = "zButtonAmmoAdjustment", handler = "zButtonAmmoAdjustment_OnEvent", getter = "zButtonAmmo_GetSaved" }, + { frame = "zButtonPetAdjustment", handler = "zButtonPetAdjustment_OnEvent", getter = "zButtonPet_GetSaved" }, + { frame = "zButtonMountsAdjustment", handler = "zButtonMountsAdjustment_OnEvent", getter = "zButtonMounts_GetSaved" }, + { frame = "zButtonCompanionsAdjustment", handler = "zButtonCompanionsAdjustment_OnEvent", getter = "zButtonCompanions_GetSaved" }, + { frame = "zButtonToysAdjustment", handler = "zButtonToysAdjustment_OnEvent", getter = "zButtonToys_GetSaved" }, + } + + local function isFeatureEnabled(getterName) + local getter = getglobal(getterName) + if type(getter) ~= "function" then + return true + end + local ok, saved = pcall(getter) + if not ok or type(saved) ~= "table" then + return true + end + if saved["enabled"] == false or saved["enabled"] == 0 then + return false + end + return true + end + + for _, spec in ipairs(frameSpecs) do + local frameName = spec.frame + local handlerName = spec.handler + local frame = getglobal(frameName) + if frame and frame.SetScript then + if active then + if isFeatureEnabled(spec.getter) then + local handler = getglobal(handlerName) + if type(handler) == "function" then + frame:SetScript("OnEvent", handler) + else + frame:SetScript("OnEvent", nil) + end + else + frame:SetScript("OnEvent", nil) + end + else + frame:SetScript("OnEvent", nil) + end + end + end +end + local function MTH_ZH_ClearRuntimeFeatureFlags() return end @@ -310,9 +361,11 @@ function MTH_ZHunter:setEnabled(enabled) MTH_ZH_SyncSavedVariables() if enabled then MTH_ZH_EnsureBootstrapRestoreFrame() + MTH_ZH_SetAdjustmentHandlersActive(true) MTH_ZH_ApplyEnabledRuntimeState("setEnabled") else MTH_ZH_ClearRuntimeFeatureFlags() + MTH_ZH_SetAdjustmentHandlersActive(false) MTH_ZH_SetButtonsVisible(false) MTH_ZH_SetAuxFramesVisible(false) end diff --git a/modules/zhunter/zButtonAmmo.lua b/modules/zhunter/zButtonAmmo.lua index b6bf190..5bb66b7 100644 --- a/modules/zhunter/zButtonAmmo.lua +++ b/modules/zhunter/zButtonAmmo.lua @@ -29,6 +29,69 @@ ZHunterMod_Ammo_Buttons = nil local ZHUNTER_AMMO_MAX_COUNT = math.max(table.getn(MTH_AMMO_ARROWS or {}), table.getn(MTH_AMMO_BULLETS or {})) local zButtonAmmo_Cache = {} local zButtonAmmo_LastCacheSignature = nil +local zButtonAmmo_LastRefreshAt = 0 +local zButtonAmmo_MinRefreshInterval = 0.20 +local zButtonAmmo_MinBagRefreshInterval = 0.75 +local zButtonAmmo_KnownAmmoTypes = nil +local zButtonAmmo_AmmoBags = {} +local zButtonAmmo_LastFullScanAt = 0 +local zButtonAmmo_MinUnknownBagScanInterval = 5.0 +local zButtonAmmo_EmptyScanStreak = 0 +local zButtonAmmo_GetKnownAmmoTypes + +local function zButtonAmmo_BagHasAmmoNow(bagId) + local bag = tonumber(bagId) + if not bag or bag < 0 or bag > 4 then + return false + end + local knownAmmo = zButtonAmmo_GetKnownAmmoTypes and zButtonAmmo_GetKnownAmmoTypes() or nil + for slot = 1, (GetContainerNumSlots(bag) or 0) do + local link = GetContainerItemLink(bag, slot) + if link then + local _, _, linkName = string.find(link, "%[(.+)%]") + if linkName and knownAmmo and knownAmmo[linkName] then + return true + end + local _, _, id = string.find(link, "Hitem:((%d+).-)") + if id then + local itemName, _, _, _, _, itemSubtype, _, itemSlot = GetItemInfo(id) + if itemSlot == "INVTYPE_AMMO" then + return true + end + if itemName and knownAmmo and knownAmmo[itemName] then + return true + end + if itemSubtype == "Arrow" or itemSubtype == "Bullet" then + return true + end + end + end + end + return false +end + +local function zButtonAmmo_UpdateParentCountFast() + if zButtonAmmo and not zButtonAmmo.isspell and zButtonAmmo.ammoname then + local ammoSlot = 0 + if GetInventorySlotInfo then + local slotId = GetInventorySlotInfo("AmmoSlot") + if slotId and slotId > 0 then + ammoSlot = slotId + end + end + local slotForCount = ammoSlot and ammoSlot ~= 0 and ammoSlot or 0 + if GetInventoryItemCount then + local liveCount = GetInventoryItemCount("player", slotForCount) + local cachedInfo = zButtonAmmo_Cache[zButtonAmmo.ammoname] + if liveCount == 0 and cachedInfo and (tonumber(cachedInfo.count) or 0) > 0 then + zButtonAmmo.ammocount = cachedInfo.count + elseif liveCount ~= nil then + zButtonAmmo.ammocount = liveCount + end + end + zButtonAmmo_UpdateButton(zButtonAmmo) + end +end local function zButtonAmmo_GetSaved() local currentRoot = zButtonAmmo_GetRoot() @@ -38,6 +101,21 @@ local function zButtonAmmo_GetSaved() return currentRoot["zButtonAmmo"] end +zButtonAmmo_GetKnownAmmoTypes = function() + if zButtonAmmo_KnownAmmoTypes then + return zButtonAmmo_KnownAmmoTypes + end + local knownAmmo = {} + for i = 1, table.getn(MTH_AMMO_ARROWS or {}) do + knownAmmo[(MTH_AMMO_ARROWS or {})[i]] = "Arrow" + end + for i = 1, table.getn(MTH_AMMO_BULLETS or {}) do + knownAmmo[(MTH_AMMO_BULLETS or {})[i]] = "Bullet" + end + zButtonAmmo_KnownAmmoTypes = knownAmmo + return knownAmmo +end + local zButtonAmmo_ShortArrowLabels = { [ARROWS_ROUGH] = "Rough", [ARROWS_SHARP] = "Sharp", @@ -164,13 +242,7 @@ local function zButtonAmmo_GetEquippedAmmoLink() end local function zButtonAmmo_RebuildCache() - local knownAmmo = {} - for i = 1, table.getn(MTH_AMMO_ARROWS or {}) do - knownAmmo[(MTH_AMMO_ARROWS or {})[i]] = "Arrow" - end - for i = 1, table.getn(MTH_AMMO_BULLETS or {}) do - knownAmmo[(MTH_AMMO_BULLETS or {})[i]] = "Bullet" - end + local knownAmmo = zButtonAmmo_GetKnownAmmoTypes() local equippedAmmo = nil local equippedLink = zButtonAmmo_GetEquippedAmmoLink() @@ -185,11 +257,14 @@ local function zButtonAmmo_RebuildCache() end end - zButtonAmmo_Cache = {} + local newCache = {} + local newAmmoBags = {} + local scannedLinkCount = 0 for bag = 0, 4 do for slot = 1, GetContainerNumSlots(bag) do local link = GetContainerItemLink(bag, slot) if link then + scannedLinkCount = scannedLinkCount + 1 local _, _, id = string.find(link, "Hitem:((%d+).-)") if id then local _, _, linkName = string.find(link, "%[(.+)%]") @@ -208,7 +283,8 @@ local function zButtonAmmo_RebuildCache() end if isAmmo and ammoName then - local ammoInfo = zButtonAmmo_Cache[ammoName] + newAmmoBags[bag] = true + local ammoInfo = newCache[ammoName] if not ammoInfo then ammoInfo = { ammo = ammoName, @@ -224,7 +300,7 @@ local function zButtonAmmo_RebuildCache() count = 0, equip = nil, } - zButtonAmmo_Cache[ammoName] = ammoInfo + newCache[ammoName] = ammoInfo end ammoInfo.count = (ammoInfo.count or 0) + (itemCount or 0) if equippedAmmo and equippedAmmo == ammoName then @@ -235,6 +311,26 @@ local function zButtonAmmo_RebuildCache() end end end + + local hasAmmo = next(newCache) ~= nil + local previousHadAmmo = next(zButtonAmmo_Cache) ~= nil + if hasAmmo then + zButtonAmmo_EmptyScanStreak = 0 + zButtonAmmo_Cache = newCache + zButtonAmmo_AmmoBags = newAmmoBags + else + local reliableEmptyScan = scannedLinkCount > 0 and not equippedAmmo + if reliableEmptyScan then + zButtonAmmo_EmptyScanStreak = (zButtonAmmo_EmptyScanStreak or 0) + 1 + else + zButtonAmmo_EmptyScanStreak = 0 + end + if not previousHadAmmo or zButtonAmmo_EmptyScanStreak >= 3 then + zButtonAmmo_Cache = newCache + zButtonAmmo_AmmoBags = newAmmoBags + end + end + zButtonAmmo_LastFullScanAt = GetTime and GetTime() or zButtonAmmo_LastFullScanAt end local function zButtonAmmo_AssignButtonFromAmmoInfo(button, ammoInfo) @@ -257,15 +353,41 @@ local function zButtonAmmo_AssignButtonFromAmmoInfo(button, ammoInfo) return true end -local function zButtonAmmo_GetCacheSignature() - local names = {} - for ammoName, ammoInfo in pairs(zButtonAmmo_Cache or {}) do - if ammoName and ammoInfo and (tonumber(ammoInfo.count) or 0) > 0 then - table.insert(names, tostring(ammoName)) +local function zButtonAmmo_GetCacheSignature(ammoList) + local hash = 17 + local list = ammoList or {} + local mod = math.fmod or math.mod + for i = 1, table.getn(list) do + local ammoName = list[i] + local ammoInfo = ammoName and zButtonAmmo_Cache[ammoName] or nil + local ammoCount = (ammoInfo and tonumber(ammoInfo.count)) or 0 + local value = (hash * 131) + ammoCount + (string.len(tostring(ammoName or "")) * 17) + if mod then + hash = mod(value, 2147483647) + else + hash = value - (math.floor(value / 2147483647) * 2147483647) end end - table.sort(names) - return table.concat(names, "|") + return hash +end + +local function zButtonAmmo_AssignParentFromChild(child) + if not (zButtonAmmo and child) then + return + end + zButtonAmmo.id = child.ammoid or child.id + zButtonAmmo.icon = child.icon + zButtonAmmo.isspell = nil + zButtonAmmo.ammoname = child.ammoname + zButtonAmmo.ammobrol = child.ammobrol + zButtonAmmo.ammoqual = child.ammoqual + zButtonAmmo.ammolvl = child.ammolvl + zButtonAmmo.ammotype = child.ammotype + zButtonAmmo.ammobag = child.ammobag + zButtonAmmo.ammoslot = child.ammoslot + zButtonAmmo.ammoid = child.ammoid or child.id + zButtonAmmo.ammolink = child.ammolink + zButtonAmmo.ammocount = child.ammocount end local function zButtonAmmo_GetEquippedAmmoInfo() @@ -461,7 +583,7 @@ function zButtonAmmo_SetButtons(parent, ammoList) else parent:Show() end - zButtonAmmo_LastCacheSignature = zButtonAmmo_GetCacheSignature() + zButtonAmmo_LastCacheSignature = zButtonAmmo_GetCacheSignature(ammoList) return count - 1 end @@ -685,6 +807,8 @@ function zButtonAmmo_OnEvent() zButtonAmmoAdjustment = CreateFrame("Frame", "zButtonAmmoAdjustment") zButtonAmmoAdjustment:RegisterEvent("UNIT_INVENTORY_CHANGED") zButtonAmmoAdjustment:RegisterEvent("BAG_UPDATE") + zButtonAmmoAdjustment:RegisterEvent("PLAYER_ALIVE") + zButtonAmmoAdjustment:RegisterEvent("PLAYER_UNGHOST") zButtonAmmoAdjustment:SetScript("OnEvent", zButtonAmmoAdjustment_OnEvent) zButtonAmmo_SetupSizeAndPosition() zButtonAmmo_DeferredStartupSync() @@ -750,11 +874,17 @@ function zButtonAmmo_SetupSizeAndPosition() zButtonAmmo_EnsureSpellOrder(ZHunterMod_Ammo_Buttons or zButtonAmmo_GetAmmoList()) local saved = zButtonAmmo_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonAmmoAdjustment and zButtonAmmoAdjustment.SetScript then + zButtonAmmoAdjustment:SetScript("OnEvent", nil) + end if zButtonAmmo and zButtonAmmo.Hide then zButtonAmmo:Hide() end return end + if zButtonAmmoAdjustment and zButtonAmmoAdjustment.SetScript then + zButtonAmmoAdjustment:SetScript("OnEvent", zButtonAmmoAdjustment_OnEvent) + end local ammoList = ZHunterMod_Ammo_Buttons or zButtonAmmo_GetAmmoList() local arrangeCount = zButtonAmmo.found or table.getn(ammoList) if arrangeCount < 0 then @@ -801,14 +931,71 @@ function zButtonAmmoAdjustment_OnEvent() if not zButtonAmmo or not zButtonAmmo.count then return end + if event == "UNIT_INVENTORY_CHANGED" and arg1 and arg1 ~= "player" then + return + end local saved = zButtonAmmo_GetSaved() + if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonAmmoAdjustment and zButtonAmmoAdjustment.SetScript then + zButtonAmmoAdjustment:SetScript("OnEvent", nil) + end + if zButtonAmmo and zButtonAmmo.Hide then + zButtonAmmo:Hide() + end + return + end - if event == "UNIT_INVENTORY_CHANGED" or event == "BAG_UPDATE" then + if event == "UNIT_INVENTORY_CHANGED" or event == "BAG_UPDATE" or event == "PLAYER_ALIVE" or event == "PLAYER_UNGHOST" then + local now = GetTime and GetTime() or 0 + local bagIsAmmoRelated = false + if event == "BAG_UPDATE" and arg1 ~= nil then + local bagId = tonumber(arg1) + local bagHasAmmoNow = nil + if type(zButtonAmmo_BagHasAmmoNow) == "function" then + bagHasAmmoNow = zButtonAmmo_BagHasAmmoNow + elseif type(_G) == "table" and type(_G["zButtonAmmo_BagHasAmmoNow"]) == "function" then + bagHasAmmoNow = _G["zButtonAmmo_BagHasAmmoNow"] + end + local unknownBagHasAmmo = false + if bagId and bagHasAmmoNow then + unknownBagHasAmmo = bagHasAmmoNow(bagId) and true or false + end + if bagId and (zButtonAmmo_AmmoBags[bagId] or unknownBagHasAmmo) then + bagIsAmmoRelated = true + end + if bagId and not zButtonAmmo_AmmoBags[bagId] then + if (not unknownBagHasAmmo) and now > 0 and zButtonAmmo_LastFullScanAt > 0 and (now - zButtonAmmo_LastFullScanAt) < zButtonAmmo_MinUnknownBagScanInterval then + zButtonAmmo_UpdateParentCountFast() + return + end + end + end + local minInterval = zButtonAmmo_MinRefreshInterval + if event == "BAG_UPDATE" then + if bagIsAmmoRelated then + minInterval = 0 + else + minInterval = zButtonAmmo_MinBagRefreshInterval + end + if UnitAffectingCombat and UnitAffectingCombat("player") then + minInterval = 1.50 + if bagIsAmmoRelated then + minInterval = 0 + end + end + end + if now > 0 and zButtonAmmo_LastRefreshAt > 0 and (now - zButtonAmmo_LastRefreshAt) < minInterval then + zButtonAmmo_UpdateParentCountFast() + return + end + zButtonAmmo_LastRefreshAt = now + local ammoList = zButtonAmmo_GetAmmoList() local listChanged = (ZHunterMod_Ammo_Buttons ~= ammoList) ZHunterMod_Ammo_Buttons = ammoList zButtonAmmo_RebuildCache() - local cacheSignature = zButtonAmmo_GetCacheSignature() + local cacheSignature = zButtonAmmo_GetCacheSignature(ammoList) + local rebuiltButtons = false if listChanged or cacheSignature ~= zButtonAmmo_LastCacheSignature then zButtonAmmo_EnsureSpellOrder(ammoList) local info = {} @@ -822,15 +1009,10 @@ function zButtonAmmoAdjustment_OnEvent() end zButtonAmmo.found = ZSpellButton_SetButtons(zButtonAmmo, info) zButtonAmmo_SetupSizeAndPosition() - zButtonAmmo_LastCacheSignature = zButtonAmmo_GetCacheSignature() - end - -- Update ammo counts for child buttons - for i=1, zButtonAmmo.count do - local button = getglobal("zButtonAmmo"..i) - if button and button.ammoname and not button.isspell then - zButtonAmmo_UpdateButton(button) - end + zButtonAmmo_LastCacheSignature = cacheSignature + rebuiltButtons = true end + local equippedAmmoName = zButtonAmmo_GetEquippedAmmoName() local equippedInfo = nil if equippedAmmoName then @@ -839,40 +1021,51 @@ function zButtonAmmoAdjustment_OnEvent() for i=1, zButtonAmmo.count do local child = getglobal("zButtonAmmo"..i) if child and child.ammoname == equippedAmmoName and not child.isspell then - equippedInfo = { - ammo = child.ammoname, - brol = child.ammobrol, - quality = child.ammoqual, - lvl = child.ammolvl, - type = child.ammotype, - bag = child.ammobag, - slot = child.ammoslot, - id = child.ammoid or child.id, - link = child.ammolink, - icon = child.icon, - count = child.ammocount, - } + zButtonAmmo_AssignParentFromChild(child) break end end end end - if not equippedInfo then + if not equippedInfo and event ~= "BAG_UPDATE" then equippedInfo = zButtonAmmo_GetEquippedAmmoInfo() if equippedInfo and equippedInfo.ammo and not equippedAmmoName then equippedAmmoName = equippedInfo.ammo end end + if not rebuiltButtons then + for i=1, zButtonAmmo.count do + local child = getglobal("zButtonAmmo"..i) + if child and child.ammoname and not child.isspell then + local cacheInfo = zButtonAmmo_Cache[child.ammoname] + local newCount = cacheInfo and cacheInfo.count or 0 + if (child.ammocount or 0) ~= (newCount or 0) then + if cacheInfo then + child.ammobag = cacheInfo.bag + child.ammoslot = cacheInfo.slot + child.ammoqual = cacheInfo.quality + child.ammobrol = cacheInfo.brol + child.ammolvl = cacheInfo.lvl + child.ammotype = cacheInfo.type + child.ammoid = cacheInfo.id + child.ammolink = cacheInfo.link + child.icon = cacheInfo.icon + end + child.ammocount = newCount + zButtonAmmo_UpdateButton(child) + end + end + end + end + if equippedInfo and equippedInfo.ammo and equippedInfo.ammo ~= zButtonAmmo.ammoname then zButtonAmmo_AssignButtonFromAmmoInfo(zButtonAmmo, equippedInfo) end -- Also update parent if it's showing ammo (not a spell) - if zButtonAmmo and not zButtonAmmo.isspell and zButtonAmmo.ammoname then - zButtonAmmo_UpdateButton(zButtonAmmo) - end + zButtonAmmo_UpdateParentCountFast() -- Save the currently equipped ammo for restoration on next reload if equippedAmmoName then diff --git a/modules/zhunter/zButtonAspect.lua b/modules/zhunter/zButtonAspect.lua index 0b913f6..9deb2ca 100644 --- a/modules/zhunter/zButtonAspect.lua +++ b/modules/zhunter/zButtonAspect.lua @@ -32,6 +32,12 @@ ZHunterMod_Aspect_Spells = { } local ZHUNTER_ASPECT_MAX = table.getn(ZHunterMod_Aspect_Spells) +local zButtonAspect_LastAuraUpdateAt = 0 +local zButtonAspect_MinAuraInterval = 0.15 +local zButtonAspect_MinAuraIntervalCombat = 0.60 +local zButtonAspect_LastAuraTexture = nil +local zButtonAspect_LastParentId = nil +local zButtonAspect_AuraProbeTextLeft1 = nil local function zButtonAspect_GetSaved() local currentRoot = zButtonAspect_GetRoot() @@ -158,7 +164,13 @@ function zButtonAspect_OnEvent() zButtonAspectAdjustment:RegisterEvent("CHARACTER_POINTS_CHANGED") zButtonAspectAdjustment:RegisterEvent("LEARNED_SPELL_IN_TAB") zButtonAspectAdjustment:SetScript("OnEvent", zButtonAspectAdjustment_OnEvent) - zButtonAspect_Tooltip = CreateFrame("GameTooltip", "zButtonAspect_Tooltip", nil, "GameTooltipTemplate") + zButtonAspect_Tooltip = CreateFrame("GameTooltip", "zButtonAspect_AuraProbeScan", nil, "GameTooltipTemplate") + if zButtonAspect_Tooltip and zButtonAspect_Tooltip.GetName then + local tooltipName = zButtonAspect_Tooltip:GetName() + if tooltipName then + zButtonAspect_AuraProbeTextLeft1 = getglobal(tooltipName .. "TextLeft1") + end + end zButtonAspect_SetupSizeAndPosition() end end @@ -191,11 +203,17 @@ function zButtonAspect_SetupSizeAndPosition() zButtonAspect_EnsureConfig() local saved = zButtonAspect_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonAspectAdjustment and zButtonAspectAdjustment.SetScript then + zButtonAspectAdjustment:SetScript("OnEvent", nil) + end if zButtonAspect and zButtonAspect.Hide then zButtonAspect:Hide() end return end + if zButtonAspectAdjustment and zButtonAspectAdjustment.SetScript then + zButtonAspectAdjustment:SetScript("OnEvent", zButtonAspectAdjustment_OnEvent) + end local displayCount = zButtonAspect.found or ZHUNTER_ASPECT_MAX if displayCount < 0 then displayCount = 0 @@ -234,6 +252,17 @@ function zButtonAspectAdjustment_OnEvent() if not zButtonAspect or not zButtonAspect.count then return end + if event == "PLAYER_AURAS_CHANGED" then + local now = GetTime and GetTime() or 0 + local minInterval = zButtonAspect_MinAuraInterval + if UnitAffectingCombat and UnitAffectingCombat("player") then + minInterval = zButtonAspect_MinAuraIntervalCombat + end + if now > 0 and zButtonAspect_LastAuraUpdateAt > 0 and (now - zButtonAspect_LastAuraUpdateAt) < minInterval then + return + end + zButtonAspect_LastAuraUpdateAt = now + end if event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then zButtonAspect_CreateButtons() zButtonAspect_SetupSizeAndPosition() @@ -245,45 +274,78 @@ function zButtonAspectAdjustment_OnEvent() if not zButtonAspect1.id then return end - local buttontextures = {} local button - for i=1, zButtonAspect.count do - button = getglobal(zButtonAspect.name..i) - button.icon = nil - if button.id then - buttontextures[GetSpellTexture(button.id, "spell")] = button + local isParentTooltipOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonAspect) + local childrenShown = zButtonAspect.children and zButtonAspect.children.IsShown and zButtonAspect.children:IsShown() + local needChildIconScan = (event ~= "PLAYER_AURAS_CHANGED") or childrenShown or isParentTooltipOwned + local buttontextures = nil + local hasDuplicateChildTexture = nil + if needChildIconScan then + buttontextures = {} + local textureSeen = {} + for i=1, zButtonAspect.count do + button = getglobal(zButtonAspect.name..i) + button.icon = nil + if button.id then + local btnTexture = GetSpellTexture(button.id, "spell") + if btnTexture then + if textureSeen[btnTexture] then + hasDuplicateChildTexture = true + elseif hasDuplicateChildTexture == nil then + hasDuplicateChildTexture = false + end + textureSeen[btnTexture] = 1 + buttontextures[btnTexture] = button + end + end end end local i = 1 local texture = GetSpellTexture(zButtonAspect1.id, "spell") local buff = UnitBuff("player", i) local spellname, buffname - zButtonAspect.id = zButtonAspect1.id + local newParentId = zButtonAspect1.id while buff do if texture == buff and zButtonAspect2.id then - zButtonAspect.id = zButtonAspect2.id + newParentId = zButtonAspect2.id end - if buttontextures[buff] then - zButtonAspect_Tooltip:SetOwner(this, "ANCHOR_NONE") - zButtonAspect_Tooltip:SetUnitBuff("player", i) - buffname = zButtonAspect_TooltipTextLeft1:GetText() - spellname = GetSpellName(buttontextures[buff].id, "spell") - if buffname == spellname then + if needChildIconScan and buttontextures[buff] then + if hasDuplicateChildTexture then + zButtonAspect_Tooltip:SetOwner(this, "ANCHOR_NONE") + zButtonAspect_Tooltip:SetUnitBuff("player", i) + if zButtonAspect_AuraProbeTextLeft1 and zButtonAspect_AuraProbeTextLeft1.GetText then + buffname = zButtonAspect_AuraProbeTextLeft1:GetText() + else + buffname = nil + end + spellname = GetSpellName(buttontextures[buff].id, "spell") + if buffname == spellname then + buttontextures[buff].icon = "Interface\\Icons\\Spell_Nature_WispSplode" + end + else buttontextures[buff].icon = "Interface\\Icons\\Spell_Nature_WispSplode" end end i = i + 1 buff = UnitBuff("player", i) end + if event == "PLAYER_AURAS_CHANGED" and zButtonAspect_LastAuraTexture == texture and zButtonAspect.id == newParentId and zButtonAspect_LastParentId == newParentId and not isParentTooltipOwned then + return + end + zButtonAspect_LastAuraTexture = texture + zButtonAspect_LastParentId = newParentId + zButtonAspect.id = newParentId ZSpellButton_UpdateButton(zButtonAspect) ZSpellButton_UpdateCooldown(zButtonAspect) - for i=1, zButtonAspect.count do - button = getglobal(zButtonAspect.name..i) - if button.id then - ZSpellButton_UpdateButton(button) + if needChildIconScan then + for i=1, zButtonAspect.count do + button = getglobal(zButtonAspect.name..i) + if button.id then + ZSpellButton_UpdateButton(button) + end end end - if GameTooltip:IsOwned(zButtonAspect) then + if isParentTooltipOwned then ZSpellButtonParent_OnEnter(zButtonAspect) end end diff --git a/modules/zhunter/zButtonCompanions.lua b/modules/zhunter/zButtonCompanions.lua index f778032..eb99861 100644 --- a/modules/zhunter/zButtonCompanions.lua +++ b/modules/zhunter/zButtonCompanions.lua @@ -22,6 +22,20 @@ if not root["zButtonCompanions"] then end local ZHUNTER_COMPANIONS_BUTTON_MAX = 80 +local zButtonCompanions_LastSpellSignature = nil +local zButtonCompanions_LastRefreshAt = 0 +local zButtonCompanions_MinRefreshInterval = 0.50 + +local function zButtonCompanions_GetSpellSignature(spellIds) + if type(spellIds) ~= "table" or table.getn(spellIds) == 0 then + return "" + end + local parts = {} + for i = 1, table.getn(spellIds) do + parts[i] = tostring(spellIds[i] or "") + end + return table.concat(parts, ",") +end local function zButtonCompanions_GetSaved() local currentRoot = zButtonCompanions_GetRoot() @@ -233,11 +247,17 @@ end function zButtonCompanions_SetupSizeAndPosition() local saved = zButtonCompanions_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonCompanionsAdjustment and zButtonCompanionsAdjustment.SetScript then + zButtonCompanionsAdjustment:SetScript("OnEvent", nil) + end if zButtonCompanions and zButtonCompanions.Hide then zButtonCompanions:Hide() end return end + if zButtonCompanionsAdjustment and zButtonCompanionsAdjustment.SetScript then + zButtonCompanionsAdjustment:SetScript("OnEvent", zButtonCompanionsAdjustment_OnEvent) + end local displayCount = zButtonCompanions.found or 0 if displayCount < 0 then displayCount = 0 @@ -275,6 +295,18 @@ function zButtonCompanionsAdjustment_OnEvent() if not zButtonCompanions then return end + if event == "SPELLS_CHANGED" then + local now = GetTime and GetTime() or 0 + if now > 0 and zButtonCompanions_LastRefreshAt > 0 and (now - zButtonCompanions_LastRefreshAt) < zButtonCompanions_MinRefreshInterval then + return + end + local signature = zButtonCompanions_GetSpellSignature(zButtonCompanions_CollectTabSpellIds()) + if signature == zButtonCompanions_LastSpellSignature and not GameTooltip:IsOwned(zButtonCompanions) then + return + end + zButtonCompanions_LastSpellSignature = signature + zButtonCompanions_LastRefreshAt = now + end zButtonCompanions_RefreshSpells() zButtonCompanions_SetupSizeAndPosition() if GameTooltip:IsOwned(zButtonCompanions) then diff --git a/modules/zhunter/zButtonMounts.lua b/modules/zhunter/zButtonMounts.lua index 85e42fd..5907413 100644 --- a/modules/zhunter/zButtonMounts.lua +++ b/modules/zhunter/zButtonMounts.lua @@ -22,6 +22,20 @@ if not root["zButtonMounts"] then end local ZHUNTER_MOUNTS_BUTTON_MAX = 80 +local zButtonMounts_LastSpellSignature = nil +local zButtonMounts_LastRefreshAt = 0 +local zButtonMounts_MinRefreshInterval = 0.50 + +local function zButtonMounts_GetSpellSignature(spellIds) + if type(spellIds) ~= "table" or table.getn(spellIds) == 0 then + return "" + end + local parts = {} + for i = 1, table.getn(spellIds) do + parts[i] = tostring(spellIds[i] or "") + end + return table.concat(parts, ",") +end local function zButtonMounts_GetSaved() local currentRoot = zButtonMounts_GetRoot() @@ -233,11 +247,17 @@ end function zButtonMounts_SetupSizeAndPosition() local saved = zButtonMounts_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonMountsAdjustment and zButtonMountsAdjustment.SetScript then + zButtonMountsAdjustment:SetScript("OnEvent", nil) + end if zButtonMounts and zButtonMounts.Hide then zButtonMounts:Hide() end return end + if zButtonMountsAdjustment and zButtonMountsAdjustment.SetScript then + zButtonMountsAdjustment:SetScript("OnEvent", zButtonMountsAdjustment_OnEvent) + end local displayCount = zButtonMounts.found or 0 if displayCount < 0 then displayCount = 0 @@ -275,6 +295,18 @@ function zButtonMountsAdjustment_OnEvent() if not zButtonMounts then return end + if event == "SPELLS_CHANGED" then + local now = GetTime and GetTime() or 0 + if now > 0 and zButtonMounts_LastRefreshAt > 0 and (now - zButtonMounts_LastRefreshAt) < zButtonMounts_MinRefreshInterval then + return + end + local signature = zButtonMounts_GetSpellSignature(zButtonMounts_CollectTabSpellIds()) + if signature == zButtonMounts_LastSpellSignature and not GameTooltip:IsOwned(zButtonMounts) then + return + end + zButtonMounts_LastSpellSignature = signature + zButtonMounts_LastRefreshAt = now + end zButtonMounts_RefreshSpells() zButtonMounts_SetupSizeAndPosition() if GameTooltip:IsOwned(zButtonMounts) then diff --git a/modules/zhunter/zButtonPet.lua b/modules/zhunter/zButtonPet.lua index e2c52e3..c7827eb 100644 --- a/modules/zhunter/zButtonPet.lua +++ b/modules/zhunter/zButtonPet.lua @@ -43,6 +43,11 @@ ZHunterMod_Pet_Spells = { local ZHUNTER_PET_MAX = table.getn(ZHunterMod_Pet_Spells) local zButtonPet_SetButtons +local zButtonPet_LastRefreshAt = 0 +local zButtonPet_MinRefreshInterval = 0.15 +local zButtonPet_MinCombatRefreshInterval = 0.30 +local zButtonPet_RuntimeSpellMap = nil +local zButtonPet_LastStateKey = nil local function zButtonPet_GetSaved() local currentRoot = zButtonPet_GetRoot() @@ -134,6 +139,40 @@ local function zButtonPet_ApplyRuntimeSettings() zButtonPet.hideonclick = saved["children"] and saved["children"]["hideonclick"] and true or false end +local function zButtonPet_RebuildRuntimeSpellMap() + if not zButtonPet or not zButtonPet.count then + zButtonPet_RuntimeSpellMap = {} + return zButtonPet_RuntimeSpellMap + end + local spells = {} + for i=1, zButtonPet.count do + local button = getglobal(zButtonPet.name..i) + if button and button.id then + local spellName = GetSpellName(button.id, "spell") + if spellName and spellName ~= "" then + spells[spellName] = button.id + end + end + end + zButtonPet_RuntimeSpellMap = spells + return spells +end + +local function zButtonPet_GetPreferredDefaultParentId(spells) + if zButtonPet1 and zButtonPet1.id then + return zButtonPet1.id + end + if zButtonPet and zButtonPet.count then + for i = 1, zButtonPet.count do + local child = getglobal("zButtonPet" .. i) + if child and child.id then + return child.id + end + end + end + return spells[ZHUNTER_PET_EYES] or spells[ZHUNTER_PET_POSITION] or spells[ZHUNTER_PET_DISMISS] +end + function zButtonPet_OnLoad() this:RegisterEvent("VARIABLES_LOADED") end @@ -161,7 +200,6 @@ function zButtonPet_OnEvent() zButtonPetAdjustment:RegisterEvent("SPELLS_CHANGED") zButtonPetAdjustment:RegisterEvent("LEARNED_SPELL_IN_TAB") zButtonPetAdjustment:SetScript("OnEvent", zButtonPetAdjustment_OnEvent) - zButtonPet_Tooltip = CreateFrame("GameTooltip", "zButtonPet_Tooltip", nil, "GameTooltipTemplate") zButtonPet_SetupSizeAndPosition() zButtonPetAdjustment_OnEvent() end @@ -188,16 +226,24 @@ function zButtonPet_CreateButtons() end zButtonPet.found = ZSpellButton_SetButtons(zButtonPet, info) zButtonPet_ApplyRuntimeSettings() + zButtonPet_RebuildRuntimeSpellMap() + zButtonPet_LastStateKey = nil end function zButtonPet_SetupSizeAndPosition() local saved = zButtonPet_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonPetAdjustment and zButtonPetAdjustment.SetScript then + zButtonPetAdjustment:SetScript("OnEvent", nil) + end if zButtonPet and zButtonPet.Hide then zButtonPet:Hide() end return end + if zButtonPetAdjustment and zButtonPetAdjustment.SetScript then + zButtonPetAdjustment:SetScript("OnEvent", zButtonPetAdjustment_OnEvent) + end local displayCount = zButtonPet.found or ZHUNTER_PET_MAX if displayCount < 0 then displayCount = 0 @@ -434,20 +480,12 @@ local function zButtonPet_RefreshParentSpell() zButtonPet_RefreshSavedPetState(saved, corePetInfo) local status, happiness, dead - local spells = {} - local name - local choice - for i=1, zButtonPet.count do - local button - button = getglobal(zButtonPet.name..i) - if button and button.id then - name = GetSpellName(button.id, "spell") - spells[name] = button - if not choice and (name == ZHUNTER_PET_DISMISS or name == ZHUNTER_PET_EYES or name == ZHUNTER_PET_POSITION) then - choice = button - end - end + local spells = zButtonPet_RuntimeSpellMap + if not spells then + spells = zButtonPet_RebuildRuntimeSpellMap() end + local name + local choiceId = zButtonPet_GetPreferredDefaultParentId(spells) status = saved["pet"]["status"] happiness = saved["pet"]["happiness"] dead = saved["pet"]["dead"] @@ -464,22 +502,34 @@ local function zButtonPet_RefreshParentSpell() name = ZHUNTER_PET_MEND elseif happiness ~= 3 then name = ZHUNTER_PET_FEED - elseif choice then - id = choice.id + elseif choiceId then + id = choiceId end if name and spells[name] then - id = spells[name].id + id = spells[name] elseif name then local fallbackId = zButtonPet_FindSpellIdByName(name) if fallbackId then + spells[name] = fallbackId id = fallbackId end end + local isOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonPet) + local stateKey = tostring(hasCurrentPet and 1 or 0) + ..":"..tostring(hasLivePet and 1 or 0) + ..":"..tostring(status or "") + ..":"..tostring(happiness or "") + ..":"..tostring(dead or 0) + ..":"..tostring(id or 0) + if zButtonPet.id == id and zButtonPet_LastStateKey == stateKey and not isOwned then + return + end + zButtonPet_LastStateKey = stateKey zButtonPet.id = id ZSpellButton_UpdateButton(zButtonPet) ZSpellButton_UpdateCooldown(zButtonPet) - if GameTooltip:IsOwned(zButtonPet) then + if isOwned then ZSpellButtonParent_OnEnter(zButtonPet) end end @@ -503,6 +553,21 @@ function zButtonPetAdjustment_OnEvent() if event == "UNIT_PET" and arg1 and arg1 ~= "player" then return end + if event == "SPELLS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then + zButtonPet_CreateButtons() + zButtonPet_SetupSizeAndPosition() + end + if event == "UNIT_HEALTH" or event == "UNIT_HAPPINESS" or event == "PET_BAR_UPDATE" then + local now = GetTime and GetTime() or 0 + local minInterval = zButtonPet_MinRefreshInterval + if UnitAffectingCombat and UnitAffectingCombat("player") then + minInterval = zButtonPet_MinCombatRefreshInterval + end + if now > 0 and zButtonPet_LastRefreshAt > 0 and (now - zButtonPet_LastRefreshAt) < minInterval then + return + end + zButtonPet_LastRefreshAt = now + end zButtonPet_RefreshParentSpell() end diff --git a/modules/zhunter/zButtonRanged.lua b/modules/zhunter/zButtonRanged.lua index 95d5e40..919b101 100644 --- a/modules/zhunter/zButtonRanged.lua +++ b/modules/zhunter/zButtonRanged.lua @@ -27,6 +27,11 @@ ZHunterMod_Ranged_Weapons = ZHunterMod_Ranged_Weapons or {} local ZHUNTER_RANGED_MAX_COUNT = 80 local zButtonRanged_Cache = {} local zButtonRanged_TooltipWarmed = false +local zButtonRanged_LastRefreshAt = 0 +local zButtonRanged_MinBagRefreshInterval = 0.75 +local zButtonRanged_IdleBagRefreshInterval = 3.00 +local zButtonRanged_BagDirty = false +local zButtonRanged_RefreshButtonsFromState local function zButtonRanged_GetKnownItemSpeed(itemId) local idNum = tonumber(itemId) @@ -437,6 +442,15 @@ local function zButtonRanged_OnEnter(button) if not button then return end + if zButtonRanged_BagDirty then + local previousFound = tonumber(zButtonRanged and zButtonRanged.found) or 0 + zButtonRanged_RefreshButtonsFromState() + if (tonumber(zButtonRanged and zButtonRanged.found) or 0) ~= previousFound then + zButtonRanged_SetupSizeAndPosition() + else + zButtonRanged_UpdateButton(zButtonRanged) + end + end if not (zButtonRanged and zButtonRanged.tooltip) then return end @@ -503,7 +517,7 @@ local function zButtonRanged_BeforeClick(button) return true end -local function zButtonRanged_RefreshButtonsFromState() +zButtonRanged_RefreshButtonsFromState = function() if not zButtonRanged then return end @@ -525,6 +539,7 @@ local function zButtonRanged_RefreshButtonsFromState() end zButtonRanged.found = ZSpellButton_SetButtons(zButtonRanged, info) + zButtonRanged_BagDirty = false zButtonRanged_ApplyRuntimeSettings() end @@ -620,11 +635,17 @@ end function zButtonRanged_SetupSizeAndPosition() local saved = zButtonRanged_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonRangedAdjustment and zButtonRangedAdjustment.SetScript then + zButtonRangedAdjustment:SetScript("OnEvent", nil) + end if zButtonRanged and zButtonRanged.Hide then zButtonRanged:Hide() end return end + if zButtonRangedAdjustment and zButtonRangedAdjustment.SetScript then + zButtonRangedAdjustment:SetScript("OnEvent", zButtonRangedAdjustment_OnEvent) + end local displayCount = zButtonRanged.found or 0 if displayCount < 0 then displayCount = 0 @@ -665,8 +686,32 @@ function zButtonRangedAdjustment_OnEvent() if event == "UNIT_INVENTORY_CHANGED" and arg1 and arg1 ~= "player" then return end + if event == "BAG_UPDATE" then + local now = GetTime and GetTime() or 0 + local isInspecting = false + if zButtonRanged.children and zButtonRanged.children.IsShown and zButtonRanged.children:IsShown() then + isInspecting = true + elseif GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonRanged) then + isInspecting = true + end + if not isInspecting then + zButtonRanged_BagDirty = true + return + end + local minInterval = isInspecting and zButtonRanged_MinBagRefreshInterval or zButtonRanged_IdleBagRefreshInterval + if now > 0 and zButtonRanged_LastRefreshAt > 0 and (now - zButtonRanged_LastRefreshAt) < minInterval then + return + end + end + + zButtonRanged_LastRefreshAt = GetTime and GetTime() or 0 + local previousFound = tonumber(zButtonRanged.found) or 0 zButtonRanged_RefreshButtonsFromState() - zButtonRanged_SetupSizeAndPosition() + if (tonumber(zButtonRanged.found) or 0) ~= previousFound then + zButtonRanged_SetupSizeAndPosition() + else + zButtonRanged_UpdateButton(zButtonRanged) + end if event == "PLAYER_ENTERING_WORLD" then zButtonRanged_PrewarmTooltipOnce() end diff --git a/modules/zhunter/zButtonToys.lua b/modules/zhunter/zButtonToys.lua index d040c7a..4f054a3 100644 --- a/modules/zhunter/zButtonToys.lua +++ b/modules/zhunter/zButtonToys.lua @@ -22,6 +22,20 @@ if not root["zButtonToys"] then end local ZHUNTER_TOYS_BUTTON_MAX = 80 +local zButtonToys_LastSpellSignature = nil +local zButtonToys_LastRefreshAt = 0 +local zButtonToys_MinRefreshInterval = 0.50 + +local function zButtonToys_GetSpellSignature(spellIds) + if type(spellIds) ~= "table" or table.getn(spellIds) == 0 then + return "" + end + local parts = {} + for i = 1, table.getn(spellIds) do + parts[i] = tostring(spellIds[i] or "") + end + return table.concat(parts, ",") +end local function zButtonToys_GetSaved() local currentRoot = zButtonToys_GetRoot() @@ -233,11 +247,17 @@ end function zButtonToys_SetupSizeAndPosition() local saved = zButtonToys_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonToysAdjustment and zButtonToysAdjustment.SetScript then + zButtonToysAdjustment:SetScript("OnEvent", nil) + end if zButtonToys and zButtonToys.Hide then zButtonToys:Hide() end return end + if zButtonToysAdjustment and zButtonToysAdjustment.SetScript then + zButtonToysAdjustment:SetScript("OnEvent", zButtonToysAdjustment_OnEvent) + end local displayCount = zButtonToys.found or 0 if displayCount < 0 then displayCount = 0 @@ -275,6 +295,18 @@ function zButtonToysAdjustment_OnEvent() if not zButtonToys then return end + if event == "SPELLS_CHANGED" then + local now = GetTime and GetTime() or 0 + if now > 0 and zButtonToys_LastRefreshAt > 0 and (now - zButtonToys_LastRefreshAt) < zButtonToys_MinRefreshInterval then + return + end + local signature = zButtonToys_GetSpellSignature(zButtonToys_CollectTabSpellIds()) + if signature == zButtonToys_LastSpellSignature and not GameTooltip:IsOwned(zButtonToys) then + return + end + zButtonToys_LastSpellSignature = signature + zButtonToys_LastRefreshAt = now + end zButtonToys_RefreshSpells() zButtonToys_SetupSizeAndPosition() if GameTooltip:IsOwned(zButtonToys) then diff --git a/modules/zhunter/zButtonTrack.lua b/modules/zhunter/zButtonTrack.lua index 6a5b301..c90d71b 100644 --- a/modules/zhunter/zButtonTrack.lua +++ b/modules/zhunter/zButtonTrack.lua @@ -36,6 +36,156 @@ ZHunterMod_Track_Spells = { } local ZHUNTER_TRACK_MAX = table.getn(ZHunterMod_Track_Spells) +local zButtonTrack_LastTrackingTexture = nil +local zButtonTrack_SyncActiveChild + +local function zButtonTrack_NormalizeTexture(texture) + if not texture then + return "" + end + local value = string.lower(tostring(texture)) + value = string.gsub(value, "\\", "/") + local _, _, basename = string.find(value, "([^/]+)$") + return basename or value +end + +local function zButtonTrack_FindSpellIdByTrackingTexture(trackingTexture) + if not trackingTexture or not zButtonTrack or not zButtonTrack.count then + return nil + end + local wanted = zButtonTrack_NormalizeTexture(trackingTexture) + for i = 1, zButtonTrack.count do + local child = getglobal("zButtonTrack" .. i) + if child and child.id then + local spellTexture = GetSpellTexture and GetSpellTexture(child.id, "spell") or nil + if zButtonTrack_NormalizeTexture(spellTexture) == wanted then + return child.id + end + if child.icon and zButtonTrack_NormalizeTexture(child.icon) == wanted then + return child.id + end + if child.icontexture and child.icontexture.GetTexture then + local childTexture = child.icontexture:GetTexture() + if zButtonTrack_NormalizeTexture(childTexture) == wanted then + return child.id + end + end + end + end + return nil +end + +local function zButtonTrack_FindSpellIdByTrackingName(trackingName) + if not trackingName or trackingName == "" or not zButtonTrack or not zButtonTrack.count then + return nil + end + for i = 1, zButtonTrack.count do + local child = getglobal("zButtonTrack" .. i) + if child and child.id then + local spellName = GetSpellName and GetSpellName(child.id, "spell") or nil + if spellName == trackingName then + return child.id + end + end + end + return nil +end + +local function zButtonTrack_GetActiveTrackingSpellId() + local activeName = nil + local activeTexture = nil + if GetNumTrackingTypes and GetTrackingInfo then + local numTracking = GetNumTrackingTypes() or 0 + for i = 1, numTracking do + local name, texture, active = GetTrackingInfo(i) + if active then + activeName = name + activeTexture = texture + break + end + end + end + local byName = zButtonTrack_FindSpellIdByTrackingName(activeName) + if byName then + return byName, activeTexture + end + if activeTexture then + return zButtonTrack_FindSpellIdByTrackingTexture(activeTexture), activeTexture + end + local buff = GetTrackingTexture and GetTrackingTexture() or nil + return zButtonTrack_FindSpellIdByTrackingTexture(buff), buff +end + +local function zButtonTrack_AfterClick(button) + if not zButtonTrack then + return + end + if button and button.id then + zButtonTrack.id = button.id + ZSpellButton_UpdateButton(zButtonTrack) + ZSpellButton_UpdateCooldown(zButtonTrack) + zButtonTrack_SyncActiveChild(button.id) + if GetSpellTexture then + zButtonTrack_LastTrackingTexture = GetSpellTexture(button.id, "spell") or zButtonTrack_LastTrackingTexture + end + local isOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonTrack) + if isOwned then + ZSpellButtonParent_OnEnter(zButtonTrack) + end + end +end + +zButtonTrack_SyncActiveChild = function(activeId) + if not zButtonTrack or not zButtonTrack.count then + return + end + for i = 1, zButtonTrack.count do + local child = getglobal("zButtonTrack" .. i) + if child and child.SetChecked then + child:SetChecked((activeId and child.id == activeId) and 1 or 0) + end + end + if zButtonTrack.SetChecked then + zButtonTrack:SetChecked(0) + end +end + +local function zButtonTrack_RefreshTrackingState() + if not zButtonTrack or not zButtonTrack.count then + return + end + local newId, trackingTexture = zButtonTrack_GetActiveTrackingSpellId() + local buff = trackingTexture or (GetTrackingTexture and GetTrackingTexture() or nil) + if not newId and not buff then + if zButtonTrack.id then + zButtonTrack_SyncActiveChild(zButtonTrack.id) + else + zButtonTrack_LastTrackingTexture = nil + zButtonTrack_SyncActiveChild(nil) + end + return + end + if not newId then + newId = zButtonTrack.id or nil + end + if not newId then + return + end + if zButtonTrack_NormalizeTexture(zButtonTrack_LastTrackingTexture) == zButtonTrack_NormalizeTexture(buff) and zButtonTrack.id == newId then + zButtonTrack_SyncActiveChild(newId) + return + end + + local isOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonTrack) + zButtonTrack_LastTrackingTexture = buff + zButtonTrack.id = newId + ZSpellButton_UpdateButton(zButtonTrack) + ZSpellButton_UpdateCooldown(zButtonTrack) + zButtonTrack_SyncActiveChild(newId) + if isOwned then + ZSpellButtonParent_OnEnter(zButtonTrack) + end +end local function zButtonTrack_GetSaved() local currentRoot = zButtonTrack_GetRoot() @@ -119,11 +269,10 @@ function zButtonTrack_OnEvent() return end zButtonTrack_CreateButtons() + zButtonTrack.afterclick = zButtonTrack_AfterClick zButtonTrackAdjustment = CreateFrame("Frame", "zButtonTrackAdjustment") - zButtonTrackAdjustment:RegisterEvent("PLAYER_AURAS_CHANGED") + zButtonTrackAdjustment:RegisterEvent("MINIMAP_UPDATE_TRACKING") zButtonTrackAdjustment:RegisterEvent("PLAYER_ENTERING_WORLD") - zButtonTrackAdjustment:RegisterEvent("SPELLS_CHANGED") - zButtonTrackAdjustment:RegisterEvent("CHARACTER_POINTS_CHANGED") zButtonTrackAdjustment:SetScript("OnEvent", zButtonTrackAdjustment_OnEvent) zButtonTrack_SetupSizeAndPosition() end @@ -156,11 +305,17 @@ function zButtonTrack_SetupSizeAndPosition() zButtonTrack_EnsureConfig() local saved = zButtonTrack_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonTrackAdjustment and zButtonTrackAdjustment.SetScript then + zButtonTrackAdjustment:SetScript("OnEvent", nil) + end if zButtonTrack and zButtonTrack.Hide then zButtonTrack:Hide() end return end + if zButtonTrackAdjustment and zButtonTrackAdjustment.SetScript then + zButtonTrackAdjustment:SetScript("OnEvent", zButtonTrackAdjustment_OnEvent) + end local displayCount = zButtonTrack.found or ZHUNTER_TRACK_MAX if displayCount < 0 then displayCount = 0 @@ -171,6 +326,7 @@ function zButtonTrack_SetupSizeAndPosition() ZSpellButton_ArrangeChildren(zButtonTrack, saved["rows"], displayCount, saved["horizontal"], saved["vertical"]) + zButtonTrack_RefreshTrackingState() end function zButtonTrack_Reset() @@ -199,25 +355,9 @@ function zButtonTrackAdjustment_OnEvent() if not zButtonTrack or not zButtonTrack.count then return end - if event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" then - zButtonTrack_CreateButtons() - zButtonTrack_SetupSizeAndPosition() - end - if event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_ENTERING_WORLD" - or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" then - local i = 1 - local texture = zButtonTrack1IconTexture:GetTexture() - local buff = GetTrackingTexture() - if texture == buff and zButtonTrack2.id then - zButtonTrack.id = zButtonTrack2.id - else - zButtonTrack.id = zButtonTrack1.id - end - ZSpellButton_UpdateButton(zButtonTrack) - ZSpellButton_UpdateCooldown(zButtonTrack) - if GameTooltip:IsOwned(zButtonTrack) then - ZSpellButtonParent_OnEnter(zButtonTrack) - end + if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_ENTERING_WORLD" + then + zButtonTrack_RefreshTrackingState() end end @@ -229,9 +369,9 @@ function zButtonTrack_KeyBinding(index) local button if index then button = getglobal("zButtonTrack"..index) - else + else button = zButtonTrack - end + end if button.id then CastSpell(button.id, "spell") if zButtonTrack.hideonclick then diff --git a/modules/zhunter/zButtonTrap.lua b/modules/zhunter/zButtonTrap.lua index 2bd48c1..beab10a 100644 --- a/modules/zhunter/zButtonTrap.lua +++ b/modules/zhunter/zButtonTrap.lua @@ -29,6 +29,8 @@ ZHunterMod_Trap_Spells = { } local ZHUNTER_TRAP_MAX = table.getn(ZHunterMod_Trap_Spells) +local zButtonTrap_LastParentId = nil +local zButtonTrap_LastCombatState = nil local function zButtonTrap_GetSaved() local currentRoot = zButtonTrap_GetRoot() @@ -150,11 +152,17 @@ function zButtonTrap_SetupSizeAndPosition() zButtonTrap_EnsureConfig() local saved = zButtonTrap_GetSaved() if saved["enabled"] == false or saved["enabled"] == 0 then + if zButtonTrapAdjustment and zButtonTrapAdjustment.SetScript then + zButtonTrapAdjustment:SetScript("OnEvent", nil) + end if zButtonTrap and zButtonTrap.Hide then zButtonTrap:Hide() end return end + if zButtonTrapAdjustment and zButtonTrapAdjustment.SetScript then + zButtonTrapAdjustment:SetScript("OnEvent", zButtonTrapAdjustment_OnEvent) + end local displayCount = zButtonTrap.found or ZHUNTER_TRAP_MAX if displayCount < 0 then displayCount = 0 @@ -193,9 +201,11 @@ function zButtonTrapAdjustment_OnEvent() if not zButtonTrap or not zButtonTrap.count then return end + local rebuild = false if event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" then zButtonTrap_CreateButtons() zButtonTrap_SetupSizeAndPosition() + rebuild = true end local nextbutton local button @@ -215,7 +225,13 @@ function zButtonTrapAdjustment_OnEvent() end end end - zButtonTrap.id = nextbutton and nextbutton.id or zButtonTrap1.id + local newParentId = nextbutton and nextbutton.id or zButtonTrap1.id + if (not rebuild) and zButtonTrap_LastParentId == newParentId and zButtonTrap_LastCombatState == combat and not GameTooltip:IsOwned(zButtonTrap) then + return + end + zButtonTrap_LastParentId = newParentId + zButtonTrap_LastCombatState = combat + zButtonTrap.id = newParentId ZSpellButton_UpdateButton(zButtonTrap) ZSpellButton_UpdateCooldown(zButtonTrap) if GameTooltip:IsOwned(zButtonTrap) then