otclient-redemption/modules/modulelib/watchlist.lua
kokekanon 4a87cebb11
feat: protocol 15.24 packets and features (#1604)
New Features:
- Task Board (Bounty, Weekly, Shop) with Preferred & Soulseal modals, Kill Tracker, Offline Training dialog, Vocation tutorial, infobanner notifications, redesigned NPC chat/trade, and multi-slot action bar.

Gameplay Enhancements:
- Expanded Cyclopedia stats, talisman upgrades, weekly rewards/progress, soulseal fights, taskboard shop/offers and offline training actions.

UI / Graphics:
- Per-source spell-effect opacity sliders, status icon bar, HUD/scroll improvements, many new templates/styles, screenshot UI updates, and small label/icon fixes.

Bug Fixes:
- Miscellaneous UI behavior, layout and robustness improvements.
2026-05-20 11:26:11 -03:00

175 lines
4.8 KiB
Lua

local WATCH_LIST = {}
local WATCH_CYCLE_CHECK_MS = 50
local WATCH_EVENT = nil
local WidgetWatch = {}
function WidgetWatch.update()
table.remove_if(WATCH_LIST, function(i, obj)
if not obj or not obj.widget then
return true
end
local isDestroyed = obj.widget:isDestroyed()
if isDestroyed then
obj.widget = nil
elseif obj.methodName == 'conditionif' or obj.methodName == 'visible' or obj.widget:isVisible() then
obj.fnc(obj)
end
return isDestroyed
end)
if #WATCH_LIST == 0 then
removeEvent(WATCH_EVENT)
WATCH_EVENT = nil
end
end
function WidgetWatch.register(obj)
assert(type(obj) == "table" and obj.widget, "WidgetWatch.register: obj.widget é obrigatório")
local w = obj.widget
if not w then
pwarning("WidgetWatch.register: obj.widget is nil")
return
end
if w:isDestroyed() then
pwarning(string.format(
"WidgetWatch.register: widget '%s' is already destroyed.",
w:getId()
))
return
end
table.insert(WATCH_LIST, obj)
if WATCH_EVENT ~= nil or #WATCH_LIST == 0 then
return
end
WATCH_EVENT = cycleEvent(WidgetWatch.update, WATCH_CYCLE_CHECK_MS)
end
_G.WidgetWatch = WidgetWatch
-- table.watchList
-- Watches an array-like table (1..N) for structural changes.
-- Emits only inserts and removes (no move callbacks).
-- Keys come from ops.keyOf(item, index) or default to the item reference itself.
-- initial inserts via ops.initialScan = true or w:scan(true).
function table.watchList(realList, ops)
local self = {}
local keyOf = (ops and ops.keyOf) or function(x) return x end
local beginBatch = ops and ops.beginBatch
local endBatch = ops and ops.endBatch
local onInsert = ops and ops.onInsert
local onRemove = ops and ops.onRemove
local onUpdate = ops and ops.onUpdate
self.list = realList
self.keyOf = keyOf
self.prev = {}
self.prevK = {}
for i = 1, #realList do
self.prev[i] = realList[i]
self.prevK[i] = self.keyOf(realList[i], i)
end
local function buildIndexMap(keys)
local m = {}
for i = 1, #keys do m[keys[i]] = i end
return m
end
function self:scan(initial)
if initial then
if beginBatch then beginBatch() end
if onInsert then
for i = 1, #self.list do onInsert(i, self.list[i]) end
end
if endBatch then endBatch() end
return
end
local curr = self.list
local wantK = {}
for i = 1, #curr do wantK[i] = self.keyOf(curr[i], i) end
if beginBatch then beginBatch() end
local prevK = self.prevK
local prev = self.prev
local needsUpdate = #curr ~= #prev
if onUpdate and not needsUpdate then
for i = 1, #curr do
if curr[i] ~= prev[i] then
needsUpdate = true
break
end
end
end
local wantSet = {}
for i = 1, #wantK do wantSet[wantK[i]] = true end
if onRemove then
for i = #prevK, 1, -1 do
local k = prevK[i]
if not wantSet[k] then onRemove(i, prev[i]) end
end
end
local curK = {}
for i = 1, #prevK do
local k = prevK[i]
if wantSet[k] then curK[#curK + 1] = k end
end
local idxByKey = buildIndexMap(curK)
local i = 1
while i <= #wantK do
local want = wantK[i]
if curK[i] == want then
i = i + 1
else
local j = idxByKey[want]
if j then
local movedKey = table.remove(curK, j)
table.insert(curK, i, movedKey)
if j > i then
for p = i + 1, j do idxByKey[curK[p]] = p end
else
for p = j, i do idxByKey[curK[p]] = p end
end
idxByKey[movedKey] = i
i = i + 1
else
if onInsert then onInsert(i, curr[i]) end
table.insert(curK, i, want)
for p = i, #curK do idxByKey[curK[p]] = p end
i = i + 1
end
end
end
if onUpdate and needsUpdate then
for i = 1, #curr do onUpdate(i, curr[i]) end
end
if endBatch then endBatch() end
self.prev = {}
for k = 1, #curr do self.prev[k] = curr[k] end
self.prevK = wantK
end
if ops and ops.initialScan then
self:scan(true)
end
return self
end