Files
Questie-X/Modules/QuestLinks/Link.lua
T

390 lines
16 KiB
Lua

---@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)
questName = questName or (questLink and string.match(questLink, "%[(.-)%]")) or "Quest"
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)
if not coloredQuestName then
return nil
end
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(...)
-- FIX: Added InCombatLockdown guard to prevent tainting secure execution paths.
if InCombatLockdown() then return end
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 success, quest = pcall(QuestieDB.GetQuest, QuestieDB, questId)
if success and 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)