v9.7.2: Ebonhold Database integration and core logic refinements

This commit is contained in:
Xurkon
2026-02-14 21:13:44 -06:00
parent 0a2cc15641
commit 6ef85d4e2d
573 changed files with 1751730 additions and 2 deletions
+116
View File
@@ -0,0 +1,116 @@
---@class ChatFilter
local ChatFilter = QuestieLoader:CreateModule("ChatFilter")
---@type QuestieLink
local QuestieLink = QuestieLoader:ImportModule("QuestieLink")
---@type QuestieDB
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
---------------------------------------------------------------------------------------------------
-- These must be loaded in order together and loaded before the hook for custom quest links
-- The Hyperlink hook is located in Link.lua
---------------------------------------------------------------------------------------------------
--- Message Event Filter which intercepts incoming linked quests and replaces them with Hyperlinks
ChatFilter.Filter = function(chatFrame, _, msg, playerName, languageName, channelName, playerName2, specialFlags, zoneChannelID, channelIndex, channelBaseName, unused, lineID, senderGUID, bnSenderID, ...)
if (not Questie.started) then
return
end
if string.find(msg, "%[(..-) %((%d+)%)%]") then
if chatFrame and ((chatFrame.historyBuffer and #(chatFrame.historyBuffer.elements) > 0) or QuestieCompat.Is335) and chatFrame ~= _G.ChatFrame2 then
for k in string.gmatch(msg, "%[%[?%d?..?%]?..-%]") do
local sqid, questId, questLevel, questName
questName, sqid = string.match(k, "%[(..-) %((%d+)%)%]")
if questName and sqid then
questId = tonumber(sqid)
if string.find(questName, "(%[%d+.-%]) ") ~= nil then
questLevel, questName = string.match(questName, "%[(..-)%] (.+)")
end
end
if questId and QuestieDB.QuestPointers[questId] then
if (not senderGUID) then
playerName = BNGetFriendInfoByID(bnSenderID)
senderGUID = bnSenderID
end
local questLink = QuestieLink:GetQuestHyperLink(questId, senderGUID)
-- Escape the magic characters
local function escapeMagic(toEsc)
return (toEsc
:gsub("%%", "%%%%")
:gsub("^%^", "%%^")
:gsub("%$$", "%%$")
:gsub("%(", "%%(")
:gsub("%)", "%%)")
:gsub("%.", "%%.")
:gsub("%[", "%%[")
:gsub("%]", "%%]")
:gsub("%*", "%%*")
:gsub("%+", "%%+")
:gsub("%-", "%%-")
:gsub("%?", "%%?")
:gsub("%|", "%%|")
)
end
if questName then
questName = escapeMagic(questName)
end
if questLevel then
questLevel = escapeMagic(questLevel)
end
if questLevel then
msg = string.gsub(msg, "%[%["..questLevel.."%] "..questName.." %("..sqid.."%)%]", questLink)
else
msg = string.gsub(msg, "%["..questName.." %("..sqid.."%)%]", questLink)
end
end
end
return false, msg, playerName, languageName, channelName, playerName2, specialFlags, zoneChannelID, channelIndex, channelBaseName, unused, lineID, senderGUID, bnSenderID, ...
end
end
end
function ChatFilter:RegisterEvents() -- todo: register immediately and cache calls until db is available
-- The message filter that triggers the above local function
-- Party
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY_LEADER", ChatFilter.Filter)
-- Raid
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_LEADER", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_WARNING", ChatFilter.Filter)
-- Guild
ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_OFFICER", ChatFilter.Filter)
-- Battleground
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT_LEADER", ChatFilter.Filter)
-- Whisper
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", ChatFilter.Filter)
-- Battle Net
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER_INFORM", ChatFilter.Filter)
-- Open world
ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", ChatFilter.Filter)
ChatFrame_AddMessageEventFilter("CHAT_MSG_YELL", ChatFilter.Filter)
-- Emote
ChatFrame_AddMessageEventFilter("CHAT_MSG_EMOTE", ChatFilter.Filter)
end
+56
View File
@@ -0,0 +1,56 @@
local WatchFrame_Update = QuestWatch_Update or WatchFrame_Update
---@class Hooks
local Hooks = QuestieLoader:CreateModule("Hooks")
---@type QuestieTracker
local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker")
---@type QuestieLink
local QuestieLink = QuestieLoader:ImportModule("QuestieLink")
--- COMPATIBILITY ---
local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle
local GetQuestIDFromLogIndex = QuestieCompat.GetQuestIDFromLogIndex
function Hooks:HookQuestLogTitle()
Questie:Debug(Questie.DEBUG_DEVELOP, "[Hooks] Hooking Quest Log Title")
local baseQLTB_OnClick = QuestLogTitleButton_OnClick
-- We can not use hooksecurefunc because this needs to be a pre-hook to work properly unfortunately
QuestLogTitleButton_OnClick = function(self, button)
if (not self) or self.isHeader then
baseQLTB_OnClick(self, button)
return
end
local questLogLineIndex
if Questie.IsWotlk or QuestieCompat.Is335 then
-- With Wotlk the offset is no longer required cause the API already hands the correct index
questLogLineIndex = self:GetID()
else
questLogLineIndex = self:GetID() + FauxScrollFrame_GetOffset(QuestLogListScrollFrame)
end
-- Handle Shift+Click quest linking
if (IsModifiedClick("CHATLINK") and ChatEdit_GetActiveWindow()) then
-- Follow Ascension's exact native pattern
local questLink = GetQuestLink(questLogLineIndex)
if questLink then
ChatEdit_InsertLink(questLink)
end
QuestLog_SetSelection(questLogLineIndex)
return
end
-- For all other clicks (including tracking/untracking), use the original function
-- only call Questie's tracker if we actually want to fix this quest (normal quests already call AQW_insert)
if Questie.db.profile.trackerEnabled and GetNumQuestLeaderBoards(questLogLineIndex) == 0 and (not IsQuestWatched(questLogLineIndex)) then
QuestieTracker:AQW_Insert(questLogLineIndex, QUEST_WATCH_NO_EXPIRE)
WatchFrame_Update()
QuestLog_SetSelection(questLogLineIndex)
QuestLog_Update()
else
baseQLTB_OnClick(self, button)
end
end
end
+381
View File
@@ -0,0 +1,381 @@
---@class QuestieLink
local QuestieLink = QuestieLoader:CreateModule("QuestieLink")
-------------------------
--Import modules
-------------------------
---@type QuestieDB
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
---@type QuestieLib
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
---@type QuestiePlayer
local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer")
---@type TrackerUtils
local TrackerUtils = QuestieLoader:ImportModule("TrackerUtils")
---@type l10n
local l10n = QuestieLoader:ImportModule("l10n")
--- COMPATIBILITY ---
local GetQuestLink = QuestieCompat.GetQuestLink
local CALENDAR_WEEKDAY_NAMES = QuestieCompat.CALENDAR_WEEKDAY_NAMES
local CALENDAR_FULLDATE_MONTH_NAMES = QuestieCompat.CALENDAR_FULLDATE_MONTH_NAMES
QuestieLink.lastItemRefTooltip = ""
-- Forward declaration
local _AddQuestTitle, _AddQuestStatus, _AddQuestDescription, _AddQuestRequirements, _GetQuestStarter, _GetQuestFinisher, _AddPlayerQuestProgress
local _AddTooltipLine, _AddColoredTooltipLine
local oldItemSetHyperlink = ItemRefTooltip.SetHyperlink
--- Override of the default SetHyperlink function to filter Questie links
---@param link string
function ItemRefTooltip:SetHyperlink(link, ...)
local questiePrefix, questId = string.match(link, "(questie):(%d+):")
local isQuestieLink = questiePrefix == "questie"
if (not ItemRefTooltip:IsShown()) then
QuestieLink.lastItemRefTooltip = ""
else
QuestieLink.lastItemRefTooltip = QuestieLink.lastItemRefTooltip or link
end
if isQuestieLink and questId then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:ItemRefTooltip] SetHyperlink:", link)
ShowUIPanel(ItemRefTooltip)
ItemRefTooltip:SetOwner(UIParent, "ANCHOR_PRESERVE");
QuestieLink:CreateQuestTooltip(link)
ItemRefTooltip:Show()
local tooltipText = ItemRefTooltipTextLeft1:GetText()
if QuestieLink.lastItemRefTooltip == tooltipText then
ItemRefTooltip:Hide()
QuestieLink.lastItemRefTooltip = ""
return
end
QuestieLink.lastItemRefTooltip = tooltipText
return
else
-- Make sure to call the default function so everything that is not Questie can be handled (item links e.g.)
oldItemSetHyperlink(self, link, ...)
end
end
---@return string
function QuestieLink:GetQuestLinkStringById(questId)
local questName = QuestieDB.QueryQuestSingle(questId, "name");
local questLevel, _ = QuestieLib.GetTbcLevel(questId);
return QuestieLink:GetQuestLinkString(questLevel, questName, questId)
end
---@return string
function QuestieLink:GetQuestLinkString(questLevel, questName, questId)
local questLink = GetQuestLink and GetQuestLink(questId)
local questString = "["..questName.." ("..tostring(questId)..")]"
if Questie.db.profile.trackerShowQuestLevel then
questString = questString:gsub("%[", "[["..tostring(questLevel).."] ")
end
return questLink and questLink:gsub("%[(.-)%]", questString) or questString
end
---@return string
function QuestieLink:GetQuestHyperLink(questId, senderGUID)
local coloredQuestName = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.trackerShowQuestLevel, true, false)
local questLevel, _ = QuestieLib.GetTbcLevel(questId)
local isRepeatable = QuestieDB.IsRepeatable(questId)
if (not senderGUID) then
senderGUID = UnitGUID("player")
end
return "|Hquestie:"..questId..":"..senderGUID.."|h"..QuestieLib:PrintDifficultyColor(questLevel, "[", isRepeatable, QuestieDB.IsActiveEventQuest(questId), QuestieDB.IsPvPQuest(questId))..coloredQuestName..QuestieLib:PrintDifficultyColor(questLevel, "]", isRepeatable, QuestieDB.IsActiveEventQuest(questId), QuestieDB.IsPvPQuest(questId)).."|h"
end
function QuestieLink:CreateQuestTooltip(link)
local isQuestieLink, _, _ = string.match(link, "questie:(%d+):.*")
if isQuestieLink then
---@type string
local questIdStr = select(2, strsplit(":", link))
local questId = tonumber(questIdStr)
local quest = QuestieDB.GetQuest(questId)
if quest then
_AddQuestTitle(quest)
_AddQuestStatus(quest)
_AddTooltipLine(" ")
_AddQuestDescription(quest)
_AddQuestRequirements(quest)
local starterName, starterZoneName = _GetQuestStarter(quest)
local finisherName, finisherZoneName = _GetQuestFinisher(quest)
_AddPlayerQuestProgress(quest, starterName, starterZoneName, finisherName, finisherZoneName)
end
end
end
---@param text string
---@param wrapText boolean?
_AddTooltipLine = function (text, wrapText)
ItemRefTooltip:AddLine(text, 1, 1, 1, wrapText)
end
---@param text string
---@param color string
---@param wrapText boolean?
_AddColoredTooltipLine = function (text, color, wrapText)
text = Questie:Colorize(text, color)
ItemRefTooltip:AddLine(text, 1, 1, 1, wrapText)
end
_AddQuestTitle = function(quest)
local questLevel = QuestieLib:GetLevelString(quest.Id, quest.name, quest.level, false)
local titleColor = "gold"
if quest.specialFlags == 1 then
titleColor = "blue"
end
if Questie.db.profile.trackerShowQuestLevel and Questie.db.profile.enableTooltipsQuestID then
_AddColoredTooltipLine(questLevel .. quest.name .. " (" .. quest.Id .. ")", titleColor)
elseif Questie.db.profile.trackerShowQuestLevel and (not Questie.db.profile.enableTooltipsQuestID) then
_AddColoredTooltipLine(questLevel .. quest.name, titleColor)
elseif Questie.db.profile.enableTooltipsQuestID and (not Questie.db.profile.trackerShowQuestLevel) then
_AddColoredTooltipLine(quest.name .. " (" .. quest.Id .. ")", titleColor)
else
_AddColoredTooltipLine(quest.name, titleColor)
end
end
_AddQuestStatus = function (quest)
if QuestiePlayer.currentQuestlog[quest.Id] then
local onQuestText = l10n("You are on this quest")
local stateText
local questIsComplete = QuestieDB.IsComplete(quest.Id)
if questIsComplete == 1 then
stateText = Questie:Colorize(l10n("Complete"), "green")
elseif questIsComplete == -1 then
stateText = Questie:Colorize(l10n("Failed"), "red")
end
if stateText then
_AddTooltipLine(onQuestText .. " (" .. stateText .. ")")
else
_AddColoredTooltipLine(onQuestText, "green")
end
elseif Questie.db.char.complete[quest.Id] then
_AddColoredTooltipLine(l10n("You have completed this quest"), "green")
elseif (UnitLevel("player") < quest.requiredLevel or (not QuestieDB.IsDoable(quest.Id))) and (not Questie.db.char.hidden[quest.Id]) then
_AddColoredTooltipLine(l10n("You are ineligible for this quest"), "red")
elseif quest.specialFlags == 1 then
_AddColoredTooltipLine(l10n("This quest is repeatable"), "yellow")
else
_AddColoredTooltipLine(l10n("You have not done this quest"), "yellow")
end
end
_AddQuestDescription = function (quest)
if quest and quest.Description and quest.Description[1] then
_AddColoredTooltipLine(quest.Description[1], "white", true)
if #quest.Description > 2 then
for i = 2, #quest.Description do
--_AddTooltipLine(" ") -- this is just adding extra lines between text definitions in DB files
_AddColoredTooltipLine(quest.Description[i], "white", true)
end
end
else
_AddColoredTooltipLine(l10n("This quest is an automatic completion quest and does not contain an objective."), "white", true)
end
end
_AddQuestRequirements = function (quest)
if #quest.ObjectiveData > 0 and not (QuestiePlayer.currentQuestlog[quest.Id] or Questie.db.char.complete[quest.Id]) then
for i = 1, #quest.ObjectiveData do
local currentObjective = quest.ObjectiveData[i]
if currentObjective then
if currentObjective.Text then
if currentObjective == quest.ObjectiveData[1] then
_AddTooltipLine(" ")
_AddColoredTooltipLine(l10n("Requirements"), "gold")
end
_AddColoredTooltipLine(currentObjective.Text, "white")
else
local objectiveName
if currentObjective.Type == "monster" then
objectiveName = QuestieDB.QueryNPCSingle(currentObjective.Id, "name")
else
objectiveName = QuestieDB.QueryItemSingle(currentObjective.Id, "name")
end
if objectiveName then
if currentObjective == quest.ObjectiveData[1] then
_AddTooltipLine(" ")
_AddColoredTooltipLine(l10n("Requirements"), "gold")
end
_AddColoredTooltipLine(objectiveName, "white")
end
end
end
end
end
end
_GetQuestStarter = function (quest)
if quest.Starts then
local starterName, starterZoneName
if quest.Starts.NPC ~= nil then
local npc = QuestieDB:GetNPC(quest.Starts.NPC[1])
starterName = npc.name
if npc.zoneID ~= 0 then
starterZoneName = TrackerUtils:GetZoneNameByID(npc.zoneID)
else
starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
elseif quest.Starts.Item ~= nil then
local item = QuestieDB:GetItem(quest.Starts.Item[1])
starterName = item.name
if item.Sources and item.Sources[1] and item.Sources[1].Type then
local itemSource = item.Sources[1]
local dropStart
if itemSource.Type == "monster" then
dropStart = QuestieDB:GetNPC(itemSource.Id)
elseif itemSource.Type == "object" then
dropStart = QuestieDB:GetObject(itemSource.Id)
end
if item.zoneID ~= 0 then
starterZoneName = TrackerUtils:GetZoneNameByID(dropStart.zoneID)
else
starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
else
starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
elseif quest and quest.Starts and quest.Starts.GameObject and quest.Starts.GameObject[1] then
local object = QuestieDB:GetObject(quest.Starts.GameObject[1])
starterName = object.name
if object.zoneID ~= 0 then
starterZoneName = TrackerUtils:GetZoneNameByID(object.zoneID)
else
starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
end
return starterName, starterZoneName
end
return nil, nil
end
_GetQuestFinisher = function (quest)
if quest.Finisher and quest.Finisher.Id then
local finisherName, finisherZoneName
if quest.Finisher.Type == "monster" then
local npc = QuestieDB:GetNPC(quest.Finisher.Id)
finisherName = npc.name
if npc.zoneID ~= 0 then
finisherZoneName = TrackerUtils:GetZoneNameByID(npc.zoneID)
else
finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
elseif quest.Finisher.Type == "object" then
local object = QuestieDB:GetObject(quest.Finisher.Id)
finisherName = object.name
if object.zoneID ~= 0 then
finisherZoneName = TrackerUtils:GetZoneNameByID(object.zoneID)
else
finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
else
finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
end
return finisherName, finisherZoneName
end
return nil, nil
end
_AddPlayerQuestProgress = function (quest, starterName, starterZoneName, finisherName, finisherZoneName)
if QuestiePlayer.currentQuestlog[quest.Id] then
-- On Quest: display quest progress
if (QuestieDB.IsComplete(quest.Id) == 0) then
_AddTooltipLine(" ")
_AddTooltipLine(l10n("Your progress")..":")
for _, objective in pairs(quest.Objectives) do
local objDesc = objective.Description:gsub("%.", "")
if objective.Needed > 0 then
local lineEnding = tostring(objective.Collected) .. "/" .. tostring(objective.Needed)
_AddTooltipLine(" - " .. QuestieLib:GetRGBForObjective(objective) .. objDesc .. ": " .. lineEnding.."|r")
end
end
-- Completed Quest (not turned in): display quest ended by npc and zone
else
if finisherName then
_AddTooltipLine(" ")
_AddTooltipLine((l10n("Ended by")..": " .. Questie:Colorize(finisherName, "gray")))
end
if finisherZoneName then
_AddTooltipLine(" ")
_AddTooltipLine((l10n("Found in")..": " .. Questie:Colorize(finisherZoneName, "gray")))
end
end
else
-- Completed Quest (turned in)
if Questie.db.char.complete[quest.Id] == true then
if Questie.db.char.journey then
local timestamp
for i = 1, #Questie.db.char.journey do
if Questie.db.char.journey[i].Quest ~= nil and Questie.db.char.journey[i].Quest == quest.Id then
local year = tonumber(date("%Y", Questie.db.char.journey[i].Timestamp))
local day = CALENDAR_WEEKDAY_NAMES[ tonumber(date("%w", Questie.db.char.journey[i].Timestamp)) + 1 ]
local month = CALENDAR_FULLDATE_MONTH_NAMES[ tonumber(date("%m", Questie.db.char.journey[i].Timestamp)) ]
timestamp = Questie:Colorize(date( "[ "..day ..", ".. month .." %d, "..year.." @ %H:%M ] " , Questie.db.char.journey[i].Timestamp), "blue")
end
end
if timestamp then
_AddTooltipLine(" ")
_AddTooltipLine(l10n("Completed on:"))
_AddTooltipLine(timestamp)
end
end
-- Not on Quest: display quest started by npc and zone
else
if starterName then
_AddTooltipLine(" ")
_AddTooltipLine((l10n("Started by")..": " .. Questie:Colorize(starterName, "gray")))
end
if starterZoneName then
_AddTooltipLine((l10n("Found in")..": " .. Questie:Colorize(starterZoneName, "gray")))
end
end
end
end
hooksecurefunc("ChatFrame_OnHyperlinkShow", function(...)
local _, link, _, button = ...
if (IsShiftKeyDown() and ChatEdit_GetActiveWindow() and button == "LeftButton") then
local linkType, questId, _ = string.split(":", link)
if linkType and linkType == "questie" and questId then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:OnHyperlinkShow] Relinking Quest Link to chat:", link)
questId = tonumber(questId)
local quest = QuestieDB.GetQuest(questId)
if quest then
local msg = ChatFrame1EditBox:GetText()
if msg then
ChatFrame1EditBox:SetText("")
ChatEdit_InsertLink(QuestieLink:GetQuestLinkString(quest.level, quest.name, questId))
end
end
end
end
end)