From 71a1a32972a92fd97e7487cf40f18f07d15a43e6 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 2 Dec 2025 20:55:00 +0100 Subject: [PATCH 01/24] orders: add search overlay for manager orders Adds search overlay to find and navigate manager orders with arrow indicators showing current search result. Search uses Alt+S to focus, Alt+P/N for prev/next navigation. Overlays are disabled by default. --- docs/changelog.txt | 1 + plugins/lua/orders.lua | 398 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 398 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 91c50db23a..54e33d712d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ Template for new versions: ## New Tools ## New Features +- `orders`: added search overlay to find and navigate to matching manager orders with arrow indicators ## Fixes diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 2774bd80ee..c99e24fd21 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -6,6 +6,11 @@ local overlay = require('plugins.overlay') local textures = require('gui.textures') local utils = require('utils') local widgets = require('gui.widgets') +local stockflow = reqscript('internal/quickfort/stockflow') + +-- Shared state for search cursor visibility +local search_cursor_visible = false +local search_last_scroll_position = -1 -- -- OrdersOverlay @@ -74,7 +79,7 @@ local mi = df.global.game.main_interface OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay.ATTRS{ desc='Adds import, export, and other functions to the manager orders screen.', - default_pos={x=53,y=-6}, + default_pos={x=41,y=-6}, default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', frame={w=43, h=4}, @@ -709,11 +714,401 @@ function QuantityRightClickOverlay:onInput(keys) end end +-- +-- OrdersSearchOverlay +-- + +local search_cursor_visible = false +local search_last_scroll_position = -1 + +local function make_order_key(order) + local mat_cat_str = '' + if order.material_category then + local keys = {} + for k in pairs(order.material_category) do + if type(k) == 'string' then + table.insert(keys, k) + end + end + table.sort(keys) + for _, k in ipairs(keys) do + mat_cat_str = mat_cat_str .. k .. '=' .. tostring(order.material_category[k]) .. ';' + end + end + + local encrust_str = '' + if order.specflag and order.specflag.encrust_flags then + local flags = {'finished_goods', 'furniture', 'ammo'} + for _, flag in ipairs(flags) do + if order.specflag.encrust_flags[flag] then + encrust_str = encrust_str .. flag .. ';' + end + end + end + + return string.format('%d:%d:%d:%d:%d:%s:%s:%s', + order.job_type, + order.item_type, + order.item_subtype, + order.mat_type, + order.mat_index, + order.reaction_name or '', + mat_cat_str, + encrust_str) +end + +local function build_reaction_map() + local map = {} + local reactions = stockflow.collect_reactions() + + for _, reaction in ipairs(reactions) do + local key = make_order_key(reaction.order) + map[key] = reaction.name:lower() + end + + return map +end + +local reaction_map_cache = nil + +local function get_cached_reaction_map() + if not reaction_map_cache then + reaction_map_cache = build_reaction_map() + end + return reaction_map_cache +end + +local function get_order_search_key(order) + local reaction_map = get_cached_reaction_map() + local key = make_order_key(order) + if reaction_map[key] then + return reaction_map[key] + end + return "" +end + +local function matches_all_search_words(search_key, filter_text) + local search_words = {} + for word in filter_text:gmatch('%S+') do + table.insert(search_words, word) + end + + -- Check if all search words are found in search_key (order-independent) + for _, search_word in ipairs(search_words) do + if not search_key:find(search_word, 1, true) then + return false + end + end + return true +end + +OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) +OrdersSearchOverlay.ATTRS{ + desc='Adds a search box to find and navigate to matching manager orders.', + default_pos={x=85, y=-6}, + default_enabled=false, + viewscreens='dwarfmode/Info/WORK_ORDERS/Default', + frame={w=34, h=4}, +} + +function OrdersSearchOverlay:init() + local main_panel = widgets.Panel{ + view_id='main_panel', + frame={t=0, l=0, r=0, h=4}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, + frame_title='Search', + visible=function() return not self.minimized end, + subviews={ + widgets.EditField{ + view_id='filter', + frame={t=0, l=0}, + key='CUSTOM_ALT_S', + on_change=self:callback('update_filter'), + }, + widgets.HotkeyLabel{ + view_id='prev_match', + frame={t=1, l=0}, + label='prev', + key='CUSTOM_ALT_P', + auto_width=true, + on_activate=self:callback('jump_to_previous_match'), + enabled=function() return self:has_matches() end, + }, + widgets.HotkeyLabel{ + view_id='next_match', + frame={t=1, l=17}, + label='next', + key='CUSTOM_ALT_N', + auto_width=true, + on_activate=self:callback('jump_to_next_match'), + enabled=function() return self:has_matches() end, + }, + }, + } + + local minimized_panel = widgets.Panel{ + frame={t=0, r=0, w=3, h=1}, + subviews={ + widgets.Label{ + frame={t=0, l=0, w=1, h=1}, + text='[', + text_pen=COLOR_RED, + visible=function() return self.minimized end, + }, + widgets.Label{ + frame={t=0, l=1, w=1, h=1}, + text={{text=function() return self.minimized and string.char(31) or string.char(30) end}}, + text_pen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_GREY}, + text_hpen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE}, + on_click=function() self.minimized = not self.minimized end, + }, + widgets.Label{ + frame={t=0, r=0, w=1, h=1}, + text=']', + text_pen=COLOR_RED, + visible=function() return self.minimized end, + }, + }, + } + + self:addviews{ + main_panel, + minimized_panel, + } + + -- Initialize search state + self.filter_text = '' + self.matched_indices = {} + self.current_match_idx = 0 + self.minimized = false +end + +function OrdersSearchOverlay:update_filter(text) + self.filter_text = text:lower() + self.matched_indices = {} + self.current_match_idx = 0 + + if self.filter_text == '' then + self.subviews.main_panel.frame_title = 'Search' + return + end + + local orders = df.global.world.manager_orders.all + for i = 0, #orders - 1 do + local order = orders[i] + local search_key = get_order_search_key(order) + if matches_all_search_words(search_key, self.filter_text) then + table.insert(self.matched_indices, i) + end + end + + self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() +end + +function OrdersSearchOverlay:jump_to_next_match() + if #self.matched_indices == 0 then return end + + self.current_match_idx = self.current_match_idx + 1 + if self.current_match_idx > #self.matched_indices then + self.current_match_idx = 1 + end + + local order_idx = self.matched_indices[self.current_match_idx] + mi.info.work_orders.scroll_position_work_orders = order_idx + search_last_scroll_position = order_idx + search_cursor_visible = true + + self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() +end + +function OrdersSearchOverlay:jump_to_previous_match() + if #self.matched_indices == 0 then return end + + self.current_match_idx = self.current_match_idx - 1 + if self.current_match_idx < 1 then + self.current_match_idx = #self.matched_indices + end + + local order_idx = self.matched_indices[self.current_match_idx] + mi.info.work_orders.scroll_position_work_orders = order_idx + search_last_scroll_position = order_idx + search_cursor_visible = true + + self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() +end + +function OrdersSearchOverlay:get_match_text() + if self.filter_text == '' then + return '' + end + + local total_matches = #self.matched_indices + + if self.current_match_idx == 0 then + return string.format('%d matches', total_matches) + end + + return string.format('%d of %d', self.current_match_idx, total_matches) +end + +function OrdersSearchOverlay:has_matches() + return #self.matched_indices > 0 +end + +local function is_mouse_key(keys) + return keys._MOUSE_L + or keys._MOUSE_R + or keys._MOUSE_M + or keys.CONTEXT_SCROLL_UP + or keys.CONTEXT_SCROLL_DOWN + or keys.CONTEXT_SCROLL_PAGEUP + or keys.CONTEXT_SCROLL_PAGEDOWN +end + +function OrdersSearchOverlay:onInput(keys) + local filter_field = self.subviews.filter + if not filter_field then return false end + + -- Unfocus search on right-click + if keys._MOUSE_R and filter_field.focus then + filter_field:setFocus(false) + return true + end + + -- Let parent handle input first (for HotkeyLabel clicks and widget interactions) + if OrdersSearchOverlay.super.onInput(self, keys) then + return true + end + + -- Unfocus search on left-click when focused (for workshop and number of times changes) + -- And let the click pass through + if keys._MOUSE_L and filter_field.focus then + filter_field:setFocus(false) + return false + end + + -- Only consume input if search field has focus and it's not a mouse key + -- This allows scrolling, navigation, and mouse interaction in the orders list + if filter_field.focus and not is_mouse_key(keys) then + return true + end + + return false +end + +-- ------------------- +-- OrderHighlightOverlay +-- ------------------- + +OrderHighlightOverlay = defclass(OrderHighlightOverlay, overlay.OverlayWidget) +OrderHighlightOverlay.ATTRS{ + desc='Shows arrows next to the work order found by orders.search', + default_enabled=false, + viewscreens='dwarfmode/Info/WORK_ORDERS/Default', + frame={w=80, h=3}, +} + +function OrderHighlightOverlay:init() + self.ORDER_HEIGHT = 3 + self.TABS_WIDTH_THRESHOLD = 155 + self.LIST_START_Y_ONE_TABS_ROW = 8 + self.LIST_START_Y_TWO_TABS_ROWS = 10 + self.BOTTOM_MARGIN = 9 + self.ARROW_X_FIRST = 5 + self.ARROW_X_SECOND = 6 + self.ARROW_CHAR = '>' + + self.cached_list_start_y = nil + self.cached_viewport_size = nil + self.cached_screen_width = nil + self.cached_screen_height = nil +end + +function OrderHighlightOverlay:getListStartY() + local rect = gui.get_interface_rect() + + if rect.width >= self.TABS_WIDTH_THRESHOLD then + return self.LIST_START_Y_ONE_TABS_ROW + else + return self.LIST_START_Y_TWO_TABS_ROWS + end +end + +function OrderHighlightOverlay:getViewportSize() + local rect = gui.get_interface_rect() + local list_start_y = self:getListStartY() + + local available_height = rect.height - list_start_y - self.BOTTOM_MARGIN + return math.floor(available_height / self.ORDER_HEIGHT) +end + +function OrderHighlightOverlay:calculateSelectedOrderY() + local orders = df.global.world.manager_orders.all + local scroll_pos = mi.info.work_orders.scroll_position_work_orders + + if #orders == 0 or scroll_pos < 0 or scroll_pos >= #orders then + return nil + end + + local list_start_y = self:getListStartY() + local viewport_size = self:getViewportSize() + + local viewport_start = scroll_pos + local viewport_end = scroll_pos + viewport_size - 1 + + -- Selected order tries to be at the top unless we're at the end of the list + if viewport_end >= #orders then + viewport_end = #orders - 1 + viewport_start = math.max(0, viewport_end - viewport_size + 1) + end + + local pos_in_viewport = scroll_pos - viewport_start + + local selected_y = list_start_y + (pos_in_viewport * self.ORDER_HEIGHT) + + return selected_y +end + +function OrderHighlightOverlay:render(dc) + if search_cursor_visible then + local current_scroll = mi.info.work_orders.scroll_position_work_orders + + -- Hide cursor when user manually scrolls + if search_last_scroll_position ~= -1 and current_scroll ~= search_last_scroll_position then + search_cursor_visible = false + end + search_last_scroll_position = current_scroll + end + + OrderHighlightOverlay.super.render(self, dc) +end + +function OrderHighlightOverlay:onRenderFrame(dc, rect) + OrderHighlightOverlay.super.onRenderFrame(self, dc, rect) + + if not search_cursor_visible then return end + + local selected_y = self:calculateSelectedOrderY() + if not selected_y then return end + + local highlight_pen = dfhack.pen.parse{ + fg=COLOR_LIGHTGREEN, + bold=true, + } + + local y = selected_y + 1 -- Middle line of the 3-line order + dc:seek(self.ARROW_X_FIRST, y):string(self.ARROW_CHAR, highlight_pen) + dc:seek(self.ARROW_X_SECOND, y):string(self.ARROW_CHAR, highlight_pen) +end + -- ------------------- OVERLAY_WIDGETS = { recheck=RecheckOverlay, importexport=OrdersOverlay, + search=OrdersSearchOverlay, + highlight=OrderHighlightOverlay, skillrestrictions=SkillRestrictionOverlay, laborrestrictions=LaborRestrictionsOverlay, conditionsrightclick=ConditionsRightClickOverlay, @@ -722,3 +1117,4 @@ OVERLAY_WIDGETS = { } return _ENV + From cfaa14062102150eb59e6be6d5b338c3941896db Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 2 Dec 2025 21:39:56 +0100 Subject: [PATCH 02/24] Fix trailing whitespace --- plugins/lua/orders.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index c99e24fd21..0095fe8c27 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1056,7 +1056,7 @@ function OrderHighlightOverlay:calculateSelectedOrderY() local viewport_start = scroll_pos local viewport_end = scroll_pos + viewport_size - 1 - + -- Selected order tries to be at the top unless we're at the end of the list if viewport_end >= #orders then viewport_end = #orders - 1 @@ -1116,5 +1116,4 @@ OVERLAY_WIDGETS = { quantityrightclick=QuantityRightClickOverlay, } -return _ENV - +return _ENV \ No newline at end of file From 16ab556bb04bfb1558a70ad48d16e069b4f31a35 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 2 Dec 2025 21:45:39 +0100 Subject: [PATCH 03/24] Newline at the end --- plugins/lua/orders.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 0095fe8c27..22236e4769 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1116,4 +1116,4 @@ OVERLAY_WIDGETS = { quantityrightclick=QuantityRightClickOverlay, } -return _ENV \ No newline at end of file +return _ENV From e36860793be04b18d5612cbdff1b9d495b34cacf Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 17:49:00 +0100 Subject: [PATCH 04/24] orders: load cache reaction map on init --- plugins/lua/orders.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 22236e4769..ef78d5b303 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -758,6 +758,11 @@ local function make_order_key(order) end local function build_reaction_map() + local can_read_stockflow = dfhack.isWorldLoaded() and dfhack.isMapLoaded() + if not can_read_stockflow then + return nil + end + local map = {} local reactions = stockflow.collect_reactions() @@ -812,6 +817,8 @@ OrdersSearchOverlay.ATTRS{ } function OrdersSearchOverlay:init() + get_cached_reaction_map() + local main_panel = widgets.Panel{ view_id='main_panel', frame={t=0, l=0, r=0, h=4}, From b46eb7fa34925f3691df03faa26fefbf3e25485d Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 19:05:07 +0100 Subject: [PATCH 05/24] Add Enter/Shift+Enter navigation and refactor jump to match --- plugins/lua/orders.lua | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index ef78d5b303..31b9f76cbb 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -832,23 +832,23 @@ function OrdersSearchOverlay:init() frame={t=0, l=0}, key='CUSTOM_ALT_S', on_change=self:callback('update_filter'), + on_submit=self:callback('on_submit'), + on_submit2=self:callback('on_submit2'), }, widgets.HotkeyLabel{ - view_id='prev_match', frame={t=1, l=0}, label='prev', key='CUSTOM_ALT_P', auto_width=true, - on_activate=self:callback('jump_to_previous_match'), + on_activate=self:callback('cycle_match', -1), enabled=function() return self:has_matches() end, }, widgets.HotkeyLabel{ - view_id='next_match', frame={t=1, l=17}, label='next', key='CUSTOM_ALT_N', auto_width=true, - on_activate=self:callback('jump_to_next_match'), + on_activate=self:callback('cycle_match', 1), enabled=function() return self:has_matches() end, }, }, @@ -913,27 +913,23 @@ function OrdersSearchOverlay:update_filter(text) self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() end -function OrdersSearchOverlay:jump_to_next_match() - if #self.matched_indices == 0 then return end - - self.current_match_idx = self.current_match_idx + 1 - if self.current_match_idx > #self.matched_indices then - self.current_match_idx = 1 - end - - local order_idx = self.matched_indices[self.current_match_idx] - mi.info.work_orders.scroll_position_work_orders = order_idx - search_last_scroll_position = order_idx - search_cursor_visible = true +function OrdersSearchOverlay:on_submit() + self:cycle_match(1) + self.subviews.filter:setFocus(true) +end - self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() +function OrdersSearchOverlay:on_submit2() + self:cycle_match(-1) + self.subviews.filter:setFocus(true) end -function OrdersSearchOverlay:jump_to_previous_match() +function OrdersSearchOverlay:cycle_match(direction) if #self.matched_indices == 0 then return end - self.current_match_idx = self.current_match_idx - 1 - if self.current_match_idx < 1 then + self.current_match_idx = self.current_match_idx + direction + if direction > 0 and self.current_match_idx > #self.matched_indices then + self.current_match_idx = 1 + elseif direction < 0 and self.current_match_idx < 1 then self.current_match_idx = #self.matched_indices end From cde147713f745c1130f2eb135b9a23e60422dfd5 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 19:09:30 +0100 Subject: [PATCH 06/24] Hide search highlight when filter text changes --- plugins/lua/orders.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 31b9f76cbb..a4672eeeda 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -895,6 +895,7 @@ function OrdersSearchOverlay:update_filter(text) self.filter_text = text:lower() self.matched_indices = {} self.current_match_idx = 0 + search_cursor_visible = false if self.filter_text == '' then self.subviews.main_panel.frame_title = 'Search' From 7fa03fb4c200ded3cee46089e8cbf2bee3945bdc Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 19:18:06 +0100 Subject: [PATCH 07/24] Use full_interface for OrderHighlightOverlay to prevent repositioning --- plugins/lua/orders.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index a4672eeeda..e4cb70e0f6 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1010,7 +1010,7 @@ OrderHighlightOverlay.ATTRS{ desc='Shows arrows next to the work order found by orders.search', default_enabled=false, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - frame={w=80, h=3}, + full_interface=true, } function OrderHighlightOverlay:init() From c163d3c159137f8584355e0dbc13cbc7aeec392c Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 19:35:18 +0100 Subject: [PATCH 08/24] Hide highlight when order list changes --- plugins/lua/orders.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index e4cb70e0f6..7692529440 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -11,6 +11,7 @@ local stockflow = reqscript('internal/quickfort/stockflow') -- Shared state for search cursor visibility local search_cursor_visible = false local search_last_scroll_position = -1 +local order_count_at_highlight = 0 -- -- OrdersOverlay @@ -938,6 +939,7 @@ function OrdersSearchOverlay:cycle_match(direction) mi.info.work_orders.scroll_position_work_orders = order_idx search_last_scroll_position = order_idx search_cursor_visible = true + order_count_at_highlight = #df.global.world.manager_orders.all self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() end @@ -1077,12 +1079,18 @@ end function OrderHighlightOverlay:render(dc) if search_cursor_visible then local current_scroll = mi.info.work_orders.scroll_position_work_orders + local current_order_count = #df.global.world.manager_orders.all -- Hide cursor when user manually scrolls if search_last_scroll_position ~= -1 and current_scroll ~= search_last_scroll_position then search_cursor_visible = false end search_last_scroll_position = current_scroll + + -- Hide cursor when order list changes (orders added or removed) + if order_count_at_highlight ~= current_order_count then + search_cursor_visible = false + end end OrderHighlightOverlay.super.render(self, dc) From f37e46adec9964b48bc8ada72e298c538b90be5a Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 19:47:35 +0100 Subject: [PATCH 09/24] Consolidate onRenderFrame into render method --- plugins/lua/orders.lua | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 7692529440..eb2b4207b5 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1085,33 +1085,25 @@ function OrderHighlightOverlay:render(dc) if search_last_scroll_position ~= -1 and current_scroll ~= search_last_scroll_position then search_cursor_visible = false end - search_last_scroll_position = current_scroll -- Hide cursor when order list changes (orders added or removed) if order_count_at_highlight ~= current_order_count then search_cursor_visible = false end - end - - OrderHighlightOverlay.super.render(self, dc) -end - -function OrderHighlightOverlay:onRenderFrame(dc, rect) - OrderHighlightOverlay.super.onRenderFrame(self, dc, rect) - - if not search_cursor_visible then return end - local selected_y = self:calculateSelectedOrderY() - if not selected_y then return end - - local highlight_pen = dfhack.pen.parse{ - fg=COLOR_LIGHTGREEN, - bold=true, - } + -- Draw highlight arrows + local selected_y = self:calculateSelectedOrderY() + if selected_y then + local highlight_pen = dfhack.pen.parse{ + fg=COLOR_LIGHTGREEN, + bold=true, + } - local y = selected_y + 1 -- Middle line of the 3-line order - dc:seek(self.ARROW_X_FIRST, y):string(self.ARROW_CHAR, highlight_pen) - dc:seek(self.ARROW_X_SECOND, y):string(self.ARROW_CHAR, highlight_pen) + local y = selected_y + 1 -- Middle line of the 3-line order + dc:seek(self.ARROW_X_FIRST, y):string(self.ARROW_CHAR, highlight_pen) + dc:seek(self.ARROW_X_SECOND, y):string(self.ARROW_CHAR, highlight_pen) + end + end end -- ------------------- From 506ca40b1166657cff24c07e46305bdf198ece40 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 20:09:09 +0100 Subject: [PATCH 10/24] Use utils.search_text instead of custom search --- plugins/lua/orders.lua | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index eb2b4207b5..28d16068d8 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -793,21 +793,6 @@ local function get_order_search_key(order) return "" end -local function matches_all_search_words(search_key, filter_text) - local search_words = {} - for word in filter_text:gmatch('%S+') do - table.insert(search_words, word) - end - - -- Check if all search words are found in search_key (order-independent) - for _, search_word in ipairs(search_words) do - if not search_key:find(search_word, 1, true) then - return false - end - end - return true -end - OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) OrdersSearchOverlay.ATTRS{ desc='Adds a search box to find and navigate to matching manager orders.', @@ -886,19 +871,17 @@ function OrdersSearchOverlay:init() } -- Initialize search state - self.filter_text = '' self.matched_indices = {} self.current_match_idx = 0 self.minimized = false end function OrdersSearchOverlay:update_filter(text) - self.filter_text = text:lower() self.matched_indices = {} self.current_match_idx = 0 search_cursor_visible = false - if self.filter_text == '' then + if text == '' then self.subviews.main_panel.frame_title = 'Search' return end @@ -907,7 +890,7 @@ function OrdersSearchOverlay:update_filter(text) for i = 0, #orders - 1 do local order = orders[i] local search_key = get_order_search_key(order) - if matches_all_search_words(search_key, self.filter_text) then + if search_key and utils.search_text(search_key, text) then table.insert(self.matched_indices, i) end end @@ -945,12 +928,12 @@ function OrdersSearchOverlay:cycle_match(direction) end function OrdersSearchOverlay:get_match_text() - if self.filter_text == '' then + local total_matches = #self.matched_indices + + if total_matches == 0 then return '' end - local total_matches = #self.matched_indices - if self.current_match_idx == 0 then return string.format('%d matches', total_matches) end From 7fabf2b8b887d795ca2f1c6d3c3e5d057e29e325 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 20:13:46 +0100 Subject: [PATCH 11/24] Force new version of overlay position --- plugins/lua/orders.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 28d16068d8..db2a187f42 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -84,6 +84,7 @@ OrdersOverlay.ATTRS{ default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', frame={w=43, h=4}, + version=1, } function OrdersOverlay:init() From 2cbf2d7860b09b730f9853c88c50ec55e18452db Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 20:24:51 +0100 Subject: [PATCH 12/24] Narrow search overlay and adjust button positions Now there is 16 visible chars in search --- plugins/lua/orders.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index db2a187f42..c2600ce5b0 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -800,7 +800,7 @@ OrdersSearchOverlay.ATTRS{ default_pos={x=85, y=-6}, default_enabled=false, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - frame={w=34, h=4}, + frame={w=26, h=4}, } function OrdersSearchOverlay:init() @@ -831,7 +831,7 @@ function OrdersSearchOverlay:init() enabled=function() return self:has_matches() end, }, widgets.HotkeyLabel{ - frame={t=1, l=17}, + frame={t=1, l=12}, label='next', key='CUSTOM_ALT_N', auto_width=true, From 1f2705d500ba045cc2aef8d3f3080e81bd8b540b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 6 Dec 2025 21:31:49 +0100 Subject: [PATCH 13/24] Reshape arrow and contrasting colors --- plugins/lua/orders.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index c2600ce5b0..0df4e06e98 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1005,9 +1005,9 @@ function OrderHighlightOverlay:init() self.LIST_START_Y_ONE_TABS_ROW = 8 self.LIST_START_Y_TWO_TABS_ROWS = 10 self.BOTTOM_MARGIN = 9 - self.ARROW_X_FIRST = 5 - self.ARROW_X_SECOND = 6 - self.ARROW_CHAR = '>' + self.ARROW_X = 10 + self.ARROW_FG = COLOR_BLACK + self.ARROW_BG = COLOR_WHITE self.cached_list_start_y = nil self.cached_viewport_size = nil @@ -1079,13 +1079,14 @@ function OrderHighlightOverlay:render(dc) local selected_y = self:calculateSelectedOrderY() if selected_y then local highlight_pen = dfhack.pen.parse{ - fg=COLOR_LIGHTGREEN, + fg=self.ARROW_FG, + bg=self.ARROW_BG, bold=true, } - local y = selected_y + 1 -- Middle line of the 3-line order - dc:seek(self.ARROW_X_FIRST, y):string(self.ARROW_CHAR, highlight_pen) - dc:seek(self.ARROW_X_SECOND, y):string(self.ARROW_CHAR, highlight_pen) + dc:seek(self.ARROW_X, selected_y):string('|', highlight_pen) + dc:seek(self.ARROW_X, selected_y + 1):string('>', highlight_pen) + dc:seek(self.ARROW_X, selected_y + 2):string('|', highlight_pen) end end end From 93f6b1c28d51be3d6b9858367e1bd1b46312c69f Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 7 Dec 2025 10:27:47 +0100 Subject: [PATCH 14/24] Hide overlay when job_details is open. Add author --- docs/about/Authors.rst | 1 + plugins/lua/orders.lua | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/about/Authors.rst b/docs/about/Authors.rst index 8743b75a19..a154d314b6 100644 --- a/docs/about/Authors.rst +++ b/docs/about/Authors.rst @@ -163,6 +163,7 @@ Omniclasm Ong Ying Gao ong-yinggao98 oorzkws oorzkws OwnageIsMagic OwnageIsMagic +pajawojciech pajawojciech palenerd dlmarquis PassionateAngler PassionateAngler Patrik Lundell PatrikLundell diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 0df4e06e98..8cde607826 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -957,6 +957,8 @@ local function is_mouse_key(keys) end function OrdersSearchOverlay:onInput(keys) + if mi.job_details.open then return end + local filter_field = self.subviews.filter if not filter_field then return false end @@ -987,6 +989,11 @@ function OrdersSearchOverlay:onInput(keys) return false end +function OrdersSearchOverlay:render(dc) + if mi.job_details.open then return end + OrdersSearchOverlay.super.render(self, dc) +end + -- ------------------- -- OrderHighlightOverlay -- ------------------- @@ -1061,6 +1068,8 @@ function OrderHighlightOverlay:calculateSelectedOrderY() end function OrderHighlightOverlay:render(dc) + if mi.job_details.open then return end + if search_cursor_visible then local current_scroll = mi.info.work_orders.scroll_position_work_orders local current_order_count = #df.global.world.manager_orders.all From 6bd16adfcff2d9b064e4d901de5d3151fb173205 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 7 Dec 2025 10:31:48 +0100 Subject: [PATCH 15/24] Remove trailing spaces --- plugins/lua/orders.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 8cde607826..0960bfd83d 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -958,7 +958,7 @@ end function OrdersSearchOverlay:onInput(keys) if mi.job_details.open then return end - + local filter_field = self.subviews.filter if not filter_field then return false end From d8eb61e1b8eb0401f00c5a64428137d70867432d Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:19:38 +0100 Subject: [PATCH 16/24] Rebuild manager order search results on every navigation to fix stale results after sorting/clearing/importing orders --- plugins/lua/orders.lua | 58 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 0960bfd83d..8641757de6 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -877,14 +877,11 @@ function OrdersSearchOverlay:init() self.minimized = false end -function OrdersSearchOverlay:update_filter(text) - self.matched_indices = {} - self.current_match_idx = 0 - search_cursor_visible = false +function OrdersSearchOverlay:perform_search(text) + local matches = {} if text == '' then - self.subviews.main_panel.frame_title = 'Search' - return + return matches end local orders = df.global.world.manager_orders.all @@ -892,11 +889,23 @@ function OrdersSearchOverlay:update_filter(text) local order = orders[i] local search_key = get_order_search_key(order) if search_key and utils.search_text(search_key, text) then - table.insert(self.matched_indices, i) + table.insert(matches, i) end end - self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() + return matches +end + +function OrdersSearchOverlay:update_filter(text) + self.matched_indices = self:perform_search(text) + self.current_match_idx = 0 + search_cursor_visible = false + + if text == '' then + self.subviews.main_panel.frame_title = 'Search' + else + self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() + end end function OrdersSearchOverlay:on_submit() @@ -910,22 +919,37 @@ function OrdersSearchOverlay:on_submit2() end function OrdersSearchOverlay:cycle_match(direction) - if #self.matched_indices == 0 then return end + local search_text = self.subviews.filter.text + + local new_matches = self:perform_search(search_text) - self.current_match_idx = self.current_match_idx + direction - if direction > 0 and self.current_match_idx > #self.matched_indices then - self.current_match_idx = 1 - elseif direction < 0 and self.current_match_idx < 1 then - self.current_match_idx = #self.matched_indices + if #new_matches == 0 then + self.matched_indices = {} + self.current_match_idx = 0 + search_cursor_visible = false + self.subviews.main_panel.frame_title = 'Search' + return end + local new_match_idx = self.current_match_idx + direction + + if new_match_idx > #new_matches then + new_match_idx = 1 + elseif new_match_idx < 1 then + new_match_idx = #new_matches + end + + self.matched_indices = new_matches + self.current_match_idx = new_match_idx + + -- Scroll to the selected match local order_idx = self.matched_indices[self.current_match_idx] mi.info.work_orders.scroll_position_work_orders = order_idx search_last_scroll_position = order_idx search_cursor_visible = true order_count_at_highlight = #df.global.world.manager_orders.all - self.subviews.main_panel.frame_title = 'Search: ' .. self:get_match_text() + self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() end function OrdersSearchOverlay:get_match_text() @@ -936,10 +960,10 @@ function OrdersSearchOverlay:get_match_text() end if self.current_match_idx == 0 then - return string.format('%d matches', total_matches) + return string.format(': %d matches', total_matches) end - return string.format('%d of %d', self.current_match_idx, total_matches) + return string.format(': %d of %d', self.current_match_idx, total_matches) end function OrdersSearchOverlay:has_matches() From 860a2a1c9d5b50e85d02d6791bfd43f82bb3ab22 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:23:36 +0100 Subject: [PATCH 17/24] Consolidate search state variables in OrdersSearchOverlay section to remove shadowing --- plugins/lua/orders.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 8641757de6..1d9e8659d0 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -8,11 +8,6 @@ local utils = require('utils') local widgets = require('gui.widgets') local stockflow = reqscript('internal/quickfort/stockflow') --- Shared state for search cursor visibility -local search_cursor_visible = false -local search_last_scroll_position = -1 -local order_count_at_highlight = 0 - -- -- OrdersOverlay -- @@ -722,6 +717,7 @@ end local search_cursor_visible = false local search_last_scroll_position = -1 +local order_count_at_highlight = 0 local function make_order_key(order) local mat_cat_str = '' From ab257aa54b8424bb288c153e5b78ba0e89eb581e Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:27:26 +0100 Subject: [PATCH 18/24] Guard get_order_search_key against nil reaction map and return nil for missing entries --- plugins/lua/orders.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 1d9e8659d0..a47574c96b 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -783,11 +783,11 @@ end local function get_order_search_key(order) local reaction_map = get_cached_reaction_map() - local key = make_order_key(order) - if reaction_map[key] then - return reaction_map[key] + if not reaction_map then + return nil end - return "" + local key = make_order_key(order) + return reaction_map[key] end OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) From 3a727a097a97294cbf1301059946fabaf7bf3a3f Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:29:37 +0100 Subject: [PATCH 19/24] Remove unused cached variables from OrderHighlightOverlay init --- plugins/lua/orders.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index a47574c96b..f8da92677e 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1035,11 +1035,6 @@ function OrderHighlightOverlay:init() self.ARROW_X = 10 self.ARROW_FG = COLOR_BLACK self.ARROW_BG = COLOR_WHITE - - self.cached_list_start_y = nil - self.cached_viewport_size = nil - self.cached_screen_width = nil - self.cached_screen_height = nil end function OrderHighlightOverlay:getListStartY() From d3d7067d1378e0e924849d7c8678c2fdcf5a95f4 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:33:42 +0100 Subject: [PATCH 20/24] Convert OrderHighlightOverlay constants from instance fields to module-locals and inline arrow colors --- plugins/lua/orders.lua | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index f8da92677e..dcfd92d4e5 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1018,6 +1018,13 @@ end -- OrderHighlightOverlay -- ------------------- +local ORDER_HEIGHT = 3 +local TABS_WIDTH_THRESHOLD = 155 +local LIST_START_Y_ONE_TABS_ROW = 8 +local LIST_START_Y_TWO_TABS_ROWS = 10 +local BOTTOM_MARGIN = 9 +local ARROW_X = 10 + OrderHighlightOverlay = defclass(OrderHighlightOverlay, overlay.OverlayWidget) OrderHighlightOverlay.ATTRS{ desc='Shows arrows next to the work order found by orders.search', @@ -1026,24 +1033,13 @@ OrderHighlightOverlay.ATTRS{ full_interface=true, } -function OrderHighlightOverlay:init() - self.ORDER_HEIGHT = 3 - self.TABS_WIDTH_THRESHOLD = 155 - self.LIST_START_Y_ONE_TABS_ROW = 8 - self.LIST_START_Y_TWO_TABS_ROWS = 10 - self.BOTTOM_MARGIN = 9 - self.ARROW_X = 10 - self.ARROW_FG = COLOR_BLACK - self.ARROW_BG = COLOR_WHITE -end - function OrderHighlightOverlay:getListStartY() local rect = gui.get_interface_rect() - if rect.width >= self.TABS_WIDTH_THRESHOLD then - return self.LIST_START_Y_ONE_TABS_ROW + if rect.width >= TABS_WIDTH_THRESHOLD then + return LIST_START_Y_ONE_TABS_ROW else - return self.LIST_START_Y_TWO_TABS_ROWS + return LIST_START_Y_TWO_TABS_ROWS end end @@ -1051,8 +1047,8 @@ function OrderHighlightOverlay:getViewportSize() local rect = gui.get_interface_rect() local list_start_y = self:getListStartY() - local available_height = rect.height - list_start_y - self.BOTTOM_MARGIN - return math.floor(available_height / self.ORDER_HEIGHT) + local available_height = rect.height - list_start_y - BOTTOM_MARGIN + return math.floor(available_height / ORDER_HEIGHT) end function OrderHighlightOverlay:calculateSelectedOrderY() @@ -1077,7 +1073,7 @@ function OrderHighlightOverlay:calculateSelectedOrderY() local pos_in_viewport = scroll_pos - viewport_start - local selected_y = list_start_y + (pos_in_viewport * self.ORDER_HEIGHT) + local selected_y = list_start_y + (pos_in_viewport * ORDER_HEIGHT) return selected_y end @@ -1103,14 +1099,14 @@ function OrderHighlightOverlay:render(dc) local selected_y = self:calculateSelectedOrderY() if selected_y then local highlight_pen = dfhack.pen.parse{ - fg=self.ARROW_FG, - bg=self.ARROW_BG, + fg=COLOR_BLACK, + bg=COLOR_WHITE, bold=true, } - dc:seek(self.ARROW_X, selected_y):string('|', highlight_pen) - dc:seek(self.ARROW_X, selected_y + 1):string('>', highlight_pen) - dc:seek(self.ARROW_X, selected_y + 2):string('|', highlight_pen) + dc:seek(ARROW_X, selected_y):string('|', highlight_pen) + dc:seek(ARROW_X, selected_y + 1):string('>', highlight_pen) + dc:seek(ARROW_X, selected_y + 2):string('|', highlight_pen) end end end From 2cd1c6efa7986852aa5e760dbc82283221955493 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:42:01 +0100 Subject: [PATCH 21/24] Use early return guard for search cursor visibility in OrderHighlightOverlay render. Return when disable cursor --- plugins/lua/orders.lua | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index dcfd92d4e5..e11d11c9ef 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1079,35 +1079,35 @@ function OrderHighlightOverlay:calculateSelectedOrderY() end function OrderHighlightOverlay:render(dc) - if mi.job_details.open then return end - - if search_cursor_visible then - local current_scroll = mi.info.work_orders.scroll_position_work_orders - local current_order_count = #df.global.world.manager_orders.all + if mi.job_details.open or not search_cursor_visible then return end - -- Hide cursor when user manually scrolls - if search_last_scroll_position ~= -1 and current_scroll ~= search_last_scroll_position then - search_cursor_visible = false - end + local current_scroll = mi.info.work_orders.scroll_position_work_orders + local current_order_count = #df.global.world.manager_orders.all - -- Hide cursor when order list changes (orders added or removed) - if order_count_at_highlight ~= current_order_count then - search_cursor_visible = false - end + -- Hide cursor when user manually scrolls + if search_last_scroll_position ~= -1 and current_scroll ~= search_last_scroll_position then + search_cursor_visible = false + return + end - -- Draw highlight arrows - local selected_y = self:calculateSelectedOrderY() - if selected_y then - local highlight_pen = dfhack.pen.parse{ - fg=COLOR_BLACK, - bg=COLOR_WHITE, - bold=true, - } + -- Hide cursor when order list changes (orders added or removed) + if order_count_at_highlight ~= current_order_count then + search_cursor_visible = false + return + end - dc:seek(ARROW_X, selected_y):string('|', highlight_pen) - dc:seek(ARROW_X, selected_y + 1):string('>', highlight_pen) - dc:seek(ARROW_X, selected_y + 2):string('|', highlight_pen) - end + -- Draw highlight arrows + local selected_y = self:calculateSelectedOrderY() + if selected_y then + local highlight_pen = dfhack.pen.parse{ + fg=COLOR_BLACK, + bg=COLOR_WHITE, + bold=true, + } + + dc:seek(ARROW_X, selected_y):string('|', highlight_pen) + dc:seek(ARROW_X, selected_y + 1):string('>', highlight_pen) + dc:seek(ARROW_X, selected_y + 2):string('|', highlight_pen) end end From d605165a83d8de5253e48655fac480ee59cfa70c Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:46:02 +0100 Subject: [PATCH 22/24] Add unconditional super render call to OrderHighlightOverlay for future compatibility --- plugins/lua/orders.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index e11d11c9ef..67ea8ab8b0 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -1079,6 +1079,8 @@ function OrderHighlightOverlay:calculateSelectedOrderY() end function OrderHighlightOverlay:render(dc) + OrderHighlightOverlay.super.render(self, dc) + if mi.job_details.open or not search_cursor_visible then return end local current_scroll = mi.info.work_orders.scroll_position_work_orders From 14f46c897bf3a9c7b8acb2f2a6986bc2af78a59b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 09:48:54 +0100 Subject: [PATCH 23/24] Inline build_reaction_map into get_cached_reaction_map with early return pattern --- plugins/lua/orders.lua | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 67ea8ab8b0..14e79d9f25 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -755,7 +755,13 @@ local function make_order_key(order) encrust_str) end -local function build_reaction_map() +local reaction_map_cache = nil + +local function get_cached_reaction_map() + if reaction_map_cache then + return reaction_map_cache + end + local can_read_stockflow = dfhack.isWorldLoaded() and dfhack.isMapLoaded() if not can_read_stockflow then return nil @@ -769,15 +775,7 @@ local function build_reaction_map() map[key] = reaction.name:lower() end - return map -end - -local reaction_map_cache = nil - -local function get_cached_reaction_map() - if not reaction_map_cache then - reaction_map_cache = build_reaction_map() - end + reaction_map_cache = map return reaction_map_cache end From 1bdd533b3038fa3fbd189b711b18e840f6e5d8db Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 13 Dec 2025 10:00:37 +0100 Subject: [PATCH 24/24] Free C++ manager_order objects allocated by collect_reactions to prevent memory leak --- plugins/lua/orders.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 14e79d9f25..f0e7bc502e 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -773,7 +773,9 @@ local function get_cached_reaction_map() for _, reaction in ipairs(reactions) do local key = make_order_key(reaction.order) map[key] = reaction.name:lower() + df.delete(reaction.order) end + reactions = nil reaction_map_cache = map return reaction_map_cache