diff --git a/data/images/topbuttons/quest_tracker.png b/data/images/topbuttons/quest_tracker.png new file mode 100644 index 0000000..cf6948c Binary files /dev/null and b/data/images/topbuttons/quest_tracker.png differ diff --git a/layouts/retro/images/topbuttons/quest_tracker.png b/layouts/retro/images/topbuttons/quest_tracker.png new file mode 100644 index 0000000..0cacbc4 Binary files /dev/null and b/layouts/retro/images/topbuttons/quest_tracker.png differ diff --git a/modules/game_questlog/questlinewindow.otui b/modules/game_questlog/questlinewindow.otui deleted file mode 100644 index 7d6a5cf..0000000 --- a/modules/game_questlog/questlinewindow.otui +++ /dev/null @@ -1,51 +0,0 @@ -MissionLabel < Label - font: verdana-11px-monochrome - background-color: alpha - text-offset: 2 0 - focusable: true - - $focus: - background-color: #ffffff22 - color: #ffffff - -QuestLineWindow < MainWindow - id: questLineWindow - !text: tr('Quest Log') - size: 500 400 - @onEscape: self:destroy() - - TextList - id: missionList - anchors.top: parent.top - anchors.left: parent.left - anchors.right: missionListScrollBar.left - height: 100 - padding: 1 - focusable: false - vertical-scrollbar: missionListScrollBar - - VerticalScrollBar - id: missionListScrollBar - anchors.top: parent.top - anchors.right: parent.right - height: 100 - step: 14 - pixels-scroll: true - - FlatLabel - id: missionDescription - anchors.top: missionList.bottom - anchors.left: parent.left - anchors.right: missionListScrollBar.right - anchors.bottom: closeButton.top - margin-bottom: 10 - margin-top: 10 - text-wrap: true - - Button - id: closeButton - anchors.bottom: parent.bottom - anchors.right: parent.right - !text: tr('Close') - width: 90 - @onClick: self:getParent():destroy() diff --git a/modules/game_questlog/questlog.lua b/modules/game_questlog/questlog.lua index f30c6f7..f391edd 100644 --- a/modules/game_questlog/questlog.lua +++ b/modules/game_questlog/questlog.lua @@ -1,92 +1,288 @@ questLogButton = nil -questLineWindow = nil +questTrackerButton = nil +window = nil +trackerWindow = nil +settings = {} + +local callDelay = 1000 -- each call delay is also increased by random values (0-callDelay/2) +local dispatcher = {} function init() g_ui.importStyle('questlogwindow') - g_ui.importStyle('questlinewindow') + + window = g_ui.createWidget('QuestLogWindow', rootWidget) + window:hide() + trackerWindow = g_ui.createWidget('QuestTracker', modules.game_interface.getRightPanel()) + trackerWindow:setup() + trackerWindow:hide() if not g_app.isMobile() then questLogButton = modules.client_topmenu.addLeftGameButton('questLogButton', tr('Quest Log'), '/images/topbuttons/questlog', function() g_game.requestQuestLog() end, false, 8) + questTrackerButton = modules.client_topmenu.addLeftGameButton('questTrackerButton', tr('Quest Tracker'), '/images/topbuttons/quest_tracker', toggle, false, 9) end connect(g_game, { onQuestLog = onGameQuestLog, onQuestLine = onGameQuestLine, - onGameEnd = destroyWindows}) + onGameEnd = offline, + onGameStart = online}) + online() end function terminate() disconnect(g_game, { onQuestLog = onGameQuestLog, onQuestLine = onGameQuestLine, - onGameEnd = destroyWindows}) + onGameEnd = offline, + onGameStart = online}) - destroyWindows() + offline() if questLogButton then questLogButton:destroy() end + if questTrackerButton then + questTrackerButton:destroy() + end end -function destroyWindows() - if questLogWindow then - questLogWindow:destroy() +function toggle() + if trackerWindow:isVisible() then + trackerWindow:hide() + else + trackerWindow:show() end +end - if questLineWindow then - questLineWindow:destroy() +function offline() + if window then + window:hide() end + if trackerWindow then + trackerWindow:hide() + end + save() + -- reset tracker + trackerWindow.contentsPanel.list:destroyChildren() + trackerWindow.contentsPanel.list:setHeight(0) +end + +function online() + local playerName = g_game.getCharacterName() + if not playerName then return end -- just to be sure + load() + refreshQuests() + refreshTrackerWidgets() + + local playerName = g_game.getCharacterName() + settings[playerName] = settings[playerName] or {} + local settings = settings[playerName] + local missionList = window.missionlog.missionList + local track = window.missionlog.track + local missionDescription = window.missionlog.missionDescription + + connect(missionList, { + onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + missionDescription:setText(focusedChild.description) + if focusedChild:isVisible() then + track:setEnabled(true) + end + track:setChecked(settings[focusedChild.trackData]) + end + } + ) +end + +function show(questlog) + if questlog then + window:raise() + window:show() + window:focus() + window.missionlog.currentQuest = nil -- reset current quest + window.questlog:setVisible(true) + window.missionlog:setVisible(false) + window.closeButton:setText('Close') + window.showButton:setVisible(true) + window.missionlog.track:setEnabled(false) + window.missionlog.track:setChecked(false) + window.missionlog.missionDescription:setText('') + else + window.questlog:setVisible(false) + window.missionlog:setVisible(true) + window.closeButton:setText('Back') + window.showButton:setVisible(false) + end +end + +function back() + if window:isVisible() then + if window.questlog:isVisible() then + window:hide() + else + show(true) + end + end +end + +function showQuestLine() + local questList = window.questlog.questList + local child = questList:getFocusedChild() + + g_game.requestQuestLine(child.questId) + window.missionlog.questName:setText(child.questName) + window.missionlog.currentQuest = child.questId end function onGameQuestLog(quests) - destroyWindows() + show(true) - questLogWindow = g_ui.createWidget('QuestLogWindow', rootWidget) - local questList = questLogWindow:getChildById('questList') + local questList = window.questlog.questList + questList:destroyChildren() for i,questEntry in pairs(quests) do local id, name, completed = unpack(questEntry) local questLabel = g_ui.createWidget('QuestLabel', questList) - questLabel:setOn(completed) + questLabel:setChecked(i % 2 == 0) + questLabel.questId = id -- for quest tracker + questLabel.questName = name + name = completed and name.." (completed)" or name questLabel:setText(name) questLabel.onDoubleClick = function() - questLogWindow:hide() + window.missionlog.currentQuest = id g_game.requestQuestLine(id) + window.missionlog.questName:setText(questLabel.questName) end end - - questLogWindow.onDestroy = function() - questLogWindow = nil - end - questList:focusChild(questList:getFirstChild()) end function onGameQuestLine(questId, questMissions) - if questLogWindow then questLogWindow:hide() end - if questLineWindow then questLineWindow:destroy() end - - questLineWindow = g_ui.createWidget('QuestLineWindow', rootWidget) - local missionList = questLineWindow:getChildById('missionList') - local missionDescription = questLineWindow:getChildById('missionDescription') - - connect(missionList, { onChildFocusChange = function(self, focusedChild) - if focusedChild == nil then return end - missionDescription:setText(focusedChild.description) - end }) + show(false) + local missionList = window.missionlog.missionList + if questId == window.missionlog.currentQuest then + missionList:destroyChildren() + end for i,questMission in pairs(questMissions) do local name, description = unpack(questMission) - local missionLabel = g_ui.createWidget('MissionLabel') + --questlog + local missionLabel = g_ui.createWidget('QuestLabel', missionList) + local widgetId = questId..'.'..i + missionLabel:setChecked(i % 2 == 0) + missionLabel:setId(widgetId) + missionLabel.questId = questId + missionLabel.trackData = widgetId missionLabel:setText(name) missionLabel.description = description - missionList:addChild(missionLabel) - end + missionLabel:setVisible(questId == window.missionlog.currentQuest) - questLineWindow.onDestroy = function() - if questLogWindow then questLogWindow:show() end - questLineWindow = nil + --tracker + local trackerLabel = trackerWindow.contentsPanel.list[widgetId] + trackerLabel = trackerLabel or g_ui.createWidget('QuestTrackerLabel', trackerWindow.contentsPanel.list) + trackerLabel:setId(widgetId) + trackerLabel.description:setText(description) + local data = settings[g_game.getCharacterName()] + trackerLabel:setVisible(description:len() > 0 and data[widgetId]) + end + local focusTarget = missionList:getFirstChild() + if focusTarget and focusTarget:isVisible() then + missionList:focusChild(focusTarget) end - - missionList:focusChild(missionList:getFirstChild()) end +function onTrackOptionChange(checkbox) + local newStatus = not checkbox:isChecked() + checkbox:setChecked(newStatus) + + local missionList = window.missionlog.missionList + local focused = missionList:getFocusedChild() + if not focused then return end + local settings = settings[g_game.getCharacterName()] + local trackdata = focused.trackData + + -- settings + settings[trackdata] = newStatus + + local trackerWidget = trackerWindow.contentsPanel.list[trackdata] + if trackerWidget then + trackerWidget:setVisible(newStatus) + end + + refreshQuests() + save() +end + +function refreshQuests() + if not g_game.isOnline() then return end + local data = settings[g_game.getCharacterName()] + data = data or {} + + -- do not execute when questlost is in use + if not window:isVisible() then + for questData, track in pairs(data) do + local id = string.split(questData, ".")[1] + + if not track then + dispatcher[questData] = nil -- remove from dispatcher if no longer tracked + else + dispatcher[questData] = dispatcher[questData] or g_clock.millis() + end + + if dispatcher[questData] and g_clock.millis() > dispatcher[questData] + callDelay + math.random(callDelay/2) then + dispatcher[questData] = g_clock.millis() + scheduleEvent(function() + g_game.requestQuestLine(id) -- request update + end, math.random(callDelay/2) ) + end + end + end + + scheduleEvent(refreshQuests, callDelay) +end + +function refreshTrackerWidgets() + if not g_game.isOnline() then return end + local data = settings[g_game.getCharacterName()] + data = data or {} + + for questData, enabled in pairs(data) do + local data = string.split(questData, ".") + local id = tonumber(data[1]) + + local widget = trackerWindow.contentsPanel.list[questData] + if not widget then + g_game.requestQuestLine(id) + end + end +end + +-- json handlers +function load() + local file = "/settings/questlog.json" + if g_resources.fileExists(file) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(file)) + end) + if not status then + return g_logger.error( + "Error while reading profiles file. To fix this problem you can delete storage.json. Details: " .. + result) + end + settings = result + end +end + +function save() + local file = "/settings/questlog.json" + 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) + end + if result:len() > 100 * 1024 * 1024 then + return g_logger.error( + "Something went wrong, file is above 100MB, won't be saved") + end + g_resources.writeFileContents(file, result) +end \ No newline at end of file diff --git a/modules/game_questlog/questlog.otmod b/modules/game_questlog/questlog.otmod index 7bafbeb..04b7aa7 100644 --- a/modules/game_questlog/questlog.otmod +++ b/modules/game_questlog/questlog.otmod @@ -1,8 +1,8 @@ Module name: game_questlog - description: View game quests status - author: edubart - website: https://github.com/edubart/otclient + description: Quest status preview and tracking + author: Vithrax + website: https://github.com/Vithrax sandboxed: true scripts: [ questlog ] @onLoad: init() diff --git a/modules/game_questlog/questlogwindow.otui b/modules/game_questlog/questlogwindow.otui index c173c16..8304eca 100644 --- a/modules/game_questlog/questlogwindow.otui +++ b/modules/game_questlog/questlogwindow.otui @@ -1,54 +1,188 @@ +QuestTrackerLabel < Panel + height: 20 + layout: + type: verticalBox + fit-children: true + + $!first: + margin-top: 3 + + Label + id: description + text-align: center + text-wrap: true + text-auto-resize: true + + HorizontalSeparator + margin-top: 3 + QuestLabel < Label font: verdana-11px-monochrome - text-offset: 2 0 + height: 18 + text-offset: 2 1 focusable: true color: #aaaaaa - background-color: alpha + background-color: #484848 - $on: - color: #00aa00 - $!on: - color: #aaaaaa + $checked: + background-color: #414141 $focus: - background-color: #444444 - - $on focus: - color: #00ff00 - $!on focus: - color: #ffffff - -QuestLogWindow < MainWindow - id: questLogWindow - !text: tr('Quest Log') - size: 500 400 - @onEscape: self:destroy() - $mobile: - size: 500 350 + background-color: #ffffff22 +QuestLog < Panel TextList id: questList - anchors.top: parent.top - anchors.bottom: closeButton.top - anchors.left: parent.left - anchors.right: questListScrollBar.left - margin-bottom: 10 + anchors.fill: parent + margin-bottom: 20 focusable: false + background-color: #484848 vertical-scrollbar: questListScrollBar VerticalScrollBar id: questListScrollBar - anchors.top: parent.top - anchors.bottom: closeButton.top - anchors.right: parent.right - margin-bottom: 10 + anchors.top: questList.top + anchors.bottom: questList.bottom + anchors.right: questList.right step: 14 pixels-scroll: true +MissionLog < Panel + Label + id: questName + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: left + text: questline name + + TextList + id: missionList + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 133 + padding: 1 + focusable: false + vertical-scrollbar: missionListScrollBar + background-color: #484848 + + VerticalScrollBar + id: missionListScrollBar + anchors.top: missionList.top + anchors.right: missionList.right + anchors.bottom: missionList.bottom + step: 14 + pixels-scroll: true + + CheckBox + id: track + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 25 + !text: tr('Show in quest tracker') + @onClick: modules.game_questlog.onTrackOptionChange(self) + enabled: false + + FlatLabel + id: missionDescription + anchors.top: missionList.bottom + anchors.left: parent.left + anchors.right: missionListScrollBar.right + anchors.bottom: prev.top + background-color: #363636 + margin-bottom: 10 + margin-top: 10 + text-wrap: true + +QuestLogWindow < MainWindow + id: questLogWindow + !text: tr('Quest Log') + size: 330 405 + @onEscape: modules.game_questlog.back() + $mobile: + size: 330 350 + + QuestLog + id: questlog + anchors.top: parent.top + anchors.bottom: bottomSep.top + anchors.left: parent.left + anchors.right: parent.right + visible: false + + MissionLog + id: missionlog + anchors.fill: prev + + HorizontalSeparator + id: bottomSep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + Button id: closeButton - anchors.bottom: parent.bottom - anchors.right: parent.right !text: tr('Close') - width: 90 - @onClick: self:getParent():destroy() + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + color: #ffffff + size: 45 21 + @onClick: modules.game_questlog.back() + + Button + id: showButton + anchors.verticalCenter: prev.verticalCenter + anchors.right: prev.left + margin-right: 3 + color: #ffffff + size: 45 21 + !text: tr('Show') + font: cipsoftFont + @onClick: modules.game_questlog.showQuestLine() + + Button + id: trackerButton + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + margin-right: 3 + color: #ffffff + size: 80 21 + text-align: center + !text: tr('Quest Tracker') + font: cipsoftFont + @onClick: modules.game_questlog.toggle() + +QuestTracker < MiniWindow + id: questTracker + !text: tr('Quest Tracker') + height: 60 + icon: /images/topbuttons/quest_tracker + + MiniWindowContents + padding-left: 5 + padding-right: 5 + padding-top: 5 + layout: verticalBox + + Panel + id: list + layout: + type: verticalBox + fit-children: true + + Panel + height: 20 + margin-top: 5 + Button + !text: tr('Add Tracked Quest') + anchors.fill: parent + margin-left: 30 + margin-right: 30 + font: cipsoftFont + color: #FFFFFF + @onClick: g_game.requestQuestLog() \ No newline at end of file diff --git a/otclient_dx.exe b/otclient_dx.exe index 6d45533..c82b239 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index 2416a37..28cbfed 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclientv8.apk b/otclientv8.apk index a099377..efc9e18 100644 Binary files a/otclientv8.apk and b/otclientv8.apk differ