• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!
Resource icon

Custom task menu 1.2.1

No permission to download
After merge to my current branch there were few merge conflicts and todos.
They are now removed.
I'm lazy so i will write it lazy way:
  • Esc now closes the window
  • Added x on tracked tab so it can be closed through gui, not esc button
  • Show/hide task now correctly tracks
  • Now correctly clean itself after logout/leaving the game
  • More performance changes

Remember to add in core.lua on server side this line:
dofile('data/lib/tasks.lua')

Did oopsie with task_lua. here is correct code:
LUA:
local killTask = CreatureEvent("TaskKill")

local taskSendQueue = {}
local taskSendScheduled = {}

local function queueTaskUpdate(player, taskData)
  local pid = player:getId()
  taskSendQueue[pid] = taskSendQueue[pid] or {}
  table.insert(taskSendQueue[pid], taskData)

  if taskSendScheduled[pid] then
    return
  end

  taskSendScheduled[pid] = true
  addEvent(function()
    local p = Player(pid)
    if not p then
      taskSendQueue[pid] = nil
      taskSendScheduled[pid] = nil
      return
    end

    for _, data in ipairs(taskSendQueue[pid] or {}) do
      print("[Task System] Sending payload via ExtendedOpcode 125:", serializeTaskTable(data))
      p:sendExtendedOpcode(125, serializeTaskTable(data))
    end

    taskSendQueue[pid] = nil
    taskSendScheduled[pid] = nil
  end, 50)
end

function killTask.onKill(creature, target)
  local player = creature:getPlayer()
  if not player or not target or not target:isMonster() then
    return true
  end

  local targetName = target:getName():lower()
  print("[Task System] onKill triggered! target:", targetName)

  if not TASKS then
    print("[Task System] ERROR: 'TASKS' global table is not defined!")
    return true
  end

  for taskId, task in pairs(TASKS) do
    local match = false
    local multiplier = 1

    if type(task.monster) == "string" then
      match = (targetName == task.monster:lower())
    elseif type(task.monster) == "table" then
      for _, m in ipairs(task.monster) do
        if targetName == tostring(m.name):lower() then
          multiplier = tonumber(m.points) or 1
          match = true
          break
        end
      end
    end

    if match then
      local key = task.storage or (10000 + taskId)
      local current = math.max(0, player:getStorageValue(key))
      
      print(string.format("[Task System] Match logic! current: %d, required: %d, multiplier: %d", current, task.amount, multiplier))

      if current < task.amount then
        local newProgress = math.min(current + multiplier, task.amount)
        player:setStorageValue(key, newProgress)

        local rewards = {}
        for _, reward in ipairs(task.reward or {}) do
            local item = ItemType(reward.itemId)
            if item and item:getId() ~= 0 then
                table.insert(rewards, { itemId = item:getId(), count = reward.count })
            else
                print(string.format("[Task] Invalid reward itemId %s in task '%s'", tostring(reward.itemId), tostring(task.name)))
            end
        end

        local monsters = {}
        if type(task.monster) == "table" then
          for _, m in ipairs(task.monster) do
            table.insert(monsters, { name = m.name, lookType = m.lookType, points = m.points })
          end
        else
          table.insert(monsters, { name = task.monster, lookType = task.lookType, points = 1 })
        end

        queueTaskUpdate(player, {
          progress = newProgress,
          total = task.amount,
          name = task.name,
          rewards = rewards,
          lookType = task.lookType,
          monster = monsters
        })

        if newProgress >= task.amount then
          for _, reward in ipairs(task.reward or {}) do
            player:addItem(reward.itemId, reward.count)
          end
          player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You completed the task: " .. task.name .. "!")
        end
      end
    end
  end

  return true
end

killTask:register()
1749748581161.webp





You can now toggle visibility of completed tasks with a single click. This helps you focus on what’s still pending and keeps your task list clean.​





Track your progress in real time with new floating mini windows!​



  • You can choose a task to “track” using the Show/Hide Tracker button.
  • The mini window updates live as your task progresses.
  • It stays visible even if you switch between tasks.
  • If you close it manually (e.g. with Esc), it won’t reopen unless you re-enable it.

These mini windows make it much easier to monitor multiple goals while playing — especially during grinding or questing!

Server-side Fixes​


  • Switched to using storageId for task tracking.
  • Optimized network load by sending all tasks in a single batch, reducing opcode spam to the client.
  • Introduced debounce logic in task_kill.lua to prevent rapid execution.
  • Various minor improvements and code cleanups.



️ UI Fixes & Improvements​


  • Added search bar with debounce for smoother filtering.
  • Implemented dynamic styling for completed tasks.
  • Added debounce on task click to avoid UI glitches.
  • Enabled keyboard navigation using arrow keys.
  • Minor layout adjustments and polish.
  • Like
Reactions: Reblux nonpvp
Back
Top