mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
fix: 0xD0 payload + server-side quest tracker persistence for current protocol (#1711)
Bug Fixes: - Popup menu checkboxes now reliably toggle and report their state. - Quest tracker initializes, saves and syncs more reliably; auto-track/untrack preferences persist and apply correctly. - Mini-window “Remove completed quests” consistently clears completed entries and updates the tracker UI. Refactor: - Tracker sync and local state handling simplified for more consistent, efficient communication and UI rebuilding.
This commit is contained in:
parent
4b42443944
commit
e8ef4cc044
7 changed files with 318 additions and 414 deletions
|
|
@ -97,6 +97,7 @@ function UIPopupMenu:addCheckBox(text, checked, callback)
|
|||
checkBox:setText(text)
|
||||
checkBox:setChecked(checked or false)
|
||||
checkBox.onClick = function()
|
||||
checkBox:setChecked(not checkBox:isChecked())
|
||||
self:destroy()
|
||||
callback(checkBox, checkBox:isChecked())
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ local currentQuestId = nil -- Track the currently selected quest ID
|
|||
local missionToQuestMap = {} -- Map missionId to questId for navigation
|
||||
local isNavigating = false -- Flag to prevent checkbox events during navigation
|
||||
local isUpdatingCheckbox = false -- Flag to prevent recursive checkbox events
|
||||
local isReceivingQuestTracker = false -- Prevent stale local cache from being sent while applying server tracker data
|
||||
local questTrackerSettingsLoaded = false
|
||||
local saveTimer = nil
|
||||
local questLogCache = {
|
||||
items = {},
|
||||
completed = 0,
|
||||
|
|
@ -54,6 +57,7 @@ local COLORS = {
|
|||
SELECTED = "#585858"
|
||||
}
|
||||
local file = "/settings/questtracking.json"
|
||||
local settingsDirectory = "/settings/"
|
||||
|
||||
--[[=================================================
|
||||
= Local Functions =
|
||||
|
|
@ -84,53 +88,6 @@ local function removeNumber(key, id)
|
|||
end
|
||||
end
|
||||
|
||||
-- Auto-untrack completed quests by checking all tracked quests
|
||||
local function autoUntrackCompletedQuests()
|
||||
if not settings.autoUntrackCompleted or not settings[namePlayer] or not trackerMiniWindow then
|
||||
return
|
||||
end
|
||||
|
||||
local removedMissionIds = {}
|
||||
|
||||
-- Check all tracked missions for completion status
|
||||
if trackerMiniWindow.contentsPanel and trackerMiniWindow.contentsPanel.list then
|
||||
for i = trackerMiniWindow.contentsPanel.list:getChildCount(), 1, -1 do
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildByIndex(i)
|
||||
if trackerLabel and trackerLabel.description then
|
||||
local description = trackerLabel.description:getText()
|
||||
local missionId = tonumber(trackerLabel:getId())
|
||||
|
||||
-- Check if the mission is completed based on description text
|
||||
local isCompleted = description and (
|
||||
string.find(string.lower(description), "%(completed%)") or
|
||||
(string.find(string.lower(description), "complete") and
|
||||
(string.find(string.lower(description), "quest") or string.find(string.lower(description), "mission")))
|
||||
)
|
||||
|
||||
if isCompleted then
|
||||
table.insert(removedMissionIds, missionId)
|
||||
|
||||
-- Remove from settings
|
||||
removeNumber(namePlayer, missionId)
|
||||
|
||||
-- Remove from tracker display
|
||||
trackerLabel:destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #removedMissionIds > 0 then
|
||||
-- Update tracker layout
|
||||
if trackerMiniWindow.contentsPanel and trackerMiniWindow.contentsPanel.list then
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():update()
|
||||
end
|
||||
|
||||
-- Save the updated settings
|
||||
save()
|
||||
end
|
||||
end
|
||||
|
||||
local function load()
|
||||
if g_resources.fileExists(file) then
|
||||
local status, result = pcall(function()
|
||||
|
|
@ -145,26 +102,102 @@ local function load()
|
|||
end
|
||||
end
|
||||
|
||||
local function ensureQuestTrackerState()
|
||||
if g_game.getClientVersion() < 1280 then
|
||||
return false
|
||||
end
|
||||
|
||||
if not namePlayer or namePlayer == "" then
|
||||
local characterName = g_game.getCharacterName()
|
||||
if not characterName or characterName == "" then
|
||||
return false
|
||||
end
|
||||
|
||||
namePlayer = characterName:lower()
|
||||
end
|
||||
|
||||
if not questTrackerSettingsLoaded then
|
||||
settings = load() or {}
|
||||
questTrackerSettingsLoaded = true
|
||||
|
||||
g_logger.debug(string.format(
|
||||
"[QuestTracker] loaded local cache player=%s missions=%d autoTrack=%s autoUntrack=%s",
|
||||
namePlayer,
|
||||
settings[namePlayer] and #settings[namePlayer] or 0,
|
||||
settings.autoTrackNewQuests == true and "true" or "false",
|
||||
settings.autoUntrackCompleted == true and "true" or "false"
|
||||
))
|
||||
end
|
||||
|
||||
if settings.autoTrackNewQuests == nil then
|
||||
settings.autoTrackNewQuests = false
|
||||
end
|
||||
if settings.autoUntrackCompleted == nil then
|
||||
settings.autoUntrackCompleted = false
|
||||
end
|
||||
if not settings[namePlayer] then
|
||||
settings[namePlayer] = {}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function save()
|
||||
if not namePlayer or namePlayer == "" then
|
||||
g_logger.debug("[QuestTracker] skipped local cache save without player name")
|
||||
return false
|
||||
end
|
||||
|
||||
local status, result = pcall(function()
|
||||
return json.encode(settings, 2)
|
||||
end)
|
||||
if not status then
|
||||
return g_logger.error("Error while saving profile settings. Data won't be saved. Details: " .. result)
|
||||
g_logger.error("Error while saving quest tracker settings. Data won't be saved. Details: " .. result)
|
||||
return false
|
||||
end
|
||||
if result:len() > 100 * 1024 * 1024 then
|
||||
return g_logger.error("Something went wrong, file is above 100MB, won't be saved")
|
||||
g_logger.error("Something went wrong, quest tracker settings file is above 100MB, won't be saved")
|
||||
return false
|
||||
end
|
||||
|
||||
if not g_resources.directoryExists(settingsDirectory) then
|
||||
g_resources.makeDir(settingsDirectory)
|
||||
end
|
||||
|
||||
-- Safely attempt to write the file, ignoring errors during logout
|
||||
local writeStatus, writeError = pcall(function()
|
||||
local writeStatus, writeResult = pcall(function()
|
||||
return g_resources.writeFileContents(file, result)
|
||||
end)
|
||||
|
||||
if not writeStatus then
|
||||
-- Log the error but don't spam the console during normal logout
|
||||
g_logger.debug("Could not save quest log settings during logout: " .. tostring(writeError))
|
||||
g_logger.error("Could not save quest tracker settings: " .. tostring(writeResult))
|
||||
return false
|
||||
end
|
||||
|
||||
if not writeResult then
|
||||
g_logger.error("Could not save quest tracker settings: writeFileContents returned false")
|
||||
return false
|
||||
end
|
||||
|
||||
g_logger.debug(string.format(
|
||||
"[QuestTracker] saved local cache player=%s missions=%d autoTrack=%s autoUntrack=%s",
|
||||
namePlayer or "",
|
||||
namePlayer and settings[namePlayer] and #settings[namePlayer] or 0,
|
||||
settings.autoTrackNewQuests == true and "true" or "false",
|
||||
settings.autoUntrackCompleted == true and "true" or "false"
|
||||
))
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function deferredSave()
|
||||
if saveTimer then
|
||||
saveTimer:cancel()
|
||||
end
|
||||
|
||||
saveTimer = scheduleEvent(function()
|
||||
saveTimer = nil
|
||||
save()
|
||||
end, 500)
|
||||
end
|
||||
|
||||
local sortFunctions = {
|
||||
|
|
@ -201,11 +234,42 @@ local sortFunctions = {
|
|||
}
|
||||
|
||||
local function sendQuestTracker(listToMap)
|
||||
local map = {}
|
||||
for _, entry in ipairs(listToMap) do
|
||||
map[entry[1]] = entry[2]
|
||||
if not ensureQuestTrackerState() then
|
||||
g_logger.debug("[QuestTracker] skipped 0xD0 send before local cache was loaded")
|
||||
return
|
||||
end
|
||||
g_game.sendRequestTrackerQuestLog(map)
|
||||
|
||||
local missionIds = {}
|
||||
local added = {}
|
||||
|
||||
for _, entry in ipairs(listToMap or {}) do
|
||||
local missionId = tonumber(entry[1])
|
||||
if missionId and not added[missionId] then
|
||||
missionIds[#missionIds + 1] = missionId
|
||||
added[missionId] = true
|
||||
|
||||
if #missionIds >= 255 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local extra = math.random(0, 0xff)
|
||||
|
||||
g_logger.debug(string.format(
|
||||
"[QuestTracker] sending 0xD0 payload count=%d autoTrack=%s autoUntrack=%s extra=0x%02X",
|
||||
#missionIds,
|
||||
settings.autoTrackNewQuests == true and "true" or "false",
|
||||
settings.autoUntrackCompleted == true and "true" or "false",
|
||||
extra
|
||||
))
|
||||
|
||||
g_game.sendRequestTrackerQuestLog(
|
||||
missionIds,
|
||||
settings.autoTrackNewQuests == true,
|
||||
settings.autoUntrackCompleted == true,
|
||||
extra
|
||||
)
|
||||
end
|
||||
|
||||
local function rebuildTrackerFromSettings()
|
||||
|
|
@ -213,38 +277,21 @@ local function rebuildTrackerFromSettings()
|
|||
return
|
||||
end
|
||||
|
||||
-- Clear existing tracker items
|
||||
trackerMiniWindow.contentsPanel.list:destroyChildren()
|
||||
|
||||
-- Recreate tracker items from our settings
|
||||
for i, entry in ipairs(settings[namePlayer]) do
|
||||
local missionId, missionName, missionDescription, questId = unpack(entry)
|
||||
|
||||
-- Try to get questId from our mapping if not in settings
|
||||
if not questId or questId == 0 then
|
||||
questId = missionToQuestMap[tonumber(missionId)] or 0
|
||||
end
|
||||
|
||||
local trackerLabel = g_ui.createWidget('QuestTrackerLabel', trackerMiniWindow.contentsPanel.list)
|
||||
trackerLabel:setId(tostring(missionId))
|
||||
trackerLabel.questId = questId -- Store questId for navigation
|
||||
trackerLabel.missionId = missionId -- Store missionId for navigation
|
||||
-- Use description if available, fallback to name
|
||||
trackerLabel.questId = questId
|
||||
trackerLabel.missionId = missionId
|
||||
trackerLabel.description:setText(missionDescription or missionName)
|
||||
|
||||
-- If we don't have questId, request quest tracker to get updated data from server
|
||||
if not questId or questId == 0 then
|
||||
-- Missing questId, will be updated by server
|
||||
end
|
||||
end
|
||||
|
||||
-- Request server to send complete tracker data to fill in missing questIds
|
||||
if settings[namePlayer] and #settings[namePlayer] > 0 then
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
end
|
||||
|
||||
-- Check for completed quests to auto-untrack
|
||||
scheduleEvent(autoUntrackCompletedQuests, 1000) -- Delay to ensure tracker is fully loaded
|
||||
end
|
||||
|
||||
local function findQuestIdForMission(missionId)
|
||||
|
|
@ -530,14 +577,39 @@ end
|
|||
= miniWindows =
|
||||
=================================================== ]] --
|
||||
function onOpenTracker()
|
||||
buttonQuestLogTrackerButton:setOn(true)
|
||||
if buttonQuestLogTrackerButton then
|
||||
buttonQuestLogTrackerButton:setOn(true)
|
||||
end
|
||||
end
|
||||
|
||||
function onCloseTracker()
|
||||
buttonQuestLogTrackerButton:setOn(false)
|
||||
if buttonQuestLogTrackerButton then
|
||||
buttonQuestLogTrackerButton:setOn(false)
|
||||
end
|
||||
end
|
||||
|
||||
local function isCompletedMissionText(text)
|
||||
if not text then
|
||||
return false
|
||||
end
|
||||
|
||||
return string.find(string.lower(text), "%(completed%)") ~= nil
|
||||
end
|
||||
|
||||
local function ensureQuestTrackerButton()
|
||||
if buttonQuestLogTrackerButton then
|
||||
return
|
||||
end
|
||||
|
||||
buttonQuestLogTrackerButton = modules.game_mainpanel.addToggleButton("QuestLogTracker",
|
||||
tr("Open QuestLog Tracker"), "/images/options/button_questlog_tracker", function()
|
||||
questLogController:toggleMiniWindowsTracker()
|
||||
end, false, 1001)
|
||||
end
|
||||
|
||||
local function showQuestTracker()
|
||||
ensureQuestTrackerButton()
|
||||
|
||||
if trackerMiniWindow then
|
||||
toggleTracker()
|
||||
return
|
||||
|
|
@ -624,6 +696,7 @@ local function showQuestTracker()
|
|||
-- Clear the settings and mapping
|
||||
table.clear(settings[namePlayer])
|
||||
table.clear(missionToQuestMap) -- Clear the mapping as well
|
||||
save()
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
|
||||
-- Clear the tracker display
|
||||
|
|
@ -648,176 +721,66 @@ local function showQuestTracker()
|
|||
-- Update layouts
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():enableUpdates()
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():update()
|
||||
-- Save the cleared settings
|
||||
save()
|
||||
end
|
||||
end)
|
||||
menu:addOption('Remove completed quests', function()
|
||||
if settings[namePlayer] then
|
||||
-- Store the mission IDs that are being removed for checkbox updates
|
||||
local removedMissionIds = {}
|
||||
local completedMissionIds = {}
|
||||
|
||||
-- Check for completed missions by looking for "(completed)" in their names/descriptions
|
||||
-- and also check the isComplete property if available
|
||||
for i, entry in ipairs(settings[namePlayer]) do
|
||||
local missionId, missionName, missionDescription, questId = unpack(entry)
|
||||
|
||||
local isCompleted = false
|
||||
|
||||
-- Method 1: Check for "(completed)" string in mission name
|
||||
if missionName and string.find(string.lower(missionName), "%(completed%)") then
|
||||
isCompleted = true
|
||||
end
|
||||
|
||||
-- Method 2: Check for "(completed)" string in mission description
|
||||
if not isCompleted and missionDescription and string.find(string.lower(missionDescription), "%(completed%)") then
|
||||
isCompleted = true
|
||||
end
|
||||
|
||||
-- Method 3: Check tracker label text for "(completed)"
|
||||
if not isCompleted and trackerMiniWindow and trackerMiniWindow.contentsPanel and trackerMiniWindow.contentsPanel.list then
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(tostring(missionId))
|
||||
if trackerLabel and trackerLabel.description then
|
||||
local trackerText = trackerLabel.description:getText()
|
||||
if trackerText and string.find(string.lower(trackerText), "%(completed%)") then
|
||||
isCompleted = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Method 4: Check if we can find the mission in the quest log and check its completion status
|
||||
if not isCompleted and questId and UITextList.questLogList then
|
||||
local questItem = UITextList.questLogList:getChildById(tostring(questId))
|
||||
if questItem then
|
||||
-- Temporarily click the quest to load its missions
|
||||
questItem:onClick()
|
||||
|
||||
-- Check if mission is loaded and has isComplete property
|
||||
scheduleEvent(function()
|
||||
if UITextList.questLogLine and UITextList.questLogLine:hasChildren() then
|
||||
local missionItem = UITextList.questLogLine:getChildById(tostring(missionId))
|
||||
if missionItem then
|
||||
-- Check mission text for "(completed)"
|
||||
local missionText = missionItem:getText()
|
||||
if missionText and string.find(string.lower(missionText), "%(completed%)") then
|
||||
table.insert(removedMissionIds, missionId)
|
||||
table.insert(completedMissionIds, missionId)
|
||||
elseif missionItem.isComplete then
|
||||
table.insert(removedMissionIds, missionId)
|
||||
table.insert(completedMissionIds, missionId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Process removal after checking all missions
|
||||
if i == #settings[namePlayer] then
|
||||
scheduleEvent(function()
|
||||
processCompletedMissionRemoval()
|
||||
end, 100)
|
||||
end
|
||||
end, 50)
|
||||
end
|
||||
end
|
||||
|
||||
-- If we found completion through methods 1-3, mark for removal immediately
|
||||
if isCompleted then
|
||||
table.insert(removedMissionIds, missionId)
|
||||
table.insert(completedMissionIds, missionId)
|
||||
end
|
||||
end
|
||||
|
||||
-- If we found completed missions without needing to load quest data, process removal
|
||||
if #removedMissionIds > 0 then
|
||||
scheduleEvent(function()
|
||||
processCompletedMissionRemoval()
|
||||
end, 100)
|
||||
else
|
||||
end
|
||||
|
||||
-- Function to process the actual removal
|
||||
function processCompletedMissionRemoval()
|
||||
if #removedMissionIds > 0 then
|
||||
-- Remove completed missions from settings
|
||||
for j = #settings[namePlayer], 1, -1 do
|
||||
local checkMissionId = settings[namePlayer][j][1]
|
||||
for _, removedId in ipairs(removedMissionIds) do
|
||||
if checkMissionId == removedId then
|
||||
table.remove(settings[namePlayer], j)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove from mission mapping
|
||||
for _, missionId in ipairs(removedMissionIds) do
|
||||
if missionToQuestMap[tonumber(missionId)] then
|
||||
missionToQuestMap[tonumber(missionId)] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Send updated tracker state to server
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
|
||||
-- Remove completed missions from tracker display
|
||||
for _, missionId in ipairs(removedMissionIds) do
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(tostring(missionId))
|
||||
if trackerLabel then
|
||||
trackerLabel:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
-- Update the checkbox in Quest Log window if it's open and a completed mission is selected
|
||||
if questLogController.ui and questLogController.ui:isVisible() then
|
||||
if UITextList.questLogLine and UITextList.questLogLine:hasChildren() then
|
||||
-- Update checkbox for any currently selected mission if it was a completed one
|
||||
if UITextList.questLogLine:getFocusedChild() then
|
||||
local currentMissionId = tonumber(UITextList.questLogLine:getFocusedChild():getId())
|
||||
for _, removedId in ipairs(removedMissionIds) do
|
||||
if currentMissionId == removedId then
|
||||
isUpdatingCheckbox = true
|
||||
UICheckBox.showInQuestTracker:setChecked(false)
|
||||
isUpdatingCheckbox = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Update layouts
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():enableUpdates()
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():update()
|
||||
|
||||
-- Save the updated settings
|
||||
save()
|
||||
if not settings[namePlayer] then
|
||||
return
|
||||
end
|
||||
|
||||
local removedMissionIds = {}
|
||||
|
||||
for i = #settings[namePlayer], 1, -1 do
|
||||
local missionId, missionName, missionDescription = unpack(settings[namePlayer][i])
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(tostring(missionId))
|
||||
local trackerText = trackerLabel and trackerLabel.description and trackerLabel.description:getText() or nil
|
||||
|
||||
if isCompletedMissionText(missionName) or isCompletedMissionText(missionDescription) or isCompletedMissionText(trackerText) then
|
||||
removedMissionIds[#removedMissionIds + 1] = missionId
|
||||
table.remove(settings[namePlayer], i)
|
||||
missionToQuestMap[tonumber(missionId)] = nil
|
||||
|
||||
if trackerLabel then
|
||||
trackerLabel:destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #removedMissionIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if questLogController.ui and questLogController.ui:isVisible() and UITextList.questLogLine and UITextList.questLogLine:hasChildren() then
|
||||
local focusedChild = UITextList.questLogLine:getFocusedChild()
|
||||
if focusedChild then
|
||||
local currentMissionId = tonumber(focusedChild:getId())
|
||||
for _, removedId in ipairs(removedMissionIds) do
|
||||
if currentMissionId == removedId then
|
||||
isUpdatingCheckbox = true
|
||||
UICheckBox.showInQuestTracker:setChecked(false)
|
||||
isUpdatingCheckbox = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():enableUpdates()
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():update()
|
||||
|
||||
save()
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
end)
|
||||
menu:addSeparator()
|
||||
menu:addCheckBox('Automatically track new quests', settings.autoTrackNewQuests or false, function(widget, checked)
|
||||
settings.autoTrackNewQuests = checked
|
||||
save()
|
||||
sendQuestTracker(settings[namePlayer] or {})
|
||||
end)
|
||||
menu:addCheckBox('Automatically untrack completed quests', settings.autoUntrackCompleted or false, function(widget, checked)
|
||||
settings.autoUntrackCompleted = checked
|
||||
save()
|
||||
|
||||
-- Start/stop periodic auto-untrack check
|
||||
if checked then
|
||||
-- Start periodic check
|
||||
scheduleEvent(function()
|
||||
local function periodicAutoUntrack()
|
||||
autoUntrackCompletedQuests()
|
||||
-- Schedule next check
|
||||
if settings.autoUntrackCompleted then
|
||||
scheduleEvent(periodicAutoUntrack, 30000) -- 30 seconds
|
||||
end
|
||||
end
|
||||
periodicAutoUntrack()
|
||||
end, 1000) -- Start after 1 second
|
||||
end
|
||||
sendQuestTracker(settings[namePlayer] or {})
|
||||
end)
|
||||
|
||||
menu:display(mousePos)
|
||||
|
|
@ -837,25 +800,12 @@ local function showQuestTracker()
|
|||
trackerMiniWindow:setup()
|
||||
|
||||
-- Rebuild tracker from saved settings when first created
|
||||
if settings[namePlayer] and #settings[namePlayer] > 0 then
|
||||
if not isReceivingQuestTracker and settings[namePlayer] and #settings[namePlayer] > 0 then
|
||||
rebuildTrackerFromSettings()
|
||||
end
|
||||
|
||||
toggleTracker()
|
||||
|
||||
-- Set up periodic auto-untrack check (every 30 seconds)
|
||||
if settings.autoUntrackCompleted then
|
||||
scheduleEvent(function()
|
||||
local function periodicAutoUntrack()
|
||||
autoUntrackCompletedQuests()
|
||||
-- Schedule next check
|
||||
if settings.autoUntrackCompleted then
|
||||
scheduleEvent(periodicAutoUntrack, 30000) -- 30 seconds
|
||||
end
|
||||
end
|
||||
periodicAutoUntrack()
|
||||
end, 5000) -- Start after 5 seconds
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[[=================================================
|
||||
|
|
@ -902,24 +852,6 @@ local function onQuestLine(questId, questMissions)
|
|||
itemCat.description = missionDescription
|
||||
setupQuestItemClickHandler(itemCat, false)
|
||||
categoryColor = categoryColor == COLORS.BASE_1 and COLORS.BASE_2 or COLORS.BASE_1
|
||||
|
||||
-- Auto-track new quests if the setting is enabled
|
||||
if settings.autoTrackNewQuests and not isIdInTracker(namePlayer, missionId) then
|
||||
-- Check if this mission appears to be new (not completed)
|
||||
local isCompleted = missionDescription and (
|
||||
string.find(string.lower(missionDescription), "%(completed%)") or
|
||||
string.find(string.lower(missionDescription), "complete") and string.find(string.lower(missionDescription), "quest")
|
||||
)
|
||||
|
||||
if not isCompleted then
|
||||
-- Add the mission to tracker
|
||||
addUniqueIdQuest(namePlayer, questId, missionId, missionName, missionDescription)
|
||||
save()
|
||||
|
||||
-- Rebuild tracker to show the new quest
|
||||
rebuildTrackerFromSettings()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Auto-select the first mission but prevent checkbox updates during this automatic selection
|
||||
|
|
@ -938,124 +870,90 @@ local function onQuestLine(questId, questMissions)
|
|||
end
|
||||
|
||||
local function onQuestTracker(remainingQuests, missions)
|
||||
if not trackerMiniWindow then
|
||||
showQuestTracker()
|
||||
end
|
||||
|
||||
-- Don't clear the tracker if we have locally tracked quests
|
||||
-- The server response might not include all our tracked quests
|
||||
if not missions or type(missions[1]) ~= "table" then
|
||||
-- If server sends empty response, rebuild from our local settings
|
||||
if settings[namePlayer] and #settings[namePlayer] > 0 then
|
||||
-- Keep existing tracker items that match our local settings
|
||||
return
|
||||
end
|
||||
trackerMiniWindow.contentsPanel.list:destroyChildren()
|
||||
if not ensureQuestTrackerState() then
|
||||
return
|
||||
end
|
||||
|
||||
-- Only update tracker items that are actually in our local tracked list
|
||||
for index, mission in ipairs(missions) do
|
||||
|
||||
if not trackerMiniWindow then
|
||||
isReceivingQuestTracker = true
|
||||
showQuestTracker()
|
||||
isReceivingQuestTracker = false
|
||||
end
|
||||
|
||||
trackerMiniWindow.contentsPanel.list:destroyChildren()
|
||||
table.clear(settings[namePlayer])
|
||||
table.clear(missionToQuestMap)
|
||||
|
||||
if not missions then
|
||||
save()
|
||||
return
|
||||
end
|
||||
|
||||
for _, mission in ipairs(missions) do
|
||||
local questId, missionId, questName, missionName, missionDesc = unpack(mission)
|
||||
|
||||
-- Update the mission to quest mapping (always update this)
|
||||
missionToQuestMap[tonumber(missionId)] = tonumber(questId)
|
||||
|
||||
-- Check if this mission is actually being tracked by the user
|
||||
local isTracked = false
|
||||
if settings[namePlayer] then
|
||||
for _, entry in ipairs(settings[namePlayer]) do
|
||||
if entry[1] == tonumber(missionId) then
|
||||
isTracked = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if isTracked then
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(tostring(missionId))
|
||||
|
||||
if not trackerLabel then
|
||||
-- Create new tracker label if it doesn't exist and is tracked
|
||||
trackerLabel = g_ui.createWidget('QuestTrackerLabel', trackerMiniWindow.contentsPanel.list)
|
||||
trackerLabel:setId(tostring(missionId))
|
||||
else
|
||||
-- Updating existing tracker label
|
||||
end
|
||||
|
||||
-- Store navigation data (always update with server data)
|
||||
questId = tonumber(questId) or 0
|
||||
missionId = tonumber(missionId)
|
||||
|
||||
if missionId then
|
||||
missionToQuestMap[missionId] = questId
|
||||
settings[namePlayer][#settings[namePlayer] + 1] = {missionId, missionName, missionDesc or missionName, questId}
|
||||
|
||||
local trackerLabel = g_ui.createWidget('QuestTrackerLabel', trackerMiniWindow.contentsPanel.list)
|
||||
trackerLabel:setId(tostring(missionId))
|
||||
trackerLabel.questId = questId
|
||||
trackerLabel.missionId = missionId
|
||||
|
||||
-- Update the description from server data
|
||||
trackerLabel.description:setText(missionDesc or missionName)
|
||||
|
||||
-- Update our local settings with the full description and questId
|
||||
for i, entry in ipairs(settings[namePlayer]) do
|
||||
if entry[1] == tonumber(missionId) then
|
||||
-- Update the stored description and questId with server data
|
||||
settings[namePlayer][i] = {tonumber(missionId), missionName, missionDesc or missionName, questId}
|
||||
break
|
||||
end
|
||||
end
|
||||
save() -- Save the updated data
|
||||
else
|
||||
-- Ignoring non-tracked mission from server
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for completed quests to auto-untrack after processing all missions
|
||||
if settings.autoUntrackCompleted then
|
||||
scheduleEvent(autoUntrackCompletedQuests, 500) -- Short delay to ensure all updates are processed
|
||||
end
|
||||
|
||||
deferredSave()
|
||||
end
|
||||
|
||||
local function onUpdateQuestTracker(questId, missionId, questName, missionName, missionDesc)
|
||||
if not trackerMiniWindow then
|
||||
if not ensureQuestTrackerState() then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if not trackerMiniWindow then
|
||||
isReceivingQuestTracker = true
|
||||
showQuestTracker()
|
||||
isReceivingQuestTracker = false
|
||||
end
|
||||
|
||||
questId = tonumber(questId) or 0
|
||||
missionId = tonumber(missionId)
|
||||
if not missionId then
|
||||
return
|
||||
end
|
||||
|
||||
missionToQuestMap[missionId] = questId
|
||||
|
||||
local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(tostring(missionId))
|
||||
if trackerLabel then
|
||||
trackerLabel.description:setText(missionDesc or missionName)
|
||||
|
||||
-- Ensure navigation data is set
|
||||
trackerLabel.questId = questId
|
||||
trackerLabel.missionId = missionId
|
||||
|
||||
-- Update our local settings with the updated description
|
||||
if settings[namePlayer] then
|
||||
for i, entry in ipairs(settings[namePlayer]) do
|
||||
if entry[1] == tonumber(missionId) then
|
||||
settings[namePlayer][i] = {tonumber(missionId), missionName, missionDesc or missionName, questId}
|
||||
save() -- Save the updated description
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Auto-untrack completed quests if the setting is enabled
|
||||
if settings.autoUntrackCompleted then
|
||||
local isCompleted = missionDesc and (
|
||||
string.find(string.lower(missionDesc), "%(completed%)") or
|
||||
(string.find(string.lower(missionDesc), "complete") and
|
||||
(string.find(string.lower(missionDesc), "quest") or string.find(string.lower(missionDesc), "mission")))
|
||||
)
|
||||
|
||||
if isCompleted then
|
||||
-- Remove from settings
|
||||
removeNumber(namePlayer, missionId)
|
||||
save()
|
||||
|
||||
-- Remove from tracker display
|
||||
trackerLabel:destroy()
|
||||
|
||||
-- Update tracker layout
|
||||
trackerMiniWindow.contentsPanel.list:getLayout():update()
|
||||
end
|
||||
if not trackerLabel then
|
||||
trackerLabel = g_ui.createWidget('QuestTrackerLabel', trackerMiniWindow.contentsPanel.list)
|
||||
trackerLabel:setId(tostring(missionId))
|
||||
end
|
||||
|
||||
trackerLabel.questId = questId
|
||||
trackerLabel.missionId = missionId
|
||||
trackerLabel.description:setText(missionDesc or missionName)
|
||||
|
||||
local updated = false
|
||||
for i, entry in ipairs(settings[namePlayer]) do
|
||||
if entry[1] == missionId then
|
||||
settings[namePlayer][i] = {missionId, missionName, missionDesc or missionName, questId}
|
||||
updated = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not updated then
|
||||
settings[namePlayer][#settings[namePlayer] + 1] = {missionId, missionName, missionDesc or missionName, questId}
|
||||
end
|
||||
|
||||
deferredSave()
|
||||
end
|
||||
|
||||
--[[=================================================
|
||||
= onCall otui / html =
|
||||
=================================================== ]] --
|
||||
|
|
@ -1100,16 +998,17 @@ function questLogController:onCheckChangeQuestTracker(event)
|
|||
if isUpdatingCheckbox then
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure we have a player name
|
||||
if not namePlayer or namePlayer == "" then
|
||||
namePlayer = g_game.getCharacterName():lower()
|
||||
|
||||
if not ensureQuestTrackerState() then
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure tracker window exists
|
||||
if not trackerMiniWindow then
|
||||
showQuestTracker()
|
||||
return
|
||||
if not trackerMiniWindow then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Make sure we have a selected mission
|
||||
|
|
@ -1179,10 +1078,10 @@ function questLogController:onCheckChangeQuestTracker(event)
|
|||
end
|
||||
|
||||
-- Send updated tracker state to server and save
|
||||
save()
|
||||
if settings[namePlayer] then
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
end
|
||||
save()
|
||||
end
|
||||
|
||||
function questLogController:onFilterQuestLog(event)
|
||||
|
|
@ -1237,12 +1136,11 @@ function onQuestLogMousePress(widget, mousePos, mouseButton)
|
|||
menu:addOption(tr('remove'), function()
|
||||
local missionId = widget:getParent():getId() -- This is actually the missionId, not questId
|
||||
removeNumber(namePlayer, missionId)
|
||||
save()
|
||||
if settings[namePlayer] then
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
end
|
||||
widget:getParent():destroy()
|
||||
-- Save the settings after removal
|
||||
save()
|
||||
|
||||
-- Also remove from the mapping
|
||||
if missionToQuestMap[tonumber(missionId)] then
|
||||
|
|
@ -1253,7 +1151,9 @@ function onQuestLogMousePress(widget, mousePos, mouseButton)
|
|||
if UITextList.questLogLine:hasChildren() and UITextList.questLogLine:getFocusedChild() then
|
||||
local currentId = UITextList.questLogLine:getFocusedChild():getId()
|
||||
if tostring(currentId) == tostring(missionId) then
|
||||
isUpdatingCheckbox = true
|
||||
UICheckBox.showInQuestTracker:setChecked(false)
|
||||
isUpdatingCheckbox = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
|
@ -1406,36 +1306,21 @@ end
|
|||
|
||||
function questLogController:onGameStart()
|
||||
if g_game.getClientVersion() >= 1280 then
|
||||
namePlayer = g_game.getCharacterName():lower()
|
||||
settings = load() or {}
|
||||
|
||||
-- Initialize default auto-tracking settings if they don't exist
|
||||
if settings.autoTrackNewQuests == nil then
|
||||
settings.autoTrackNewQuests = false
|
||||
end
|
||||
if settings.autoUntrackCompleted == nil then
|
||||
settings.autoUntrackCompleted = false
|
||||
end
|
||||
|
||||
-- Initialize the player's settings if they don't exist
|
||||
if not settings[namePlayer] then
|
||||
settings[namePlayer] = {}
|
||||
end
|
||||
|
||||
if settings[namePlayer] then
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
end
|
||||
if not buttonQuestLogTrackerButton then
|
||||
buttonQuestLogTrackerButton = modules.game_mainpanel.addToggleButton("QuestLogTracker",
|
||||
tr("Open QuestLog Tracker"), "/images/options/button_questlog_tracker", function()
|
||||
questLogController:toggleMiniWindowsTracker()
|
||||
end, false, 1001)
|
||||
if not ensureQuestTrackerState() then
|
||||
return
|
||||
end
|
||||
|
||||
ensureQuestTrackerButton()
|
||||
|
||||
if trackerMiniWindow then
|
||||
trackerMiniWindow:setupOnStart()
|
||||
-- Rebuild tracker from saved settings
|
||||
rebuildTrackerFromSettings()
|
||||
end
|
||||
|
||||
-- Send local tracker cache on login, matching the official client behavior.
|
||||
-- The server reconciles this payload during the initial sync window.
|
||||
sendQuestTracker(settings[namePlayer])
|
||||
else
|
||||
UICheckBox.showInQuestTracker:setVisible(false)
|
||||
questLogController.ui.buttonsPanel.trackerButton:setVisible(false)
|
||||
|
|
@ -1452,4 +1337,6 @@ function questLogController:onGameEnd()
|
|||
end
|
||||
-- Clear the mission to quest mapping
|
||||
missionToQuestMap = {}
|
||||
questTrackerSettingsLoaded = false
|
||||
namePlayer = ""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2118,12 +2118,18 @@ void Game::requestGetRewardDaily(const uint8_t bonusShrine, const std::map<uint1
|
|||
m_protocolGame->sendGetRewardDaily(bonusShrine, items);
|
||||
}
|
||||
|
||||
void Game::sendRequestTrackerQuestLog(const std::map<uint16_t, std::string>& quests)
|
||||
void Game::sendRequestTrackerQuestLog(const std::vector<uint16_t>& missionIds, const bool autoTrackNewQuests, const bool autoUntrackCompletedQuests, const uint8_t extra)
|
||||
{
|
||||
if (!canPerformGameAction())
|
||||
if (!canPerformGameAction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_protocolGame->sendRequestTrackerQuestLog(quests);
|
||||
m_protocolGame->sendRequestTrackerQuestLog(
|
||||
missionIds,
|
||||
autoTrackNewQuests,
|
||||
autoUntrackCompletedQuests,
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
void Game::processCyclopediaCharacterOffenceStats(const CyclopediaCharacterOffenceStats& data)
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ public:
|
|||
void sendOpenRewardWall();
|
||||
void requestOpenRewardHistory();
|
||||
void requestGetRewardDaily(const uint8_t bonusShrine, const std::map<uint16_t, uint8_t>& items);
|
||||
void sendRequestTrackerQuestLog(const std::map<uint16_t, std::string>& quests);
|
||||
void sendRequestTrackerQuestLog(const std::vector<uint16_t>& missionIds, bool autoTrackNewQuests, bool autoUntrackCompletedQuests, uint8_t extra = 0x2A);
|
||||
void processCyclopediaCharacterOffenceStats(const CyclopediaCharacterOffenceStats& data);
|
||||
void processCyclopediaCharacterDefenceStats(const CyclopediaCharacterDefenceStats& data);
|
||||
void processCyclopediaCharacterMiscStats(const CyclopediaCharacterMiscStats& data);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public:
|
|||
void sendCancelAttackAndFollow();
|
||||
void sendRefreshContainer(uint8_t containerId);
|
||||
void sendRequestBless();
|
||||
void sendRequestTrackerQuestLog(const std::map<uint16_t, std::string>& quests);
|
||||
void sendRequestTrackerQuestLog(const std::vector<uint16_t>& missionIds, bool autoTrackNewQuests, bool autoUntrackCompletedQuests, uint8_t extra);
|
||||
void sendRequestOutfit();
|
||||
void sendTyping(bool typing);
|
||||
void sendChangeOutfit(const Outfit& outfit);
|
||||
|
|
|
|||
|
|
@ -3241,11 +3241,11 @@ void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg)
|
|||
const uint8_t missionCount = msg->getU8();
|
||||
std::vector<std::tuple<uint16_t, uint16_t, std::string, std::string, std::string>> missions;
|
||||
for (uint8_t i = 0; i < missionCount; ++i) {
|
||||
uint8_t questId = 0;
|
||||
const uint16_t missionId = msg->getU16();
|
||||
uint16_t questId = 0;
|
||||
if (g_game.getClientVersion() >= 1410) {
|
||||
questId = msg->getU16();
|
||||
}
|
||||
const uint16_t missionId = msg->getU16();
|
||||
const std::string& questName = msg->getString();
|
||||
const std::string& missionName = msg->getString();
|
||||
const std::string& missionDesc = msg->getString();
|
||||
|
|
@ -3254,7 +3254,7 @@ void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg)
|
|||
return g_lua.callGlobalField("g_game", "onQuestTracker", remainingQuests, missions);
|
||||
}
|
||||
case 0: {
|
||||
uint8_t questId = 0;
|
||||
uint16_t questId = 0;
|
||||
if (g_game.getClientVersion() >= 1410) {
|
||||
questId = msg->getU16();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
#include "thingtype.h"
|
||||
#include "framework/util/crypt.h"
|
||||
|
||||
#ifndef USE_PRECOMPILED_HEADERS
|
||||
#include <algorithm>
|
||||
#endif
|
||||
|
||||
void ProtocolGame::onSend() {}
|
||||
void ProtocolGame::sendExtendedOpcode(const uint8_t opcode, const std::string& buffer)
|
||||
{
|
||||
|
|
@ -767,17 +771,23 @@ void ProtocolGame::sendRequestBless()
|
|||
send(msg);
|
||||
}
|
||||
|
||||
void ProtocolGame::sendRequestTrackerQuestLog(const std::map<uint16_t, std::string>& quests)
|
||||
void ProtocolGame::sendRequestTrackerQuestLog(const std::vector<uint16_t>& missionIds, const bool autoTrackNewQuests, const bool autoUntrackCompletedQuests, const uint8_t extra)
|
||||
{
|
||||
const auto msg = std::make_shared<OutputMessage>();
|
||||
msg->addU8(Proto::ClientRequestTrackerQuestLog);
|
||||
msg->addU8(static_cast<uint8_t>(quests.size()));
|
||||
for (const auto& [questId, questName] : quests) {
|
||||
msg->addU16(questId);
|
||||
if (g_game.getClientVersion() >= 1410) {
|
||||
msg->addString(questName);
|
||||
}
|
||||
const auto missionCount = std::min(missionIds.size(), static_cast<size_t>(255));
|
||||
msg->addU8(static_cast<uint8_t>(missionCount));
|
||||
|
||||
for (size_t i = 0; i < missionCount; ++i) {
|
||||
msg->addU16(missionIds[i]);
|
||||
}
|
||||
|
||||
if (g_game.getClientVersion() >= 1410) {
|
||||
msg->addU8(autoTrackNewQuests ? 1 : 0);
|
||||
msg->addU8(autoUntrackCompletedQuests ? 1 : 0);
|
||||
msg->addU8(extra);
|
||||
}
|
||||
|
||||
send(msg);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue