diff --git a/CHANGELOG.md b/CHANGELOG.md index a23ccbb..5a0f4a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to MetaHunt will be documented in this file. +## [1.0.6] - 2026-03-05 + +### Fixed +- Added missing abilities to all pet families. +- Rescraped from Twow DB the previously in correct `Roar of Fortitude` pet-ability. +- Fixed an issue with Tooltip on pet action bar abilities, that were falsely displayed as not learned yet for rankless abilities. +- Restored visible Stable Master auto-scan feedback (`Stable scan complete: X slot(s).`) when opening the stable window. +- Fixed stable scan icon persistence for active pets that were never stabled by capturing current-pet icon data during stable scans. +- Hardened stable-scan bootstrap to initialize on both `PET_STABLE_SHOW` and `PET_STABLE_UPDATE` event paths. +- Fixed issues with taming pet metadata that were not properly recorded since the last update. +- Fixed issues with pet runaway interception that was broken since the last update. +- Updated version-update notification text to include clearer upgrade guidance and plain GitHub URL text for updates. +- Fixed an issue with `zTrack` buttons spells being incorrect after having respec the talents using brainwashing device. + + ## [1.0.5] - 2026-03-01 ### Fixed diff --git a/MetaHunt.toc b/MetaHunt.toc index 9c30691..c0465ba 100644 --- a/MetaHunt.toc +++ b/MetaHunt.toc @@ -1,5 +1,5 @@ ## Interface: 11200 -## Version: 1.0.5 +## Version: 1.0.6 ## 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 bebfd71..87f10b1 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.5" +MTH_CONST.version = MTH_CONST.version or "1.0.6" MTH_CONST.WEAPON_TYPES = { BOWS = "Bows", diff --git a/api/core-framework.lua b/api/core-framework.lua index 5b0b8cd..58546f9 100644 --- a/api/core-framework.lua +++ b/api/core-framework.lua @@ -1,5 +1,5 @@ MTH = MTH or { - version = "1.0.5", + version = "1.0.6", name = "MetaHunt", modules = {}, config = {}, @@ -16,6 +16,7 @@ local MTH_MESSAGE_DEFAULTS = { spellbookScan = false, petRanAway = true, mapMarkers = true, + stableScan = true, } local function MTH_ClassGateTrace(_step, _detail) diff --git a/api/core-scan-stablemaster.lua b/api/core-scan-stablemaster.lua index 770ab30..6846176 100644 --- a/api/core-scan-stablemaster.lua +++ b/api/core-scan-stablemaster.lua @@ -4,7 +4,6 @@ end local MTH_PETS_SCHEMA_VERSION = 2 local MTH_PETS_CORE_HOOK_BOUNDARY_KEY = "core-pet-rename-hook" -local MTH_PETS_TRACE_RUNAWAY = false MTH_PETS_TRACE_CONSISTENCY = false local MTH_StableFrame = nil @@ -17,7 +16,6 @@ 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 @@ -390,6 +388,17 @@ local function MTH_PETS_NormalizeSystemMessage(rawMessage) return message end +local function MTH_PETS_NormalizeSystemMessageKey(rawMessage) + local message = MTH_PETS_NormalizeSystemMessage(rawMessage) + message = string.lower(tostring(message or "")) + message = string.gsub(message, "%%s", " ") + message = string.gsub(message, "[^%w%s]", " ") + message = string.gsub(message, "%s+", " ") + message = string.gsub(message, "^%s+", "") + message = string.gsub(message, "%s+$", "") + return message +end + local function MTH_PETS_IsRunawaySystemMessage(rawMessage) local message = MTH_PETS_NormalizeSystemMessage(rawMessage) if message == "" then @@ -397,8 +406,20 @@ local function MTH_PETS_IsRunawaySystemMessage(rawMessage) end local lostPetMessage = _G and _G["PETTAME_LOSTPET"] - if type(lostPetMessage) == "string" and lostPetMessage ~= "" and message == lostPetMessage then - return true + if type(lostPetMessage) == "string" and lostPetMessage ~= "" then + if message == lostPetMessage then + return true + end + local messageKey = MTH_PETS_NormalizeSystemMessageKey(message) + local lostKey = MTH_PETS_NormalizeSystemMessageKey(lostPetMessage) + if messageKey ~= "" and lostKey ~= "" then + if messageKey == lostKey then + return true + end + if string.find(messageKey, lostKey, 1, true) then + return true + end + end end local lower = string.lower(message) @@ -406,14 +427,12 @@ local function MTH_PETS_IsRunawaySystemMessage(rawMessage) if string.find(lower, "pet ran away", 1, true) then return true end if string.find(lower, "pet has fled", 1, true) then return true end if string.find(lower, "pet fled", 1, true) then return true end + if string.find(lower, "pet has left", 1, true) then return true end + if string.find(lower, "pet left", 1, true) then return true end return false end -local function MTH_PETS_TraceRunawayEvent(evt, rawMessage, matched) - return -end - local function MTH_PETS_ParseCreatureIdFromGuid(guid) local guidText = tostring(guid or "") if guidText == "" then @@ -717,6 +736,38 @@ local function MTH_PETS_GetPendingTameBeastId(snapshot) return nil end +local function MTH_PETS_ResolveTameBeastIdForRow(row, snapshot) + if type(row) ~= "table" then + return nil + end + + local pendingResolved = MTH_PETS_GetPendingTameBeastId(snapshot) + if pendingResolved and pendingResolved > 0 then + return pendingResolved + end + + local fromSnapshot = snapshot and tonumber(snapshot.beastId) or nil + if fromSnapshot and fromSnapshot > 0 then + return fromSnapshot + end + + local fromRow = tonumber(row.beastId) + if fromRow and fromRow > 0 then + return fromRow + end + + local lookup = MTH_PETS_FindBeastIdByDataset( + tostring((snapshot and snapshot.name) or row.name or ""), + tostring((snapshot and snapshot.family) or row.family or ""), + tonumber((snapshot and snapshot.level) or row.level) + ) + if lookup and lookup > 0 then + return lookup + end + + return nil +end + local function MTH_PETS_MakeSignature(name, family, level) local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name)) local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family)) @@ -724,6 +775,114 @@ local function MTH_PETS_MakeSignature(name, family, level) return cleanName .. "|" .. cleanFamily .. "|" .. tostring(numericLevel) end +local function MTH_PETS_RowHasTameRecord(row) + if type(row) ~= "table" then + return false + end + if row.tameRecorded == true then + return true + end + if tonumber(row.tamedAt) and tonumber(row.tamedAt) > 0 then + return true + end + if tonumber(row.tameBeastId) and tonumber(row.tameBeastId) > 0 then + return true + end + if type(row.tameZone) == "string" and row.tameZone ~= "" then + return true + end + return false +end + +local function MTH_PETS_BackfillTameMetadataFromEvents(row) + if type(row) ~= "table" then + return false + end + if MTH_PETS_RowHasTameRecord(row) then + return false + end + if type(row.events) ~= "table" then + return false + end + + local bestEvent = nil + local stableFirstSeenAt = tonumber(row.stableFirstSeenAt) or tonumber(row.stabledAt) or 0 + for i = 1, table.getn(row.events) do + local ev = row.events[i] + if type(ev) == "table" and tostring(ev.type or "") == "pet-acquired" then + local evAt = tonumber(ev.at) or 0 + local timelineOk = true + if stableFirstSeenAt > 0 and evAt > 0 and evAt > stableFirstSeenAt then + -- If we only saw this pet as stabled before the acquire event, it was likely a call-out, not a tame. + timelineOk = false + end + if timelineOk then + if not bestEvent then + bestEvent = ev + else + local bestAt = tonumber(bestEvent.at) or 0 + if bestAt <= 0 or (evAt > 0 and evAt < bestAt) then + bestEvent = ev + end + end + end + end + end + + if type(bestEvent) ~= "table" then + return false + end + + local context = type(bestEvent.context) == "table" and bestEvent.context or nil + local changed = false + + if row.tamedAt == nil or tonumber(row.tamedAt) == nil or tonumber(row.tamedAt) <= 0 then + local candidateAt = tonumber(bestEvent.at) or (context and tonumber(context.timestamp) or nil) or tonumber(row.createdAt) + if candidateAt and candidateAt > 0 then + row.tamedAt = candidateAt + changed = true + end + end + + if context then + if row.tameHunterLevel == nil and context.hunterLevel ~= nil then + row.tameHunterLevel = context.hunterLevel + changed = true + end + if (row.tameZone == nil or row.tameZone == "") and context.zone ~= nil and tostring(context.zone) ~= "" then + row.tameZone = context.zone + changed = true + end + if (row.tameSubZone == nil or row.tameSubZone == "") and context.subZone ~= nil and tostring(context.subZone) ~= "" then + row.tameSubZone = context.subZone + changed = true + end + if row.tameX == nil and context.x ~= nil then + row.tameX = context.x + changed = true + end + if row.tameY == nil and context.y ~= nil then + row.tameY = context.y + changed = true + end + end + + if (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) and row.beastId ~= nil then + local beastId = tonumber(row.beastId) + if beastId and beastId > 0 then + row.tameBeastId = beastId + changed = true + end + end + + if changed then + row.tameRecorded = true + row.lastUpdated = time() + end + + return changed +end + local function MTH_PETS_EnsurePetStoreSchema(pets) if type(pets.petStore) ~= "table" then pets.petStore = {} @@ -767,6 +926,7 @@ local function MTH_PETS_EnsurePetStoreSchema(pets) end row.stableInfo.loyalty = nil end + MTH_PETS_BackfillTameMetadataFromEvents(row) end end end @@ -919,6 +1079,9 @@ local function MTH_PETS_EnsureSchema(pets) if pets.updatedAt == nil then pets.updatedAt = 0 end + if pets.createdAt == nil or tonumber(pets.createdAt) == nil or tonumber(pets.createdAt) <= 0 then + pets.createdAt = now + end if pets.currentPetId == nil then local cpId = (type(pets.currentPet) == "table") and pets.currentPet.id or nil pets.currentPetId = MTH_PETS_NormalizePetId(cpId) @@ -1244,8 +1407,11 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context) row.guid = snapshot.guid or row.guid row.beastId = snapshot.beastId or row.beastId local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table" - if hasPendingTame and snapshot.beastId and (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) then - row.tameBeastId = snapshot.beastId + if hasPendingTame and (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) then + local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot) + if resolvedTameBeastId and resolvedTameBeastId > 0 then + row.tameBeastId = resolvedTameBeastId + end end if hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet") and type(context) == "table" then row.tameRecorded = true @@ -1294,8 +1460,11 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context) row.tameSubZone = row.tameSubZone or context.subZone row.tameX = row.tameX or context.x row.tameY = row.tameY or context.y - if snapshot.beastId and tonumber(row.tameBeastId) == nil then - row.tameBeastId = snapshot.beastId + if tonumber(row.tameBeastId) == nil then + local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot) + if resolvedTameBeastId and resolvedTameBeastId > 0 then + row.tameBeastId = resolvedTameBeastId + end end end end @@ -1950,6 +2119,7 @@ function MTH_PETS_RefreshCurrentPet() local cp = pets.currentPet local previousCurrentId = cp and cp.id or nil + local hadPetBeforeRefresh = MTH_ST_LastUnitPetHadPet and true or false local now = time() local snapshot = MTH_PETS_MakeSnapshotFromLivePet() if pets.currentPetSuppressed == true and pets.currentPetSuppressedAwaitNoLive == true then @@ -1993,6 +2163,7 @@ function MTH_PETS_RefreshCurrentPet() MTH_PETS_SetCurrentPetId(pets, keepCurrentId) MTH_PETS_LogConsistency("RefreshCurrentPet no-live result current=" .. MTH_PETS_FormatCurrentConsistency(cp) .. " activeCurrentId=" .. tostring(petStore and petStore.activeCurrentId or nil)) + MTH_ST_LastUnitPetHadPet = false pets.updatedAt = now return end @@ -2029,11 +2200,54 @@ function MTH_PETS_RefreshCurrentPet() if cp.id and cp.id ~= previousCurrentId then MTH_PETS_RequestPetSpellScan("refresh-current-change") end + + local row = MTH_PETS_GetPetById(pets, petId) + if type(row) == "table" then + if type(row.events) ~= "table" then + row.events = {} + end + + local eventType = "pet-updated" + if created and (not hadPetBeforeRefresh) then + eventType = "pet-acquired" + end + + local eventContext = MTH_PETS_CaptureContext() + table.insert(row.events, { + type = eventType, + source = "refresh-current-pet", + at = now, + context = eventContext, + }) + + if eventType == "pet-acquired" then + local hasTameRecord = (row.tameRecorded == true) + or (tonumber(row.tamedAt) and tonumber(row.tamedAt) > 0) + or (type(row.tameZone) == "string" and row.tameZone ~= "") + if not hasTameRecord and type(eventContext) == "table" then + row.tameRecorded = true + row.tamedAt = row.tamedAt or eventContext.timestamp or now + row.tameHunterLevel = row.tameHunterLevel or eventContext.hunterLevel + row.tameZone = row.tameZone or eventContext.zone + row.tameSubZone = row.tameSubZone or eventContext.subZone + row.tameX = row.tameX or eventContext.x + row.tameY = row.tameY or eventContext.y + if tonumber(row.tameBeastId) == nil then + local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot) + if resolvedTameBeastId and resolvedTameBeastId > 0 then + row.tameBeastId = resolvedTameBeastId + end + end + end + end + end + if hadPendingTame and cp.id and cp.id ~= previousCurrentId then MTH_PETS_LogTame("RefreshCurrentPet acquire/change completed; clearing pending tame attempt") MTH_PETS_LastTameAttempt = nil end MTH_PETS_LogConsistency("RefreshCurrentPet live result current=" .. MTH_PETS_FormatCurrentConsistency(cp)) + MTH_ST_LastUnitPetHadPet = true pets.updatedAt = now end @@ -2300,6 +2514,7 @@ function MTH_ST_Scan(reason) local slotCount = tonumber(GetNumStableSlots()) or 0 local store = MTH_ST_GetStore() local petsRoot = MTH_PETS_GetRootStore() + MTH_PETS_RefreshCurrentPet() local scanContext = MTH_PETS_CaptureContext() local scanStableMasterName = MTH_PETS_GetStableMasterName() store.lastScan = time() @@ -2331,6 +2546,28 @@ function MTH_ST_Scan(reason) } end + local c1, c2, c3, c4, c5, c6, c7, c8 = GetStablePetInfo(0) + local currentPetId = (type(petsRoot) == "table" and type(petsRoot.currentPet) == "table") and petsRoot.currentPet.id or nil + if currentPetId and type(petsRoot.petStore) == "table" and type(petsRoot.petStore.activeById) == "table" then + local currentRow = petsRoot.petStore.activeById[currentPetId] + if type(currentRow) == "table" then + local currentIcon = tostring(c1 or "") + if currentIcon ~= "" then + currentRow.icon = currentIcon + currentRow.lastUpdated = time() + end + end + end + store.current = { + petId = currentPetId, + icon = c1, + name = c2, + level = c3, + family = c4, + loyalty = c5, + raw = { c1, c2, c3, c4, c5, c6, c7, c8 }, + } + MTH_PETS_MarkStableVisited(petsRoot, tostring(reason or "stable-scan")) MTH_PETS_RefreshCurrentPet() @@ -2541,6 +2778,54 @@ local function MTH_ST_DebugDumpLine(line) end end +local MTH_PETS_DebugBaselineSnapshot = nil + +local function MTH_ST_DebugCloneValue(value, seen) + if type(value) ~= "table" then + return value + end + seen = seen or {} + if seen[value] then + return seen[value] + end + local clone = {} + seen[value] = clone + for key, child in pairs(value) do + clone[MTH_ST_DebugCloneValue(key, seen)] = MTH_ST_DebugCloneValue(child, seen) + end + return clone +end + +local function MTH_PETS_CaptureDebugSnapshot(targetPetId) + local pets = MTH_PETS_GetRootStore() + if type(pets) ~= "table" then + return nil, "pets datastore unavailable" + end + + MTH_PETS_RefreshCurrentPet() + + local cp = (type(pets.currentPet) == "table") and pets.currentPet or nil + local petId = targetPetId or (cp and cp.id) or pets.currentPetId + if petId == nil or tostring(petId) == "" then + return nil, "no current pet id" + end + + local row = MTH_PETS_GetPetById(pets, petId) + if type(row) ~= "table" then + return nil, "active row missing for pet id=" .. tostring(petId) + end + + local snapshot = { + capturedAt = time(), + petId = tostring(petId), + currentPetId = pets.currentPetId, + currentPet = MTH_ST_DebugCloneValue(cp or {}), + row = MTH_ST_DebugCloneValue(row), + stableScan = MTH_ST_DebugCloneValue(pets.stableScan or {}), + } + return snapshot, nil +end + local function MTH_ST_DebugSortKeys(tbl) local keys = {} for k in pairs(tbl or {}) do @@ -2770,6 +3055,64 @@ function MTH_CommandPetsDump() end end +function MTH_CommandPetsSnap() + local snapshot, err = MTH_PETS_CaptureDebugSnapshot(nil) + if not snapshot then + MTH:Print("Pet snapshot failed: " .. tostring(err or "unknown")) + return false + end + MTH_PETS_DebugBaselineSnapshot = snapshot + MTH:Print("Pet snapshot saved for id=" .. tostring(snapshot.petId) .. ". Run /mth petsdiff after reproducing the issue.") + MTH_ST_DebugDumpLine("[PETSNAP] BASELINE BEGIN") + MTH_ST_DebugDumpValue("[PETSNAP].baseline", snapshot, 0, 5, {}) + MTH_ST_DebugDumpLine("[PETSNAP] BASELINE END") + return true +end + +function MTH_CommandPetsDiff() + if type(MTH_PETS_DebugBaselineSnapshot) ~= "table" then + MTH:Print("No pet snapshot baseline found. Run /mth petssnap first.") + return false + end + + local baseline = MTH_PETS_DebugBaselineSnapshot + local current, err = MTH_PETS_CaptureDebugSnapshot(baseline.petId) + if not current then + MTH:Print("Pet snapshot compare failed: " .. tostring(err or "unknown")) + return false + end + + MTH:Print("Pet snapshot compare for id=" .. tostring(current.petId) .. ".") + MTH_ST_DebugDumpLine("[PETSNAP] BEFORE BEGIN") + MTH_ST_DebugDumpValue("[PETSNAP].before", baseline, 0, 5, {}) + MTH_ST_DebugDumpLine("[PETSNAP] BEFORE END") + MTH_ST_DebugDumpLine("[PETSNAP] AFTER BEGIN") + MTH_ST_DebugDumpValue("[PETSNAP].after", current, 0, 5, {}) + MTH_ST_DebugDumpLine("[PETSNAP] AFTER END") + + local diffKeys = { + "name", "family", "level", "beastId", "icon", + "tameRecorded", "tamedAt", "tameZone", "tameSubZone", "tameX", "tameY", "tameBeastId", + "stableSlot", "stabledAt", "stableFirstSeenAt", "lastUnstabledAt", "lastSource", "lastUpdated", + } + local diffCount = 0 + for i = 1, table.getn(diffKeys) do + local key = diffKeys[i] + local beforeValue = baseline.row and baseline.row[key] or nil + local afterValue = current.row and current.row[key] or nil + if tostring(beforeValue) ~= tostring(afterValue) then + diffCount = diffCount + 1 + MTH_ST_DebugDumpLine("[PETSNAP] DIFF row." .. tostring(key) .. ": " .. tostring(beforeValue) .. " -> " .. tostring(afterValue)) + end + end + if diffCount <= 0 then + MTH_ST_DebugDumpLine("[PETSNAP] DIFF none") + end + + MTH_PETS_DebugBaselineSnapshot = current + return true +end + function MTH_CommandStableScan() if UnitClass then local _, classToken = UnitClass("player") @@ -2790,9 +3133,10 @@ function MTH_CommandStableScan() MTH:Print("Stable scan captured " .. tostring(slotCount) .. " slot(s).") end -local function MTH_ST_OnEvent(_, evt, eventArg1) +local function MTH_ST_OnEvent(_, evt, eventArg1, eventArg2) evt = evt or event eventArg1 = eventArg1 or arg1 + eventArg2 = eventArg2 or arg2 if not evt then return end @@ -2805,41 +3149,39 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) return end - if evt == "SPELLCAST_CHANNEL_START" then - MTH_PETS_RecordTameAttempt(evt, eventArg1) + local isUnitSpellcast = (evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_STOP" + or evt == "UNIT_SPELLCAST_FAILED" or evt == "UNIT_SPELLCAST_INTERRUPTED" + or evt == "UNIT_SPELLCAST_CHANNEL_START" or evt == "UNIT_SPELLCAST_CHANNEL_STOP") + local castSpellName = eventArg1 + if isUnitSpellcast then + if eventArg1 ~= "player" then + return + end + castSpellName = eventArg2 + end + + if evt == "SPELLCAST_START" or evt == "SPELLCAST_CHANNEL_START" + or evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_CHANNEL_START" then + MTH_PETS_RecordTameAttempt(evt, castSpellName) return end - if evt == "SPELLCAST_STOP" or evt == "SPELLCAST_CHANNEL_STOP" or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED" then - 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 evt == "SPELLCAST_STOP" or evt == "SPELLCAST_CHANNEL_STOP" or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED" + or evt == "UNIT_SPELLCAST_STOP" or evt == "UNIT_SPELLCAST_CHANNEL_STOP" + or evt == "UNIT_SPELLCAST_FAILED" or evt == "UNIT_SPELLCAST_INTERRUPTED" then + local terminalIsTame = MTH_PETS_IsTameBeastSpellName(castSpellName) if not terminalIsTame and type(MTH_PETS_LastTameAttempt) == "table" then terminalIsTame = true - 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 - 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 - 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 - if MTH_PETS_TRACE_TAME then - MTH_PETS_LogTame("Tame channel ended; awaiting UNIT_PET to confirm acquire") - end end end return @@ -2884,9 +3226,10 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) return end - if evt == "CHAT_MSG_SYSTEM" or evt == "UI_ERROR_MESSAGE" then + if evt == "CHAT_MSG_SYSTEM" or evt == "UI_ERROR_MESSAGE" + or evt == "CHAT_MSG_SPELL_PET_INFO" or evt == "CHAT_MSG_SPELL_PET_DAMAGE" + or evt == "CHAT_MSG_COMBAT_PET_HITS" or evt == "CHAT_MSG_COMBAT_PET_MISSES" then local matchedRunaway = MTH_PETS_IsRunawaySystemMessage(eventArg1) - MTH_PETS_TraceRunawayEvent(evt, eventArg1, matchedRunaway) if matchedRunaway then MTH_PETS_RecordPetRunaway(evt, eventArg1) MTH_PETS_RefreshCurrentPet() @@ -2896,7 +3239,18 @@ local function MTH_ST_OnEvent(_, evt, eventArg1) end if evt == "PET_STABLE_SHOW" then - MTH_ST_RunScanThrottled(evt, 1) + local scanned = MTH_ST_RunScanThrottled(evt, 1) + if scanned and MTH and MTH.Print then + local shouldPrint = true + if MTH.IsMessageEnabled then + shouldPrint = MTH:IsMessageEnabled("stableScan", true) + end + if shouldPrint then + local store = MTH_ST_GetStore() + local slotCount = tonumber(store and store.slotCount) or 0 + MTH:Print("Stable scan complete: " .. tostring(slotCount) .. " slot(s).") + end + end return end @@ -2925,16 +3279,28 @@ function MTH_ST_InitService() MTH_StableFrame = CreateFrame("Frame", "MTHStableScanFrame") MTH_StableFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + MTH_StableFrame:RegisterEvent("SPELLCAST_START") + MTH_StableFrame:RegisterEvent("SPELLCAST_STOP") MTH_StableFrame:RegisterEvent("SPELLCAST_CHANNEL_START") MTH_StableFrame:RegisterEvent("SPELLCAST_CHANNEL_STOP") MTH_StableFrame:RegisterEvent("SPELLCAST_FAILED") MTH_StableFrame:RegisterEvent("SPELLCAST_INTERRUPTED") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_START") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_STOP") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_FAILED") + MTH_StableFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") MTH_StableFrame:RegisterEvent("UNIT_PET") MTH_StableFrame:RegisterEvent("PET_BAR_UPDATE") MTH_StableFrame:RegisterEvent("UNIT_HAPPINESS") MTH_StableFrame:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN") MTH_StableFrame:RegisterEvent("PLAYER_XP_UPDATE") MTH_StableFrame:RegisterEvent("CHAT_MSG_SYSTEM") + MTH_StableFrame:RegisterEvent("CHAT_MSG_SPELL_PET_INFO") + MTH_StableFrame:RegisterEvent("CHAT_MSG_SPELL_PET_DAMAGE") + MTH_StableFrame:RegisterEvent("CHAT_MSG_COMBAT_PET_HITS") + MTH_StableFrame:RegisterEvent("CHAT_MSG_COMBAT_PET_MISSES") MTH_StableFrame:RegisterEvent("UI_ERROR_MESSAGE") MTH_StableFrame:RegisterEvent("PET_STABLE_SHOW") MTH_StableFrame:RegisterEvent("PET_STABLE_UPDATE") @@ -2960,15 +3326,17 @@ function MTH_ST_InitBootstrap() if not frame then return end + frame:RegisterEvent("PLAYER_ENTERING_WORLD") frame:RegisterEvent("PET_STABLE_SHOW") + frame:RegisterEvent("PET_STABLE_UPDATE") frame:SetScript("OnEvent", function(self, evt) evt = evt or event - if evt ~= "PET_STABLE_SHOW" then + if evt ~= "PLAYER_ENTERING_WORLD" and evt ~= "PET_STABLE_SHOW" and evt ~= "PET_STABLE_UPDATE" then return end MTH_ST_InitService() - if type(MTH_ST_Scan) == "function" then - MTH_ST_Scan("bootstrap:PET_STABLE_SHOW") + if (evt == "PET_STABLE_SHOW" or evt == "PET_STABLE_UPDATE") and type(MTH_ST_Scan) == "function" then + MTH_ST_Scan("bootstrap:" .. tostring(evt)) end if self and self.UnregisterAllEvents then self:UnregisterAllEvents() diff --git a/api/core.lua b/api/core.lua index 57debf7..71cdfa3 100644 --- a/api/core.lua +++ b/api/core.lua @@ -284,12 +284,37 @@ function SlashCmdList.MTH(msg, editbox) local lowerMsg = string.lower(msg) if msg == "" then MTH:Print("Available: /mth options, /mth book") + MTH:Print("Debug: /mth petsstate, /mth petsdump, /mth petssnap, /mth petsdiff") elseif lowerMsg == "options" then MTH_CommandOptions() elseif lowerMsg == "book" or lowerMsg == "hunterbook" then MTH_CommandBook() elseif lowerMsg == "peers" or lowerMsg == "who" then MTH_CommandPeers() + elseif lowerMsg == "petsstate" then + if type(MTH_CommandPetsState) == "function" then + MTH_CommandPetsState() + else + MTH:Print("Pets state command is not available yet") + end + elseif lowerMsg == "petsdump" then + if type(MTH_CommandPetsDump) == "function" then + MTH_CommandPetsDump() + else + MTH:Print("Pets dump command is not available yet") + end + elseif lowerMsg == "petssnap" then + if type(MTH_CommandPetsSnap) == "function" then + MTH_CommandPetsSnap() + else + MTH:Print("Pets snapshot command is not available yet") + end + elseif lowerMsg == "petsdiff" then + if type(MTH_CommandPetsDiff) == "function" then + MTH_CommandPetsDiff() + else + MTH:Print("Pets diff command is not available yet") + end elseif lowerMsg == "err" then if MTH_DebugFrame and type(MTH_DebugFrame.Toggle) == "function" then MTH_DebugFrame:Toggle() @@ -299,6 +324,7 @@ function SlashCmdList.MTH(msg, editbox) else MTH:Print("Unknown command: " .. tostring(msg)) MTH:Print("Available: /mth options, /mth book") + MTH:Print("Debug: /mth petsstate, /mth petsdump, /mth petssnap, /mth petsdiff") end end diff --git a/api/hunterbook-tab-families.lua b/api/hunterbook-tab-families.lua index 95c5222..cd049b1 100644 --- a/api/hunterbook-tab-families.lua +++ b/api/hunterbook-tab-families.lua @@ -7,8 +7,8 @@ MTH_HUNTERBOOK_TABS.families = { { x = 10, width = 106, align = "LEFT" }, { x = 122, width = 52, align = "LEFT" }, { x = 176, width = 52, align = "LEFT" }, - { x = 232, width = 292, align = "LEFT" }, - { x = 460, width = 184, align = "LEFT" }, + { x = 232, width = 364, align = "LEFT" }, + { x = 532, width = 112, align = "LEFT" }, }, } @@ -27,6 +27,11 @@ local function MTH_BOOKTAB_FamiliesTrim(value) return text end +local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) + local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName)) + return token == "bears" or token == "boars" +end + local function MTH_BOOKTAB_FindAbilityBundle(abilityName) if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then return nil @@ -95,25 +100,49 @@ function MTH_BOOKTAB_BuildFamiliesRows() if token ~= "" and not seen[token] then seen[token] = true local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {} + local firstAnySpell = nil local firstBeastSpell = nil - local rankMin = nil - local rankMax = nil - local rankMap = {} + local beastRankMin = nil + local beastRankMax = nil + local beastRankMap = {} + local anyRankMin = nil + local anyRankMax = nil + local anyRankMap = {} for s = 1, table.getn(spellRows) do local spell = spellRows[s] - if type(spell) == "table" and MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then - if not firstBeastSpell then - firstBeastSpell = spell + if type(spell) == "table" then + if not firstAnySpell then + firstAnySpell = spell end local rank = tonumber(spell.rankNumber) if rank and rank > 0 then - rankMap[rank] = true - if not rankMin or rank < rankMin then rankMin = rank end - if not rankMax or rank > rankMax then rankMax = rank end + anyRankMap[rank] = true + if not anyRankMin or rank < anyRankMin then anyRankMin = rank end + if not anyRankMax or rank > anyRankMax then anyRankMax = rank end + end + if MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then + if not firstBeastSpell then + firstBeastSpell = spell + end + if rank and rank > 0 then + beastRankMap[rank] = true + if not beastRankMin or rank < beastRankMin then beastRankMin = rank end + if not beastRankMax or rank > beastRankMax then beastRankMax = rank end + end end end end + local displaySpell = firstBeastSpell or firstAnySpell + local rankMap = beastRankMap + local rankMin = beastRankMin + local rankMax = beastRankMax + if not firstBeastSpell then + rankMap = anyRankMap + rankMin = anyRankMin + rankMax = anyRankMax + end + local rankCount = 0 for _ in pairs(rankMap) do rankCount = rankCount + 1 @@ -123,8 +152,8 @@ function MTH_BOOKTAB_BuildFamiliesRows() name = canonicalMap[token] or canonical, token = token, isUnique = (abilityCounts[token] or 0) == 1, - icon = firstBeastSpell and firstBeastSpell.icon or nil, - description = firstBeastSpell and firstBeastSpell.description or "", + icon = displaySpell and displaySpell.icon or nil, + description = displaySpell and displaySpell.description or "", rankMin = rankMin, rankMax = rankMax, rankCount = rankCount, @@ -141,17 +170,23 @@ function MTH_BOOKTAB_BuildFamiliesRows() return MTH_BOOKTAB_FamiliesSafeLower(a.name) < MTH_BOOKTAB_FamiliesSafeLower(b.name) end) - local dietItems = {} - if type(familyRow.food) == "table" then - for i = 1, table.getn(familyRow.food) do - local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i]) - if food ~= "" then - table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2)) + local dietText = "" + if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then + dietText = "ALL" + else + local dietItems = {} + if type(familyRow.food) == "table" then + for i = 1, table.getn(familyRow.food) do + local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i]) + if food ~= "" then + table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2)) + end end + table.sort(dietItems, function(a, b) + return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b) + end) end - table.sort(dietItems, function(a, b) - return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b) - end) + dietText = table.concat(dietItems, ", ") end table.insert(results, { @@ -159,7 +194,7 @@ function MTH_BOOKTAB_BuildFamiliesRows() named = tonumber(familyRow.named) or 0, coords = tonumber(familyRow.coords) or 0, abilities = abilities, - dietText = table.concat(dietItems, ", "), + dietText = dietText, }) end end @@ -238,7 +273,7 @@ function MTH_BOOKTAB_EnsureFamiliesUI() ui.headerAbilities:SetText("Abilities") ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") - ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 460, -2) + ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2) ui.headerDiet:SetTextColor(1.00, 0.82, 0.00) ui.headerDiet:SetText("Diet") @@ -270,8 +305,8 @@ function MTH_BOOKTAB_EnsureFamiliesUI() row.coords:SetJustifyH("LEFT") row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - row.diet:SetPoint("LEFT", row, "LEFT", 460, 0) - row.diet:SetWidth(184) + row.diet:SetPoint("LEFT", row, "LEFT", 532, 0) + row.diet:SetWidth(112) row.diet:SetJustifyH("LEFT") row.abilityButtons = {} @@ -363,6 +398,7 @@ function MTH_BOOKTAB_RenderFamiliesList() rowFrame.diet:SetText(tostring(row.dietText or "-")) local offsetX = 232 + local abilityMaxRight = 526 for b = 1, table.getn(rowFrame.abilityButtons) do local button = rowFrame.abilityButtons[b] local ability = row.abilities and row.abilities[b] or nil @@ -375,17 +411,23 @@ function MTH_BOOKTAB_RenderFamiliesList() button.text:SetText(nameText) local textW = button.text:GetStringWidth() or 40 if textW < 24 then textW = 24 end - button:SetWidth(14 + textW) - button:ClearAllPoints() - button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0) - local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil - if iconPath then - button.icon:SetTexture(iconPath) + local buttonWidth = 14 + textW + if (offsetX + buttonWidth) > abilityMaxRight then + button.entry = nil + button:Hide() else - button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark") + button:SetWidth(buttonWidth) + button:ClearAllPoints() + button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0) + local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil + if iconPath then + button.icon:SetTexture(iconPath) + else + button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark") + end + button:Show() + offsetX = offsetX + button:GetWidth() + 6 end - button:Show() - offsetX = offsetX + button:GetWidth() + 6 else button.entry = nil button:Hide() diff --git a/api/hunterbook.lua b/api/hunterbook.lua index f8d6116..eddd642 100644 --- a/api/hunterbook.lua +++ b/api/hunterbook.lua @@ -1002,9 +1002,9 @@ end local function MTH_BOOK_ParseProjectileDPSFromText(text) if type(text) ~= "string" or text == "" then return nil end local lowered = string.lower(text) - local raw = string.match(lowered, "adds%s+([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?") + local _, _, raw = string.find(lowered, "adds%s+([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?") if not raw then - raw = string.match(lowered, "([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?") + _, _, raw = string.find(lowered, "([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?") end if not raw then return nil end local normalized = string.gsub(raw, ",", ".") diff --git a/api/version-check.lua b/api/version-check.lua index b44ef0c..9f90ba6 100644 --- a/api/version-check.lua +++ b/api/version-check.lua @@ -4,6 +4,7 @@ MTH.VersionCheck = MTH.VersionCheck or {} local VC = MTH.VersionCheck VC.abbrev = "MTH" VC.channelName = "LFT" +VC.updateUrl = "https://github.com/DuvelCorp/MetaHunt" VC.nextPublishAt = nil VC.joinAt = nil VC.notified = false @@ -290,7 +291,12 @@ function VC:HandleRemoteMessage(message, author, channelLabel) self.notified = true self.nextPublishAt = nil if MTH and type(MTH.Print) == "function" then - MTH:Print("A new version is available !") + local localVersionText = tostring(self:GetLocalVersionString() or "?") + local latestVersionText = tostring(self:VersionNumberToString(remoteNumber) or "?") + MTH:Print("A new version is available!") + MTH:Print("YOU SHOULD UPGRADE ! Your version : " .. localVersionText + .. " - Latest version " .. latestVersionText + .. " --> " .. tostring(self.updateUrl or "")) end if VC.frame then diff --git a/data/ds-beasts-families.lua b/data/ds-beasts-families.lua index b650302..9ccc50e 100644 --- a/data/ds-beasts-families.lua +++ b/data/ds-beasts-families.lua @@ -13,8 +13,10 @@ MTH_DS_Families = { "fungus", }, ["abilities"] = { + "Bite", "Cower", "Dive", + "Growl", "Screech", }, }, @@ -23,14 +25,17 @@ MTH_DS_Families = { ["coords"] = 45, ["food"] = { "meat", - "fruit", "fish", - "fungus", - "cheese", "bread", + "cheese", + "fruit", + "fungus", }, ["abilities"] = { + "Bite", "Claw", + "Cower", + "Growl", "Roar of Fortitude", }, }, @@ -39,15 +44,18 @@ MTH_DS_Families = { ["coords"] = 87, ["food"] = { "meat", - "fruit", "fish", - "fungus", - "cheese", "bread", + "cheese", + "fruit", + "fungus", }, ["abilities"] = { + "Bite", "Charge", + "Cower", "Dash", + "Growl", }, }, ["Carrion Birds"] = { @@ -57,7 +65,11 @@ MTH_DS_Families = { "meat", }, ["abilities"] = { + "Bite", + "Claw", + "Cower", "Dive", + "Growl", "Screech", }, }, @@ -69,9 +81,11 @@ MTH_DS_Families = { "fish", }, ["abilities"] = { + "Bite", "Claw", "Cower", "Dash", + "Growl", "Prowl", }, }, @@ -87,6 +101,8 @@ MTH_DS_Families = { ["abilities"] = { "Bubble Barrier", "Claw", + "Cower", + "Growl", }, }, ["Crocolisks"] = { @@ -97,10 +113,10 @@ MTH_DS_Families = { "fish", }, ["abilities"] = { - "BIte", "Bite", - "Claw", + "Cower", "Death Roll", + "Growl", }, }, ["Foxes"] = { @@ -112,7 +128,10 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", + "Dash", "Grace", + "Growl", }, }, ["Gorillas"] = { @@ -123,6 +142,9 @@ MTH_DS_Families = { "fungus", }, ["abilities"] = { + "Bite", + "Cower", + "Growl", "Thunderstomp", }, }, @@ -135,7 +157,9 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", "Dash", + "Growl", "Packleader", }, }, @@ -147,7 +171,9 @@ MTH_DS_Families = { }, ["abilities"] = { "Claw", + "Cower", "Dive", + "Growl", "Screech", }, }, @@ -158,6 +184,11 @@ MTH_DS_Families = { "meat", }, ["abilities"] = { + "Bite", + "Claw", + "Cower", + "Dash", + "Growl", "Savage Rend", }, }, @@ -169,6 +200,8 @@ MTH_DS_Families = { }, ["abilities"] = { "Claw", + "Cower", + "Growl", "Scorpid Poison", }, }, @@ -181,6 +214,8 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", + "Growl", "Poison Spit", }, }, @@ -192,6 +227,8 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", + "Growl", "Web", }, }, @@ -206,6 +243,8 @@ MTH_DS_Families = { ["abilities"] = { "Bite", "Cower", + "Dash", + "Growl", "Strider Presence", }, }, @@ -219,6 +258,8 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", + "Growl", "Shell Shield", }, }, @@ -231,7 +272,10 @@ MTH_DS_Families = { "bread", }, ["abilities"] = { + "Bite", + "Cower", "Dive", + "Growl", "Lightning Breath", }, }, @@ -243,8 +287,10 @@ MTH_DS_Families = { }, ["abilities"] = { "Bite", + "Cower", "Dash", "Furious Howl", + "Growl", }, }, } \ No newline at end of file diff --git a/data/ds-pet-spells.lua b/data/ds-pet-spells.lua index f4106f7..a2a1f3d 100644 --- a/data/ds-pet-spells.lua +++ b/data/ds-pet-spells.lua @@ -1355,14 +1355,15 @@ MTH_DS_PetSpells = { { ["ability"] = "Roar of Fortitude", ["castTime"] = "Instant", - ["categoryCooldown"] = "180 seconds", - ["cooldown"] = "n/a", - ["cost"] = "None", - ["description"] = "Increases max hitpoints of allies in your party by 200 for 120 sec", + ["categoryCooldown"] = "n/a", + ["cooldown"] = "120 seconds", + ["cost"] = "50 mana", + ["description"] = "Let out a fortifying roar, reducing damage taken by 8% and increasing all damage dealt by 3% for your party members within 30 yards for 12 sec.", ["effects"] = { - "(6) Apply Aura #34: Mod Increase Health Value: 200 Radius: 20 yards", + "(6) Apply Aura #87: Mod Dmg % Taken (127) Value: -8 Radius: 30 yards", + "(6) Apply Aura #79: Mod Dmg % (127) Value: 3 Radius: 30 yards", }, - ["icon"] = "Ability_BullRush", + ["icon"] = "Ability_Druid_ChallangingRoar", ["id"] = 36535, ["name"] = "Roar of Fortitude", ["range"] = "0 yards (Self Only)", @@ -3245,14 +3246,15 @@ MTH_DS_PetSpells = { { ["ability"] = "Roar of Fortitude", ["castTime"] = "Instant", - ["categoryCooldown"] = "180 seconds", - ["cooldown"] = "n/a", - ["cost"] = "None", - ["description"] = "Increases max hitpoints of allies in your party by 200 for 120 sec", + ["categoryCooldown"] = "n/a", + ["cooldown"] = "120 seconds", + ["cost"] = "50 mana", + ["description"] = "Let out a fortifying roar, reducing damage taken by 8% and increasing all damage dealt by 3% for your party members within 30 yards for 12 sec.", ["effects"] = { - "(6) Apply Aura #34: Mod Increase Health Value: 200 Radius: 20 yards", + "(6) Apply Aura #87: Mod Dmg % Taken (127) Value: -8 Radius: 30 yards", + "(6) Apply Aura #79: Mod Dmg % (127) Value: 3 Radius: 30 yards", }, - ["icon"] = "Ability_BullRush", + ["icon"] = "Ability_Druid_ChallangingRoar", ["id"] = 36535, ["name"] = "Roar of Fortitude", ["range"] = "0 yards (Self Only)", diff --git a/modules/autobuy/module.lua b/modules/autobuy/module.lua index d76ee60..19ec24c 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.5", + version = "1.0.6", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", diff --git a/modules/chronometer/module.lua b/modules/chronometer/module.lua index c73de47..f705453 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.5", + version = "1.0.6", events = {}, initialized = false, } diff --git a/modules/feedomatic/module.lua b/modules/feedomatic/module.lua index 175741f..c87a7d2 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.5", + version = "1.0.6", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", diff --git a/modules/smartammo/module.lua b/modules/smartammo/module.lua index 6405e04..45802b2 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.5", + version = "1.0.6", events = { "VARIABLES_LOADED", "PLAYER_ENTERING_WORLD", diff --git a/modules/tooltips/module.lua b/modules/tooltips/module.lua index 47fd8ff..7442b1d 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.5", + version = "1.0.6", events = { "UPDATE_MOUSEOVER_UNIT", "UNIT_NAME_UPDATE", @@ -864,7 +864,37 @@ local function MTH_TT_FindAbilityCanonicalName(abilityName) return nil end -local function MTH_TT_ParseRankFromText(value) +local function MTH_TT_AbilityHasPositiveRanks(canonicalAbilityName) + if canonicalAbilityName == nil or canonicalAbilityName == "" then + return false + end + if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then + return false + end + + local bucket = MTH_DS_PetSpells.byAbility[canonicalAbilityName] + if not (bucket and type(bucket.spells) == "table") then + return false + end + + for i = 1, table.getn(bucket.spells) do + local spell = bucket.spells[i] + if spell then + local rank = tonumber(spell.rankNumber) + if not rank and spell.rank then + local _, _, parsed = string.find(tostring(spell.rank), "(%d+)") + rank = tonumber(parsed) + end + if rank and rank > 0 then + return true + end + end + end + + return false +end + +local function MTH_TT_ParseRankFromText(value, allowTrailingNumber) local text = MTH_TT_Trim(value) if text == "" then return nil @@ -873,9 +903,11 @@ local function MTH_TT_ParseRankFromText(value) if rankText then return tonumber(rankText) end - local _, _, trailing = string.find(text, "(%d+)$") - if trailing then - return tonumber(trailing) + if allowTrailingNumber == true then + local _, _, trailing = string.find(text, "(%d+)$") + if trailing then + return tonumber(trailing) + end end return nil end @@ -903,10 +935,10 @@ local function MTH_TT_GetTooltipSpellNameAndRank() if ok then if type(a) == "string" then spellName = a - rankNumber = MTH_TT_ParseRankFromText(b) + rankNumber = MTH_TT_ParseRankFromText(b, true) elseif type(b) == "string" then spellName = b - rankNumber = MTH_TT_ParseRankFromText(a) + rankNumber = MTH_TT_ParseRankFromText(a, true) end end end @@ -918,24 +950,28 @@ local function MTH_TT_GetTooltipSpellNameAndRank() end end + if not rankNumber then + rankNumber = MTH_TT_ParseRankFromText(spellName, true) + end + if not rankNumber then local right1 = getglobal("GameTooltipTextRight1") if right1 and right1.GetText then - rankNumber = MTH_TT_ParseRankFromText(right1:GetText()) + rankNumber = MTH_TT_ParseRankFromText(right1:GetText(), false) end end if not rankNumber then local left2 = getglobal("GameTooltipTextLeft2") if left2 and left2.GetText then - rankNumber = MTH_TT_ParseRankFromText(left2:GetText()) + rankNumber = MTH_TT_ParseRankFromText(left2:GetText(), false) end end if not rankNumber then local right2 = getglobal("GameTooltipTextRight2") if right2 and right2.GetText then - rankNumber = MTH_TT_ParseRankFromText(right2:GetText()) + rankNumber = MTH_TT_ParseRankFromText(right2:GetText(), false) end end @@ -944,7 +980,7 @@ local function MTH_TT_GetTooltipSpellNameAndRank() local left = getglobal("GameTooltipTextLeft" .. i) if left and left.GetText then local text = MTH_TT_Trim(left:GetText()) - local parsed = MTH_TT_ParseRankFromText(text) + local parsed = MTH_TT_ParseRankFromText(text, false) if parsed and parsed > 0 then rankNumber = parsed break @@ -952,7 +988,7 @@ local function MTH_TT_GetTooltipSpellNameAndRank() end local right = getglobal("GameTooltipTextRight" .. i) if right and right.GetText then - local parsedRight = MTH_TT_ParseRankFromText(right:GetText()) + local parsedRight = MTH_TT_ParseRankFromText(right:GetText(), false) if parsedRight and parsedRight > 0 then rankNumber = parsedRight break @@ -964,6 +1000,87 @@ local function MTH_TT_GetTooltipSpellNameAndRank() return MTH_TT_CleanSpellName(spellName), rankNumber end +local function MTH_TT_GetCurrentPetRowForTooltip() + local pets = nil + if type(MTH_PETS_GetRootStore) == "function" then + pets = MTH_PETS_GetRootStore() + elseif type(MTH_CharSavedVariables) == "table" then + pets = MTH_CharSavedVariables.MTH_Pets + end + if type(pets) ~= "table" then + return nil + end + + local currentId = pets.currentPetId + if (currentId == nil or tostring(currentId) == "") and type(pets.currentPet) == "table" and pets.currentPet.exists == true then + currentId = pets.currentPet.id + end + if currentId == nil or tostring(currentId) == "" then + return nil + end + + local activeById = type(pets.petStore) == "table" and pets.petStore.activeById or nil + if type(activeById) ~= "table" then + return nil + end + return activeById[currentId] or activeById[tostring(currentId)] +end + +local function MTH_TT_CurrentPetKnowsAbility(abilityLower, rankNumber) + if abilityLower == "" then + return false + end + local row = MTH_TT_GetCurrentPetRowForTooltip() + if type(row) ~= "table" then + return false + end + + local wantedRank = tonumber(rankNumber) + + local spellbook = row.petSpellbook + if type(spellbook) == "table" and type(spellbook.spells) == "table" then + for i = 1, table.getn(spellbook.spells) do + local spell = spellbook.spells[i] + if type(spell) == "table" then + local spellLower = MTH_TT_Lower(MTH_TT_CleanSpellName(spell.name)) + if spellLower == abilityLower then + if wantedRank then + local spellRank = tonumber(spell.rank) + if not spellRank then + spellRank = MTH_TT_ParseRankFromText(spell.name, true) + end + if spellRank and tonumber(spellRank) == wantedRank then + return true + end + else + return true + end + end + end + end + end + + if type(row.abilities) == "table" then + for _, ability in pairs(row.abilities) do + if type(ability) == "table" then + local abilityLowerName = MTH_TT_Lower(MTH_TT_CleanSpellName(ability.name)) + if abilityLowerName == abilityLower then + if wantedRank then + local abilityRank = tonumber(ability.rank) + if abilityRank and abilityRank == wantedRank then + return true + end + else + return true + end + end + end + end + end + + return false +end + local function MTH_TT_IsPetActionTooltipOwner() if not GameTooltip or type(GameTooltip.GetOwner) ~= "function" then return false @@ -1050,7 +1167,15 @@ local function MTH_TT_AddPetActionNotLearnedHint() return end - local known = MTH_TT_HunterKnowsAbility(MTH_TT_Lower(canonical), rankNumber) + if rankNumber and rankNumber > 0 and not MTH_TT_AbilityHasPositiveRanks(canonical) then + rankNumber = nil + end + + local canonicalLower = MTH_TT_Lower(canonical) + local known = MTH_TT_HunterKnowsAbility(canonicalLower, rankNumber) + if not known then + known = MTH_TT_CurrentPetKnowsAbility(canonicalLower, rankNumber) + end if known then return end diff --git a/modules/zhunter/ZSpellButtonTemplate.lua b/modules/zhunter/ZSpellButtonTemplate.lua index eee910f..98ffd6d 100644 --- a/modules/zhunter/ZSpellButtonTemplate.lua +++ b/modules/zhunter/ZSpellButtonTemplate.lua @@ -384,9 +384,36 @@ function ZSpellButton_OnEnter() if this.isspell then GameTooltip:SetSpell(this.id, "spell") else - GameTooltip:SetText(this.ammocount .. " x "..this.ammolink, 1, 1, 1) - --GameTooltip:AddLine("Q="..this.ammocount) + local showedItemTooltip = nil + if this.ammobag and this.ammoslot and type(GameTooltip.SetBagItem) == "function" then + GameTooltip:ClearLines() + GameTooltip:SetBagItem(this.ammobag, this.ammoslot) + if type(GameTooltip.NumLines) == "function" and GameTooltip:NumLines() > 0 then + local left1 = getglobal("GameTooltipTextLeft1") + if left1 and left1.GetText and tostring(left1:GetText() or "") ~= "" then + showedItemTooltip = true + end + end + end + if (not showedItemTooltip) and this.ammolink and type(GameTooltip.SetHyperlink) == "function" then + GameTooltip:ClearLines() + GameTooltip:SetHyperlink(this.ammolink) + if type(GameTooltip.NumLines) == "function" and GameTooltip:NumLines() > 0 then + local left1 = getglobal("GameTooltipTextLeft1") + if left1 and left1.GetText and tostring(left1:GetText() or "") ~= "" then + showedItemTooltip = true + end + end + end + if not showedItemTooltip then + local label = this.ammoname or this.ammolink or "Ammo" + if this.ammocount then + label = tostring(this.ammocount) .. " x " .. tostring(label) + end + GameTooltip:SetText(label, 1, 1, 1) + end end + GameTooltip:Show() end end @@ -488,11 +515,38 @@ function ZSpellButtonParent_OnEnter(frame) if string.len(rank) > 0 then msg = msg.." ("..rank..")" end + GameTooltip:SetText(msg, 1, 1, 1) else - msg = frame.ammoname + local showedItemTooltip = nil + if frame.ammobag and frame.ammoslot and type(GameTooltip.SetBagItem) == "function" then + GameTooltip:ClearLines() + GameTooltip:SetBagItem(frame.ammobag, frame.ammoslot) + if type(GameTooltip.NumLines) == "function" and GameTooltip:NumLines() > 0 then + local left1 = getglobal("GameTooltipTextLeft1") + if left1 and left1.GetText and tostring(left1:GetText() or "") ~= "" then + showedItemTooltip = true + end + end + end + if (not showedItemTooltip) and frame.ammolink and type(GameTooltip.SetHyperlink) == "function" then + GameTooltip:ClearLines() + GameTooltip:SetHyperlink(frame.ammolink) + if type(GameTooltip.NumLines) == "function" and GameTooltip:NumLines() > 0 then + local left1 = getglobal("GameTooltipTextLeft1") + if left1 and left1.GetText and tostring(left1:GetText() or "") ~= "" then + showedItemTooltip = true + end + end + end + if not showedItemTooltip then + msg = frame.ammoname or frame.ammolink or "Ammo" + if frame.ammocount then + msg = tostring(frame.ammocount) .. " x " .. tostring(msg) + end + GameTooltip:SetText(msg, 1, 1, 1) + end end - GameTooltip:SetText(msg, 1, 1, 1) GameTooltip:AddLine("Alt+Drag To Move This Button") GameTooltip:Show() end diff --git a/modules/zhunter/module.lua b/modules/zhunter/module.lua index b704cb2..0ed7bb2 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.5", + version = "1.0.6", events = { "VARIABLES_LOADED", "PLAYER_ENTERING_WORLD", diff --git a/modules/zhunter/zButtonTrack.lua b/modules/zhunter/zButtonTrack.lua index c90d71b..12f822a 100644 --- a/modules/zhunter/zButtonTrack.lua +++ b/modules/zhunter/zButtonTrack.lua @@ -38,6 +38,7 @@ ZHunterMod_Track_Spells = { local ZHUNTER_TRACK_MAX = table.getn(ZHunterMod_Track_Spells) local zButtonTrack_LastTrackingTexture = nil local zButtonTrack_SyncActiveChild +local zButtonTrack_GetSaved local function zButtonTrack_NormalizeTexture(texture) if not texture then @@ -187,7 +188,7 @@ local function zButtonTrack_RefreshTrackingState() end end -local function zButtonTrack_GetSaved() +zButtonTrack_GetSaved = function() local currentRoot = zButtonTrack_GetRoot() if not currentRoot["zButtonTrack"] then currentRoot["zButtonTrack"] = {} @@ -273,6 +274,9 @@ function zButtonTrack_OnEvent() zButtonTrackAdjustment = CreateFrame("Frame", "zButtonTrackAdjustment") zButtonTrackAdjustment:RegisterEvent("MINIMAP_UPDATE_TRACKING") zButtonTrackAdjustment:RegisterEvent("PLAYER_ENTERING_WORLD") + zButtonTrackAdjustment:RegisterEvent("SPELLS_CHANGED") + zButtonTrackAdjustment:RegisterEvent("CHARACTER_POINTS_CHANGED") + zButtonTrackAdjustment:RegisterEvent("LEARNED_SPELL_IN_TAB") zButtonTrackAdjustment:SetScript("OnEvent", zButtonTrackAdjustment_OnEvent) zButtonTrack_SetupSizeAndPosition() end @@ -355,8 +359,12 @@ function zButtonTrackAdjustment_OnEvent() if not zButtonTrack or not zButtonTrack.count then return end + if event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then + zButtonTrack_CreateButtons() + zButtonTrack_SetupSizeAndPosition() + end if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_ENTERING_WORLD" - then + or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then zButtonTrack_RefreshTrackingState() end end @@ -389,6 +397,7 @@ SlashCmdList["zButtonTrack"] = function(msg) if MTH_ZH_HandleDisabledSlash and MTH_ZH_HandleDisabledSlash("Track button is disabled while module 'zhunter' is disabled.") then return end + msg = tostring(msg or "") if msg == "reset" then zButtonTrack_Reset() zButtonTrack:ClearAllPoints()