Guest User

Untitled

a guest
Jun 19th, 2025
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.29 KB | None | 0 0
  1. Tasks = {}
  2. local selectedTask
  3. local suppressCheckChange = false
  4. local DEBOUNCE_DELAY_MS = 100
  5. local pendingSelectedEntry = nil
  6. local refreshScheduled = false
  7. local scheduledEvents = {}
  8. local currentSearchText = ''
  9. local searchDebounceEvent = nil
  10. local lastTrackedTask = nil
  11. local hideCompleted = false
  12. local toggleTrackerButton = nil
  13.  
  14.  
  15. function init()
  16.   g_ui.importStyle('custom_tasks_styles.otui')
  17.   g_ui.importStyle('reward_item.otui')
  18.   g_ui.importStyle('monster_widget.otui')
  19.   Tasks.window = g_ui.displayUI('custom_tasks.otui')
  20.   Tasks.window:hide()
  21.   if g_game.isOnline() and not Tasks.list then
  22.     g_game.getProtocolGame():sendExtendedOpcode(125)
  23.   end
  24.  
  25.   g_keyboard.bindKeyDown('Up', selectPreviousTask)
  26.   g_keyboard.bindKeyDown('Down', selectNextTask)
  27.   g_keyboard.bindKeyDown('Ctrl+D', toggle)
  28.  
  29.   taskButton = modules.game_mainpanel.addToggleButton('taskButton', tr('Tasks') .. ' (Ctrl+D)',
  30.                                                       '/images/options/ButtonBosstiary',
  31.                                                       toggle, false, 99)
  32.   ProtocolGame.registerExtendedOpcode(125, onTaskOpcode)
  33.   toggleTrackerButton = Tasks.window:recursiveGetChildById('toggleTrackerButton')
  34.  
  35.   local searchBox = Tasks.window:recursiveGetChildById('taskSearchBox')
  36.   if searchBox then
  37.     searchBox.onTextChange = function(widget, text)
  38.       currentSearchText = text:lower()
  39.       if searchDebounceEvent then removeEvent(searchDebounceEvent) end
  40.       searchDebounceEvent = scheduleEvent(function()
  41.         refreshTaskList()
  42.         searchDebounceEvent = nil
  43.       end, 250)
  44.     end
  45.   end
  46.  
  47.   local hideButton = Tasks.window:recursiveGetChildById('hideCompletedButton')
  48.   if hideButton then
  49.     hideButton.onClick = function()
  50.       hideCompleted = not hideCompleted
  51.       hideButton:setText(hideCompleted and "Show Completed" or "Hide Completed")
  52.       refreshTaskList()
  53.     end
  54.   end
  55.  
  56.   toggleTrackerButton.onClick = function()
  57.     if not selectedTask or not selectedTask.taskData then return end
  58.     local task = selectedTask.taskData
  59.  
  60.     if tracked_task.isTracked(task) then
  61.       Tasks.tryDestroyTrackedTask(task)
  62.     else
  63.       Tasks.trySetTrackedTask(task)
  64.     end
  65.   end  
  66.  
  67.   local function checkTrackedWidgetState()
  68.     if toggleTrackerButton and selectedTask and selectedTask.taskData then
  69.       local task = selectedTask.taskData
  70.       local isTracked = tracked_task.isTracked(task)
  71.       local widgetEntry = tracked_task.widgets[task.name]
  72.       local isVisible = isTracked and widgetEntry and widgetEntry.widget:isVisible()
  73.  
  74.       toggleTrackerButton:setOn(isVisible)
  75.       toggleTrackerButton:setText(isVisible and "Hide Tracker" or "Show Tracker")
  76.     end
  77.  
  78.     addEvent(checkTrackedWidgetState)
  79.   end
  80.  
  81.   addEvent(checkTrackedWidgetState)
  82.  
  83.  
  84. end
  85.  
  86. function terminate()
  87.   if Tasks.window then Tasks.window:destroy() end
  88.   g_keyboard.unbindKeyDown('Up', selectPreviousTask)
  89.   g_keyboard.unbindKeyDown('Down', selectNextTask)
  90.   g_keyboard.unbindKeyDown('Ctrl+D')
  91.   ProtocolGame.unregisterExtendedOpcode(125)
  92. end
  93.  
  94. function toggle()
  95.   if g_game.isOnline() and (not Tasks.list or #Tasks.list == 0) then
  96.     g_game.getProtocolGame():sendExtendedOpcode(125)
  97.   end
  98.   if not Tasks.window then return end
  99.  
  100.   if taskButton:isOn() then
  101.     Tasks.window:hide()
  102.     taskButton:setOn(false)
  103.   else
  104.     refreshTaskList()
  105.  
  106.     if not Tasks.window:getParent() then
  107.       local panel = modules.game_interface.findContentPanelAvailable(Tasks.window, Tasks.window:getMinimumHeight())
  108.       if not panel then return end
  109.       panel:addChild(Tasks.window)
  110.     end
  111.  
  112.     Tasks.window:show()
  113.     Tasks.window:raise()
  114.     Tasks.window:focus()
  115.     taskButton:setOn(true)
  116.   end
  117. end
  118.  
  119. function refreshTaskList()
  120.   suppressCheckChange = true
  121.  
  122.   local flatPanel = Tasks.window:getChildById('flatPanel')
  123.   if not flatPanel then return end
  124.  
  125.   local panel = flatPanel:getChildById('taskPanel')
  126.   if not panel then return end
  127.  
  128.   local scrollBar = flatPanel:getChildById('textlistScrollBar')
  129.   local scrollValue = scrollBar and scrollBar:getValue() or 0
  130.  
  131.   local previouslySelectedTaskName = selectedTask and selectedTask.taskData and selectedTask.taskData.name
  132.  
  133.   panel:destroyChildren()
  134.  
  135.   local entryToSelect = nil
  136.   local searchBox = Tasks.window:recursiveGetChildById('taskSearchBox')
  137.   local searchFilter = searchBox and searchBox:getText():lower() or ''
  138.  
  139.   local visibleIndex = 0
  140.  
  141.   for index, task in ipairs(Tasks.list or {}) do
  142.   if searchFilter == '' or task.name:lower():find(searchFilter, 1, true) then
  143.     local isCompleted = task.progress and task.total and task.progress >= task.total
  144.     if hideCompleted and isCompleted then
  145.       goto continue
  146.     end
  147.  
  148.     local entry = g_ui.createWidget('TaskListItem', panel)
  149.     entry.taskData = task
  150.  
  151.     if visibleIndex == 0 then
  152.       entry:setMarginTop(26)
  153.     end
  154.     visibleIndex = visibleIndex + 1
  155.  
  156.     if isCompleted then
  157.       entry:setOn("completed", true)
  158.     end
  159.  
  160.     entry.onClick = function()
  161.       if selectedEntry == entry then return end
  162.       if selectedEntry then
  163.         selectedEntry:setChecked(false)
  164.       end
  165.       entry:setChecked(true)
  166.       selectedEntry = entry
  167.       handleTaskSelection(entry)
  168.     end
  169.  
  170.     if previouslySelectedTaskName and previouslySelectedTaskName == task.name then
  171.       entryToSelect = entry
  172.     elseif not entryToSelect then
  173.       entryToSelect = entry
  174.     end
  175.  
  176.     local label = entry:getChildById('taskLabel')
  177.     if label then
  178.       label:setText(task.name)
  179.       label.onDoubleClick = function()
  180.         Tasks.trySetTrackedTask(entry.taskData)
  181.       end
  182.     end
  183.  
  184.     local creatureUI = entry:getChildById('creatureUI')
  185.     if creatureUI then
  186.       creatureUI:setOutfit({ type = task.lookType })
  187.     end
  188.   end
  189.   ::continue::
  190. end
  191.  
  192.  
  193.   suppressCheckChange = false
  194.  
  195.   if entryToSelect then
  196.     entryToSelect:setChecked(true)
  197.     handleTaskSelection(entryToSelect)
  198.   end
  199.  
  200.   if scrollBar then
  201.     scrollBar:setValue(scrollValue)
  202.   end
  203. end
  204.  
  205. function onTaskOpcode(protocol, opcode, buffer)
  206.   local ok, data = pcall(function() return json.decode(buffer) end)
  207.   if not ok or not data then
  208.     print("[TASK OPCODE] Failed to decode JSON:", buffer)
  209.     return
  210.   end
  211.  
  212.   Tasks.list = Tasks.list or {}
  213.  
  214.   local taskList = type(data[1]) == "table" and data or { data }
  215.  
  216.   for _, task in ipairs(taskList) do
  217.     local found = false
  218.     for _, t in ipairs(Tasks.list) do  
  219.       if t.name == task.name then
  220.         t.progress = task.progress
  221.         t.total = task.total
  222.         t.rewards = task.rewards or {}
  223.         t.monster = task.monster or {}
  224.         t.lookType = task.lookType
  225.         found = true
  226.         break
  227.       end
  228.     end
  229.     if not found then
  230.       table.insert(Tasks.list, task)
  231.     end
  232.   end
  233.  
  234.   if tracked_task then
  235.     tracked_task.tryUpdateFromList(taskList)
  236.   end
  237.  
  238.   if not refreshScheduled then
  239.     refreshScheduled = true
  240.     scheduleEvent(function()
  241.       if Tasks.window and Tasks.window:isVisible() then
  242.         refreshTaskList()
  243.       end
  244.       refreshScheduled = false
  245.     end, 100)
  246.   end
  247. end
  248.  
  249. function getSelectedTask()
  250.   local taskPanel = Tasks.window:getChildById('taskPanel')
  251.   if not taskPanel then return nil end
  252.  
  253.   for i = 1, taskPanel:getChildCount() do
  254.     local widget = taskPanel:getChildByIndex(i)
  255.     if widget:isChecked() then
  256.       return widget.taskData
  257.     end
  258.   end
  259.  
  260.   return nil
  261. end
  262.  
  263. function handleTaskSelection(widget)
  264.   if not widget or widget:isDestroyed() then return end
  265.   if selectedTask and selectedTask == widget then return end
  266.   if selectedTask and not selectedTask:isDestroyed() and selectedTask ~= widget then
  267.     selectedTask:setChecked(false)
  268.   end
  269.  
  270.   widget:setChecked(true)
  271.   selectedTask = widget
  272.   selectedEntry = widget
  273.  
  274.   local task = widget.taskData
  275.   if not task then return end
  276.  
  277.   local infoPanel = Tasks.window:getChildById('infoPanel')
  278.   if not infoPanel then return end
  279.  
  280.   local progressBar = infoPanel:getChildById('taskProgress')
  281.   if progressBar and task.total and task.progress then
  282.     progressBar:setMinimum(0)
  283.     progressBar:setMaximum(task.total)
  284.     progressBar:setValue(task.progress)
  285.     progressBar:setText(string.format("%d / %d", task.progress, task.total))
  286.   end
  287.  
  288.   local rewardPanel = infoPanel:getChildById('rewardPanel')
  289.   if rewardPanel then
  290.     rewardPanel:destroyChildren()
  291.     for _, reward in ipairs(task.rewards or {}) do
  292.       local icon = g_ui.createWidget('RewardItemWidget', rewardPanel)
  293.       if icon then
  294.         icon:setItemId(tonumber(reward.itemId or 0))
  295.         icon:setTooltip("x" .. tostring(reward.count or '?'))
  296.         icon:resize(32, 32)
  297.         if reward.count > 1 then icon:setText("x" .. tostring(reward.count or '?')) end
  298.       end
  299.     end
  300.   end
  301.  
  302.   local monsterPanel = infoPanel:getChildById('monsterPanel')
  303.   if monsterPanel then
  304.     monsterPanel:destroyChildren()
  305.     local monsters = type(task.monster) == "table" and task.monster or {
  306.       { name = task.monster, lookType = task.lookType, points = 1 }
  307.     }
  308.  
  309.     for _, monster in ipairs(monsters) do
  310.       local monsterIcon = g_ui.createWidget('MonsterUI', monsterPanel)
  311.       if monsterIcon then
  312.         monsterIcon:setOutfit({ type = monster.lookType })
  313.         monsterIcon:setTooltip(monster.name)
  314.         monsterIcon:setText("x" .. tostring(monster.points or '?'))
  315.         monsterIcon:resize(32, 32)
  316.       end
  317.     end
  318.   end
  319.  
  320.   if toggleTrackerButton and tracked_task and selectedTask and selectedTask.taskData then
  321.     local isTracked = tracked_task.isTracked(selectedTask.taskData)
  322.     toggleTrackerButton:setOn(isTracked)
  323.     toggleTrackerButton:setText(isTracked and "Hide Tracker" or "Show Tracker")
  324.   end
  325. end
  326.  
  327. function debounceSendTaskSelection(entry)
  328.   pendingSelectedEntry = entry
  329.  
  330.   if debounceEvent then
  331.     removeEvent(debounceEvent)
  332.   end
  333.  
  334.   debounceEvent = scheduleEvent(function()
  335.     if pendingSelectedEntry and pendingSelectedEntry:isChecked() then
  336.       uncheckAllTasks(pendingSelectedEntry)
  337.       handleTaskSelection(pendingSelectedEntry)
  338.     end
  339.     debounceEvent = nil
  340.     pendingSelectedEntry = nil
  341.   end, DEBOUNCE_DELAY_MS)
  342. end
  343.  
  344. function uncheckAllTasks(except)
  345.   local flatPanel = Tasks.window:getChildById('flatPanel')
  346.   if not flatPanel then return end
  347.  
  348.   local taskPanel = flatPanel:getChildById('taskPanel')
  349.   if not taskPanel then return end
  350.  
  351.   for _, child in pairs(taskPanel:getChildren()) do
  352.     if child ~= except then
  353.       child:setChecked(false)
  354.     end
  355.   end
  356. end
  357.  
  358. function selectPreviousTask()
  359.   debounceEvent("task_nav", 100, function()
  360.   if not selectedTask or not selectedTask:getParent() or not Tasks.window or not Tasks.window:isFocused() then return end
  361.  
  362.  
  363.     local flatPanel = Tasks.window:getChildById('flatPanel')
  364.     local panel = flatPanel:getChildById('taskPanel')
  365.     local scrollBar = flatPanel:getChildById('textlistScrollBar')
  366.     if not panel or not scrollBar then return end
  367.  
  368.     local children = panel:getChildren()
  369.     local step = scrollBar:getStep() or 1
  370.  
  371.     for i = #children, 1, -1 do
  372.       if children[i] == selectedTask and i > 1 then
  373.         local newSelected = children[i - 1]
  374.         handleTaskSelection(newSelected)
  375.         scrollBar:setValue((scrollBar:getValue() - step) + 5 )
  376.         break
  377.       end
  378.     end
  379.   end)
  380. end
  381.  
  382. function selectNextTask()
  383.   debounceEvent("task_nav", 100, function()
  384.     if not selectedTask or not selectedTask:getParent() or not Tasks.window or not Tasks.window:isFocused() then return end
  385.  
  386.  
  387.     local flatPanel = Tasks.window:getChildById('flatPanel')
  388.     local panel = flatPanel:getChildById('taskPanel')
  389.     local scrollBar = flatPanel:getChildById('textlistScrollBar')
  390.     if not panel or not scrollBar then return end
  391.  
  392.     local children = panel:getChildren()
  393.     local step = scrollBar:getStep() or 1
  394.  
  395.     for i = 1, #children do
  396.       if children[i] == selectedTask and i < #children then
  397.         local newSelected = children[i + 1]
  398.         handleTaskSelection(newSelected)
  399.         scrollBar:setValue((scrollBar:getValue() + step) - 5)
  400.         break
  401.       end
  402.     end
  403.   end)
  404. end
  405.  
  406. function debounceEvent(id, delay, fn)
  407.   if scheduledEvents[id] then
  408.     removeEvent(scheduledEvents[id])
  409.   end
  410.   scheduledEvents[id] = scheduleEvent(function()
  411.     scheduledEvents[id] = nil
  412.     fn()
  413.   end, delay)
  414. end
  415.  
  416. local function trySetTrackedTask(task)
  417.   if tracked_task and tracked_task.setTrackedTask then
  418.     if not tracked_task.isTracked(task) then
  419.         tracked_task.setTrackedTask(task)
  420.     end
  421.     lastTrackedTask = task
  422.  
  423.     if toggleTrackerButton and tracked_task and tracked_task.anyVisible then
  424.     local visible = tracked_task.anyVisible()
  425.     toggleTrackerButton:setOn(visible)
  426.     toggleTrackerButton:setText(visible and "Hide Tracker" or "Show Tracker")
  427.     end
  428.  
  429.   end
  430. end
  431.  
  432. local function tryDestroyTrackedTask(task)
  433.   if tracked_task and tracked_task.removeTrackedTask then
  434.     tracked_task.removeTrackedTask(task)
  435.     lastTrackedTask = nil
  436.  
  437.     if toggleTrackerButton then
  438.       toggleTrackerButton:setOn(false)
  439.       toggleTrackerButton:setText("Show Tracker")
  440.     end
  441.   end
  442. end
  443.  
  444. function table.find(t, value)
  445.   for i, v in ipairs(t) do
  446.     if v == value then return i end
  447.   end
  448. end
  449.  
  450. Tasks.trySetTrackedTask = trySetTrackedTask
  451. Tasks.tryDestroyTrackedTask = tryDestroyTrackedTask
  452.  
  453. _G.Tasks = Tasks
  454. return Tasks
Advertisement
Add Comment
Please, Sign In to add comment