From 71a1a32972a92fd97e7487cf40f18f07d15a43e6 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 2 Dec 2025 20:55:00 +0100 Subject: [PATCH 001/202] 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 91c50db23a4..54e33d712db 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 2774bd80ee0..c99e24fd213 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 002/202] 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 c99e24fd213..0095fe8c273 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 003/202] 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 0095fe8c273..22236e47696 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 004/202] 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 22236e47696..ef78d5b3033 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 005/202] 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 ef78d5b3033..31b9f76cbb0 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 006/202] 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 31b9f76cbb0..a4672eeeda1 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 007/202] 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 a4672eeeda1..e4cb70e0f68 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 008/202] 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 e4cb70e0f68..7692529440b 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 009/202] 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 7692529440b..eb2b4207b5a 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 010/202] 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 eb2b4207b5a..28d16068d8c 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 011/202] 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 28d16068d8c..db2a187f42e 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 012/202] 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 db2a187f42e..c2600ce5b0c 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 013/202] 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 c2600ce5b0c..0df4e06e988 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 014/202] 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 8743b75a196..a154d314b63 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 0df4e06e988..8cde607826f 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 015/202] 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 8cde607826f..0960bfd83d1 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 016/202] 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 0960bfd83d1..8641757de6a 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 017/202] 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 8641757de6a..1d9e8659d06 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 018/202] 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 1d9e8659d06..a47574c96b0 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 019/202] 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 a47574c96b0..f8da92677ed 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 020/202] 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 f8da92677ed..dcfd92d4e57 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 021/202] 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 dcfd92d4e57..e11d11c9efe 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 022/202] 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 e11d11c9efe..67ea8ab8b00 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 023/202] 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 67ea8ab8b00..14e79d9f25d 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 024/202] 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 14e79d9f25d..f0e7bc502eb 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 From c4e84d744181f78cc12a6e176888eab8451a5adf Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 17 Dec 2025 19:23:51 +0100 Subject: [PATCH 025/202] Refactor overlay helper functions to local scope in orders.lua --- plugins/lua/orders.lua | 72 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index f0e7bc502eb..5d96dcfc354 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -790,6 +790,25 @@ local function get_order_search_key(order) return reaction_map[key] end +local function perform_search(text) + local matches = {} + + if text == '' then + return matches + 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 search_key and utils.search_text(search_key, text) then + table.insert(matches, i) + end + end + + return matches +end + OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) OrdersSearchOverlay.ATTRS{ desc='Adds a search box to find and navigate to matching manager orders.', @@ -873,27 +892,8 @@ function OrdersSearchOverlay:init() self.minimized = false end -function OrdersSearchOverlay:perform_search(text) - local matches = {} - - if text == '' then - return matches - 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 search_key and utils.search_text(search_key, text) then - table.insert(matches, i) - end - end - - return matches -end - function OrdersSearchOverlay:update_filter(text) - self.matched_indices = self:perform_search(text) + self.matched_indices = perform_search(text) self.current_match_idx = 0 search_cursor_visible = false @@ -917,7 +917,7 @@ end function OrdersSearchOverlay:cycle_match(direction) local search_text = self.subviews.filter.text - local new_matches = self:perform_search(search_text) + local new_matches = perform_search(search_text) if #new_matches == 0 then self.matched_indices = {} @@ -1025,15 +1025,7 @@ 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', - default_enabled=false, - viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - full_interface=true, -} - -function OrderHighlightOverlay:getListStartY() +local function getListStartY() local rect = gui.get_interface_rect() if rect.width >= TABS_WIDTH_THRESHOLD then @@ -1043,15 +1035,15 @@ function OrderHighlightOverlay:getListStartY() end end -function OrderHighlightOverlay:getViewportSize() +local function getViewportSize() local rect = gui.get_interface_rect() - local list_start_y = self:getListStartY() + local list_start_y = getListStartY() local available_height = rect.height - list_start_y - BOTTOM_MARGIN return math.floor(available_height / ORDER_HEIGHT) end -function OrderHighlightOverlay:calculateSelectedOrderY() +local function calculateSelectedOrderY() local orders = df.global.world.manager_orders.all local scroll_pos = mi.info.work_orders.scroll_position_work_orders @@ -1059,8 +1051,8 @@ function OrderHighlightOverlay:calculateSelectedOrderY() return nil end - local list_start_y = self:getListStartY() - local viewport_size = self:getViewportSize() + local list_start_y = getListStartY() + local viewport_size = getViewportSize() local viewport_start = scroll_pos local viewport_end = scroll_pos + viewport_size - 1 @@ -1078,6 +1070,14 @@ function OrderHighlightOverlay:calculateSelectedOrderY() return selected_y end +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', + full_interface=true, +} + function OrderHighlightOverlay:render(dc) OrderHighlightOverlay.super.render(self, dc) @@ -1099,7 +1099,7 @@ function OrderHighlightOverlay:render(dc) end -- Draw highlight arrows - local selected_y = self:calculateSelectedOrderY() + local selected_y = calculateSelectedOrderY() if selected_y then local highlight_pen = dfhack.pen.parse{ fg=COLOR_BLACK, From c474f7555a9afff7a23e627e40385b3069011440 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 30 Dec 2025 11:19:02 +0100 Subject: [PATCH 026/202] Remove order name generation from Lua and use DF button trick to get names --- library/LuaApi.cpp | 2 + library/include/modules/Job.h | 2 + library/modules/Job.cpp | 27 +++++++++++++ plugins/lua/orders.lua | 76 +---------------------------------- 4 files changed, 32 insertions(+), 75 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4875b934c67..d46da3fc912 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -92,6 +92,7 @@ distribution. #include "df/job_item.h" #include "df/job_material_category.h" #include "df/language_word_table.h" +#include "df/manager_order.h" #include "df/material.h" #include "df/map_block.h" #include "df/nemesis_record.h" @@ -2018,6 +2019,7 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,isSuitableItem), WRAPM(Job,isSuitableMaterial), WRAPM(Job,getName), + WRAPM(Job,getManagerOrderName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), WRAPM(Job,disconnectJobItem), diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 25c357bec7e..acb37169c3a 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -41,6 +41,7 @@ namespace df struct job_item_filter; struct building; struct unit; + struct manager_order; } namespace DFHack @@ -117,6 +118,7 @@ namespace DFHack int mat_index, df::item_type itype); DFHACK_EXPORT std::string getName(df::job *job); + DFHACK_EXPORT std::string getManagerOrderName(df::manager_order *order); struct JobDeleter { void operator()(df::job *ptr) const { diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 3c70807325c..91c16dd37a4 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -49,6 +49,7 @@ distribution. #include "df/job_list_link.h" #include "df/job_postingst.h" #include "df/job_restrictionst.h" +#include "df/manager_order.h" #include "df/plotinfost.h" #include "df/specific_ref.h" #include "df/unit.h" @@ -686,3 +687,29 @@ std::string Job::getName(df::job *job) return desc; } + +std::string Job::getManagerOrderName(df::manager_order *order) +{ + CHECK_NULL_POINTER(order); + + std::string desc; + auto button = df::allocate(); + button->mstring = order->reaction_name; + button->specdata.hist_figure_id = order->specdata.hist_figure_id; + button->jobtype = order->job_type; + button->itemtype = order->item_type; + button->subtype = order->item_subtype; + button->material = order->mat_type; + button->matgloss = order->mat_index; + button->specflag = order->specflag; + button->job_item_flag = order->material_category; + button->specdata = order->specdata; + button->art_specifier = order->art_spec.type; + button->art_specifier_id1 = order->art_spec.id; + button->art_specifier_id2 = order->art_spec.subid; + + button->text(&desc); + delete button; + + return desc; +} diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 5d96dcfc354..b78096d32e3 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -6,7 +6,6 @@ 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') -- -- OrdersOverlay @@ -719,77 +718,6 @@ 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 = '' - 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 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 - end - - 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() - df.delete(reaction.order) - end - reactions = nil - - reaction_map_cache = map - return reaction_map_cache -end - -local function get_order_search_key(order) - local reaction_map = get_cached_reaction_map() - if not reaction_map then - return nil - end - local key = make_order_key(order) - return reaction_map[key] -end - local function perform_search(text) local matches = {} @@ -800,7 +728,7 @@ local function perform_search(text) 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) + local search_key = dfhack.job.getManagerOrderName(order) if search_key and utils.search_text(search_key, text) then table.insert(matches, i) end @@ -819,8 +747,6 @@ 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 99d01265965de91713e23719c7a038ecf35f0f0b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 30 Dec 2025 12:19:50 +0100 Subject: [PATCH 027/202] Enable orders search overlay by default --- 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 b78096d32e3..846027ffd54 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -741,7 +741,7 @@ 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, + default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', frame={w=26, h=4}, } @@ -999,7 +999,7 @@ end OrderHighlightOverlay = defclass(OrderHighlightOverlay, overlay.OverlayWidget) OrderHighlightOverlay.ATTRS{ desc='Shows arrows next to the work order found by orders.search', - default_enabled=false, + default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', full_interface=true, } From b9c82f68881e55758ea3ce8e9f9eb750da57fc54 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 30 Dec 2025 13:00:20 +0100 Subject: [PATCH 028/202] Move change in changelog to future --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 67890c55d0c..9ca880c3086 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 @@ -96,7 +97,6 @@ Template for new versions: - `infinite-sky`: Re-enabled with compatibility with new siege map data. ## New Features -- `orders`: added search overlay to find and navigate to matching manager orders with arrow indicators - `sort`: Places search widget can search "Siege engines" subtab by name, loaded status, and operator status ## Fixes From 4b5be796af479619eb13419c18c9f760076bf2cc Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Fri, 2 Jan 2026 17:55:41 +0100 Subject: [PATCH 029/202] Highlight all --- plugins/lua/orders.lua | 104 ++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 846027ffd54..d6b78501541 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -717,6 +717,8 @@ end local search_cursor_visible = false local search_last_scroll_position = -1 local order_count_at_highlight = 0 +local search_matched_indices = {} +local search_current_match_idx = 0 local function perform_search(text) local matches = {} @@ -812,15 +814,12 @@ function OrdersSearchOverlay:init() minimized_panel, } - -- Initialize search state - self.matched_indices = {} - self.current_match_idx = 0 self.minimized = false end function OrdersSearchOverlay:update_filter(text) - self.matched_indices = perform_search(text) - self.current_match_idx = 0 + search_matched_indices = perform_search(text) + search_current_match_idx = 0 search_cursor_visible = false if text == '' then @@ -846,14 +845,14 @@ function OrdersSearchOverlay:cycle_match(direction) local new_matches = perform_search(search_text) if #new_matches == 0 then - self.matched_indices = {} - self.current_match_idx = 0 + search_matched_indices = {} + search_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 + local new_match_idx = search_current_match_idx + direction if new_match_idx > #new_matches then new_match_idx = 1 @@ -861,11 +860,11 @@ function OrdersSearchOverlay:cycle_match(direction) new_match_idx = #new_matches end - self.matched_indices = new_matches - self.current_match_idx = new_match_idx + search_matched_indices = new_matches + search_current_match_idx = new_match_idx -- Scroll to the selected match - local order_idx = self.matched_indices[self.current_match_idx] + local order_idx = search_matched_indices[search_current_match_idx] mi.info.work_orders.scroll_position_work_orders = order_idx search_last_scroll_position = order_idx search_cursor_visible = true @@ -875,21 +874,21 @@ function OrdersSearchOverlay:cycle_match(direction) end function OrdersSearchOverlay:get_match_text() - local total_matches = #self.matched_indices + local total_matches = #search_matched_indices if total_matches == 0 then return '' end - if self.current_match_idx == 0 then + if search_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) + return string.format(': %d of %d', search_current_match_idx, total_matches) end function OrdersSearchOverlay:has_matches() - return #self.matched_indices > 0 + return #search_matched_indices > 0 end local function is_mouse_key(keys) @@ -969,31 +968,43 @@ local function getViewportSize() return math.floor(available_height / ORDER_HEIGHT) end -local function calculateSelectedOrderY() +local function getVisibleOrderIndices() 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 + if #orders == 0 then return 0, -1 end - local list_start_y = getListStartY() local viewport_size = 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 + -- Handle end-of-list case 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 + return viewport_start, viewport_end +end + +local function calculateOrderY(order_idx) + local orders = df.global.world.manager_orders.all + + if #orders == 0 or order_idx < 0 or order_idx >= #orders then + return nil + end + + local viewport_start, viewport_end = getVisibleOrderIndices() + + -- Check if order is in viewport + if order_idx < viewport_start or order_idx > viewport_end then + return nil + end - local selected_y = list_start_y + (pos_in_viewport * ORDER_HEIGHT) + local list_start_y = getListStartY() + local pos_in_viewport = order_idx - viewport_start - return selected_y + return list_start_y + (pos_in_viewport * ORDER_HEIGHT) end OrderHighlightOverlay = defclass(OrderHighlightOverlay, overlay.OverlayWidget) @@ -1024,18 +1035,37 @@ function OrderHighlightOverlay:render(dc) return end - -- Draw highlight arrows - local selected_y = 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) + -- Draw highlight arrows for all matches in viewport + if #search_matched_indices == 0 then return end + + local selected_pen = dfhack.pen.parse{ + fg=COLOR_BLACK, + bg=COLOR_RED, + bold=true, + } + + local match_pen = dfhack.pen.parse{ + fg=COLOR_BLACK, + bg=COLOR_WHITE, + bold=true, + } + + -- Get the order index of the currently selected match + local selected_order_idx = search_current_match_idx > 0 and + search_matched_indices[search_current_match_idx] or nil + + -- Draw highlights for all matching orders in viewport + for _, match_order_idx in ipairs(search_matched_indices) do + local match_y = calculateOrderY(match_order_idx) + + if match_y then + -- Use red pen for selected match, white for others + local pen = (match_order_idx == selected_order_idx) and selected_pen or match_pen + + dc:seek(ARROW_X, match_y):string('|', pen) + dc:seek(ARROW_X, match_y + 1):string('>', pen) + dc:seek(ARROW_X, match_y + 2):string('|', pen) + end end end From 5cfb0c7a6b4fce18d9bdea8aa75a9303d5706ac0 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Fri, 2 Jan 2026 18:14:03 +0100 Subject: [PATCH 030/202] Stop hiding on scroll --- plugins/lua/orders.lua | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index d6b78501541..135043562c7 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -715,7 +715,6 @@ end -- local search_cursor_visible = false -local search_last_scroll_position = -1 local order_count_at_highlight = 0 local search_matched_indices = {} local search_current_match_idx = 0 @@ -820,7 +819,13 @@ end function OrdersSearchOverlay:update_filter(text) search_matched_indices = perform_search(text) search_current_match_idx = 0 - search_cursor_visible = false + + if #search_matched_indices > 0 then + search_cursor_visible = true + order_count_at_highlight = #df.global.world.manager_orders.all + else + search_cursor_visible = false + end if text == '' then self.subviews.main_panel.frame_title = 'Search' @@ -866,7 +871,6 @@ function OrdersSearchOverlay:cycle_match(direction) -- Scroll to the selected match local order_idx = search_matched_indices[search_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 @@ -1020,16 +1024,8 @@ function OrderHighlightOverlay:render(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 - 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 - return - end - -- Hide cursor when order list changes (orders added or removed) + local current_order_count = #df.global.world.manager_orders.all if order_count_at_highlight ~= current_order_count then search_cursor_visible = false return @@ -1040,13 +1036,13 @@ function OrderHighlightOverlay:render(dc) local selected_pen = dfhack.pen.parse{ fg=COLOR_BLACK, - bg=COLOR_RED, + bg=COLOR_WHITE, bold=true, } local match_pen = dfhack.pen.parse{ - fg=COLOR_BLACK, - bg=COLOR_WHITE, + fg=COLOR_WHITE, + bg=COLOR_BLACK, bold=true, } From 0a6378da7542d36c7559d58438cee63682b3ad93 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Fri, 2 Jan 2026 19:08:33 +0100 Subject: [PATCH 031/202] Periodic orders change detection --- plugins/lua/orders.lua | 43 +++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 135043562c7..fe3f13736bc 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -715,9 +715,10 @@ end -- local search_cursor_visible = false -local order_count_at_highlight = 0 local search_matched_indices = {} local search_current_match_idx = 0 +local order_names_checksum = nil +local search_overlay_instance = nil local function perform_search(text) local matches = {} @@ -738,6 +739,19 @@ local function perform_search(text) return matches end +local function calculate_order_names_checksum() + local orders = df.global.world.manager_orders.all + if #orders == 0 then return "" end + + local names = {} + for i = 0, #orders - 1 do + local name = dfhack.job.getManagerOrderName(orders[i]) + table.insert(names, name or "") + end + + return table.concat(names, "|") +end + OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) OrdersSearchOverlay.ATTRS{ desc='Adds a search box to find and navigate to matching manager orders.', @@ -814,15 +828,16 @@ function OrdersSearchOverlay:init() } self.minimized = false + search_overlay_instance = self end -function OrdersSearchOverlay:update_filter(text) +function OrdersSearchOverlay:update_filter() + local text = self.subviews.filter.text search_matched_indices = perform_search(text) search_current_match_idx = 0 if #search_matched_indices > 0 then search_cursor_visible = true - order_count_at_highlight = #df.global.world.manager_orders.all else search_cursor_visible = false end @@ -872,7 +887,6 @@ function OrdersSearchOverlay:cycle_match(direction) local order_idx = search_matched_indices[search_current_match_idx] mi.info.work_orders.scroll_position_work_orders = 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 @@ -953,6 +967,8 @@ local LIST_START_Y_ONE_TABS_ROW = 8 local LIST_START_Y_TWO_TABS_ROWS = 10 local BOTTOM_MARGIN = 9 local ARROW_X = 10 +local CHECK_FRAME_INTERVAL = 50 +local check_frame_counter = 0 local function getListStartY() local rect = gui.get_interface_rect() @@ -1024,11 +1040,20 @@ function OrderHighlightOverlay:render(dc) if mi.job_details.open or not search_cursor_visible then return end - -- Hide cursor when order list changes (orders added or removed) - local current_order_count = #df.global.world.manager_orders.all - if order_count_at_highlight ~= current_order_count then - search_cursor_visible = false - return + -- Periodic check for order name changes + check_frame_counter = check_frame_counter + 1 + if check_frame_counter >= CHECK_FRAME_INTERVAL then + check_frame_counter = 0 + + local new_checksum = calculate_order_names_checksum() + if new_checksum ~= order_names_checksum then + order_names_checksum = new_checksum + + -- Auto re-run search if active + if search_overlay_instance and not search_overlay_instance.minimized then + search_overlay_instance:update_filter() + end + end end -- Draw highlight arrows for all matches in viewport From 9b1becbd70a5a3e63c22ebf423ea2b991efa751b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Fri, 2 Jan 2026 20:06:52 +0100 Subject: [PATCH 032/202] Remove unnecessary field assignments --- library/modules/Job.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 91c16dd37a4..cbcfebcc23f 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -695,7 +695,6 @@ std::string Job::getManagerOrderName(df::manager_order *order) std::string desc; auto button = df::allocate(); button->mstring = order->reaction_name; - button->specdata.hist_figure_id = order->specdata.hist_figure_id; button->jobtype = order->job_type; button->itemtype = order->item_type; button->subtype = order->item_subtype; @@ -704,7 +703,6 @@ std::string Job::getManagerOrderName(df::manager_order *order) button->specflag = order->specflag; button->job_item_flag = order->material_category; button->specdata = order->specdata; - button->art_specifier = order->art_spec.type; button->art_specifier_id1 = order->art_spec.id; button->art_specifier_id2 = order->art_spec.subid; From 2f1f78a15ad04b8a9c2aa004d68410d13f174d15 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Fri, 2 Jan 2026 20:10:17 +0100 Subject: [PATCH 033/202] Add documentation entry to lua api --- docs/dev/Lua API.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 3ba63890880..bb6f82748fe 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1430,6 +1430,10 @@ Job module Returns the job's description, as seen in the Units and Jobs screens. +* ``dfhack.job.getManagerOrderName(manager_order)`` + + Returns the manager order's description, as seen in the Work orders screen. + Hotkey module ------------- From 8c8d3fcbfa26a846852140bb2c47273c9ee2a674 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 13:38:03 +0100 Subject: [PATCH 034/202] Remove redundant search_cursor_visible variable --- plugins/lua/orders.lua | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index fe3f13736bc..061e421287a 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -714,7 +714,6 @@ end -- OrdersSearchOverlay -- -local search_cursor_visible = false local search_matched_indices = {} local search_current_match_idx = 0 local order_names_checksum = nil @@ -836,12 +835,6 @@ function OrdersSearchOverlay:update_filter() search_matched_indices = perform_search(text) search_current_match_idx = 0 - if #search_matched_indices > 0 then - search_cursor_visible = true - else - search_cursor_visible = false - end - if text == '' then self.subviews.main_panel.frame_title = 'Search' else @@ -867,7 +860,6 @@ function OrdersSearchOverlay:cycle_match(direction) if #new_matches == 0 then search_matched_indices = {} search_current_match_idx = 0 - search_cursor_visible = false self.subviews.main_panel.frame_title = 'Search' return end @@ -886,7 +878,6 @@ function OrdersSearchOverlay:cycle_match(direction) -- Scroll to the selected match local order_idx = search_matched_indices[search_current_match_idx] mi.info.work_orders.scroll_position_work_orders = order_idx - search_cursor_visible = true self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() end @@ -1038,7 +1029,7 @@ OrderHighlightOverlay.ATTRS{ function OrderHighlightOverlay:render(dc) OrderHighlightOverlay.super.render(self, dc) - if mi.job_details.open or not search_cursor_visible then return end + if mi.job_details.open or #search_matched_indices == 0 then return end -- Periodic check for order name changes check_frame_counter = check_frame_counter + 1 From f8e9af512676ebcab21b6d8f0e39cd3c9a6cc6bb Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 13:55:52 +0100 Subject: [PATCH 035/202] Move periodic order change detection to SearchOverlay using overlay_onupdate --- plugins/lua/orders.lua | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 061e421287a..05c47e1979c 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -717,7 +717,6 @@ end local search_matched_indices = {} local search_current_match_idx = 0 local order_names_checksum = nil -local search_overlay_instance = nil local function perform_search(text) local matches = {} @@ -758,6 +757,7 @@ OrdersSearchOverlay.ATTRS{ default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', frame={w=26, h=4}, + overlay_onupdate_max_freq_seconds=1, } function OrdersSearchOverlay:init() @@ -827,7 +827,16 @@ function OrdersSearchOverlay:init() } self.minimized = false - search_overlay_instance = self +end + +function OrdersSearchOverlay:overlay_onupdate() + if self.minimized then return end + + local new_checksum = calculate_order_names_checksum() + if new_checksum ~= order_names_checksum then + order_names_checksum = new_checksum + self:update_filter() + end end function OrdersSearchOverlay:update_filter() @@ -958,8 +967,6 @@ local LIST_START_Y_ONE_TABS_ROW = 8 local LIST_START_Y_TWO_TABS_ROWS = 10 local BOTTOM_MARGIN = 9 local ARROW_X = 10 -local CHECK_FRAME_INTERVAL = 50 -local check_frame_counter = 0 local function getListStartY() local rect = gui.get_interface_rect() @@ -1031,25 +1038,6 @@ function OrderHighlightOverlay:render(dc) if mi.job_details.open or #search_matched_indices == 0 then return end - -- Periodic check for order name changes - check_frame_counter = check_frame_counter + 1 - if check_frame_counter >= CHECK_FRAME_INTERVAL then - check_frame_counter = 0 - - local new_checksum = calculate_order_names_checksum() - if new_checksum ~= order_names_checksum then - order_names_checksum = new_checksum - - -- Auto re-run search if active - if search_overlay_instance and not search_overlay_instance.minimized then - search_overlay_instance:update_filter() - end - end - end - - -- Draw highlight arrows for all matches in viewport - if #search_matched_indices == 0 then return end - local selected_pen = dfhack.pen.parse{ fg=COLOR_BLACK, bg=COLOR_WHITE, From 3e2b7e2665d6302fc43215e8fc69b30071dcc925 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 14:16:08 +0100 Subject: [PATCH 036/202] Consolidate search and highlight into single overlay --- plugins/lua/orders.lua | 190 +++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 104 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 05c47e1979c..19295f5aef5 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -714,9 +714,12 @@ end -- OrdersSearchOverlay -- -local search_matched_indices = {} -local search_current_match_idx = 0 -local order_names_checksum = nil +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 local function perform_search(text) local matches = {} @@ -750,6 +753,63 @@ local function calculate_order_names_checksum() return table.concat(names, "|") end +local function getListStartY() + local rect = gui.get_interface_rect() + + if rect.width >= TABS_WIDTH_THRESHOLD then + return LIST_START_Y_ONE_TABS_ROW + else + return LIST_START_Y_TWO_TABS_ROWS + end +end + +local function getViewportSize() + local rect = gui.get_interface_rect() + local list_start_y = getListStartY() + + local available_height = rect.height - list_start_y - BOTTOM_MARGIN + return math.floor(available_height / ORDER_HEIGHT) +end + +local function getVisibleOrderIndices() + local orders = df.global.world.manager_orders.all + local scroll_pos = mi.info.work_orders.scroll_position_work_orders + + if #orders == 0 then return 0, -1 end + + local viewport_size = getViewportSize() + local viewport_start = scroll_pos + local viewport_end = scroll_pos + viewport_size - 1 + + -- Handle end-of-list case + if viewport_end >= #orders then + viewport_end = #orders - 1 + viewport_start = math.max(0, viewport_end - viewport_size + 1) + end + + return viewport_start, viewport_end +end + +local function calculateOrderY(order_idx) + local orders = df.global.world.manager_orders.all + + if #orders == 0 or order_idx < 0 or order_idx >= #orders then + return nil + end + + local viewport_start, viewport_end = getVisibleOrderIndices() + + -- Check if order is in viewport + if order_idx < viewport_start or order_idx > viewport_end then + return nil + end + + local list_start_y = getListStartY() + local pos_in_viewport = order_idx - viewport_start + + return list_start_y + (pos_in_viewport * ORDER_HEIGHT) +end + OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) OrdersSearchOverlay.ATTRS{ desc='Adds a search box to find and navigate to matching manager orders.', @@ -827,22 +887,25 @@ function OrdersSearchOverlay:init() } self.minimized = false + self.matched_indices = {} + self.current_match_idx = 0 + self.order_names_checksum = nil end function OrdersSearchOverlay:overlay_onupdate() if self.minimized then return end local new_checksum = calculate_order_names_checksum() - if new_checksum ~= order_names_checksum then - order_names_checksum = new_checksum + if new_checksum ~= self.order_names_checksum then + self.order_names_checksum = new_checksum self:update_filter() end end function OrdersSearchOverlay:update_filter() local text = self.subviews.filter.text - search_matched_indices = perform_search(text) - search_current_match_idx = 0 + self.matched_indices = perform_search(text) + self.current_match_idx = 0 if text == '' then self.subviews.main_panel.frame_title = 'Search' @@ -867,13 +930,13 @@ function OrdersSearchOverlay:cycle_match(direction) local new_matches = perform_search(search_text) if #new_matches == 0 then - search_matched_indices = {} - search_current_match_idx = 0 + self.matched_indices = {} + self.current_match_idx = 0 self.subviews.main_panel.frame_title = 'Search' return end - local new_match_idx = search_current_match_idx + direction + local new_match_idx = self.current_match_idx + direction if new_match_idx > #new_matches then new_match_idx = 1 @@ -881,32 +944,32 @@ function OrdersSearchOverlay:cycle_match(direction) new_match_idx = #new_matches end - search_matched_indices = new_matches - search_current_match_idx = new_match_idx + self.matched_indices = new_matches + self.current_match_idx = new_match_idx -- Scroll to the selected match - local order_idx = search_matched_indices[search_current_match_idx] + local order_idx = self.matched_indices[self.current_match_idx] mi.info.work_orders.scroll_position_work_orders = order_idx self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() end function OrdersSearchOverlay:get_match_text() - local total_matches = #search_matched_indices + local total_matches = #self.matched_indices if total_matches == 0 then return '' end - if search_current_match_idx == 0 then + if self.current_match_idx == 0 then return string.format(': %d matches', total_matches) end - return string.format(': %d of %d', search_current_match_idx, total_matches) + return string.format(': %d of %d', self.current_match_idx, total_matches) end function OrdersSearchOverlay:has_matches() - return #search_matched_indices > 0 + return #self.matched_indices > 0 end local function is_mouse_key(keys) @@ -955,88 +1018,11 @@ end function OrdersSearchOverlay:render(dc) if mi.job_details.open then return end OrdersSearchOverlay.super.render(self, dc) + self:render_highlights(dc) 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 - -local function getListStartY() - local rect = gui.get_interface_rect() - - if rect.width >= TABS_WIDTH_THRESHOLD then - return LIST_START_Y_ONE_TABS_ROW - else - return LIST_START_Y_TWO_TABS_ROWS - end -end - -local function getViewportSize() - local rect = gui.get_interface_rect() - local list_start_y = getListStartY() - - local available_height = rect.height - list_start_y - BOTTOM_MARGIN - return math.floor(available_height / ORDER_HEIGHT) -end - -local function getVisibleOrderIndices() - local orders = df.global.world.manager_orders.all - local scroll_pos = mi.info.work_orders.scroll_position_work_orders - - if #orders == 0 then return 0, -1 end - - local viewport_size = getViewportSize() - local viewport_start = scroll_pos - local viewport_end = scroll_pos + viewport_size - 1 - - -- Handle end-of-list case - if viewport_end >= #orders then - viewport_end = #orders - 1 - viewport_start = math.max(0, viewport_end - viewport_size + 1) - end - - return viewport_start, viewport_end -end - -local function calculateOrderY(order_idx) - local orders = df.global.world.manager_orders.all - - if #orders == 0 or order_idx < 0 or order_idx >= #orders then - return nil - end - - local viewport_start, viewport_end = getVisibleOrderIndices() - - -- Check if order is in viewport - if order_idx < viewport_start or order_idx > viewport_end then - return nil - end - - local list_start_y = getListStartY() - local pos_in_viewport = order_idx - viewport_start - - return list_start_y + (pos_in_viewport * ORDER_HEIGHT) -end - -OrderHighlightOverlay = defclass(OrderHighlightOverlay, overlay.OverlayWidget) -OrderHighlightOverlay.ATTRS{ - desc='Shows arrows next to the work order found by orders.search', - default_enabled=true, - viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - full_interface=true, -} - -function OrderHighlightOverlay:render(dc) - OrderHighlightOverlay.super.render(self, dc) - - if mi.job_details.open or #search_matched_indices == 0 then return end +function OrdersSearchOverlay:render_highlights(dc) + if #self.matched_indices == 0 then return end local selected_pen = dfhack.pen.parse{ fg=COLOR_BLACK, @@ -1050,16 +1036,13 @@ function OrderHighlightOverlay:render(dc) bold=true, } - -- Get the order index of the currently selected match - local selected_order_idx = search_current_match_idx > 0 and - search_matched_indices[search_current_match_idx] or nil + local selected_order_idx = self.current_match_idx > 0 and + self.matched_indices[self.current_match_idx] or nil - -- Draw highlights for all matching orders in viewport - for _, match_order_idx in ipairs(search_matched_indices) do + for _, match_order_idx in ipairs(self.matched_indices) do local match_y = calculateOrderY(match_order_idx) if match_y then - -- Use red pen for selected match, white for others local pen = (match_order_idx == selected_order_idx) and selected_pen or match_pen dc:seek(ARROW_X, match_y):string('|', pen) @@ -1075,7 +1058,6 @@ OVERLAY_WIDGETS = { recheck=RecheckOverlay, importexport=OrdersOverlay, search=OrdersSearchOverlay, - highlight=OrderHighlightOverlay, skillrestrictions=SkillRestrictionOverlay, laborrestrictions=LaborRestrictionsOverlay, conditionsrightclick=ConditionsRightClickOverlay, From e6cd89514c37832b2a4c95e4e884aa383ddd5df1 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 14:22:23 +0100 Subject: [PATCH 037/202] Rename checksum to concat_order_names and cached_order_names --- plugins/lua/orders.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 19295f5aef5..a64ce81684c 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -740,7 +740,7 @@ local function perform_search(text) return matches end -local function calculate_order_names_checksum() +local function concat_order_names() local orders = df.global.world.manager_orders.all if #orders == 0 then return "" end @@ -889,15 +889,15 @@ function OrdersSearchOverlay:init() self.minimized = false self.matched_indices = {} self.current_match_idx = 0 - self.order_names_checksum = nil + self.cached_order_names = nil end function OrdersSearchOverlay:overlay_onupdate() if self.minimized then return end - local new_checksum = calculate_order_names_checksum() - if new_checksum ~= self.order_names_checksum then - self.order_names_checksum = new_checksum + local current_order_names = concat_order_names() + if current_order_names ~= self.cached_order_names then + self.cached_order_names = current_order_names self:update_filter() end end From 0f7174dcdac9931cecf85dcf554b67ff9992787e Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 14:37:45 +0100 Subject: [PATCH 038/202] Hoist pens to module-level constants and inline has_matches --- plugins/lua/orders.lua | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index a64ce81684c..19e6e0d9bb3 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -721,6 +721,9 @@ local LIST_START_Y_TWO_TABS_ROWS = 10 local BOTTOM_MARGIN = 9 local ARROW_X = 10 +local SELECTED_PEN = dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE, bold=true} +local MATCH_PEN = dfhack.pen.parse{fg=COLOR_WHITE, bg=COLOR_BLACK, bold=true} + local function perform_search(text) local matches = {} @@ -843,7 +846,7 @@ function OrdersSearchOverlay:init() key='CUSTOM_ALT_P', auto_width=true, on_activate=self:callback('cycle_match', -1), - enabled=function() return self:has_matches() end, + enabled=function() return #self.matched_indices > 0 end, }, widgets.HotkeyLabel{ frame={t=1, l=12}, @@ -851,7 +854,7 @@ function OrdersSearchOverlay:init() key='CUSTOM_ALT_N', auto_width=true, on_activate=self:callback('cycle_match', 1), - enabled=function() return self:has_matches() end, + enabled=function() return #self.matched_indices > 0 end, }, }, } @@ -968,10 +971,6 @@ function OrdersSearchOverlay:get_match_text() 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 @@ -1024,18 +1023,6 @@ end function OrdersSearchOverlay:render_highlights(dc) if #self.matched_indices == 0 then return end - local selected_pen = dfhack.pen.parse{ - fg=COLOR_BLACK, - bg=COLOR_WHITE, - bold=true, - } - - local match_pen = dfhack.pen.parse{ - fg=COLOR_WHITE, - bg=COLOR_BLACK, - bold=true, - } - local selected_order_idx = self.current_match_idx > 0 and self.matched_indices[self.current_match_idx] or nil @@ -1043,7 +1030,7 @@ function OrdersSearchOverlay:render_highlights(dc) local match_y = calculateOrderY(match_order_idx) if match_y then - local pen = (match_order_idx == selected_order_idx) and selected_pen or match_pen + local pen = (match_order_idx == selected_order_idx) and SELECTED_PEN or MATCH_PEN dc:seek(ARROW_X, match_y):string('|', pen) dc:seek(ARROW_X, match_y + 1):string('>', pen) From 806ff7b1f3d35ba8ab8d0d9221f134509f444c5c Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sun, 4 Jan 2026 14:48:56 +0100 Subject: [PATCH 039/202] Only scroll to selected match when outside visible viewport --- plugins/lua/orders.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 19e6e0d9bb3..80a6196e130 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -950,9 +950,12 @@ function OrdersSearchOverlay:cycle_match(direction) self.matched_indices = new_matches self.current_match_idx = new_match_idx - -- Scroll to the selected match + -- Scroll to the selected match only if not already visible local order_idx = self.matched_indices[self.current_match_idx] - mi.info.work_orders.scroll_position_work_orders = order_idx + local viewport_start, viewport_end = getVisibleOrderIndices() + if order_idx < viewport_start or order_idx > viewport_end then + mi.info.work_orders.scroll_position_work_orders = order_idx + end self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() end From d04b116626177a9d7ae50f6894e24abd7f16a9c9 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 6 Jan 2026 20:35:23 +0100 Subject: [PATCH 040/202] Add death cause button to dead/missing tab --- docs/changelog.txt | 1 + plugins/lua/sort.lua | 1 + plugins/lua/sort/deathcause_button.lua | 78 ++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 plugins/lua/sort/deathcause_button.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index 2eb584644fc..4dfb1419fe6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ Template for new versions: ## New Tools ## New Features +- `sort`: Add death cause button to dead/missing tab in the creatures screen ## Fixes diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index bdad4ae90e8..a81bff5beb5 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -1294,6 +1294,7 @@ OVERLAY_WIDGETS = { candidates=require('plugins.sort.info').CandidatesOverlay, interrogation=require('plugins.sort.info').InterrogationOverlay, conviction=require('plugins.sort.info').ConvictionOverlay, + deathcause_button=require('plugins.sort.deathcause_button').DeathCauseOverlay, location_selector=require('plugins.sort.locationselector').LocationSelectorOverlay, -- TODO: maybe rewrite for 50.12 -- burrow_assignment=require('plugins.sort.unitselector').BurrowAssignmentOverlay, diff --git a/plugins/lua/sort/deathcause_button.lua b/plugins/lua/sort/deathcause_button.lua new file mode 100644 index 00000000000..e5f424c2a91 --- /dev/null +++ b/plugins/lua/sort/deathcause_button.lua @@ -0,0 +1,78 @@ +local _ENV = mkmodule('plugins.sort.deathcause_button') + +local dialogs = require('gui.dialogs') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +DeathCauseOverlay = defclass(DeathCauseOverlay, overlay.OverlayWidget) +DeathCauseOverlay.ATTRS{ + desc='Adds a button to view death cause on the dead/missing tab.', + default_pos={x=50, y=-7}, + default_enabled=true, + viewscreens='dwarfmode/Info/CREATURES/DECEASED', + frame={w=21, h=1}, +} + +function DeathCauseOverlay:init() + local deathcause = reqscript('deathcause') + + local function get_selected_unit() + -- Navigate to the creatures/deceased widget hierarchy: + -- list_widget - the main deceased list + -- ├─ children[0]: scrollbar widget + -- └─ children[1]: container widget (list_container) + -- ├─ grandchildren[0]: header + -- ├─ grandchildren[1]: header or other UI + -- └─ grandchildren[2]: scrollable rows container (scrollable_list) + -- └─ rows: row widgets (each row = one unit in the list) + -- └─ row.children[x]: unit widget + -- └─ unit_widget.u: pointer to the df.unit object + + local creatures = df.global.game.main_interface.info.creatures + local list_widget = dfhack.gui.getWidget(creatures, 'Tabs', 'Dead/Missing') + if not list_widget then return nil end + + local children = dfhack.gui.getWidgetChildren(list_widget) + local list_container = children[1] + local grandchildren = dfhack.gui.getWidgetChildren(list_container) + + local scrollable_list = grandchildren[2] + if not scrollable_list then + return nil + end + + local rows = dfhack.gui.getWidgetChildren(scrollable_list) + + local cursor_idx = list_widget.cursor_idx or 0 + + if cursor_idx >= 0 and cursor_idx < #rows then + local row = rows[cursor_idx + 1] + + local ok, unit = pcall(function() return dfhack.gui.getWidget(row, 0).u end) + if ok and unit then + return unit + end + end + + return nil + end + + self:addviews{ + widgets.TextButton{ + frame={t=0, l=0}, + label='Show death cause', + key='CUSTOM_D', + on_activate=function() + local unit = get_selected_unit() + if not unit then + dialogs.showMessage('Death Cause', 'No unit selected.') + return + end + local cause = deathcause.getDeathCause(unit) + dialogs.showMessage('Death Cause', dfhack.df2console(cause)) + end, + }, + } +end + +return _ENV From 9e916367761ca33cc6a1eabe08997710888a949c Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 6 Jan 2026 20:54:12 +0100 Subject: [PATCH 041/202] Trim trailing whitespace --- plugins/lua/sort/deathcause_button.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/lua/sort/deathcause_button.lua b/plugins/lua/sort/deathcause_button.lua index e5f424c2a91..b149601a7a9 100644 --- a/plugins/lua/sort/deathcause_button.lua +++ b/plugins/lua/sort/deathcause_button.lua @@ -31,20 +31,20 @@ function DeathCauseOverlay:init() local creatures = df.global.game.main_interface.info.creatures local list_widget = dfhack.gui.getWidget(creatures, 'Tabs', 'Dead/Missing') if not list_widget then return nil end - + local children = dfhack.gui.getWidgetChildren(list_widget) local list_container = children[1] local grandchildren = dfhack.gui.getWidgetChildren(list_container) - + local scrollable_list = grandchildren[2] if not scrollable_list then return nil end - + local rows = dfhack.gui.getWidgetChildren(scrollable_list) - + local cursor_idx = list_widget.cursor_idx or 0 - + if cursor_idx >= 0 and cursor_idx < #rows then local row = rows[cursor_idx + 1] From 5e1fb2d5515bd52e20d63d8d7acbd2240bb927a5 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 7 Jan 2026 22:10:05 +0100 Subject: [PATCH 042/202] Add Uniformed filter to squad selection screen --- plugins/lua/sort.lua | 73 +++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index bdad4ae90e8..19413be269d 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -1029,12 +1029,29 @@ function SquadFilterOverlay:init() local left_panel = widgets.Panel{ view_id='left_panel', - frame={t=1, b=0, l=0, w=NARROW_WIDTH-4}, + frame={t=0, b=0, l=0, w=NARROW_WIDTH-4}, visible=true, subviews={ + widgets.HotkeyLabel{ + view_id='toggle_all', + frame={t=0, l=0}, + key='CUSTOM_SHIFT_A', + label='Toggle all', + on_activate=function() + local target = self.subviews.military:getOptionValue() == 'exclude' and 'include' or 'exclude' + self.subviews.military:setOption(target) + self.subviews.officials:setOption(target) + self.subviews.nobles:setOption(target) + self.subviews.infant:setOption(target) + self.subviews.unstable:setOption(target) + self.subviews.maimed:setOption(target) + self.subviews.labor_conflict:setOption(target) + poke_list() + end, + }, widgets.CycleHotkeyLabel{ view_id='military', - frame={t=0, l=0}, + frame={t=1, l=0}, key='CUSTOM_SHIFT_Q', label='Other squads:', options={ @@ -1047,7 +1064,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='officials', - frame={t=1, l=0}, + frame={t=2, l=0}, key='CUSTOM_SHIFT_O', label=' Officials:', options={ @@ -1060,7 +1077,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='nobles', - frame={t=2, l=0}, + frame={t=3, l=0}, key='CUSTOM_SHIFT_N', label=' Nobility:', options={ @@ -1076,12 +1093,25 @@ function SquadFilterOverlay:init() local right_panel = widgets.Panel{ view_id='right_panel', - frame={t=1, b=0, r=2, w=NARROW_WIDTH-4}, + frame={t=0, b=0, r=2, w=NARROW_WIDTH-4}, visible=false, subviews={ widgets.CycleHotkeyLabel{ - view_id='infant', + view_id='labor_conflict', frame={t=0, l=0}, + key='CUSTOM_SHIFT_U', + label=' Uniformed:', + options={ + {label='Include', value='include', pen=COLOR_GREEN}, + {label='Only', value='only', pen=COLOR_YELLOW}, + {label='Exclude', value='exclude', pen=COLOR_LIGHTRED}, + }, + initial_option='include', + on_change=poke_list, + }, + widgets.CycleHotkeyLabel{ + view_id='infant', + frame={t=1, l=0}, key='CUSTOM_SHIFT_M', label='With infants:', options={ @@ -1094,7 +1124,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='unstable', - frame={t=1, l=0}, + frame={t=2, l=0}, key='CUSTOM_SHIFT_D', label='Hates combat:', options={ @@ -1107,7 +1137,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='maimed', - frame={t=2, l=0}, + frame={t=3, l=0}, key='CUSTOM_SHIFT_I', label=' Maimed:', options={ @@ -1125,21 +1155,6 @@ function SquadFilterOverlay:init() frame_style=gui.FRAME_MEDIUM, frame_background=gui.CLEAR_PEN, subviews={ - widgets.HotkeyLabel{ - frame={t=0, w=NARROW_WIDTH-3}, - key='CUSTOM_SHIFT_A', - label='Toggle all filters', - on_activate=function() - local target = self.subviews.military:getOptionValue() == 'exclude' and 'include' or 'exclude' - self.subviews.military:setOption(target) - self.subviews.officials:setOption(target) - self.subviews.nobles:setOption(target) - self.subviews.infant:setOption(target) - self.subviews.unstable:setOption(target) - self.subviews.maimed:setOption(target) - poke_list() - end, - }, left_panel, widgets.Label{ view_id='shifter', @@ -1167,9 +1182,8 @@ function SquadFilterOverlay:init() main_panel, widgets.Divider{ view_id='divider', - frame={l=NARROW_WIDTH-1, w=1, t=2}, + frame={l=NARROW_WIDTH-1, w=1, t=0}, frame_style=gui.FRAME_MEDIUM, - frame_style_t=false, visible=false, }, widgets.HelpButton{ @@ -1253,6 +1267,12 @@ local function is_maimed(unit) unit.status2.limbs_stand_count == 0 end +local function has_labor_conflict(unit) + return unit.status.labors[df.unit_labor.MINE] or + unit.status.labors[df.unit_labor.CUTWOOD] or + unit.status.labors[df.unit_labor.HUNT] +end + local function filter_matches(unit, filter) if filter.military == 'only' and not is_in_military(unit) then return false end if filter.military == 'exclude' and is_in_military(unit) then return false end @@ -1266,6 +1286,8 @@ local function filter_matches(unit, filter) if filter.unstable == 'exclude' and is_unstable(unit) then return false end if filter.maimed == 'only' and not is_maimed(unit) then return false end if filter.maimed == 'exclude' and is_maimed(unit) then return false end + if filter.labor_conflict == 'only' and not has_labor_conflict(unit) then return false end + if filter.labor_conflict == 'exclude' and has_labor_conflict(unit) then return false end return true end @@ -1281,6 +1303,7 @@ function do_squad_filter(unit) infant=self.subviews.infant:getOptionValue(), unstable=self.subviews.unstable:getOptionValue(), maimed=self.subviews.maimed:getOptionValue(), + labor_conflict=self.subviews.labor_conflict:getOptionValue(), } return filter_matches(unit, filter) end From 7daf2e7fa66d6ee9679b3a7ca1afdc0f8272da59 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 7 Jan 2026 22:12:44 +0100 Subject: [PATCH 043/202] Changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index da2aa1ef4ea..0786cbaa0db 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ Template for new versions: ## New Tools ## New Features +- `sort`: added ``Uniformed`` filter to squad assignment screen to filter dwarves with mining, woodcutting, or hunting labors ## Fixes From c050add7bc472ec9e6daa8b0e5752120c20dc03d Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 10 Jan 2026 12:57:04 +0100 Subject: [PATCH 044/202] Create plugin for clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI. --- docs/changelog.txt | 1 + docs/plugins/logcleaner.rst | 62 ++++++++ plugins/CMakeLists.txt | 1 + plugins/logcleaner/logcleaner.cpp | 239 ++++++++++++++++++++++++++++++ plugins/lua/logcleaner.lua | 66 +++++++++ 5 files changed, 369 insertions(+) create mode 100644 docs/plugins/logcleaner.rst create mode 100644 plugins/logcleaner/logcleaner.cpp create mode 100644 plugins/lua/logcleaner.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index da2aa1ef4ea..377577da84b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ Template for new versions: # Future ## New Tools +- ``logcleaner``: New plugin for time-triggered clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI. ## New Features diff --git a/docs/plugins/logcleaner.rst b/docs/plugins/logcleaner.rst new file mode 100644 index 00000000000..d9d0930d82e --- /dev/null +++ b/docs/plugins/logcleaner.rst @@ -0,0 +1,62 @@ +logcleaner +========== +.. dfhack-tool:: + :summary: Automatically clear combat, sparring, and hunting reports. + :tags: fort auto units + +This plugin prevents spam from cluttering your announcement history and filling +the 3000-item reports buffer. It runs every 100 ticks and clears selected report +types from both the global reports buffer and per-unit logs. + +Usage +----- + +Basic commands +~~~~~~~~~~~~~~ + +``logcleaner`` + Show the current status of the plugin. +``logcleaner enable`` + Enable the plugin (persists per save). +``logcleaner disable`` + Disable the plugin. + +Configuring filters +~~~~~~~~~~~~~~~~~~~ + +``logcleaner combat`` + Clear combat reports (also enables the plugin if disabled). +``logcleaner sparring`` + Clear sparring reports. +``logcleaner hunting`` + Clear hunting reports. +``logcleaner combat,sparring`` + Clear multiple report types (comma-separated). +``logcleaner all`` + Enable all three filter types. +``logcleaner none`` + Disable all filter types. + +Examples +~~~~~~~~ + +Clear only sparring reports:: + + logcleaner sparring + +Clear combat and hunting, but not sparring:: + + logcleaner combat,hunting + +Overlay UI +---------- + +Run ``gui/logcleaner`` to open the settings overlay, or access it from the +control panel under the Gameplay tab. + +The overlay provides: + +- **Enable toggle**: Turn the plugin on or off (``Shift+E``) +- **Combat toggle**: Clear combat reports (``Shift+C``) +- **Sparring toggle**: Clear sparring reports (``Shift+S``) +- **Hunting toggle**: Clear hunting reports (``Shift+H``) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 355cc0ac4c0..4a4423f48f2 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -66,6 +66,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(createitem createitem.cpp) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) + dfhack_plugin(logcleaner logcleaner/logcleaner.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp LINK_LIBRARIES lua) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp new file mode 100644 index 00000000000..b6be95a70e2 --- /dev/null +++ b/plugins/logcleaner/logcleaner.cpp @@ -0,0 +1,239 @@ +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "PluginLua.h" + +#include "modules/Persistence.h" +#include "modules/World.h" + +#include +#include +#include +#include + +using namespace DFHack; + +DFHACK_PLUGIN("logcleaner"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +static const std::string CONFIG_KEY = std::string(plugin_name) + "/config"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CLEAR_COMBAT = 1, + CONFIG_CLEAR_SPARING = 2, + CONFIG_CLEAR_HUNTING = 3, +}; + +static bool clear_combat = false; +static bool clear_sparring = true; +static bool clear_hunting = false; + +namespace DFHack { + DBG_DECLARE(logcleaner, control, DebugCategory::LINFO); + DBG_DECLARE(logcleaner, cleanup, DebugCategory::LINFO); +} + +static void cleanupLogs(color_ostream& out); +static command_result do_command(color_ostream& out, std::vector& params); +static void do_enable(); +static void do_disable(); + +// Getter functions for Lua +static bool logcleaner_getCombat() { return clear_combat; } +static bool logcleaner_getSparring() { return clear_sparring; } +static bool logcleaner_getHunting() { return clear_hunting; } + +// Setter functions for Lua (also persist to config) +static void logcleaner_setCombat(color_ostream& out, bool val) { + clear_combat = val; + config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); +} + +static void logcleaner_setSparring(color_ostream& out, bool val) { + clear_sparring = val; + config.set_bool(CONFIG_CLEAR_SPARING, clear_sparring); +} + +static void logcleaner_setHunting(color_ostream& out, bool val) { + clear_hunting = val; + config.set_bool(CONFIG_CLEAR_HUNTING, clear_hunting); +} + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector& commands) { + commands.push_back(PluginCommand( + plugin_name, + "Prevent report buffer from filling up by clearing selected report types (combat, sparring, hunting).", + do_command)); + + return CR_OK; +} + +static command_result do_command(color_ostream& out, std::vector& params) { + if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { + out.printerr("Cannot use {} without a loaded fort.\n", plugin_name); + return CR_FAILURE; + } + + bool show_help = false; + if (!Lua::CallLuaModuleFunction(out, "plugins.logcleaner", "parse_commandline", params, + 1, [&](lua_State *L) { + show_help = !lua_toboolean(L, -1); + })) { + return CR_FAILURE; + } + + return show_help ? CR_WRONG_USAGE : CR_OK; +} + +static void do_enable() { +} + +static void do_disable() { +} + +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { + if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(control, out).print("{} from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + config.set_bool(CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_enable(); + else + do_disable(); + } else { + DEBUG(control, out).print("{} from the API, but already {}; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + DEBUG(control, out).print("shutting down {}\n", plugin_name); + return CR_OK; +} + +DFhackCExport command_result plugin_load_site_data(color_ostream& out) { + config = World::GetPersistentSiteData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(control, out).print("no config found in this save; initializing\n"); + config = World::AddPersistentSiteData(CONFIG_KEY); + config.set_bool(CONFIG_IS_ENABLED, is_enabled); + config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); + config.set_bool(CONFIG_CLEAR_SPARING, clear_sparring); + config.set_bool(CONFIG_CLEAR_HUNTING, clear_hunting); + } + + is_enabled = config.get_bool(CONFIG_IS_ENABLED); + clear_combat = config.get_bool(CONFIG_CLEAR_COMBAT); + clear_sparring = config.get_bool(CONFIG_CLEAR_SPARING); + clear_hunting = config.get_bool(CONFIG_CLEAR_HUNTING); + + DEBUG(control, out).print("loading persisted enabled state: {}\n", + is_enabled ? "true" : "false"); + if (is_enabled) + do_enable(); + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED && is_enabled) { + DEBUG(control, out).print("world unloaded; disabling {}\n", plugin_name); + is_enabled = false; + do_disable(); + } + return CR_OK; +} + +static void cleanupLogs(color_ostream& out) { + if (!is_enabled || !world) + return; + + // Collect all report IDs from unit combat/sparring/hunting logs + std::unordered_set report_ids_to_remove; + + for (auto unit : world->units.all) { + // Combat logs (index 0) + if (clear_combat) { + auto& log = unit->reports.log[0]; + for (auto report_id : log) { + report_ids_to_remove.insert(report_id); + } + log.clear(); + } + // Sparring logs (index 1) + if (clear_sparring) { + auto& log = unit->reports.log[1]; + for (auto report_id : log) { + report_ids_to_remove.insert(report_id); + } + log.clear(); + } + // Hunting logs (index 2) + if (clear_hunting) { + auto& log = unit->reports.log[2]; + for (auto report_id : log) { + report_ids_to_remove.insert(report_id); + } + log.clear(); + } + } + + if (report_ids_to_remove.empty()) + return; + + // Remove collected reports from global buffers + auto& reports = world->status.reports; + + int reports_erased = 0; + + for (auto report_id : report_ids_to_remove) { + df::report* report = df::report::find(report_id); + if (!report) + continue; + + auto it = std::find(reports.begin(), reports.end(), report); + if (it != reports.end()) { + delete report; + reports.erase(it); + reports_erased++; + } + } +} + +DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_event event) { + static int32_t tick_counter = 0; + + if (!is_enabled || !world) + return CR_OK; + + tick_counter++; + if (tick_counter >= 100) { + tick_counter = 0; + cleanupLogs(out); + } + + return CR_OK; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(logcleaner_getCombat), + DFHACK_LUA_FUNCTION(logcleaner_getSparring), + DFHACK_LUA_FUNCTION(logcleaner_getHunting), + DFHACK_LUA_FUNCTION(logcleaner_setCombat), + DFHACK_LUA_FUNCTION(logcleaner_setSparring), + DFHACK_LUA_FUNCTION(logcleaner_setHunting), + DFHACK_LUA_END +}; diff --git a/plugins/lua/logcleaner.lua b/plugins/lua/logcleaner.lua new file mode 100644 index 00000000000..f7fa328ea62 --- /dev/null +++ b/plugins/lua/logcleaner.lua @@ -0,0 +1,66 @@ +local _ENV = mkmodule('plugins.logcleaner') + +local function print_status() + print(('logcleaner is %s'):format(isEnabled() and "enabled" or "disabled")) + print(' Combat: ' .. (logcleaner_getCombat() and 'enabled' or 'disabled')) + print(' Sparring: ' .. (logcleaner_getSparring() and 'enabled' or 'disabled')) + print(' Hunting: ' .. (logcleaner_getHunting() and 'enabled' or 'disabled')) +end + +function parse_commandline(...) + local args = {...} + local command = args[1] + + -- Show status if no command or "status" + if not command or command == 'status' then + print_status() + return true + end + + -- Start with all disabled, enable only what's specified + local new_combat, new_sparring, new_hunting = false, false, false + local has_filter = false + + for _, param in ipairs(args) do + if param == 'all' then + new_combat, new_sparring, new_hunting = true, true, true + has_filter = true + elseif param == 'none' then + new_combat, new_sparring, new_hunting = false, false, false + else + -- Split by comma for multiple options in one parameter + for token in param:gmatch('([^,]+)') do + if token == 'combat' then + new_combat = true + has_filter = true + elseif token == 'sparring' then + new_sparring = true + has_filter = true + elseif token == 'hunting' then + new_hunting = true + has_filter = true + else + dfhack.printerr('Unknown option: ' .. token) + return false + end + end + end + end + + -- Auto-enable plugin when filters are being configured + if has_filter and not isEnabled() then + dfhack.run_command('enable', 'logcleaner') + print('logcleaner enabled') + end + + logcleaner_setCombat(new_combat) + logcleaner_setSparring(new_sparring) + logcleaner_setHunting(new_hunting) + + print('Log cleaning config updated:') + print_status() + + return true +end + +return _ENV From 67b26213c5c7db21db12f93914c87e53b8150700 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Sat, 10 Jan 2026 15:08:31 +0100 Subject: [PATCH 045/202] Remove debugs, add enable / disable, refactor --- plugins/logcleaner/logcleaner.cpp | 72 +++++-------------------------- plugins/lua/logcleaner.lua | 63 +++++++++++++++++---------- 2 files changed, 52 insertions(+), 83 deletions(-) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index b6be95a70e2..56804613ad2 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -1,4 +1,3 @@ -#include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" #include "PluginLua.h" @@ -32,15 +31,8 @@ static bool clear_combat = false; static bool clear_sparring = true; static bool clear_hunting = false; -namespace DFHack { - DBG_DECLARE(logcleaner, control, DebugCategory::LINFO); - DBG_DECLARE(logcleaner, cleanup, DebugCategory::LINFO); -} - static void cleanupLogs(color_ostream& out); static command_result do_command(color_ostream& out, std::vector& params); -static void do_enable(); -static void do_disable(); // Getter functions for Lua static bool logcleaner_getCombat() { return clear_combat; } @@ -48,17 +40,17 @@ static bool logcleaner_getSparring() { return clear_sparring; } static bool logcleaner_getHunting() { return clear_hunting; } // Setter functions for Lua (also persist to config) -static void logcleaner_setCombat(color_ostream& out, bool val) { +static void logcleaner_setCombat(bool val) { clear_combat = val; config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); } -static void logcleaner_setSparring(color_ostream& out, bool val) { +static void logcleaner_setSparring(bool val) { clear_sparring = val; config.set_bool(CONFIG_CLEAR_SPARING, clear_sparring); } -static void logcleaner_setHunting(color_ostream& out, bool val) { +static void logcleaner_setHunting(bool val) { clear_hunting = val; config.set_bool(CONFIG_CLEAR_HUNTING, clear_hunting); } @@ -89,12 +81,6 @@ static command_result do_command(color_ostream& out, std::vector& p return show_help ? CR_WRONG_USAGE : CR_OK; } -static void do_enable() { -} - -static void do_disable() { -} - DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); @@ -103,23 +89,12 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - DEBUG(control, out).print("{} from the API; persisting\n", - is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); - if (enable) - do_enable(); - else - do_disable(); - } else { - DEBUG(control, out).print("{} from the API, but already {}; no action\n", - is_enabled ? "enabled" : "disabled", - is_enabled ? "enabled" : "disabled"); } return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream& out) { - DEBUG(control, out).print("shutting down {}\n", plugin_name); return CR_OK; } @@ -127,7 +102,6 @@ DFhackCExport command_result plugin_load_site_data(color_ostream& out) { config = World::GetPersistentSiteData(CONFIG_KEY); if (!config.isValid()) { - DEBUG(control, out).print("no config found in this save; initializing\n"); config = World::AddPersistentSiteData(CONFIG_KEY); config.set_bool(CONFIG_IS_ENABLED, is_enabled); config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); @@ -140,19 +114,12 @@ DFhackCExport command_result plugin_load_site_data(color_ostream& out) { clear_sparring = config.get_bool(CONFIG_CLEAR_SPARING); clear_hunting = config.get_bool(CONFIG_CLEAR_HUNTING); - DEBUG(control, out).print("loading persisted enabled state: {}\n", - is_enabled ? "true" : "false"); - if (is_enabled) - do_enable(); - return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED && is_enabled) { - DEBUG(control, out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; - do_disable(); } return CR_OK; } @@ -163,31 +130,17 @@ static void cleanupLogs(color_ostream& out) { // Collect all report IDs from unit combat/sparring/hunting logs std::unordered_set report_ids_to_remove; + bool log_types[] = {clear_combat, clear_sparring, clear_hunting}; for (auto unit : world->units.all) { - // Combat logs (index 0) - if (clear_combat) { - auto& log = unit->reports.log[0]; - for (auto report_id : log) { - report_ids_to_remove.insert(report_id); - } - log.clear(); - } - // Sparring logs (index 1) - if (clear_sparring) { - auto& log = unit->reports.log[1]; - for (auto report_id : log) { - report_ids_to_remove.insert(report_id); + for (int log_idx = 0; log_idx < 3; log_idx++) { + if (log_types[log_idx]) { + auto& log = unit->reports.log[log_idx]; + for (auto report_id : log) { + report_ids_to_remove.insert(report_id); + } + log.clear(); } - log.clear(); - } - // Hunting logs (index 2) - if (clear_hunting) { - auto& log = unit->reports.log[2]; - for (auto report_id : log) { - report_ids_to_remove.insert(report_id); - } - log.clear(); } } @@ -197,8 +150,6 @@ static void cleanupLogs(color_ostream& out) { // Remove collected reports from global buffers auto& reports = world->status.reports; - int reports_erased = 0; - for (auto report_id : report_ids_to_remove) { df::report* report = df::report::find(report_id); if (!report) @@ -208,7 +159,6 @@ static void cleanupLogs(color_ostream& out) { if (it != reports.end()) { delete report; reports.erase(it); - reports_erased++; } } } diff --git a/plugins/lua/logcleaner.lua b/plugins/lua/logcleaner.lua index f7fa328ea62..104c23ab88f 100644 --- a/plugins/lua/logcleaner.lua +++ b/plugins/lua/logcleaner.lua @@ -17,32 +17,51 @@ function parse_commandline(...) return true end + -- Handle enable/disable commands + if command == 'enable' then + if isEnabled() then + print('logcleaner is already enabled') + else + dfhack.run_command('enable', 'logcleaner') + print('logcleaner enabled') + end + return true + end + + if command == 'disable' then + if not isEnabled() then + print('logcleaner is already disabled') + else + dfhack.run_command('disable', 'logcleaner') + print('logcleaner disabled') + end + return true + end + -- Start with all disabled, enable only what's specified local new_combat, new_sparring, new_hunting = false, false, false local has_filter = false - for _, param in ipairs(args) do - if param == 'all' then - new_combat, new_sparring, new_hunting = true, true, true - has_filter = true - elseif param == 'none' then - new_combat, new_sparring, new_hunting = false, false, false - else - -- Split by comma for multiple options in one parameter - for token in param:gmatch('([^,]+)') do - if token == 'combat' then - new_combat = true - has_filter = true - elseif token == 'sparring' then - new_sparring = true - has_filter = true - elseif token == 'hunting' then - new_hunting = true - has_filter = true - else - dfhack.printerr('Unknown option: ' .. token) - return false - end + if command == 'all' then + new_combat, new_sparring, new_hunting = true, true, true + has_filter = true + elseif command == 'none' then + new_combat, new_sparring, new_hunting = false, false, false + else + -- Split by comma for multiple options in one parameter + for token in command:gmatch('([^,]+)') do + if token == 'combat' then + new_combat = true + has_filter = true + elseif token == 'sparring' then + new_sparring = true + has_filter = true + elseif token == 'hunting' then + new_hunting = true + has_filter = true + else + dfhack.printerr('Unknown option: ' .. token) + return false end end end From 9c28ca8a5e6314f1dfda36a4a5d41b1960f4a75c Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Mon, 12 Jan 2026 17:46:06 +0100 Subject: [PATCH 046/202] Add constant for frequency. Remove unused variables --- plugins/logcleaner/logcleaner.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index 56804613ad2..fc00a9cb135 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -31,7 +31,9 @@ static bool clear_combat = false; static bool clear_sparring = true; static bool clear_hunting = false; -static void cleanupLogs(color_ostream& out); +static const int32_t CLEANUP_TICK_INTERVAL = 97; + +static void cleanupLogs(); static command_result do_command(color_ostream& out, std::vector& params); // Getter functions for Lua @@ -124,7 +126,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_chan return CR_OK; } -static void cleanupLogs(color_ostream& out) { +static void cleanupLogs() { if (!is_enabled || !world) return; @@ -170,9 +172,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_ev return CR_OK; tick_counter++; - if (tick_counter >= 100) { + if (tick_counter >= CLEANUP_TICK_INTERVAL) { tick_counter = 0; - cleanupLogs(out); + cleanupLogs(); } return CR_OK; From 09d3789689c92830c88a1cbbe4ee507f90e935e5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:29:23 +0000 Subject: [PATCH 047/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 50190a34de7..7f8db9d9c12 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 50190a34de760732a4d8f926f28df9048fd0a5e2 +Subproject commit 7f8db9d9c1225a396678ca4057e9caaae267666f From 58af0b9d4cf064043d3ff2d8ae29508f5d007b42 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:29:27 +0000 Subject: [PATCH 048/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 7f8db9d9c12..efe712b62d5 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 7f8db9d9c1225a396678ca4057e9caaae267666f +Subproject commit efe712b62d50b1f537558168701af8e6ed2bdece From 153da7d9a783728f9e2f71e50bdfd24807a3cb7b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:39:49 +0000 Subject: [PATCH 049/202] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.36.0 → 0.36.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.36.0...0.36.1) - [github.com/Lucas-C/pre-commit-hooks: v1.5.5 → v1.5.6](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.5...v1.5.6) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d77a667a4b..c47c9ccfdfd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.0 + rev: 0.36.1 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: v1.5.6 hooks: - id: forbid-tabs exclude_types: From edda03064e9769fd92c96d2595fdc98e2d9ef9b5 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 14 Jan 2026 17:11:20 -0600 Subject: [PATCH 050/202] Add library functions for placing spatters Required for #5703 --- docs/changelog.txt | 6 + docs/dev/Lua API.rst | 16 +++ library/LuaApi.cpp | 36 +++++ library/include/modules/Items.h | 6 + library/include/modules/Maps.h | 5 + library/modules/Items.cpp | 87 ++++++----- library/modules/Maps.cpp | 246 ++++++++++++++++++++++++++++++++ 7 files changed, 363 insertions(+), 39 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9481bbbbd83..b568d027cf5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -65,8 +65,14 @@ Template for new versions: ## Documentation ## API +- Added ``Maps::addMaterialSpatter``: add a spatter of the specified material + state to the indicated tile, returning whatever amount wouldn't fit in the tile. +- Added ``Maps::addItemSpatter``: add a spatter of the specified item + material + growth print to the indicated tile, returning whatever amount wouldn't fit in the tile. +- Added ``Items::pickGrowthPrint``: given a plant material and a growth index, returns the print variant corresponding to the current in-game time. +- Added ``Items::useStandardMaterial``: given an item type, returns true if the item is made of a specific material and false if it has a race and caste instead. ## Lua +- Added ``Maps::addMaterialSpatter`` as ``dfhack.maps.addMaterialSpatter``. +- Added ``Maps::addItemSpatter`` as ``dfhack.maps.addItemSpatter``. ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 52fb7c778fc..4995e420c7a 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -2486,6 +2486,22 @@ Maps module Removes an aquifer from the given tile position. Returns *true* or *false* depending on success. +* ``dfhack.maps.addMaterialSpatter(pos, mat, matg, state, amount)`` + + Adds a material spatter to the specified map tile. If the tile is already + full of that spatter, returns the amount left over. + + Specifying a state of -1 (None) will automatically choose either Solid, + Liquid, or Gas based on the material properties and the tile temperature. + +* ``dfhack.maps.addItemSpatter(pos, i_type, i_subtype, subcat1, subcat2, print_variant, amount)`` + + Adds an item spatter to the specified map tile. If the tile is already + full of that spatter, returns the amount left over. + + For plant growths, specifying a print_variant of -1 will automatically + choose an appropriate value. For other item types, this field is ignored. + Burrows module -------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4875b934c67..ce2a8d2481a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2781,6 +2781,40 @@ static int maps_removeTileAquifer(lua_State* L) return 1; } +static int maps_addMaterialSpatter(lua_State *L) +{ + int32_t rv; + df::coord pos; + + Lua::CheckDFAssign(L, &pos, 1); + int16_t mat = lua_tointeger(L, 2); + int32_t matg = lua_tointeger(L, 3); + df::matter_state state = (df::matter_state)lua_tointeger(L, 4); + int32_t amount = lua_tointeger(L, 5); + rv = Maps::addMaterialSpatter(pos, mat, matg, state, amount); + + lua_pushinteger(L, rv); + return 1; +} + +static int maps_addItemSpatter(lua_State *L) +{ + int32_t rv; + df::coord pos; + + Lua::CheckDFAssign(L, &pos, 1); + df::item_type i_type = (df::item_type)lua_tointeger(L, 2); + int16_t i_subtype = lua_tointeger(L, 3); + int16_t i_subcat1 = lua_tointeger(L, 4); + int32_t i_subcat2 = lua_tointeger(L, 5); + int32_t print_variant = lua_tointeger(L, 6); + int32_t amount = lua_tointeger(L, 7); + rv = Maps::addItemSpatter(pos, i_type, i_subtype, i_subcat1, i_subcat2, print_variant, amount); + + lua_pushinteger(L, rv); + return 1; +} + static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "isTileVisible", maps_isTileVisible }, @@ -2796,6 +2830,8 @@ static const luaL_Reg dfhack_maps_funcs[] = { { "isTileHeavyAquifer", maps_isTileHeavyAquifer }, { "setTileAquifer", maps_setTileAquifer }, { "removeTileAquifer", maps_removeTileAquifer }, + { "addMaterialSpatter", maps_addMaterialSpatter }, + { "addItemSpatter", maps_addItemSpatter }, { NULL, NULL } }; diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index b4235df11d2..52af19e680c 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -170,6 +170,9 @@ DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int1 // Gets the value of a specific item, taking into account civ values and trade agreements if a caravan is given. DFHACK_EXPORT int getValue(df::item *item, df::caravan_state *caravan = NULL); +// Automatically choose a growth print variant for the specified plant growth subtype+material +DFHACK_EXPORT int32_t pickGrowthPrint(int16_t subtype, int16_t mat, int32_t matg); + DFHACK_EXPORT bool createItem(std::vector &out_items, df::unit *creator, df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor = false, int32_t count = 1); @@ -186,6 +189,9 @@ DFHACK_EXPORT bool markForTrade(df::item *item, df::building_tradedepotst *depot // Returns true if an active caravan will pay extra for the given item. DFHACK_EXPORT bool isRequestedTradeGood(df::item *item, df::caravan_state *caravan = NULL); +// DF standard_material_itemtype - returns true if item has material/matgloss, false if race+caste +DFHACK_EXPORT bool usesStandardMaterial(df::item_type item_type); + // Returns true if the item can currently be melted. If game_ui, then able to be marked is enough. DFHACK_EXPORT bool canMelt(df::item *item, bool game_ui = false); // Marks the item for melting. diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 052dbe3aab1..1e7eac89e16 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -38,6 +38,7 @@ distribution. #include "df/block_flags.h" #include "df/feature_type.h" #include "df/flow_type.h" +#include "df/matter_state.h" #include "df/tile_dig_designation.h" #include "df/tiletype.h" @@ -372,6 +373,10 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, std::vector *priorities = 0 ); +// Add spatters at the specified location, returning the amount that couldn't be placed (e.g. due to overflow) +extern DFHACK_EXPORT int32_t addMaterialSpatter (df::coord pos, int16_t mat, int32_t matg, df::matter_state state, int32_t amount); +extern DFHACK_EXPORT int32_t addItemSpatter (df::coord pos, df::item_type i_type, int16_t i_subtype, int16_t i_subcat1, int32_t i_subcat2, int32_t print_variant, int32_t amount); + // Remove a block event from the block by address. extern DFHACK_EXPORT bool RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event *which ); extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event *which ); // TODO: deprecate me diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 6acd3b9401c..adce8ba8a6d 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1752,6 +1752,37 @@ int Items::getValue(df::item *item, df::caravan_state *caravan) { return value; } +// Automatically choose a growth print variant for the specified plant growth subtype+material +int32_t Items::pickGrowthPrint(int16_t subtype, int16_t mat, int32_t matg) +{ + int growth_print = -1; + // Make sure it's made of a valid plant material, then grab its definition + if (mat >= 419 && mat <= 618 && matg >= 0 && (unsigned)matg < world->raws.plants.all.size()) + { + auto plant_def = world->raws.plants.all[matg]; + // Make sure it subtype is also valid + if (subtype >= 0 && (unsigned)subtype < plant_def->growths.size()) + { + auto growth_def = plant_def->growths[subtype]; + // Try and find a growth print matching the current time + // (in practice, only tree leaves use this for autumn color changes) + for (size_t i = 0; i < growth_def->prints.size(); i++) + { + auto print_def = growth_def->prints[i]; + if (print_def->timing_start <= *df::global::cur_year_tick && *df::global::cur_year_tick <= print_def->timing_end) + { + growth_print = i; + break; + } + } + // If we didn't find one, then pick the first one (if it exists) + if (growth_print == -1 && !growth_def->prints.empty()) + growth_print = 0; + } + } + return growth_print; +} + bool Items::createItem(vector &out_items, df::unit *unit, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor, int32_t count) { // Based on Quietust's plugins/createitem.cpp @@ -1802,34 +1833,7 @@ bool Items::createItem(vector &out_items, df::unit *unit, df::item_t for (auto out_item : out_items) { // Plant growths need a valid "growth print", otherwise they behave oddly if (auto growth = virtual_cast(out_item)) - { - int growth_print = -1; - // Make sure it's made of a valid plant material, then grab its definition - if (growth->mat_type >= 419 && growth->mat_type <= 618 && growth->mat_index >= 0 && (unsigned)growth->mat_index < world->raws.plants.all.size()) - { - auto plant_def = world->raws.plants.all[growth->mat_index]; - // Make sure it subtype is also valid - if (growth->subtype >= 0 && (unsigned)growth->subtype < plant_def->growths.size()) - { - auto growth_def = plant_def->growths[growth->subtype]; - // Try and find a growth print matching the current time - // (in practice, only tree leaves use this for autumn color changes) - for (size_t i = 0; i < growth_def->prints.size(); i++) - { - auto print_def = growth_def->prints[i]; - if (print_def->timing_start <= *df::global::cur_year_tick && *df::global::cur_year_tick <= print_def->timing_end) - { - growth_print = i; - break; - } - } - // If we didn't find one, then pick the first one (if it exists) - if (growth_print == -1 && !growth_def->prints.empty()) - growth_print = 0; - } - } - growth->growth_print = growth_print; - } + growth->growth_print = pickGrowthPrint(growth->subtype, growth->mat_type, growth->mat_index); if (!no_floor) out_item->moveToGround(pos.x, pos.y, pos.z); } @@ -1962,17 +1966,10 @@ bool Items::isRequestedTradeGood(df::item *item, df::caravan_state *caravan) { return false; } -/// When called with game_ui = true, this is equivalent to Bay12's itemst::meltable() -/// (i.e., returning true if and only if the item has a "designate for melting" button in game) -bool Items::canMelt(df::item *item, bool game_ui) { - CHECK_NULL_POINTER(item); - MaterialInfo mat(item); - if (mat.getCraftClass() != craft_material_class::Metal) - return false; - - switch(item->getType()) +bool Items::usesStandardMaterial(df::item_type item_type) +{ + switch(item_type) { using namespace df::enums::item_type; - // These are not meltable, even if made from metal case CORPSE: case CORPSEPIECE: case REMAINS: @@ -1984,8 +1981,20 @@ bool Items::canMelt(df::item *item, bool game_ui) { case EGG: return false; default: - break; + return true; } +} + +/// When called with game_ui = true, this is equivalent to Bay12's itemst::meltable() +/// (i.e., returning true if and only if the item has a "designate for melting" button in game) +bool Items::canMelt(df::item *item, bool game_ui) { + CHECK_NULL_POINTER(item); + MaterialInfo mat(item); + if (mat.getCraftClass() != craft_material_class::Metal) + return false; + + if (!usesStandardMaterial(item->getType())) + return false; if (item->flags.bits.artifact) return false; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 7e5707fccbf..6f449dc2fc1 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -42,6 +42,9 @@ distribution. #include "df/block_burrow.h" #include "df/block_burrow_link.h" #include "df/block_square_event_grassst.h" +#include "df/block_square_event_item_spatterst.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/block_square_event_spoorst.h" #include "df/building.h" #include "df/building_type.h" #include "df/builtin_mats.h" @@ -52,6 +55,7 @@ distribution. #include "df/flow_info.h" #include "df/map_block.h" #include "df/map_block_column.h" +#include "df/material.h" #include "df/plant.h" #include "df/plant_root_tile.h" #include "df/plant_tree_info.h" @@ -656,6 +660,248 @@ bool Maps::SortBlockEvents(df::map_block *block, return true; } +// Based on worldst::add_material_spatter_tile_capped +int32_t Maps::addMaterialSpatter (df::coord pos, int16_t mat, int32_t matg, df::matter_state state, int32_t amount) +{ + // Hardcoded maximum + int32_t cap = 255; + + // Sanity checks + if (amount > cap) + amount = cap; + // DF doesn't handle negative numbers, so disallow them + if (amount < 0) + amount = 0; + + // DF rejects materials of NONE:* + if (mat == -1) + return amount; + + // Extra check: make sure the material correctly exists + MaterialInfo matinfo(mat, matg); + if (!matinfo.isValid()) + return amount; + + df::map_block *block = Maps::getTileBlock(pos); + if (!block) + return amount; + + int16_t bx = pos.x & 0xF, by = pos.y & 0xF; + + // Extra check: specify state == NONE to auto-pick based on tile temperature + // Note that this won't choose POWDER/PASTE/PRESSED + if (state == df::matter_state::None) + { + uint16_t tile = block->temperature_1[bx][by]; + uint16_t melt = matinfo.material->heat.melting_point; + uint16_t boil = matinfo.material->heat.boiling_point; + if (boil != 60001 && tile >= boil) + state = df::matter_state::Gas; + else if (melt != 60001 && tile >= melt) + state = df::matter_state::Liquid; + else + state = df::matter_state::Solid; + } + + if (amount > 0) + { + // scan all SPOOR events and clear the PRESENT flag if the type is HFID_COMBINEDCASTE_BP, ITEMT_ITEMST_ORIENT, or MESS + for (size_t i = 0; i < block->block_events.size(); i++) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::spoor) + continue; + auto spoor = (df::block_square_event_spoorst *)evt; + if (!spoor->info.flags[bx][by].bits.present) + continue; + if (spoor->info.type[bx][by] == df::spoor_type::HFID_COMBINEDCASTE_BP || + spoor->info.type[bx][by] == df::spoor_type::ITEMT_ITEMST_ORIENT || + spoor->info.type[bx][by] == df::spoor_type::MESS) + spoor->info.flags[bx][by].bits.present = false; + } + } + + // Find existing matching material spatter + df::block_square_event_material_spatterst *spatter = nullptr; + // DF: get_material_spatter_event_even_if_empty(...) + for (int i = block->block_events.size() - 1; i >= 0; i--) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::material_spatter) + continue; + auto spt = (df::block_square_event_material_spatterst *)evt; + if (spt->mat_type == mat && spt->mat_index == matg && + spt->mat_state == state) + { + spatter = spt; + break; + } + } + + // If we didn't find one, make a new one + if (!spatter) + { + spatter = df::allocate(); + spatter->mat_type = mat; + spatter->mat_index = matg; + spatter->mat_state = state; + memset(spatter->amount, 0, sizeof(spatter->amount)); + spatter->min_temperature = spatter->max_temperature = 60001; + + uint16_t melt = matinfo.material->heat.melting_point; + uint16_t boil = matinfo.material->heat.boiling_point; + + switch (state) + { using namespace df::enums::matter_state; + case Solid: + case Powder: + case Paste: + case Pressed: + if (melt != 60001) + boil = melt; + spatter->max_temperature = boil; + break; + case Liquid: + if (melt != 60001 && melt != 0) + spatter->min_temperature = melt - 1; + spatter->max_temperature = boil; + break; + // Can't really have gas spatters, but DF has this check + // presumably, DF could convert this into a flow + case Gas: + if (boil != 60001 && boil != 0) + spatter->min_temperature = boil - 1; + else if (melt != 60001 && melt != 0) + spatter->min_temperature = melt - 1; + break; + case None: + // impossible + break; + } + // DF doesn't check heatdam/colddam/ignite points here + block->block_events.push_back(spatter); + } + + int32_t newamount = spatter->amount[bx][by] + amount; + if (newamount > cap) + { + amount = newamount - cap; + newamount = cap; + } + else + amount = 0; + + spatter->amount[bx][by] = (uint8_t)newamount; + block->flags.bits.may_have_material_spatter = 1; + + return amount; +} + +// Based on worldst::add_item_spatter_tile_capped +int32_t Maps::addItemSpatter (df::coord pos, df::item_type i_type, int16_t i_subtype, int16_t i_subcat1, int32_t i_subcat2, int32_t print_variant, int32_t amount) +{ + // DF passes this as a parameter, but it's always the same + int32_t cap = 10000; + + // Sanity checks + if (amount > cap) + amount = cap; + // DF doesn't handle negative numbers, so disallow them + if (amount < 0) + amount = 0; + + df::map_block *block = Maps::getTileBlock(pos); + if (!block) + return amount; + + int16_t bx = pos.x & 0xF, by = pos.y & 0xF; + + if (amount > 0) + { + // scan all SPOOR events and clear the PRESENT flag if the type is HFID_COMBINEDCASTE_BP, ITEMT_ITEMST_ORIENT, or MESS + for (size_t i = 0; i < block->block_events.size(); i++) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::spoor) + continue; + auto spoor = (df::block_square_event_spoorst *)evt; + if (!spoor->info.flags[bx][by].bits.present) + continue; + if (spoor->info.type[bx][by] == df::spoor_type::HFID_COMBINEDCASTE_BP || + spoor->info.type[bx][by] == df::spoor_type::ITEMT_ITEMST_ORIENT || + spoor->info.type[bx][by] == df::spoor_type::MESS) + spoor->info.flags[bx][by].bits.present = false; + } + } + + // Allow auto-selecting growth print for plant growths + if (i_type == df::item_type::PLANT_GROWTH && print_variant == -1) + print_variant = Items::pickGrowthPrint(i_subtype, i_subcat1, i_subcat2); + + // Find existing matching item spatter + df::block_square_event_item_spatterst *spatter = nullptr; + // DF: get_item_spatter_event_even_if_empty(...) + for (int i = block->block_events.size() - 1; i >= 0; i--) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::item_spatter) + continue; + auto spt = (df::block_square_event_item_spatterst *)evt; + if (spt->item_type == i_type && spt->item_subtype == i_subtype && + spt->mattype == i_subcat1 && spt->matindex == i_subcat2 && + spt->print_variant == print_variant) + { + spatter = spt; + break; + } + } + + // If we didn't find one, make a new one + if (!spatter) + { + spatter = df::allocate(); + spatter->item_type = i_type; + spatter->item_subtype = i_subtype; + spatter->mattype = i_subcat1; + spatter->matindex = i_subcat2; + spatter->print_variant = print_variant; + memset(spatter->amount, 0, sizeof(spatter->amount)); + memset(spatter->flag, 0, sizeof(spatter->flag)); + spatter->min_temperature = spatter->max_temperature = 60001; + + if (Items::usesStandardMaterial(i_type)) + { + MaterialInfo info(i_subcat1, i_subcat2); + if (info.isValid()) + { + uint16_t melt = info.material->heat.melting_point; + uint16_t boil = info.material->heat.melting_point; + if (melt != 60001) + spatter->max_temperature = melt; + else + spatter->max_temperature = boil; + // DF doesn't look at the heatdam/colddam/ignite temperatures + } + } + block->block_events.push_back(spatter); + } + + int32_t newamount = spatter->amount[bx][by] + amount; + if (newamount > cap) + { + amount = newamount - cap; + newamount = cap; + } + else + amount = 0; + + spatter->amount[bx][by] = newamount; + spatter->flag[bx][by].bits.season_full_timer = 7; + block->flags.bits.may_have_item_spatter = 1; + + return amount; +} + inline bool RemoveBlockEventInline(int32_t x, int32_t y, int32_t z, df::block_square_event * which) { df::map_block *block = Maps::getBlock(x, y, z); From a2e058bd0f4905ff321777cf0cbcc5af52094c03 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 5 Feb 2026 00:59:29 -0600 Subject: [PATCH 051/202] add python setup to build-windows.yml so that jinja2 has somewhere to install to (may be necessary in the windows runner, unclear?) --- .github/workflows/build-windows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 4ad823e696b..25e42808dc2 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -68,6 +68,8 @@ jobs: name: Build win64 runs-on: windows-2022 steps: + - name: Set up Python + uses: actions/setup-python@v5 - name: Install build dependencies run: | choco install sccache From ef54ef2cc8d744125fcfe69c6d03d0eecf5fd2c4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 5 Feb 2026 01:17:48 -0600 Subject: [PATCH 052/202] python: explicitly require 3.x (we're not that picky) --- .github/workflows/build-windows.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 25e42808dc2..1dec2111175 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -69,7 +69,9 @@ jobs: runs-on: windows-2022 steps: - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 + with: + python-version: '3.x' - name: Install build dependencies run: | choco install sccache From 92bd3642c51b3fe015fd2167530af12b6149728f Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:48:34 +0000 Subject: [PATCH 053/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 3826f45ef0f..8042f0a3574 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3826f45ef0fad7bd3357a6d55d5c9d28b56614c2 +Subproject commit 8042f0a357469d247d515893c8a01483c6c385c7 From 20240e17b4c208afad7d98d9b49b9f63ebc5cea0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 9 Feb 2026 07:57:25 +0000 Subject: [PATCH 054/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ff1b95a7b4e..ca71e41da49 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ff1b95a7b4e97be9a94218162c099dd95eaf4680 +Subproject commit ca71e41da49d6b7dbc767fab262a939b4172bfb6 From 59c900e5ae1a1cd3fa750e00d3ff627ba27bce75 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Mon, 9 Feb 2026 22:49:13 +0100 Subject: [PATCH 055/202] Add changelog entry in api section --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9ca880c3086..fe7bd82320f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -66,6 +66,7 @@ Template for new versions: ## Documentation ## API +- `dfhack.job.getManagerOrderName()`: New function to get the display name of a manager order ## Lua From aa4d3962cdcf3e3b4e86bab107a6c7477dee8de3 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Mon, 9 Feb 2026 22:59:36 +0100 Subject: [PATCH 056/202] Fix changelog --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7257bd62a1a..04321422d8b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -116,7 +116,7 @@ Template for new versions: ## Documentation ## API -- `dfhack.job.getManagerOrderName()`: New function to get the display name of a manager order +- ``dfhack.job.getManagerOrderName``: New function to get the display name of a manager order ## Lua From 7b2090017d9b338fb28efa737afecd57a86d2cee Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 10 Feb 2026 07:58:45 +0000 Subject: [PATCH 057/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ca71e41da49..1b9f8c61f76 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ca71e41da49d6b7dbc767fab262a939b4172bfb6 +Subproject commit 1b9f8c61f7615bfa361da83ed49288b305b9d3a6 From c011d31bfafd565c03d9769516755e6e28c9b4af Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Tue, 10 Feb 2026 21:13:46 +0100 Subject: [PATCH 058/202] constExpr. erase_if --- plugins/logcleaner/logcleaner.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index fc00a9cb135..770bb2f26fc 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -31,7 +31,7 @@ static bool clear_combat = false; static bool clear_sparring = true; static bool clear_hunting = false; -static const int32_t CLEANUP_TICK_INTERVAL = 97; +static constexpr int32_t CLEANUP_TICK_INTERVAL = 97; static void cleanupLogs(); static command_result do_command(color_ostream& out, std::vector& params); @@ -130,6 +130,9 @@ static void cleanupLogs() { if (!is_enabled || !world) return; + if (!clear_combat && !clear_sparring && !clear_hunting) + return; + // Collect all report IDs from unit combat/sparring/hunting logs std::unordered_set report_ids_to_remove; bool log_types[] = {clear_combat, clear_sparring, clear_hunting}; @@ -152,17 +155,12 @@ static void cleanupLogs() { // Remove collected reports from global buffers auto& reports = world->status.reports; - for (auto report_id : report_ids_to_remove) { - df::report* report = df::report::find(report_id); - if (!report) - continue; - - auto it = std::find(reports.begin(), reports.end(), report); - if (it != reports.end()) { - delete report; - reports.erase(it); - } - } + std::erase_if(reports, [&](df::report* report) { + if (!report || !report_ids_to_remove.contains(report->id)) + return false; + delete report; + return true; + }); } DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_event event) { From bbfabe6ab00d8736bb97d349ac2f1951a6b8a329 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:55:28 +0000 Subject: [PATCH 059/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 1b9f8c61f76..103e1b394f9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1b9f8c61f7615bfa361da83ed49288b305b9d3a6 +Subproject commit 103e1b394f9ba889b7909aeef45fa8281f59950b From 97e2fc5fcc6a1aae3f8a2e43c9f3c4baf6d4153a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 17 Feb 2026 07:51:07 +0000 Subject: [PATCH 060/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 103e1b394f9..16bd197d6ca 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 103e1b394f9ba889b7909aeef45fa8281f59950b +Subproject commit 16bd197d6ca5ca9adbe2f8cf0856448f3f5ac86b From 38ed2723d04baeb1b9f6b132a35c6bac77c88967 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 17 Feb 2026 08:51:28 -0600 Subject: [PATCH 061/202] add covering default case to `Maps::SortBlockEvents` temporary - replace with `case block_square_event_type::NONE` after dfhack/df-structures#2293 is merged --- library/modules/Maps.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 6f449dc2fc1..11499a42701 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -655,6 +655,9 @@ bool Maps::SortBlockEvents(df::map_block *block, if (priorities) priorities->push_back((df::block_square_event_designation_priorityst *)evt); break; + default: + assert("Unhandled block event type" && false); // FIXME temporary - replace with NONE case after structure are updated + break; } } return true; From 36d0ace6ba809902d43293fe72d6612b5a4fe85b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 18 Feb 2026 07:51:15 +0000 Subject: [PATCH 062/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index efe712b62d5..e86849a674a 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit efe712b62d50b1f537558168701af8e6ed2bdece +Subproject commit e86849a674a83ff7169330e65d18857c5dbe11cb From 380c0498fc452a47da975e8d0fd810ace83bb1f6 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:07:57 +0000 Subject: [PATCH 063/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index e86849a674a..2667acf1a8c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit e86849a674a83ff7169330e65d18857c5dbe11cb +Subproject commit 2667acf1a8c7b2be8a37e2ecd68545f733094305 From c3ccb430c2cb7dd8e0e3f8ba546c029d3fbadb51 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:32:20 +0000 Subject: [PATCH 064/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 2667acf1a8c..f393d6bfc1d 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 2667acf1a8c7b2be8a37e2ecd68545f733094305 +Subproject commit f393d6bfc1d30aec6beae543d3cf6a04e700e821 From 3468c748624fe369d70f931c882fa4aa73691046 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 21 Feb 2026 07:33:51 +0000 Subject: [PATCH 065/202] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 8042f0a3574..3100ca32f12 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8042f0a357469d247d515893c8a01483c6c385c7 +Subproject commit 3100ca32f12313bcd74437f381b72a14bf291b8d diff --git a/scripts b/scripts index 16bd197d6ca..e88bc68286d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 16bd197d6ca5ca9adbe2f8cf0856448f3f5ac86b +Subproject commit e88bc68286d6a15b86594822d114e21d197542d8 From 6d2b752f6b69780261bc88741a87ff5ef25d8aa2 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:20:55 +0000 Subject: [PATCH 066/202] Auto-update submodules scripts: master plugins/stonesense: master --- plugins/stonesense | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stonesense b/plugins/stonesense index f393d6bfc1d..a951081b3f9 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit f393d6bfc1d30aec6beae543d3cf6a04e700e821 +Subproject commit a951081b3f9edce7704841a91f1b14bb13dc27a8 diff --git a/scripts b/scripts index e88bc68286d..32739c4c760 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e88bc68286d6a15b86594822d114e21d197542d8 +Subproject commit 32739c4c7607b75e6ef2fb18ddefec34b4191740 From 465833f0365ceb0e034a9b16e945ef0f72180e8a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 24 Feb 2026 11:17:40 -0600 Subject: [PATCH 067/202] up version to 53.10-r2rc1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea1e6135564..f0cbce0acb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.10") -set(DFHACK_RELEASE "r1") -set(DFHACK_PRERELEASE FALSE) +set(DFHACK_RELEASE "r2rc1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) From 8a682c84d246683338dfba6f38f257e38fedec15 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 28 Feb 2026 15:21:46 -0800 Subject: [PATCH 068/202] Properly delete announcements --- plugins/logcleaner/logcleaner.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index 770bb2f26fc..d9c0b8dce3c 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -158,6 +158,8 @@ static void cleanupLogs() { std::erase_if(reports, [&](df::report* report) { if (!report || !report_ids_to_remove.contains(report->id)) return false; + if (report->flags.bits.announcement) + erase_from_vector(world->status.announcements, &df::report::id, report->id); delete report; return true; }); From aee495573408594857201a27d51ef112828d50ef Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 1 Mar 2026 23:43:37 -0800 Subject: [PATCH 069/202] Fix logcleaner to use ticks --- plugins/logcleaner/logcleaner.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index d9c0b8dce3c..4b59a88d1e9 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -31,7 +31,8 @@ static bool clear_combat = false; static bool clear_sparring = true; static bool clear_hunting = false; -static constexpr int32_t CLEANUP_TICK_INTERVAL = 97; +static constexpr int32_t CYCLE_TICKS = 97; +static int32_t cycle_timestamp = 0; static void cleanupLogs(); static command_result do_command(color_ostream& out, std::vector& params); @@ -116,6 +117,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream& out) { clear_sparring = config.get_bool(CONFIG_CLEAR_SPARING); clear_hunting = config.get_bool(CONFIG_CLEAR_HUNTING); + cycle_timestamp = 0; return CR_OK; } @@ -166,16 +168,13 @@ static void cleanupLogs() { } DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_event event) { - static int32_t tick_counter = 0; - if (!is_enabled || !world) return CR_OK; + else if (world->frame_counter - cycle_timestamp <= CYCLE_TICKS) + return CR_OK; - tick_counter++; - if (tick_counter >= CLEANUP_TICK_INTERVAL) { - tick_counter = 0; - cleanupLogs(); - } + cycle_timestamp = world->frame_counter; + cleanupLogs(); return CR_OK; } From a12d60c432a2bbcc047577cfc6bbc5f8aa835679 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 1 Mar 2026 23:47:01 -0800 Subject: [PATCH 070/202] Update logcleaner.rst - not exactly 100 ticks --- docs/plugins/logcleaner.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/logcleaner.rst b/docs/plugins/logcleaner.rst index d9d0930d82e..6e4cba07d6b 100644 --- a/docs/plugins/logcleaner.rst +++ b/docs/plugins/logcleaner.rst @@ -5,8 +5,8 @@ logcleaner :tags: fort auto units This plugin prevents spam from cluttering your announcement history and filling -the 3000-item reports buffer. It runs every 100 ticks and clears selected report -types from both the global reports buffer and per-unit logs. +the 3000-item reports buffer. It runs approximately every 100 ticks and clears +selected report types from both the global reports buffer and per-unit logs. Usage ----- From 3d808ccf0e3214fb937390ca298cf251afdf251d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 2 Mar 2026 00:21:07 -0800 Subject: [PATCH 071/202] Fix logcleaner and autolabor ticks offset by +1 * Update logcleaner.cpp * Update autolabor.cpp --- plugins/autolabor/autolabor.cpp | 2 +- plugins/logcleaner/logcleaner.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor/autolabor.cpp b/plugins/autolabor/autolabor.cpp index d0cec796fba..4fd73ce1814 100644 --- a/plugins/autolabor/autolabor.cpp +++ b/plugins/autolabor/autolabor.cpp @@ -740,7 +740,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (world->frame_counter - cycle_timestamp <= CYCLE_TICKS) + if (world->frame_counter - cycle_timestamp < CYCLE_TICKS) return CR_OK; cycle_timestamp = world->frame_counter; diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp index 4b59a88d1e9..89f84cf32f8 100644 --- a/plugins/logcleaner/logcleaner.cpp +++ b/plugins/logcleaner/logcleaner.cpp @@ -170,7 +170,7 @@ static void cleanupLogs() { DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_event event) { if (!is_enabled || !world) return CR_OK; - else if (world->frame_counter - cycle_timestamp <= CYCLE_TICKS) + else if (world->frame_counter - cycle_timestamp < CYCLE_TICKS) return CR_OK; cycle_timestamp = world->frame_counter; From a5c5a87ae40620cdad9d686002acb7a11c31c87d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 2 Mar 2026 06:00:02 -0600 Subject: [PATCH 072/202] revert change to autolabor out of scope for this PR --- plugins/autolabor/autolabor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor/autolabor.cpp b/plugins/autolabor/autolabor.cpp index 4fd73ce1814..a95d6636d48 100644 --- a/plugins/autolabor/autolabor.cpp +++ b/plugins/autolabor/autolabor.cpp @@ -1,4 +1,4 @@ -#include "laborstatemap.h" +g#include "laborstatemap.h" #include "Debug.h" #include "PluginManager.h" @@ -740,7 +740,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (world->frame_counter - cycle_timestamp < CYCLE_TICKS) + if (world->frame_counter - cycle_timestamp <= CYCLE_TICKS) return CR_OK; cycle_timestamp = world->frame_counter; From 80b4446849f1eed4452087200b05d16e466e19bd Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 2 Mar 2026 06:03:27 -0600 Subject: [PATCH 073/202] gah remove line noise --- plugins/autolabor/autolabor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autolabor/autolabor.cpp b/plugins/autolabor/autolabor.cpp index a95d6636d48..d0cec796fba 100644 --- a/plugins/autolabor/autolabor.cpp +++ b/plugins/autolabor/autolabor.cpp @@ -1,4 +1,4 @@ -g#include "laborstatemap.h" +#include "laborstatemap.h" #include "Debug.h" #include "PluginManager.h" From 7c9ec563663fbdae3c4ddabb318736abe095ae13 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 2 Mar 2026 07:03:25 -0600 Subject: [PATCH 074/202] cmakelist/changelog/submodules for 53.10-r2 --- CMakeLists.txt | 4 ++-- docs/changelog.txt | 24 +++++++++++++++++++++--- library/xml | 2 +- scripts | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0cbce0acb0..36e18160c7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.10") -set(DFHACK_RELEASE "r2rc1") -set(DFHACK_PRERELEASE TRUE) +set(DFHACK_RELEASE "r2") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) diff --git a/docs/changelog.txt b/docs/changelog.txt index 3d78dd83356..23204c72187 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,6 +54,24 @@ Template for new versions: # Future +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.10-r2 + ## New Tools - ``logcleaner``: New plugin for time-triggered clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI. @@ -70,14 +88,14 @@ Template for new versions: ## Documentation ## API -- Added ``Maps::addMaterialSpatter``: add a spatter of the specified material + state to the indicated tile, returning whatever amount wouldn't fit in the tile. -- Added ``Maps::addItemSpatter``: add a spatter of the specified item + material + growth print to the indicated tile, returning whatever amount wouldn't fit in the tile. - Added ``Items::pickGrowthPrint``: given a plant material and a growth index, returns the print variant corresponding to the current in-game time. - Added ``Items::useStandardMaterial``: given an item type, returns true if the item is made of a specific material and false if it has a race and caste instead. +- Added ``Maps::addItemSpatter``: add a spatter of the specified item + material + growth print to the indicated tile, returning whatever amount wouldn't fit in the tile. +- Added ``Maps::addMaterialSpatter``: add a spatter of the specified material + state to the indicated tile, returning whatever amount wouldn't fit in the tile. ## Lua -- Added ``Maps::addMaterialSpatter`` as ``dfhack.maps.addMaterialSpatter``. - Added ``Maps::addItemSpatter`` as ``dfhack.maps.addItemSpatter``. +- Added ``Maps::addMaterialSpatter`` as ``dfhack.maps.addMaterialSpatter``. ## Removed diff --git a/library/xml b/library/xml index 3100ca32f12..b3364bb752f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3100ca32f12313bcd74437f381b72a14bf291b8d +Subproject commit b3364bb752fce026a1e217cd1871125731d6224e diff --git a/scripts b/scripts index 32739c4c760..edf2d1f92f9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 32739c4c7607b75e6ef2fb18ddefec34b4191740 +Subproject commit edf2d1f92f9b68cdef590d299a3a47690ce80442 From 23d425376a7b92f091370488c3f4c2882bc950b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:18:22 +0000 Subject: [PATCH 075/202] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.36.1 → 0.37.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.36.1...0.37.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c47c9ccfdfd..90b37493a7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.1 + rev: 0.37.0 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From 191e25296621271e9eb23bed3961dd68b8c52881 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 2 Mar 2026 18:49:39 -0600 Subject: [PATCH 076/202] add type identity support for `static-wstring` as an opaque type only --- library/DataIdentity.cpp | 2 ++ library/include/DataIdentity.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/library/DataIdentity.cpp b/library/DataIdentity.cpp index 1bbeb64e77a..346565c62fe 100644 --- a/library/DataIdentity.cpp +++ b/library/DataIdentity.cpp @@ -33,6 +33,7 @@ namespace df { INTEGER_IDENTITY_TRAITS(unsigned long, "unsigned long"); INTEGER_IDENTITY_TRAITS(long long, "int64_t"); INTEGER_IDENTITY_TRAITS(unsigned long long, "uint64_t"); + INTEGER_IDENTITY_TRAITS(wchar_t, "wchar_t"); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); @@ -57,6 +58,7 @@ namespace df { OPAQUE_IDENTITY_TRAITS(std::optional >); OPAQUE_IDENTITY_TRAITS(std::variant >); OPAQUE_IDENTITY_TRAITS(std::weak_ptr); + OPAQUE_IDENTITY_TRAITS(wchar_t*); const buffer_container_identity buffer_container_identity::base_instance; diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index f8fd3c6fd7f..e58247fdaae 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -617,8 +617,10 @@ namespace df INTEGER_IDENTITY_TRAITS(unsigned long); INTEGER_IDENTITY_TRAITS(long long); INTEGER_IDENTITY_TRAITS(unsigned long long); + INTEGER_IDENTITY_TRAITS(wchar_t); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); + OPAQUE_IDENTITY_TRAITS(wchar_t*); OPAQUE_IDENTITY_TRAITS(std::condition_variable); OPAQUE_IDENTITY_TRAITS(std::fstream); OPAQUE_IDENTITY_TRAITS(std::mutex); From 9a81dc37f87ecfd2985e63461f4969b14b8beb69 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 3 Mar 2026 07:43:40 +0000 Subject: [PATCH 077/202] Auto-update submodules library/xml: master scripts: master plugins/stonesense: master --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index b3364bb752f..228ee8d1364 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b3364bb752fce026a1e217cd1871125731d6224e +Subproject commit 228ee8d13642ac65f737837e31ed6b2142cb188c diff --git a/plugins/stonesense b/plugins/stonesense index a951081b3f9..4760027eee7 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit a951081b3f9edce7704841a91f1b14bb13dc27a8 +Subproject commit 4760027eee745d8c35cca843a2fcc46c21be326a diff --git a/scripts b/scripts index edf2d1f92f9..46da47f64ef 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit edf2d1f92f9b68cdef590d299a3a47690ce80442 +Subproject commit 46da47f64ef1c8e7d49e257b36c59963770b6d53 From 6f5af720424dece4d7da4abe10684250488f11cb Mon Sep 17 00:00:00 2001 From: Halavus Nenuli Date: Wed, 4 Mar 2026 04:57:32 +0100 Subject: [PATCH 078/202] Fix spelling of 'perseverance' in sort.lua changed e to a --- plugins/lua/sort.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 978e165354f..af066fb1758 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -293,7 +293,7 @@ local function get_mental_stability(unit) local emotionally_obsessive = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE local humor = unit.status.current_soul.personality.traits.HUMOR local love_propensity = unit.status.current_soul.personality.traits.LOVE_PROPENSITY - local perseverence = unit.status.current_soul.personality.traits.PERSEVERENCE + local perseverance = unit.status.current_soul.personality.traits.PERSEVERANCE local politeness = unit.status.current_soul.personality.traits.POLITENESS local privacy = unit.status.current_soul.personality.traits.PRIVACY local stress_vulnerability = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY @@ -315,7 +315,7 @@ local function get_mental_stability(unit) + (anxiety_propensity * -0.06) + (bravery * 0.06) + (cheer_propensity * 0.41) + (curious * -0.06) + (discord * 0.14) + (dutifulness * -0.03) + (emotionally_obsessive * -0.13) - + (humor * -0.05) + (love_propensity * 0.15) + (perseverence * -0.07) + + (humor * -0.05) + (love_propensity * 0.15) + (perseverance * -0.07) + (politeness * -0.14) + (privacy * 0.03) + (stress_vulnerability * -0.20) + (tolerant * -0.11) From 3a2dfaa09e859ee6957ba162f4db64c3252163aa Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 3 Mar 2026 22:19:42 -0600 Subject: [PATCH 079/202] add changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 23204c72187..96207045207 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,7 @@ Template for new versions: ## New Features ## Fixes +- `sort`: correct misspelling of ``PERSEVERENCE``; fixes "hates combat" filter in squad selection screen ## Misc Improvements From 5cd74c711bc5190653cd521c332279acba0596a5 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 4 Mar 2026 10:16:17 -0600 Subject: [PATCH 080/202] Update version to 53.11 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36e18160c7a..9d6e55bd12f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) # set up versioning. -set(DF_VERSION "53.10") -set(DFHACK_RELEASE "r2") +set(DF_VERSION "53.11") +set(DFHACK_RELEASE "r1") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 85cdf29e27ff0b0660fc52a4eb4c3b33109c864f Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:30:02 +0000 Subject: [PATCH 081/202] Auto-update structures ref for 53.11 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 228ee8d1364..491bec74402 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 228ee8d13642ac65f737837e31ed6b2142cb188c +Subproject commit 491bec744029a33abc021e893ffceac541fc89e2 From 012306d028582e01774f8529fb1fc887ff37979a Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:34:33 +0000 Subject: [PATCH 082/202] Auto-update structures ref for 53.11 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 491bec74402..228549676e0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 491bec744029a33abc021e893ffceac541fc89e2 +Subproject commit 228549676e0cc03746d6f23da66562a94f3bf59d From 7ab06c4ef523ff2322bb7ceab246036a8417a146 Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:47:05 +0000 Subject: [PATCH 083/202] Auto-update structures ref for 53.11 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 228549676e0..435e433fd68 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 228549676e0cc03746d6f23da66562a94f3bf59d +Subproject commit 435e433fd686c842fd53868c1648938280afeea3 From ce08f11bd689454bb32aeba16adbb8a963e3f849 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 4 Mar 2026 11:02:25 -0600 Subject: [PATCH 084/202] changelog for 53.11-r1 --- docs/changelog.txt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 96207045207..79db3273136 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,7 +33,6 @@ Template for new versions: ## New Features ## Fixes -- `sort`: correct misspelling of ``PERSEVERENCE``; fixes "hates combat" filter in squad selection screen ## Misc Improvements @@ -71,6 +70,25 @@ Template for new versions: ## Removed +# 53.11-r1 + +## New Tools + +## New Features + +## Fixes +- `sort`: correct misspelling of ``PERSEVERENCE``; fixes "hates combat" filter in squad selection screen + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + # 53.10-r2 ## New Tools From 64587d365681afedc01a046f93b8eaa77ea14dd1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 4 Mar 2026 11:02:39 -0600 Subject: [PATCH 085/202] update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 435e433fd68..d7928837480 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 435e433fd686c842fd53868c1648938280afeea3 +Subproject commit d79288374802cc63b1507900030b32231ffd244b From f766031a2bd8d33815e991e98641fc430acbf662 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 5 Mar 2026 18:59:10 -0600 Subject: [PATCH 086/202] fix windows console (#5737) always use utf-8 when writing to the Windows console --- docs/changelog.txt | 1 + library/MiscUtils.cpp | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 79db3273136..09878b51103 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -61,6 +61,7 @@ Template for new versions: ## Fixes ## Misc Improvements +- General: DFHack will unconditionally use UTF-8 for the console on Windows, now that DF forces the process effective system code page to 65001 during startup ## Documentation diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 082657ea34a..dc1d4950ac2 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -653,20 +653,31 @@ std::string UTF2DF(const std::string &in) out.resize(pos); return out; } - -DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in) +static bool console_is_utf8() { - bool is_utf = false; #ifdef LINUX_BUILD + static bool checked = false; + static bool is_utf = false; + if (checked) + return is_utf; + std::string locale = ""; if (getenv("LANG")) locale += getenv("LANG"); if (getenv("LC_CTYPE")) locale += getenv("LC_CTYPE"); locale = toUpper_cp437(locale); - is_utf = (locale.find("UTF-8") != std::string::npos) || - (locale.find("UTF8") != std::string::npos); + is_utf = (locale.find("UTF-8") != std::string::npos) || (locale.find("UTF8") != std::string::npos); + checked = true; + return is_utf; +#else + return true; // Since DF 53.11, Windows console is always UTF-8 #endif +} + +DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in) +{ + bool is_utf = console_is_utf8(); return is_utf ? DF2UTF(in) : in; } From d1db2f79e6fce3e4eb8f180ae0a4415e827d6c8c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 5 Mar 2026 23:19:42 -0600 Subject: [PATCH 087/202] update for changed `manager_order` default constructor default-initialized value for `frequency` is now `NONE`, was previously `OneTime` --- docs/changelog.txt | 1 + plugins/autoclothing.cpp | 1 + plugins/autoslab.cpp | 1 + plugins/tailor.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 79db3273136..7174ed16493 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,6 +59,7 @@ Template for new versions: ## New Features ## Fixes +- `autoclothing`, `autoslab`, `tailor`: orders will no longer be created with a repetition frequency of ``NONE`` ## Misc Improvements diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 89a45ee15f3..076892f9bd6 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -613,6 +613,7 @@ static void add_clothing_orders() { newOrder->material_category = clothingOrder.material_category; newOrder->amount_left = amount; newOrder->amount_total = amount; + newOrder->frequency = df::workquota_frequency_type::OneTime; world->manager_orders.all.push_back(newOrder); } } diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 08c4c837d1f..e8c06d575d9 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -141,6 +141,7 @@ static void createSlabJob(df::unit *unit) order->specdata.hist_figure_id = unit->hist_figure_id; order->amount_left = 1; order->amount_total = 1; + order->frequency = df::workquota_frequency_type::OneTime; world->manager_orders.all.push_back(order); } diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index efd90440240..1018999ec4b 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -663,6 +663,7 @@ class Tailor { order->amount_total = c; order->status.bits.validated = false; order->status.bits.active = false; + order->frequency = df::workquota_frequency_type::OneTime; order->id = world->manager_orders.manager_order_next_id++; world->manager_orders.all.push_back(order); From 1ba56e61ea46186c4c719498de58fd4bb5d5ab1d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:47:57 +0000 Subject: [PATCH 088/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d7928837480..0f9e05ac64a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d79288374802cc63b1507900030b32231ffd244b +Subproject commit 0f9e05ac64a2ac15714172361c86f28ae9ebf821 From 850b8ac51d8faa72a501ec06ed70293ecda498f9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:44:05 +0000 Subject: [PATCH 089/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 46da47f64ef..eb772d3b49f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 46da47f64ef1c8e7d49e257b36c59963770b6d53 +Subproject commit eb772d3b49f2019cf27ba5d6af5a3917dc285712 From c92ca2af021a752752bb3527660892aafc38df78 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 13 Mar 2026 09:10:26 -0500 Subject: [PATCH 090/202] Bump version to 53.11-r2 --- CMakeLists.txt | 2 +- docs/changelog.txt | 18 ++++++++++++++++++ library/xml | 2 +- scripts | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d6e55bd12f..93df1f3576c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index fdde2f1639d..bf2ca7d998b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -58,6 +58,24 @@ Template for new versions: ## New Features +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.11-r2 + +## New Tools + +## New Features + ## Fixes - `autoclothing`, `autoslab`, `tailor`: orders will no longer be created with a repetition frequency of ``NONE`` diff --git a/library/xml b/library/xml index 0f9e05ac64a..114321c4802 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 0f9e05ac64a2ac15714172361c86f28ae9ebf821 +Subproject commit 114321c4802fb7e16e9d1092c8f2c057422a1c82 diff --git a/scripts b/scripts index eb772d3b49f..70460d491c0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit eb772d3b49f2019cf27ba5d6af5a3917dc285712 +Subproject commit 70460d491c0fc3e3cb03bc0907bf2fda381eaf42 From c2e2cae4c5e64751a29780ba7ed654c646b0ba92 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:46:07 +0000 Subject: [PATCH 091/202] Auto-update submodules depends/dfhooks: main --- depends/dfhooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/dfhooks b/depends/dfhooks index 481dc1a12b1..4c48e25a2a3 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 481dc1a12b1264ef06ce95e331ef35cbfa0e6ace +Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 From 0b56dc8a8f1905965172b0a13b02ff3f0f690f56 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 22:26:23 -0500 Subject: [PATCH 092/202] replace hardcoded `hack` path with `getHackPath()` removes dependency on `hack` being in the DF CWD at runtime also use `std::filesystem::path` in `Textures` instead of `string` for pathnames --- library/include/modules/Textures.h | 2 +- library/lua/gui/textures.lua | 20 ++++++++++---------- library/modules/Textures.cpp | 6 +++--- plugins/dig.cpp | 2 +- plugins/lua/dig.lua | 2 +- plugins/lua/hotkeys.lua | 2 +- plugins/lua/suspendmanager.lua | 2 +- plugins/pathable.cpp | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/library/include/modules/Textures.h b/library/include/modules/Textures.h index b820c3332d7..c2038c20b33 100644 --- a/library/include/modules/Textures.h +++ b/library/include/modules/Textures.h @@ -32,7 +32,7 @@ DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface, bool reserved = fal * Load tileset from image file. * Return vector of handles to obtain valid texposes. */ -DFHACK_EXPORT std::vector loadTileset(const std::string& file, +DFHACK_EXPORT std::vector loadTileset(const std::filesystem::path file, int tile_px_w = TILE_WIDTH_PX, int tile_px_h = TILE_HEIGHT_PX, bool reserved = false); diff --git a/library/lua/gui/textures.lua b/library/lua/gui/textures.lua index 44d4f0de30f..b97a10e439a 100644 --- a/library/lua/gui/textures.lua +++ b/library/lua/gui/textures.lua @@ -8,16 +8,16 @@ local _ENV = mkmodule('gui.textures') -- Use these handles if you need to get dfhack standard textures. ---@type table local texpos_handles = { - green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true), - red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12, true), - icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12, true), - on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12, true), - control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12, true), - border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12, true), - border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12, true), - border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12, true), - border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12, true), - border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12, true), + green_pin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/green-pin.png', 8, 12, true), + red_pin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/red-pin.png', 8, 12, true), + icons = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/icons.png', 8, 12, true), + on_off = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/on-off.png', 8, 12, true), + control_panel = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/control-panel.png', 8, 12, true), + border_thin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-thin.png', 8, 12, true), + border_medium = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-medium.png', 8, 12, true), + border_bold = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-bold.png', 8, 12, true), + border_panel = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-panel.png', 8, 12, true), + border_window = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-window.png', 8, 12, true), } -- Get valid texpos for preloaded texture in tileset diff --git a/library/modules/Textures.cpp b/library/modules/Textures.cpp index 21642a9bbed..4a1e29aeab5 100644 --- a/library/modules/Textures.cpp +++ b/library/modules/Textures.cpp @@ -54,7 +54,7 @@ static ReservedRange reserved_range{}; static std::unordered_map g_handle_to_texpos; static std::unordered_map g_handle_to_reserved_texpos; static std::unordered_map g_handle_to_surface; -static std::unordered_map> g_tileset_to_handles; +static std::unordered_map> g_tileset_to_handles; static std::vector g_delayed_regs; static std::mutex g_adding_mutex; static std::atomic loading_state = false; @@ -195,14 +195,14 @@ TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) { return handle; } -std::vector Textures::loadTileset(const std::string& file, int tile_px_w, +std::vector Textures::loadTileset(const std::filesystem::path file, int tile_px_w, int tile_px_h, bool reserved) { if (g_tileset_to_handles.contains(file)) return g_tileset_to_handles[file]; if (!enabler) return std::vector{}; - SDL_Surface* surface = DFIMG_Load(file.c_str()); + SDL_Surface* surface = DFIMG_Load(file.string().c_str()); if (!surface) { ERR(textures).printerr("unable to load textures from '{}'\n", file); return std::vector{}; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 1be2a9a1169..5fbf23b251f 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -68,7 +68,7 @@ static bool is_painting_warm = false; static bool is_painting_damp = false; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - textures = Textures::loadTileset("hack/data/art/damp_dig_map.png", 32, 32, true); + textures = Textures::loadTileset(Core::getInstance().getHackPath() / "data" / "art" / "damp_dig_map.png", 32, 32, true); commands.push_back(PluginCommand( "digv", diff --git a/plugins/lua/dig.lua b/plugins/lua/dig.lua index babdb3b7ab5..192989d8a16 100644 --- a/plugins/lua/dig.lua +++ b/plugins/lua/dig.lua @@ -4,7 +4,7 @@ local gui = require('gui') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local toolbar_textures = dfhack.textures.loadTileset('hack/data/art/damp_dig_toolbar.png', 8, 12, true) +local toolbar_textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/damp_dig_toolbar.png', 8, 12, true) local main_if = df.global.game.main_interface local selection_rect = df.global.selection_rect diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua index d0cecb5ba01..5c04fba2df6 100644 --- a/plugins/lua/hotkeys.lua +++ b/plugins/lua/hotkeys.lua @@ -5,7 +5,7 @@ local helpdb = require('helpdb') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12, true) +local logo_textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/logo.png', 8, 12, true) local function get_command(cmdline) local first_word = cmdline:trim():split(' +')[1] diff --git a/plugins/lua/suspendmanager.lua b/plugins/lua/suspendmanager.lua index a7de42e5a07..86f0504ed39 100644 --- a/plugins/lua/suspendmanager.lua +++ b/plugins/lua/suspendmanager.lua @@ -155,7 +155,7 @@ end -- suspend overlay (formerly in unsuspend.lua) -local textures = dfhack.textures.loadTileset('hack/data/art/unsuspend.png', 32, 32, true) +local textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/unsuspend.png', 32, 32, true) local ok, buildingplan = pcall(require, 'plugins.buildingplan') if not ok then diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index d5983bb0197..3fd1eaaad1a 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -42,7 +42,7 @@ namespace DFHack { static std::vector textures; DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32, true); + textures = Textures::loadTileset(Core::getInstance().getHackPath() / "data" / "art" / "pathable.png", 32, 32, true); return CR_OK; } From 5af6036e0804b535bb60061c629bf40ce5b11147 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 17 Mar 2026 03:48:08 +0000 Subject: [PATCH 093/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 70460d491c0..56c934eb5d4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 70460d491c0fc3e3cb03bc0907bf2fda381eaf42 +Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c From 056dd45c1603da99e163bff64845a26370b3986e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 17 Mar 2026 15:09:36 -0500 Subject: [PATCH 094/202] fix more instances of `hack` hardcoding also in some places switch to using pathnames instead of strings for paths --- library/Core.cpp | 14 +++++--------- library/Process.cpp | 2 +- library/VersionInfoFactory.cpp | 5 +++-- library/include/VersionInfoFactory.h | 3 ++- library/lua/helpdb.lua | 4 ++-- library/modules/DFSteam.cpp | 6 ++++-- plugins/orders.cpp | 9 ++++----- 7 files changed, 21 insertions(+), 22 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index a5cd9966676..8979cb3eee0 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -124,7 +124,7 @@ namespace DFHack { static const std::filesystem::path getConfigDefaultsPath() { - return Filesystem::getInstallDir() / "hack" / "data" / "dfhack-config-defaults"; + return Core::getInstance().getHackPath() / "data" / "dfhack-config-defaults"; }; class MainThread { @@ -492,7 +492,7 @@ void Core::getScriptPaths(std::vector *dest) if (save.size()) dest->emplace_back(df_pref_path / "save" / save / "scripts"); } - dest->emplace_back(df_install_path / "hack" / "scripts"); + dest->emplace_back(getHackPath() / "scripts"); for (auto & path : script_paths[2]) dest->emplace_back(path); for (auto & path : script_paths[1]) @@ -1054,7 +1054,7 @@ void Core::fatal (std::string output, const char * title) std::filesystem::path Core::getHackPath() { - return p->getPath() / "hack"; + return Filesystem::get_initial_cwd() / "hack"; } df::viewscreen * Core::getTopViewscreen() { @@ -1099,16 +1099,12 @@ bool Core::InitMainThread() { } // find out what we are... - #ifdef LINUX_BUILD - const char * path = "hack/symbols.xml"; - #else - const char * path = "hack\\symbols.xml"; - #endif + std::filesystem::path symbols_path = getHackPath() / "symbols.xml"; auto local_vif = std::make_unique(); std::cerr << "Identifying DF version.\n"; try { - local_vif->loadFile(path); + local_vif->loadFile(symbols_path); } catch(Error::All & err) { diff --git a/library/Process.cpp b/library/Process.cpp index 3e3ba6db805..c45e5aea456 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -664,7 +664,7 @@ uint32_t Process::getTickCount() #endif /* WIN32 */ } -std::filesystem::path Process::getPath() +[[deprecated]] std::filesystem::path Process::getPath() { #if defined(WIN32) || !defined(_DARWIN) return Filesystem::get_initial_cwd(); diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index b4f6eefc782..94e2560e37d 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -29,6 +29,7 @@ distribution. #include #include #include +#include #include "VersionInfoFactory.h" #include "VersionInfo.h" @@ -226,9 +227,9 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) } // method // load the XML file with offsets -bool VersionInfoFactory::loadFile(string path_to_xml) +bool VersionInfoFactory::loadFile(std::filesystem::path path_to_xml) { - TiXmlDocument doc( path_to_xml.c_str() ); + TiXmlDocument doc( path_to_xml.string().c_str() ); std::cerr << "Loading " << path_to_xml << " ... "; //bool loadOkay = doc.LoadFile(); if (!doc.LoadFile()) diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index 060d622ecd9..92a5f94e103 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -26,6 +26,7 @@ distribution. #pragma once #include +#include #include "Export.h" @@ -38,7 +39,7 @@ namespace DFHack public: VersionInfoFactory(); ~VersionInfoFactory(); - bool loadFile( std::string path_to_xml); + bool loadFile( std::filesystem::path path_to_xml); bool isInErrorState() const {return error;}; std::shared_ptr getVersionInfoByMD5(std::string md5string) const; std::shared_ptr getVersionInfoByPETimestamp(uintptr_t timestamp) const; diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 0bd64d29ccb..5af55f5697c 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -5,8 +5,8 @@ local _ENV = mkmodule('helpdb') local argparse = require('argparse') -- paths -local RENDERED_PATH = 'hack/docs/docs/tools/' -local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' +local RENDERED_PATH = dfhack.getHackPath() .. '/docs/docs/tools/' +local TAG_DEFINITIONS = dfhack.getHackPath() .. '/docs/docs/Tags.txt' -- used when reading help text embedded in script sources local SCRIPT_DOC_BEGIN = '[====[' diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index 10074eef294..b7453a0db57 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -157,7 +157,8 @@ static bool launchDFHack(color_ostream& out) { si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - static LPCWSTR procname = L"hack/launchdf.exe"; + auto procpath = Core.getInstance().getHackPath() / "launchdf.exe"; + LPCWSTR procname = procpath.wstring().c_str(); static const char * env = "\0"; // note that the environment must be explicitly zeroed out and not NULL, @@ -208,7 +209,8 @@ static bool launchDFHack(color_ostream& out) { return false; } else if (pid == 0) { // child process - static const char * command = "hack/launchdf"; + auto procpath = Core.getInstance().getHackPath() / "launchdf.exe"; + char * command = procpath.string().c_str(); unsetenv("SteamAppId"); execl(command, command, NULL); _exit(EXIT_FAILURE); diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 4bdff21fe24..95932acb520 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -44,8 +44,8 @@ DFHACK_PLUGIN("orders"); REQUIRE_GLOBAL(world); -static const std::string ORDERS_DIR = "dfhack-config/orders"; -static const std::string ORDERS_LIBRARY_DIR = "hack/data/orders"; +static std::filesystem::path ORDERS_DIR = std::filesystem::path("dfhack-config") / "orders"; +static std::filesystem::path ORDERS_LIBRARY_DIR = Core::getInstance().getHackPath() / "data" / "orders"; static command_result orders_command(color_ostream & out, std::vector & parameters); @@ -506,7 +506,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri Filesystem::mkdir(ORDERS_DIR); - std::ofstream file(ORDERS_DIR + "/" + name + ".json"); + std::ofstream file(ORDERS_DIR / ( name + ".json")); file << orders << std::endl; @@ -924,8 +924,7 @@ static command_result orders_import_command(color_ostream & out, const std::stri return CR_WRONG_USAGE; } - const std::string filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) + - "/" + fname + ".json"); + auto filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) / (fname + ".json")); Json::Value orders; { From d959609a64e5d5c281dd956cc6e53add461f4a14 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 17 Mar 2026 15:15:02 -0500 Subject: [PATCH 095/202] fix typo --- library/modules/DFSteam.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index b7453a0db57..7e42414c729 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -209,7 +209,7 @@ static bool launchDFHack(color_ostream& out) { return false; } else if (pid == 0) { // child process - auto procpath = Core.getInstance().getHackPath() / "launchdf.exe"; + auto procpath = Core::getInstance().getHackPath() / "launchdf.exe"; char * command = procpath.string().c_str(); unsetenv("SteamAppId"); execl(command, command, NULL); From 8afcf1b5141333c41b95ada60d3dc9314b4d8f2b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 17 Mar 2026 15:22:52 -0500 Subject: [PATCH 096/202] dangle me not --- library/modules/DFSteam.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index 7e42414c729..400c489232a 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -157,14 +157,13 @@ static bool launchDFHack(color_ostream& out) { si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - auto procpath = Core.getInstance().getHackPath() / "launchdf.exe"; - LPCWSTR procname = procpath.wstring().c_str(); + auto procpath = Core::getInstance().getHackPath() / "launchdf.exe"; static const char * env = "\0"; // note that the environment must be explicitly zeroed out and not NULL, // otherwise the launched process will inherit this process's environment, // and the Steam API in the launchdf process will think it is in DF's context. - BOOL res = CreateProcessW(procname, + BOOL res = CreateProcessW(procpath.wstring().c_str(), NULL, NULL, NULL, FALSE, 0, (LPVOID)env, NULL, &si, &pi); return !!res; @@ -210,9 +209,9 @@ static bool launchDFHack(color_ostream& out) { } else if (pid == 0) { // child process auto procpath = Core::getInstance().getHackPath() / "launchdf.exe"; - char * command = procpath.string().c_str(); + auto command = procpath.string(); unsetenv("SteamAppId"); - execl(command, command, NULL); + execl(command.c_str(), command.c_str(), NULL); _exit(EXIT_FAILURE); } From 6efba41ebbc6af5e840d85b561ac3cd0a4bc854d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 24 Mar 2026 08:45:59 -0500 Subject: [PATCH 097/202] correct executable name on linux --- library/modules/DFSteam.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index 400c489232a..61e50a61b8e 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -208,7 +208,7 @@ static bool launchDFHack(color_ostream& out) { return false; } else if (pid == 0) { // child process - auto procpath = Core::getInstance().getHackPath() / "launchdf.exe"; + auto procpath = Core::getInstance().getHackPath() / "launchdf"; auto command = procpath.string(); unsetenv("SteamAppId"); execl(command.c_str(), command.c_str(), NULL); From 93c91007da87dc9433f733b9080b7541be6b6e73 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:44:01 +0000 Subject: [PATCH 098/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 56c934eb5d4..b672e4afc62 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c +Subproject commit b672e4afc6225d4e5e414beeb13f530b8cf66b39 From 97c7eecb272aa8a625aef81d741b6cc8f1e3681c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 14 Mar 2026 08:16:18 -0500 Subject: [PATCH 099/202] split dfhack.dll out of dfhooks_dfhack.dll removes disparallelism with linux, and makes dealing with cross-folder hooking mechanics easier --- library/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b405469b0e8..3982a1a37c8 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -318,8 +318,6 @@ endif() # Compilation -add_definitions(-DBUILD_DFHACK_LIB) - if(UNIX) if(CONSOLE_NO_CATCH) add_definitions(-DCONSOLE_NO_CATCH) @@ -373,6 +371,7 @@ if(EXISTS ${dfhack_SOURCE_DIR}/.git/index AND EXISTS ${dfhack_SOURCE_DIR}/.git/m endif() add_library(dfhack SHARED ${PROJECT_SOURCES}) +target_compile_definitions(dfhack PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) get_target_property(xlsxio_INCLUDES xlsxio_read_STATIC INTERFACE_INCLUDE_DIRECTORIES) @@ -381,6 +380,7 @@ add_dependencies(dfhack generate_proto_core) add_dependencies(dfhack generate_headers) add_library(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS} ${CONSOLE_SOURCES}) +target_compile_definitions(dfhack-client PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) add_dependencies(dfhack-client dfhack) @@ -391,16 +391,16 @@ add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "dfhooks_dfhack" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "-include Export.h" ) - add_library(dfhooks_dfhack SHARED Hooks.cpp) - target_link_libraries(dfhooks_dfhack dfhack ${FMTLIB}) endif() +add_library(dfhooks_dfhack SHARED Hooks.cpp) +target_link_libraries(dfhooks_dfhack PUBLIC dfhack ${FMTLIB}) + # effectively disables debug builds... set_target_properties(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) @@ -450,10 +450,11 @@ if(UNIX) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run DESTINATION .) endif() - install(TARGETS dfhooks_dfhack +endif() + +install(TARGETS dfhooks_dfhack LIBRARY DESTINATION . RUNTIME DESTINATION .) -endif() # install the main lib install(TARGETS dfhack From a3a26bda97160b1809afa213070533cb9ceb943c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 14 Mar 2026 11:07:24 -0500 Subject: [PATCH 100/202] update dfhooks to resteam branch --- depends/dfhooks | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/dfhooks b/depends/dfhooks index 4c48e25a2a3..2d84a5826c5 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 +Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 diff --git a/scripts b/scripts index b672e4afc62..56c934eb5d4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b672e4afc6225d4e5e414beeb13f530b8cf66b39 +Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c From e2bbfc9fe8a987035622503b9d8c476b8c50d164 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 14:46:29 -0500 Subject: [PATCH 101/202] Further work on Steam compatibility 1. Adopt changes to dfhooks 2. Install dfhooks_dfhack the `hack` folder, and generate and install an appropriate `dfhooks_dfhack.ini` into the DF folder 3. Adjust RPATH (on Linux) so dfhooks_dfhack.dll can find dfhack.dll 4. Use dfhooks preinit to get the hack base path. Use this path when initializing Core 5. in `getHackPath()`, use the collected hack base path instead of hardcoding `./hack`. 6. Fix at one instance where `hack` was hardcoded outside of `getHackPath` (note: there are others this commit does not fix) 7. Update VersionInfoFactory to use a fs `path` instead of a `string` as its argument --- CMakeLists.txt | 13 +++++-------- library/CMakeLists.txt | 12 +++++++++--- library/Core.cpp | 5 +++-- library/Hooks.cpp | 10 +++++++++- library/include/Core.h | 4 +++- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93df1f3576c..5694d4bd9f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,13 +226,10 @@ set(DFHACK_DATA_DESTINATION hack) ## where to install things (after the build is done, classic 'make install' or package structure) # the dfhack libraries will be installed here: -if(UNIX) - # put the lib into DF/hack - set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) -else() - # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... - set(DFHACK_LIBRARY_DESTINATION .) -endif() + +# put the lib into DF/hack +# windows will find it because dfhooks will `AddDllDirectory` the hack folder at runtime +set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) # external tools will be installed here: set(DFHACK_BINARY_DESTINATION .) @@ -267,7 +264,7 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") endif() string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_INSTALL_RPATH ${DFHACK_LIBRARY_DESTINATION}) + set(CMAKE_INSTALL_RPATH "$ORIGIN/${DFHACK_LIBRARY_DESTINATION}") elseif(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 3982a1a37c8..f91afef2e2a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -131,7 +131,6 @@ endif() set(MAIN_SOURCES_WINDOWS ${CONSOLE_SOURCES} - Hooks.cpp ) if(WIN32) @@ -453,8 +452,9 @@ if(UNIX) endif() install(TARGETS dfhooks_dfhack - LIBRARY DESTINATION . - RUNTIME DESTINATION .) + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + # install the main lib install(TARGETS dfhack @@ -465,6 +465,12 @@ install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + CONTENT "${DFHACK_DATA_DESTINATION}/$") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + DESTINATION .) + endif(BUILD_LIBRARY) # install the offset file diff --git a/library/Core.cpp b/library/Core.cpp index 8979cb3eee0..9fe3cf9bc61 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1054,16 +1054,17 @@ void Core::fatal (std::string output, const char * title) std::filesystem::path Core::getHackPath() { - return Filesystem::get_initial_cwd() / "hack"; + return hack_path; } df::viewscreen * Core::getTopViewscreen() { return getInstance().top_viewscreen; } -bool Core::InitMainThread() { +bool Core::InitMainThread(std::filesystem::path path) { // this hook is always called from DF's main (render) thread, so capture this thread id df_render_thread = std::this_thread::get_id(); + hack_path = path; Filesystem::init(); diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 0f957fea063..31d711056bd 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -7,6 +7,14 @@ static bool disabled = false; DFhackCExport const int32_t dfhooks_priority = 100; +static std::filesystem::path basepath{"./hack"}; + +// called by the chainloader before the main thread is initialized and before any other hooks are called. +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath) +{ + basepath = dllpath.parent_path(); +} + // called from the main thread before the simulation thread is started // and the main event loop is initiated DFhackCExport void dfhooks_init() { @@ -17,7 +25,7 @@ DFhackCExport void dfhooks_init() { } // we need to init DF globals before we can check the commandline - if (!DFHack::Core::getInstance().InitMainThread() || !df::global::game) { + if (!DFHack::Core::getInstance().InitMainThread(basepath) || !df::global::game) { // we don't set disabled to true here so symbol generation can work return; } diff --git a/library/include/Core.h b/library/include/Core.h index 1f3cea5837f..9ccf0ca8710 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -268,7 +268,7 @@ namespace DFHack struct Private; std::unique_ptr d; - bool InitMainThread(); + bool InitMainThread(std::filesystem::path path); bool InitSimulationThread(); int Update (void); int Shutdown (void); @@ -353,6 +353,8 @@ namespace DFHack uint32_t unpaused_ms; // reset to 0 on map load + std::filesystem::path hack_path; + friend class CoreService; friend class ServerConnection; friend class CoreSuspender; From 8e20fbd4eb3ffe9bbaacc4d7429fcc8ebdc4c40d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 15:23:06 -0500 Subject: [PATCH 102/202] canonicalize hack path at init --- library/Hooks.cpp | 2 +- library/include/Hooks.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 31d711056bd..3f5ca36c329 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -25,7 +25,7 @@ DFhackCExport void dfhooks_init() { } // we need to init DF globals before we can check the commandline - if (!DFHack::Core::getInstance().InitMainThread(basepath) || !df::global::game) { + if (!DFHack::Core::getInstance().InitMainThread(std::filesystem::canonical(basepath)) || !df::global::game) { // we don't set disabled to true here so symbol generation can work return; } diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 6945de2ea68..5f49d8dabaa 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -26,6 +26,7 @@ distribution. union SDL_Event; +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath); DFhackCExport void dfhooks_init(); DFhackCExport void dfhooks_shutdown(); DFhackCExport void dfhooks_update(); From a546399f150bb352fb57f39adbb0dcc44f3a6b31 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 15:54:22 -0500 Subject: [PATCH 103/202] test: use RPATH `$ORIGIN` on Linux --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5694d4bd9f3..9be91932106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,7 +264,7 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") endif() string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_INSTALL_RPATH "$ORIGIN/${DFHACK_LIBRARY_DESTINATION}") + set(CMAKE_INSTALL_RPATH "$ORIGIN") elseif(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") From 02f64aa48c7712f54e03e8e575f671836680c0be Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 21:34:16 -0500 Subject: [PATCH 104/202] add path autodetection --- library/Hooks.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 3f5ca36c329..9865ca3276c 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -3,11 +3,36 @@ #include "df/gamest.h" +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#else +# include +#endif + static bool disabled = false; DFhackCExport const int32_t dfhooks_priority = 100; -static std::filesystem::path basepath{"./hack"}; +static std::filesystem::path getModulePath() +{ +#ifdef _WIN32 + HMODULE module = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)getModulePath, &module); + if (!module) return std::filesystem::path(); // should never happen, but just in case, return an empty path instead of crashing + + wchar_t path[MAX_PATH]; + GetModuleFileNameW(module, path, MAX_PATH); + return std::filesystem::path(path); +#else + DL_info info; + dladdr(getModulePath, &info); + return std::filesystem::path(info.dli_fname); +#endif +} + +static std::filesystem::path basepath{getModulePath()}; // called by the chainloader before the main thread is initialized and before any other hooks are called. DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath) From 0413b712ca06206943e0013a84363b07927b9cff Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 21:34:38 -0500 Subject: [PATCH 105/202] fix lua default path --- library/Core.cpp | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 9fe3cf9bc61..d7fc3f0d7bb 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1092,6 +1092,7 @@ bool Core::InitMainThread(std::filesystem::path path) { std::cerr << "Build url: " << Version::dfhack_run_url() << std::endl; } std::cerr << "Starting with working directory: " << Filesystem::getcwd() << std::endl; + std::cerr << "Hack path: " << getHackPath() << std::endl; std::cerr << "Binding to SDL.\n"; if (!DFSDL::init(con)) { @@ -1233,9 +1234,9 @@ bool Core::InitSimulationThread() { // the update hook is only called from the simulation thread, so capture this thread id df_simulation_thread = std::this_thread::get_id(); - if(started) + if (started) return true; - if(errorstate) + if (errorstate) return false; // Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown @@ -1277,20 +1278,20 @@ bool Core::InitSimulationThread() std::cout << "Console disabled.\n"; } } - else if(con.init(false)) + else if (con.init(false)) std::cerr << "Console is running.\n"; else std::cerr << "Console has failed to initialize!\n"; -/* - // dump offsets to a file - std::ofstream dump("offsets.log"); - if(!dump.fail()) - { - //dump << vinfo->PrintOffsets(); - dump.close(); - } - */ - // initialize data defs + /* + // dump offsets to a file + std::ofstream dump("offsets.log"); + if(!dump.fail()) + { + //dump << vinfo->PrintOffsets(); + dump.close(); + } + */ + // initialize data defs virtual_identity::Init(this); // create config directory if it doesn't already exist @@ -1307,7 +1308,8 @@ bool Core::InitSimulationThread() else { // ensure all config file directories exist before we start copying files - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over files if (!entry.second) continue; @@ -1317,19 +1319,22 @@ bool Core::InitSimulationThread() } // copy files from the default tree that don't already exist in the config tree - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over directories if (entry.second) continue; std::filesystem::path filename = entry.first; - if (!config_files.contains(filename)) { + if (!config_files.contains(filename)) + { std::filesystem::path src_file = getConfigDefaultsPath() / filename; if (!Filesystem::isfile(src_file)) continue; std::filesystem::path dest_file = getConfigPath() / filename; std::ifstream src(src_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary); - if (!src.good() || !dest.good()) { + if (!src.good() || !dest.good()) + { con.printerr("Copy failed: '{}'\n", filename); continue; } @@ -1340,6 +1345,17 @@ bool Core::InitSimulationThread() } } + // set lua default path if not already set + if (std::getenv("DFHACK_LUA_PATH") == nullptr) + { + std::filesystem::path lua_path = getHackPath() / "lua" / "?.lua"; +#ifdef WIN32 + _putenv_s("DFHACK_LUA_PATH", lua_path.string().c_str()); +#else + setenv("DFHACK_LUA_PATH", lua_path.string().c_str(), 1); +#endif + } + loadScriptPaths(con); // initialize common lua context From 0b1ea135a6bdecc8ab4f9735b3ddfda8a6b904f7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 22:51:37 -0500 Subject: [PATCH 106/202] `script`: try multiple locatons specifically, try in the hack path, in the hack path parent directory, and in the CWD, in that order --- library/Core.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index d7fc3f0d7bb..2deba390ff4 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -859,7 +859,22 @@ bool Core::loadScriptFile(color_ostream &out, std::filesystem::path fname, bool INFO(script,out) << "Running script: " << fname << std::endl; std::cerr << "Running script: " << fname << std::endl; } - std::ifstream script{ fname.c_str() }; + + auto pathlist = {getHackPath(), getHackPath().parent_path(), std::filesystem::current_path()}; + + std::filesystem::path path; + + for (auto& p : pathlist) + { + auto candidate = fname.is_relative() ? p / fname : fname; + if (std::filesystem::exists(candidate)) + { + path = candidate; + break; + } + } + + std::ifstream script{ path }; if ( !script ) { if(!silent) From 0e25a2fd28d93988c46e619244b635eba1e8cd60 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 23:04:09 -0500 Subject: [PATCH 107/202] fix typo --- library/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 9865ca3276c..d37f9fad082 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -26,7 +26,7 @@ static std::filesystem::path getModulePath() GetModuleFileNameW(module, path, MAX_PATH); return std::filesystem::path(path); #else - DL_info info; + Dl_info info; dladdr(getModulePath, &info); return std::filesystem::path(info.dli_fname); #endif From 56462222b00ca499d4881b9a03652a7799b8d908 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 23:10:24 -0500 Subject: [PATCH 108/202] gcc wants an explicit cast here --- library/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index d37f9fad082..42e9f859af4 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -27,7 +27,7 @@ static std::filesystem::path getModulePath() return std::filesystem::path(path); #else Dl_info info; - dladdr(getModulePath, &info); + dladdr((const void*)getModulePath, &info); return std::filesystem::path(info.dli_fname); #endif } From 1ec0bc211a9ad372c8149a5114ca0f7aee70ec07 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 11:47:07 -0500 Subject: [PATCH 109/202] Make stonesense loadable in a relocated installation --- library/PlugLoad.cpp | 3 ++- plugins/stonesense | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/PlugLoad.cpp b/library/PlugLoad.cpp index 336e7f50e71..fa58d39514f 100644 --- a/library/PlugLoad.cpp +++ b/library/PlugLoad.cpp @@ -15,10 +15,11 @@ #ifdef WIN32 #define NOMINMAX #include +#include #define global_search_handle() GetModuleHandle(nullptr) #define get_function_address(plugin, function) GetProcAddress((HMODULE)plugin, function) #define clear_error() -#define load_library(fn) LoadLibraryW(fn.c_str()) +#define load_library(fn) LoadLibraryExW(fn.wstring().c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); #define close_library(handle) (!(FreeLibrary((HMODULE)handle))) #else #include diff --git a/plugins/stonesense b/plugins/stonesense index 4760027eee7..581f8ff7317 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4760027eee745d8c35cca843a2fcc46c21be326a +Subproject commit 581f8ff7317fd15723fb31632650746afede7b6c From 2fee311c0c1f2142bb9c07cf1e21a73599b61290 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 13:57:23 -0500 Subject: [PATCH 110/202] incorporate stonesense updates --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 581f8ff7317..64cc02b1232 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 581f8ff7317fd15723fb31632650746afede7b6c +Subproject commit 64cc02b12321abf9b99e41c4e5f931f71bc81a45 From 8efc6726301f084c70e66b3d702945257f04150d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 14:48:54 -0500 Subject: [PATCH 111/202] sync stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 64cc02b1232..b1b676bcf1d 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 64cc02b12321abf9b99e41c4e5f931f71bc81a45 +Subproject commit b1b676bcf1dc810e7210aceef89c1626069136f5 From 1ba2130b4c9a94930989ea82094f047e87efe054 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:59:50 +0000 Subject: [PATCH 112/202] Auto-update submodules scripts: master plugins/stonesense: master --- plugins/stonesense | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stonesense b/plugins/stonesense index 4760027eee7..9dfa9d731f6 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4760027eee745d8c35cca843a2fcc46c21be326a +Subproject commit 9dfa9d731f6b84b6deaba42364168b7374157e6e diff --git a/scripts b/scripts index b672e4afc62..af457f9b5c8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b672e4afc6225d4e5e414beeb13f530b8cf66b39 +Subproject commit af457f9b5c86fc041a064ef5bdfbcab38f38a50f From 2d6d34ef1c7b47cd8a2915e700fe0f10c73cc79b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 27 Mar 2026 15:26:00 -0500 Subject: [PATCH 113/202] on linux, use proper rpath for plugins Co-Authored-By: Christian Doczkal <20443222+chdoc@users.noreply.github.com> --- plugins/Plugins.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 82439f69a5b..192662bccc4 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -141,6 +141,10 @@ macro(dfhack_plugin) set_target_properties(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dll) endif() + if (UNIX) + set_target_properties(${PLUGIN_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/..") + endif() + install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${DFHACK_PLUGIN_DESTINATION} RUNTIME DESTINATION ${DFHACK_PLUGIN_DESTINATION}) From 4166d211fb21db8f16c40c66e859269d4640912b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 1 Apr 2026 11:36:15 -0500 Subject: [PATCH 114/202] changes to launchdf to handle relocatable install instead of assuming colocation, this version will (when both DF and DFHack are installed in Steam) automatically inject DFHack into DF environments where not both apps are installed in Steam are (hopefully) unaffected --- package/launchdf.cpp | 74 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 5a850c6cf94..c7f9b58c894 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -10,6 +10,8 @@ #include "steam_api.h" #include +#include +#include #define xstr(s) str(s) #define str(s) #s @@ -245,14 +247,19 @@ int main(int argc, char* argv[]) { } bool nowait = false; + bool installmode = false; #ifdef WIN32 std::wstring cmdline(lpCmdLine); if (cmdline.find(L"--nowait") != std::wstring::npos) nowait = true; + if (cmdline.find(L"--install") != std::wstring::npos) + installmode = true; #else for (int idx = 0; idx < argc; ++idx) { if (strcmp(argv[idx], "--nowait") == 0) nowait = true; + if (strcmp(argv[idx], "--install") == 0) + installmode = true; } #endif @@ -294,22 +301,75 @@ int main(int argc, char* argv[]) { char buf[2048] = ""; int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); - std::string dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + std::filesystem::path dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); - std::string df_install_folder = (b2 != -1) ? std::string(buf) : ""; + std::filesystem::path df_install_folder = (b2 != -1) ? std::string(buf) : ""; + if (df_install_folder != dfhack_install_folder) + { +#ifdef WIN32 + constexpr auto dfhooks_dll_name = "dfhooks.dll"; + constexpr auto dfhook_dfhack_dll_name = "dfhooks_dfhack.dll"; +#else + constexpr auto dfhooks_dll_name = "libdfhooks.so"; + constexpr auto dfhook_dfhack_dll_name = "libdfhooks_dfhack.so"; +#endif + // DF and DFHack are not co-installed (modern case) + // inject dfhooks.dll and dfhooks_dfhack.ini into DF install folder + std::filesystem::path dfhooks_dll_src = dfhack_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_dll_dst = df_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_ini_dst = df_install_folder / "dfhooks_dfhack.ini"; + std::filesystem::path dfhooks_dfhack_dll_src = dfhack_install_folder / "hack" / dfhook_dfhack_dll_name; - if (df_install_folder != dfhack_install_folder) { - // DF and DFHack are not installed in the same library + std::error_code ec; + + std::filesystem::copy(dfhooks_dll_src, dfhooks_dll_dst, std::filesystem::copy_options::update_existing, ec); + if (!ec) + { + std::string indirection; + if (std::filesystem::exists(dfhooks_ini_dst)) + { + std::ifstream ini(dfhooks_ini_dst); + std::getline(ini, indirection); + } + + if (indirection != dfhooks_dfhack_dll_src.string()) + { + std::ofstream ini(dfhooks_ini_dst); + ini << dfhooks_dfhack_dll_src.string() << std::endl; + } + } + else + { #ifdef WIN32 - MessageBoxW(NULL, L"DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting.", NULL, 0); + std::wstring message{ + L"Failed to inject DFHack into Dwarf Fortress\n\n" + L"Details:\n" + std::filesystem::relative(dfhooks_dll_src).wstring() + + L" -> " + std::filesystem::relative(dfhooks_dll_dst).wstring() + + L"\n\nError code: " + std::to_wstring(ec.value()) + + L"\nError message: " + std::filesystem::relative(ec.message()).wstring() + }; + + MessageBoxW(NULL, message.c_str(), NULL, 0); #else - notify("DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting."); + std::string message{ + "Failed to inject DFHack into Dwarf Fortress\n\n" + "Details:\n" + std::filesystem::relative(dfhooks_dll_src).string() + + " -> " + std::filesystem::relative(dfhooks_dll_dst).string() + + "\n\nError code: " + std::to_string(ec.value()) + + "\nError message: " + std::filesystem::relative(ec.message()).string() + }; + + notify(message.c_str()); #endif - exit(1); + exit(1); + } } + if (installmode) + exit(0); + if (!wrap_launch(launch_via_steam)) exit(1); From e718a021e349db0f55f3b79bd31d990888361aad Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 12:33:46 -0500 Subject: [PATCH 115/202] add code to detect/clean legacy install on windows, will prompt user on linux, will advise user on how to manually clean also removed install mode - can't seem to make work with steam API rn --- package/launchdf.cpp | 106 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index c7f9b58c894..03085ed6d54 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -236,6 +236,80 @@ bool waitForDF(bool nowait) { #endif +constexpr const char* old_filelist[] { + "hack", + "stonesense", +#ifdef WIN32 + "binpatch.exe", + "dfhack-run.exe", + "allegro-5.2.dll", + "allegro_color-5.2.dll", + "allegro_font-5.2.dll", + "allegro_image-5.2.dll", + "allegro_primitives-5.2.dll", + "allegro_ttf-5.2.dll", + "allegro-5.2.dll", + "dfhack-client.dll", + "dfhooks_dfhack.dll", + "lua53.dll", + "protobuf-lite.dll" +#else + "binpatch", + "dfhack-run", + "liballegro-5.2.so", + "liballegro_color-5.2.so", + "liballegro_font-5.2.so", + "liballegro_image-5.2.so", + "liballegro_primitives-5.2.so", + "liballegro_ttf-5.2.so", + "liballegro-5.2.so", + "libdfhack-client.so", + "libdfhooks_dfhack.so", + "liblua53.so", + "libprotobuf-lite.so" +#endif +}; + +bool check_for_old_install(std::filesystem::path df_path) +{ + for (auto file : old_filelist) + { + std::filesystem::path p = df_path / file; + bool exists = std::filesystem::exists(p); +// std::wstring message = L"Checking for legacy files:\n" + p.wstring() + L": " + (exists ? L"found" : L"not found"); +// MessageBoxW(NULL, message.c_str(), L"Checking for legacy files", 0); + if (exists) + return true; + } + return false; +} + +void remove_old_install(std::filesystem::path df_path) +{ + std::string message{ + "Removing legacy files:" + }; + + for (auto file : old_filelist) + { + std::error_code ec; + + std::filesystem::path p = df_path / file; + + if (std::filesystem::is_directory(p)) + std::filesystem::remove_all(p, ec); + else if (std::filesystem::is_regular_file(p)) + std::filesystem::remove(p, ec); + else + continue; + + message += "\n" + p.string() + ": " + (ec ? "failed to remove - " + ec.message() : "removed successfully"); + } +#ifdef WIN32 + MessageBoxW(NULL, std::wstring(message.begin(), message.end()).c_str(), L"Legacy Install Cleanup", 0); +#endif +} + #ifdef WIN32 int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { #else @@ -247,19 +321,14 @@ int main(int argc, char* argv[]) { } bool nowait = false; - bool installmode = false; #ifdef WIN32 std::wstring cmdline(lpCmdLine); if (cmdline.find(L"--nowait") != std::wstring::npos) nowait = true; - if (cmdline.find(L"--install") != std::wstring::npos) - installmode = true; #else for (int idx = 0; idx < argc; ++idx) { if (strcmp(argv[idx], "--nowait") == 0) nowait = true; - if (strcmp(argv[idx], "--install") == 0) - installmode = true; } #endif @@ -365,10 +434,30 @@ int main(int argc, char* argv[]) { #endif exit(1); } - } + bool dirty = check_for_old_install(df_install_folder); + if (dirty) + { +#ifdef WIN32 + int ok = MessageBoxW(NULL, L"A legacy install of DFHack has been detected in the Dwarf Fortress folder. This likely means that you have installed DFHack with the old Steam client (or manually). This legacy installation will almost certainly interfere with using DFHack. Do you want to remove the old files now? (recommended)", L"Legacy DFHack Install Detected", MB_OKCANCEL); - if (installmode) - exit(0); + if (ok == IDOK) + remove_old_install(df_install_folder); +#else + int response = 0; + std::string filelist; + for (auto file : old_filelist) + if (std::filesystem::exists(df_install_folder / file)) + filelist += (filelist.empty() ? "" : std::string(",")) + file; + + std::string message{ + "A legacy install of DFHack has been detected in the Dwarf Fortress directory.This likely means that you have installed DFHack with the old Steam client (or manually).This installation will almost certainly interfere with using DFHack. \n\n" + "To remove these files, run the following command: rm -r " + df_install_folder.string() + "/{ " + filelist + "}\n\n" + }; + + notify(message.c_str()); +#endif + } + } if (!wrap_launch(launch_via_steam)) exit(1); @@ -389,6 +478,5 @@ int main(int argc, char* argv[]) { usleep(1000000); #endif } - exit(0); } From 5ecdca159200723f9455d10cf62ce93125db0633 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:34:44 -0500 Subject: [PATCH 116/202] changes to hopefully make injection work in wine changed injection/legacy check code to before instead of after wine-specific launch code also made path detection hopefully more robust (and less wet) --- package/launchdf.cpp | 79 ++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 03085ed6d54..a2e3cbaf3e5 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #define xstr(s) str(s) #define str(s) #s @@ -275,10 +276,7 @@ bool check_for_old_install(std::filesystem::path df_path) for (auto file : old_filelist) { std::filesystem::path p = df_path / file; - bool exists = std::filesystem::exists(p); -// std::wstring message = L"Checking for legacy files:\n" + p.wstring() + L": " + (exists ? L"found" : L"not found"); -// MessageBoxW(NULL, message.c_str(), L"Checking for legacy files", 0); - if (exists) + if (std::filesystem::exists(p)) return true; } return false; @@ -341,42 +339,38 @@ int main(int argc, char* argv[]) { } #ifdef WIN32 - if (is_running_on_wine()) { - // attempt launch via steam client - LPCWSTR err = launch_via_steam_posix(); - - if (err != NULL) - // steam client launch failed, attempt fallback launch - err = launch_direct(); - - if (err != NULL) - { - MessageBoxW(NULL, err, NULL, 0); - exit(1); - } - exit(0); - } + bool wine_detected = is_running_on_wine(); +#else + bool wine_detected = false; #endif - // steam detected and not running in wine + bool df_detected = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); - if (!SteamApps()->BIsAppInstalled(DF_STEAM_APPID)) { + if (!df_detected) { // Steam DF is not installed. Assume DF is installed in same directory as DFHack and do a fallback launch exit(wrap_launch(launch_direct) ? 0 : 1); } - // obtain DF app path - - char buf[2048] = ""; + // obtain DF and DFHack app paths - int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); - std::filesystem::path dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { + char buf[2048] = ""; + int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, 2048); + if (bytes == -1) + return std::nullopt; + // steam API counts the null terminator in the byte count returned + if (buf[bytes] == '\0') bytes--; + return std::string(buf, bytes); + }; - int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); - std::filesystem::path df_install_folder = (b2 != -1) ? std::string(buf) : ""; + auto opt_dfhack_install_folder = get_app_path_from_steam(DFHACK_STEAM_APPID); + auto opt_df_install_folder = get_app_path_from_steam(DF_STEAM_APPID); - if (df_install_folder != dfhack_install_folder) + if (opt_dfhack_install_folder && opt_df_install_folder && (*opt_df_install_folder != *opt_dfhack_install_folder)) { + auto& dfhack_install_folder = *opt_dfhack_install_folder; + auto& df_install_folder = *opt_df_install_folder; + #ifdef WIN32 constexpr auto dfhooks_dll_name = "dfhooks.dll"; constexpr auto dfhook_dfhack_dll_name = "dfhooks_dfhack.dll"; @@ -414,8 +408,8 @@ int main(int argc, char* argv[]) { #ifdef WIN32 std::wstring message{ L"Failed to inject DFHack into Dwarf Fortress\n\n" - L"Details:\n" + std::filesystem::relative(dfhooks_dll_src).wstring() + - L" -> " + std::filesystem::relative(dfhooks_dll_dst).wstring() + + L"Details:\n" + dfhooks_dll_src.wstring() + + L" -> " + dfhooks_dll_dst.wstring() + L"\n\nError code: " + std::to_wstring(ec.value()) + L"\nError message: " + std::filesystem::relative(ec.message()).wstring() }; @@ -424,8 +418,8 @@ int main(int argc, char* argv[]) { #else std::string message{ "Failed to inject DFHack into Dwarf Fortress\n\n" - "Details:\n" + std::filesystem::relative(dfhooks_dll_src).string() + - " -> " + std::filesystem::relative(dfhooks_dll_dst).string() + + "Details:\n" + dfhooks_dll_src.string() + + " -> " + dfhooks_dll_dst.string() + "\n\nError code: " + std::to_string(ec.value()) + "\nError message: " + std::filesystem::relative(ec.message()).string() }; @@ -459,6 +453,25 @@ int main(int argc, char* argv[]) { } } +#ifdef WIN32 + if (wine_detected) + { + // attempt launch via steam client + LPCWSTR err = launch_via_steam_posix(); + + if (err != NULL) + // steam client launch failed, attempt fallback launch + err = launch_direct(); + + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + exit(0); + } +#endif + if (!wrap_launch(launch_via_steam)) exit(1); From 7afeddc100614dec6adfab8048c400260f4ccc88 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:36:27 -0500 Subject: [PATCH 117/202] slightly safer return check for `GetAppInstallDir` --- package/launchdf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index a2e3cbaf3e5..96875e3b685 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -356,7 +356,7 @@ int main(int argc, char* argv[]) { auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { char buf[2048] = ""; int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, 2048); - if (bytes == -1) + if (bytes <= 0) return std::nullopt; // steam API counts the null terminator in the byte count returned if (buf[bytes] == '\0') bytes--; From 3483d778db665fabb764264375f03ba5763d0fba Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:41:13 -0500 Subject: [PATCH 118/202] move toward release candidate status --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9be91932106..083ea5bc38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r2") -set(DFHACK_PRERELEASE FALSE) +set(DFHACK_RELEASE "r3rc1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) From cef0742f571de6bd66d711db56c52a15488cc05c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 14:02:15 -0500 Subject: [PATCH 119/202] make gcc happy --- package/launchdf.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 96875e3b685..5ffc939e15b 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -340,8 +340,6 @@ int main(int argc, char* argv[]) { #ifdef WIN32 bool wine_detected = is_running_on_wine(); -#else - bool wine_detected = false; #endif bool df_detected = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); @@ -437,7 +435,6 @@ int main(int argc, char* argv[]) { if (ok == IDOK) remove_old_install(df_install_folder); #else - int response = 0; std::string filelist; for (auto file : old_filelist) if (std::filesystem::exists(df_install_folder / file)) From 6a0f9c89d5a928b930d370aaf5c2fa90fb732aa7 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:59:45 +0000 Subject: [PATCH 120/202] Auto-update submodules scripts: master plugins/stonesense: master depends/dfhooks: main --- depends/dfhooks | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/dfhooks b/depends/dfhooks index 2d84a5826c5..4c48e25a2a3 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 +Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 diff --git a/plugins/stonesense b/plugins/stonesense index b1b676bcf1d..9dfa9d731f6 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit b1b676bcf1dc810e7210aceef89c1626069136f5 +Subproject commit 9dfa9d731f6b84b6deaba42364168b7374157e6e diff --git a/scripts b/scripts index 56c934eb5d4..af457f9b5c8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c +Subproject commit af457f9b5c86fc041a064ef5bdfbcab38f38a50f From e88c8c16d747fa5c413be8f008d43b3e3de79ff4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 4 Apr 2026 12:24:36 -0500 Subject: [PATCH 121/202] revert inadvertent dfhooks submodule change in ff1b068 --- depends/dfhooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/dfhooks b/depends/dfhooks index 4c48e25a2a3..2d84a5826c5 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 +Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 From 95e89e9156ed9a1fb16233bfe6c7a0efc1e4e191 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 4 Apr 2026 12:21:20 -0500 Subject: [PATCH 122/202] use utf8 in windows console been wanting to do this for a very long time ref #1474 --- docs/changelog.txt | 1 + library/Console-windows.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index bf2ca7d998b..36aba6f46ea 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,7 @@ Template for new versions: ## New Features ## Fixes +- Core: Windows console will always use UTF-8 regardless of system code page settings ## Misc Improvements diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 12a3e0c2eb0..058cedebe09 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -474,6 +474,10 @@ bool Console::init(bool) HMENU hm = GetSystemMenu(d->ConsoleWindow,false); DeleteMenu(hm, SC_CLOSE, MF_BYCOMMAND); + // force console code pages to utf-8 + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); d->default_attributes = coninfo.wAttributes; From 731662185e022b8abbbf7d20b3beb4ab3bc01cc6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 6 Apr 2026 08:17:51 -0500 Subject: [PATCH 123/202] Update changelog.txt --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index bf2ca7d998b..8feb15325f2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,8 +59,10 @@ Template for new versions: ## New Features ## Fixes +- Steam launcher: Switch to injection strategy, allowing Dwarf Fortress and DFHack to be installed in disparate locations ## Misc Improvements +- Make DFHack relocatable so that it doesn't depend on being fully co-installed with Dwarf Fortress ## Documentation From 35735053c3ae1b19e7c2a9f1856700ab52e131c0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:55:10 +0000 Subject: [PATCH 124/202] Auto-update submodules depends/dfhooks: main --- depends/dfhooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/dfhooks b/depends/dfhooks index 2d84a5826c5..4c48e25a2a3 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 +Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 From 688200286b9bb7f72f7b6454fc7fe506a53b706d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:01:17 +0000 Subject: [PATCH 125/202] Auto-update submodules depends/dfhooks: main --- depends/dfhooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/dfhooks b/depends/dfhooks index 4c48e25a2a3..8a578206fb9 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 +Subproject commit 8a578206fb9b1dd32b04c8c7c35217e2b83e369e From 1680442d630365d9f3c99effd9f26ae46d6412e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:17:08 +0000 Subject: [PATCH 126/202] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.37.0 → 0.37.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.37.0...0.37.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90b37493a7c..b991bfbb742 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.0 + rev: 0.37.1 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From bafc832f218e706bc33bb986f783ab303fe685a1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 01:16:36 -0500 Subject: [PATCH 127/202] more aggressive traiiling null trimming seems to be needed on Steam Deck --- package/launchdf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 5ffc939e15b..58906af30d2 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -357,7 +357,7 @@ int main(int argc, char* argv[]) { if (bytes <= 0) return std::nullopt; // steam API counts the null terminator in the byte count returned - if (buf[bytes] == '\0') bytes--; + while (bytes && buf[bytes] == '\0') bytes--; return std::string(buf, bytes); }; From 4981e7ee09d885ed6876d75e7637865c200f981e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 01:54:23 -0500 Subject: [PATCH 128/202] improve `get_app_path_from_steam`; force UTF-8 on windows --- package/launchdf.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 58906af30d2..e2596331f0e 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -312,6 +312,10 @@ void remove_old_install(std::filesystem::path df_path) int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { #else int main(int argc, char* argv[]) { +#endif +#ifdef WIN32 + // force UTF-8 + std::setlocale(LC_ALL, ".utf8"); #endif // initialize steam context if (SteamAPI_RestartAppIfNecessary(DFHACK_STEAM_APPID)) { @@ -352,11 +356,12 @@ int main(int argc, char* argv[]) { // obtain DF and DFHack app paths auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { - char buf[2048] = ""; - int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, 2048); + constexpr auto BUFSIZE = 2048; + char buf[BUFSIZE] = ""; + int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, BUFSIZE); if (bytes <= 0) return std::nullopt; - // steam API counts the null terminator in the byte count returned + // steam API includes one or more null terminators after the path, so trim those off while (bytes && buf[bytes] == '\0') bytes--; return std::string(buf, bytes); }; From ba555de43675f4ec48768275c8aef0a325cc4539 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 9 Apr 2026 01:48:10 +0000 Subject: [PATCH 129/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 9dfa9d731f6..f910ace3abe 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 9dfa9d731f6b84b6deaba42364168b7374157e6e +Subproject commit f910ace3abea09feef79df5c211c62c91146aa37 From 174ebd48826e3bad73336cd7c24e89807d67cd00 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 20:50:44 -0500 Subject: [PATCH 130/202] fix changelog --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 039b2acbb50..88c7546a61c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,7 +33,6 @@ Template for new versions: ## New Features ## Fixes -- Core: Windows console will always use UTF-8 regardless of system code page settings ## Misc Improvements @@ -60,6 +59,7 @@ Template for new versions: ## New Features ## Fixes +- Core: Windows console will always use UTF-8 regardless of system code page settings - Steam launcher: Switch to injection strategy, allowing Dwarf Fortress and DFHack to be installed in disparate locations ## Misc Improvements From de7fa493073d025776c2536c39b1342f96bf15c1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 21:23:51 -0500 Subject: [PATCH 131/202] Update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index f910ace3abe..963dfd7c73c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit f910ace3abea09feef79df5c211c62c91146aa37 +Subproject commit 963dfd7c73c78be921c15d86e80fea9c7910faf9 From 5d4e181ba737dcb4235beff17592d82ef1736281 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 22:04:51 -0500 Subject: [PATCH 132/202] fix stupid in launchdf --- package/launchdf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index e2596331f0e..3b07abf99a4 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -362,7 +362,7 @@ int main(int argc, char* argv[]) { if (bytes <= 0) return std::nullopt; // steam API includes one or more null terminators after the path, so trim those off - while (bytes && buf[bytes] == '\0') bytes--; + for (; bytes > 0 && buf[bytes-1] == '\0'; bytes--); return std::string(buf, bytes); }; From 3f6aaeb123d24472288716b755a9cb81126ec50f Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 8 Apr 2026 22:06:03 -0500 Subject: [PATCH 133/202] Update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 963dfd7c73c..c8ddd2c5238 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 963dfd7c73c78be921c15d86e80fea9c7910faf9 +Subproject commit c8ddd2c52387d32f06d7c99d83b9303e3038b47b From 07e81d05751787c05ca1cab02a143016498096e6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 9 Apr 2026 08:46:11 -0500 Subject: [PATCH 134/202] Update CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 083ea5bc38e..b40fd0a451b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r3rc1") +set(DFHACK_RELEASE "r3rc2") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From ccb0aa186fe8737800b64ef1a86eef19809bb622 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 9 Apr 2026 11:22:16 -0500 Subject: [PATCH 135/202] make `stockpiles.lua` relocatable --- plugins/lua/stockpiles.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index ac48ed6fc9b..1b2955459dc 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -8,7 +8,7 @@ local overlay = require('plugins.overlay') local widgets = require('gui.widgets') local STOCKPILES_DIR = 'dfhack-config/stockpiles' -local STOCKPILES_LIBRARY_DIR = 'hack/data/stockpiles' +local STOCKPILES_LIBRARY_DIR = dfhack.getHackPath() .. 'data/stockpiles' local BAD_FILENAME_REGEX = '[^%w._]' From 8d602bc1eeada61a8b43b4a55c348aafe81c7869 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 13 Apr 2026 09:42:29 -0500 Subject: [PATCH 136/202] Change build version to 53.11-r3 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b40fd0a451b..307208ddf28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r3rc2") -set(DFHACK_PRERELEASE TRUE) +set(DFHACK_RELEASE "r3") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) From eacfddfb2185e5ee90dd7950ef7f686973d67d64 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 13 Apr 2026 09:42:43 -0500 Subject: [PATCH 137/202] Update changelogs --- docs/changelog.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 88c7546a61c..7fb648d8db1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,7 +52,23 @@ Template for new versions: ======== were in submodules with their own changelogs! ======== ================================================================================ -# Future +# Future## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.11-r3 ## New Tools From a835c5baa9019b62236c6b695a3aecc1a0a16ad6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 14 Apr 2026 15:44:49 -0500 Subject: [PATCH 138/202] Update CMakeLists.txt for 53.12-r1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 307208ddf28..ebc0ffe3102 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) # set up versioning. -set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r3") +set(DF_VERSION "53.12") +set(DFHACK_RELEASE "r1") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 848dbe8f7dbe747e7a0304d84696832ae0da3a9c Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:54:31 +0000 Subject: [PATCH 139/202] Auto-update structures ref for 53.12 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 114321c4802..f1cc38c163d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 114321c4802fb7e16e9d1092c8f2c057422a1c82 +Subproject commit f1cc38c163d90cafc414f9dffd120d19d45da113 From 424140be9da9102759d0ecd27dafe6ab7d25525c Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:31:26 +0000 Subject: [PATCH 140/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index f1cc38c163d..5c432a10a9e 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f1cc38c163d90cafc414f9dffd120d19d45da113 +Subproject commit 5c432a10a9e50ee7c95bee61bee0564149b1d2bc From e96c220eb9e93a8a94f5bc76318e8909889e02d8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 14 Apr 2026 17:15:28 -0500 Subject: [PATCH 141/202] fix missing `/` in stockpiles --- plugins/lua/stockpiles.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 1b2955459dc..33f6e375ada 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -8,7 +8,7 @@ local overlay = require('plugins.overlay') local widgets = require('gui.widgets') local STOCKPILES_DIR = 'dfhack-config/stockpiles' -local STOCKPILES_LIBRARY_DIR = dfhack.getHackPath() .. 'data/stockpiles' +local STOCKPILES_LIBRARY_DIR = dfhack.getHackPath() .. '/data/stockpiles' local BAD_FILENAME_REGEX = '[^%w._]' From 7e7886e698847195b8c03c539537c3b31d155d8e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 14 Apr 2026 17:15:43 -0500 Subject: [PATCH 142/202] Update changelog for 53.12-r1 --- docs/changelog.txt | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7fb648d8db1..495a5ecc928 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,11 +52,33 @@ Template for new versions: ======== were in submodules with their own changelogs! ======== ================================================================================ -# Future## New Tools +# Future + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.12-r1 + +## New Tools ## New Features +- Compatibility with Dwarf Fortress 53.12 ## Fixes +- Stockpile definitions in the default library will be correctly found and used (fixed missing path separator) ## Misc Improvements From ab5b43742761a2daa65011633e66ee7a63ed24a6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 15 Apr 2026 12:24:42 -0500 Subject: [PATCH 143/202] improve `enum_field` with more flexible casting mostly so `RemoteFortressReader` will behave with updated structures --- docs/changelog.txt | 1 + library/include/DataDefs.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 495a5ecc928..cfff16e5a46 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -65,6 +65,7 @@ Template for new versions: ## Documentation ## API +- add flexible casting to ``enum_field`` to enable explicit casting to more types ## Lua diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 08d8d020975..88c4a93651d 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -632,6 +632,10 @@ namespace df enum_field &operator=(EnumType ev) { value = IntType(ev); return *this; } + explicit operator IntType () const { return IntType(value); } + template + explicit operator T () const { return static_cast(IntType(value)); } + }; template From 6cb697cc66dd46d07582faa8691074a7c8cf7002 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 16 Apr 2026 02:19:18 +0000 Subject: [PATCH 144/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5c432a10a9e..cab90ab30c4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5c432a10a9e50ee7c95bee61bee0564149b1d2bc +Subproject commit cab90ab30c4f52796aec9f83ce21c2fa5974494b From 97e7aa75a399f62f45ac9e6554059e0a1fd13cda Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 00:34:24 -0500 Subject: [PATCH 145/202] `ColorText.cpp`: remove unneeded headers --- library/ColorText.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/library/ColorText.cpp b/library/ColorText.cpp index bdd3e98f447..a101d795a98 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -35,24 +35,12 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#include -#include -#include -#include -#include -#include #include +#include +#include #include "ColorText.h" -#include "MiscUtils.h" - -#include -#include -#include -#include -using namespace std; using namespace DFHack; bool color_ostream::log_errors_to_stderr = false; @@ -81,7 +69,7 @@ void color_ostream::end_batch() flush_proxy(); } -color_ostream::color_ostream() : ostream(new buffer(this)), cur_color(COLOR_RESET) +color_ostream::color_ostream() : std::ostream(new buffer(this)), cur_color(COLOR_RESET) { // } From d5b1fcb4d6a0e673c8e8aaa4fdbb0fb437d136a1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:21:29 -0500 Subject: [PATCH 146/202] dedup/sort/clean `Core.cpp` includes --- library/Core.cpp | 84 ++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 2deba390ff4..d7824165c28 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -26,40 +26,43 @@ distribution. #include "Internal.h" -#include "Error.h" -#include "MemAccess.h" +#include "ColorText.h" +#include "Commands.h" +#include "Console.h" +#include "CoreDefs.h" #include "DataDefs.h" #include "Debug.h" -#include "Console.h" +#include "DFHackVersion.h" +#include "Error.h" +#include "Format.h" +#include "LuaTools.h" +#include "MemAccess.h" #include "MemoryPatcher.h" #include "MiscUtils.h" +#include "MiscUtils.h" #include "Module.h" -#include "VersionInfoFactory.h" -#include "VersionInfo.h" -#include "PluginManager.h" #include "ModuleFactory.h" +#include "PluginManager.h" #include "RemoteServer.h" #include "RemoteTools.h" -#include "LuaTools.h" -#include "DFHackVersion.h" -#include "md5wrapper.h" -#include "Format.h" - -#include "Commands.h" +#include "VersionInfo.h" +#include "VersionInfoFactory.h" #include "modules/DFSDL.h" #include "modules/DFSteam.h" #include "modules/EventManager.h" #include "modules/Filesystem.h" +#include "modules/Graphic.h" #include "modules/Gui.h" #include "modules/Hotkey.h" +#include "modules/Persistence.h" #include "modules/Textures.h" #include "modules/World.h" -#include "modules/Persistence.h" -#include "df/init.h" #include "df/gamest.h" +#include "df/global_objects.h" #include "df/graphic.h" +#include "df/init.h" #include "df/interfacest.h" #include "df/plotinfost.h" #include "df/viewscreen_dwarfmodest.h" @@ -68,35 +71,51 @@ distribution. #include "df/viewscreen_loadgamest.h" #include "df/viewscreen_new_regionst.h" #include "df/viewscreen_savegamest.h" -#include "df/world.h" #include "df/world_data.h" +#include "df/world.h" -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include #include -#include -#include #include -#include -#include +#include #include -#include +#include +#include +#include +#include #include -#include -#include +#include +#include + +#include "md5wrapper.h" + +#include + #include +#include +#include +#include -#ifdef _WIN32 -#define NOMINMAX -#include +#ifdef WIN32 +#include #endif #ifdef LINUX_BUILD @@ -105,6 +124,7 @@ distribution. using namespace DFHack; using namespace df::enums; + using df::global::init; using df::global::world; using std::string; From 3d80246bc94cf2929766dbd73560144c1c9d7907 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:25:03 -0500 Subject: [PATCH 147/202] update includes in `DataIdentity.cpp` --- library/DataIdentity.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/library/DataIdentity.cpp b/library/DataIdentity.cpp index 346565c62fe..0a797c20694 100644 --- a/library/DataIdentity.cpp +++ b/library/DataIdentity.cpp @@ -1,14 +1,21 @@ -#include +#include "DataIdentity.h" + +#include "BitArray.h" +#include "DataDefs.h" #include +#include +#include #include +#include +#include +#include #include +#include #include -#include #include +#include -#include "DataFuncs.h" -#include "DataIdentity.h" // the space after the uses of "type" in OPAQUE_IDENTITY_TRAITS_NAME is _required_ // without it the macro generates a syntax error when type is a template specification From 7278c55a47c9b567316c42d162909d45161d5a01 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:37:39 -0500 Subject: [PATCH 148/202] update includes in `MiscUtils.cpp` --- library/MiscUtils.cpp | 64 +++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index dc1d4950ac2..44f93524eea 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -22,39 +22,49 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" #include "Export.h" #include "MiscUtils.h" #include "ColorText.h" -#include "modules/DFSDL.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#ifndef LINUX_BUILD -// We don't want min and max macros +#ifdef WIN32 +// Suppress warning which occurs in header on some WinSDK versions +// See dfhack/dfhack#5147 for more information #define NOMINMAX - #include - // Suppress warning which occurs in header on some WinSDK versions - // See dfhack/dfhack#5147 for more information - #pragma warning(push) - #pragma warning(disable:4091) - #include - #pragma warning(pop) -#else - #include - #include - #include +#define WIN32_LEAN_AND_MEAN +#include +#include +#pragma warning(push) +#pragma warning(disable:4091) +#include +#pragma warning(pop) #endif -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#ifdef LINUX_BUILD +#include +#include +#include +#endif NumberFormatType preferred_number_format_type = NumberFormatType::DEFAULT; @@ -499,8 +509,8 @@ uint64_t GetTimeMs64() /* Character decoding */ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -#define UTF8_ACCEPT 0 -#define UTF8_REJECT 12 +constexpr auto UTF8_ACCEPT = 0; +constexpr auto UTF8_REJECT = 12; static const uint8_t utf8d[] = { // The first part of the table maps bytes to character classes that From fcb02402c711c1edf442fe2be9f0cbb5aa2a6022 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:50:03 -0500 Subject: [PATCH 149/202] correction on `Core.cpp` --- library/Core.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index d7824165c28..793824ac8ed 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -115,6 +115,9 @@ distribution. #include #ifdef WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include #include #endif From 208d550ab7385a085df291c91bb4e26985a210b7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:50:32 -0500 Subject: [PATCH 150/202] add missing include in LuaWrapper.h --- library/include/LuaWrapper.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 7576be7a114..70f36d3f957 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -25,7 +25,8 @@ distribution. #pragma once #include -#include + +#include "DataDefs.h" /** * Internal header file of the lua wrapper. From 5be4f9549252db21cb936a6ae0da7ca472c53060 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:50:53 -0500 Subject: [PATCH 151/202] remove C include from `Graphic.h` --- library/include/modules/Graphic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/modules/Graphic.h b/library/include/modules/Graphic.h index 9fa498a7cf8..5e30d898054 100644 --- a/library/include/modules/Graphic.h +++ b/library/include/modules/Graphic.h @@ -30,9 +30,9 @@ distribution. #ifndef CL_MOD_GRAPHIC #define CL_MOD_GRAPHIC -#include #include "Export.h" #include "Module.h" + #include namespace DFHack From 371be6a8addd444b893a4813158ecda225d26dd8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:51:55 -0500 Subject: [PATCH 152/202] update includes in `PluginManager.cpp` --- library/PluginManager.cpp | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index b7e0b2c3c42..05486e885d1 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,36 +22,48 @@ must not be misrepresented as being the original software. distribution. */ -#include "modules/EventManager.h" -#include "modules/Filesystem.h" -#include "modules/Screen.h" -#include "modules/World.h" -#include "Internal.h" +#include "PluginManager.h" + +#include "ColorText.h" #include "Core.h" +#include "CoreDefs.h" +#include "LuaWrapper.h" +#include "LuaTools.h" #include "MemAccess.h" -#include "PluginManager.h" +#include "MiscUtils.h" #include "RemoteServer.h" -#include "Console.h" #include "Types.h" #include "VersionInfo.h" -#include "DataDefs.h" -#include "MiscUtils.h" -#include "DFHackVersion.h" - -#include "LuaWrapper.h" -#include "LuaTools.h" - -using namespace DFHack; +#include "modules/EventManager.h" +#include "modules/Filesystem.h" +#include "modules/Screen.h" +#include "modules/World.h" +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -using std::string; +#include +#include + +#include "df/viewscreen.h" -#include +#include +#include + +using namespace DFHack; +using std::string; #if defined(_LINUX) static const string plugin_suffix = ".plug.so"; @@ -871,7 +883,7 @@ void PluginManager::init() loadAll(); bool any_loaded = false; - for (auto p : all_plugins) + for (auto& p : all_plugins) { if (p.second->getState() == Plugin::PS_LOADED) { From 21d865acb19b3f2b2dd2bed168ac118788455530 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 01:53:47 -0500 Subject: [PATCH 153/202] clean headers in `PlugLoad.cpp` --- library/PlugLoad.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/library/PlugLoad.cpp b/library/PlugLoad.cpp index fa58d39514f..6b4dc9d4512 100644 --- a/library/PlugLoad.cpp +++ b/library/PlugLoad.cpp @@ -1,19 +1,15 @@ -#include "Core.h" #include "Debug.h" -#include "Export.h" #include "PluginManager.h" -#include "Hooks.h" #include #include #include #include -#include -#include #ifdef WIN32 #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include #include #define global_search_handle() GetModuleHandle(nullptr) From e363b443701886b1e89a6c0af9ddfc23d1a8a520 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:05:31 -0500 Subject: [PATCH 154/202] adjust includes in `PlugLoad.cpp` --- library/PlugLoad.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/library/PlugLoad.cpp b/library/PlugLoad.cpp index 6b4dc9d4512..d3b75607739 100644 --- a/library/PlugLoad.cpp +++ b/library/PlugLoad.cpp @@ -2,10 +2,7 @@ #include "PluginManager.h" #include -#include #include -#include - #ifdef WIN32 #define NOMINMAX From b14b82dc0e0824e175b7fc16167fd0b7f52b0bfc Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:06:22 -0500 Subject: [PATCH 155/202] includes in `Process.cpp` --- library/Process.cpp | 56 ++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/library/Process.cpp b/library/Process.cpp index c45e5aea456..1ff63622f01 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -22,22 +22,30 @@ must not be misrepresented as being the original software. distribution. */ -#ifndef WIN32 -#ifndef _DARWIN -#include -#endif /* ! _DARWIN */ -#endif /* ! WIN32 */ +#include "Format.h" +#include "MemAccess.h" +#include "Memory.h" +#include "MemoryPatcher.h" +#include "MiscUtils.h" +#include "VersionInfo.h" +#include "VersionInfoFactory.h" + +#include "modules/Filesystem.h" + +#include +#include +#include #include -#include +#include +#include +#include #include -#include +#include +#include #include #include -#include - -#include "Format.h" -#ifndef WIN32 +#ifdef LINUX_BUILD #include #include #include @@ -53,28 +61,24 @@ distribution. #include #include #endif /* _DARWIN */ -#endif /* ! WIN32 */ -#include "Error.h" -#include "Internal.h" -#include "MemAccess.h" -#include "Memory.h" -#include "MemoryPatcher.h" -#include "MiscUtils.h" -#include "VersionInfo.h" -#include "VersionInfoFactory.h" -#include "modules/Filesystem.h" - -#ifndef WIN32 #include "md5wrapper.h" -#else /* WIN32 */ +#endif /* LINUX_BUILD */ + +#ifdef WIN32 #define _WIN32_WINNT 0x0600 #define WINVER 0x0600 + +#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include +#include +#include +#include + +#include -#include #endif /* WIN32 */ using namespace DFHack; @@ -151,7 +155,7 @@ Process::Process(const VersionInfoFactory& known_versions) : identified(false) uint32_t pe_offset = readDWord(d->base + 0x3C); read(d->base + pe_offset, sizeof(d->pe_header), (uint8_t*)&(d->pe_header)); const size_t sectionsSize = sizeof(IMAGE_SECTION_HEADER) * d->pe_header.FileHeader.NumberOfSections; - d->sections = (IMAGE_SECTION_HEADER*)malloc(sectionsSize); + d->sections = (IMAGE_SECTION_HEADER*)std::malloc(sectionsSize); read(d->base + pe_offset + sizeof(d->pe_header), sectionsSize, (uint8_t*)(d->sections)); } catch (std::exception&) From e3605d6834c8e001c3db7976a6505c10cdaa7112 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:11:21 -0500 Subject: [PATCH 156/202] headers in `RemoteClient.cpp` --- library/RemoteClient.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index b159843761b..c917a3232ae 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -36,24 +36,25 @@ POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include +#include +#include "ColorText.h" +#include "CoreDefs.h" #include "RemoteClient.h" -#include -#include "MiscUtils.h" -#include -#include -#include +#include "ActiveSocket.h" +#include "Host.h" +#include "SimpleSocket.h" -#include #include "json/json.h" From 6a641dbef43b3181bec0681e795c6dd85d41f662 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:17:24 -0500 Subject: [PATCH 157/202] includes in `RemoteServer.cpp` --- library/RemoteServer.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 9557a15862f..0e6129b9261 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -34,28 +34,33 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - - -#include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include +#include +#include + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "Debug.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "ActiveSocket.h" +#include "Host.h" +#include "RemoteClient.h" #include "RemoteServer.h" #include "RemoteTools.h" - #include "PassiveSocket.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "Debug.h" - -#include -#include -#include +#include "SimpleSocket.h" #include #include From 9b15e57e6dfe0c0211e6c7d7bdd8eb501a72ffec Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:19:14 -0500 Subject: [PATCH 158/202] includes in `Types.cpp` --- library/Types.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/library/Types.cpp b/library/Types.cpp index 1dab657c1c3..21437723f1f 100644 --- a/library/Types.cpp +++ b/library/Types.cpp @@ -22,24 +22,20 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" -#include "Export.h" #include "MiscUtils.h" -#include "Error.h" #include "Types.h" #include "modules/Filesystem.h" #include "df/general_ref.h" +#include "df/general_ref_type.h" +#include "df/global_objects.h" #include "df/specific_ref.h" +#include "df/specific_ref_type.h" -#include -#include - -#include -#include -#include #include +#include +#include int DFHack::getdir(std::filesystem::path dir, std::vector &files) From d223a385486ed7b7c4b91612110e58cb03d2637a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:25:33 -0500 Subject: [PATCH 159/202] Includes in `RemoteTools.cpp` --- library/RemoteTools.cpp | 63 +++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index dbef7a99060..5dab76e8cb0 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -35,57 +35,58 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#include -#include -#include -#include -#include -#include -#include -#include - #include "RemoteTools.h" -#include "PluginManager.h" + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "DataDefs.h" +#include "DFHackVersion.h" +#include "LuaTools.h" #include "MiscUtils.h" +#include "PluginManager.h" #include "VersionInfo.h" -#include "DFHackVersion.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "modules/Units.h" #include "modules/World.h" -#include "LuaTools.h" - -#include "DataDefs.h" -#include "df/plotinfost.h" #include "df/adventurest.h" -#include "df/world.h" -#include "df/world_data.h" -#include "df/unit.h" -#include "df/unit_misc_trait.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" +#include "df/creature_raw.h" +#include "df/global_objects.h" +#include "df/historical_entity.h" +#include "df/historical_figure.h" +#include "df/incident.h" +#include "df/inorganic_raw.h" +#include "df/language_name.h" #include "df/material.h" #include "df/matter_state.h" -#include "df/inorganic_raw.h" -#include "df/creature_raw.h" -#include "df/plant_raw.h" #include "df/nemesis_record.h" -#include "df/historical_figure.h" -#include "df/historical_entity.h" -#include "df/squad.h" +#include "df/plant_raw.h" +#include "df/plotinfost.h" +#include "df/profession.h" #include "df/squad_position.h" -#include "df/incident.h" +#include "df/squad.h" +#include "df/unit_misc_trait.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/unit.h" +#include "df/world_data.h" +#include "df/world.h" #include "BasicApi.pb.h" +#include #include #include -#include - +#include +#include +#include #include +#include +#include +#include using namespace DFHack; using namespace df::enums; From 7c6a6c114465809ab31199d922669d16b7abb67f Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:36:00 -0500 Subject: [PATCH 160/202] constrain winsock leakage --- library/RemoteServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 0e6129b9261..aa3deb201d7 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -34,6 +34,10 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#ifdef WIN32 +#define NOMINMAX +#endif #include #include #include From a1a4ab5b6af94585c4f8266b506b7456c4ad8831 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:36:18 -0500 Subject: [PATCH 161/202] BitArray.h includes --- library/include/BitArray.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/include/BitArray.h b/library/include/BitArray.h index 360281c6b14..7658e331020 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -24,14 +24,14 @@ distribution. #pragma once #include "Error.h" -#include -#include -#include #include +#include #include -#include #include +#include +#include +#include namespace DFHack { From 01b36494cb9f5e140175a1ef7ae7cc3c1ba169ab Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:37:16 -0500 Subject: [PATCH 162/202] includes in `DFSDL.h` --- library/include/modules/DFSDL.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/include/modules/DFSDL.h b/library/include/modules/DFSDL.h index 393877e0951..953686deed6 100644 --- a/library/include/modules/DFSDL.h +++ b/library/include/modules/DFSDL.h @@ -1,12 +1,12 @@ #pragma once -#include "Export.h" #include "ColorText.h" +#include "Export.h" #include #include +#include #include -#include struct SDL_Surface; struct SDL_Rect; From ec58ce6a8bc4e51ed8d8549c3fd1092308783952 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Apr 2026 02:37:28 -0500 Subject: [PATCH 163/202] includes in `DFSteam.cpp` --- library/modules/DFSteam.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index 61e50a61b8e..31cacfea632 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -1,11 +1,30 @@ -#include "Internal.h" - #include "modules/DFSteam.h" #include "Debug.h" #include "PluginManager.h" +#include "ColorText.h" +#include "Core.h" + +#include +#include +#include +#include +#include + #include "df/gamest.h" +#include + +#ifdef WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#endif namespace DFHack { @@ -100,9 +119,6 @@ void DFSteam::cleanup(color_ostream& out) { #ifdef WIN32 -#include -#include -#include static bool is_running_on_wine() { typedef const char* (CDECL wine_get_version)(void); From 7a4c94417b2f12fe2a106d9805989e5bfc7959a5 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 1 May 2026 13:50:18 -0500 Subject: [PATCH 164/202] a few minor adjustments --- library/Core.cpp | 2 -- library/MiscUtils.cpp | 4 ++-- library/PluginManager.cpp | 6 ++---- library/Process.cpp | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 793824ac8ed..c1ab8812b33 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -106,8 +106,6 @@ distribution. #include "md5wrapper.h" -#include - #include #include #include diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 44f93524eea..d91471803b0 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -48,12 +48,12 @@ distribution. #include #ifdef WIN32 -// Suppress warning which occurs in header on some WinSDK versions -// See dfhack/dfhack#5147 for more information #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include +// Suppress warning which occurs in header on some WinSDK versions +// See dfhack/dfhack#5147 for more information #pragma warning(push) #pragma warning(disable:4091) #include diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 05486e885d1..34095898db9 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -27,6 +27,7 @@ distribution. #include "ColorText.h" #include "Core.h" #include "CoreDefs.h" +#include "Format.h" #include "LuaWrapper.h" #include "LuaTools.h" #include "MemAccess.h" @@ -54,13 +55,10 @@ distribution. #include #include -#include -#include - #include "df/viewscreen.h" -#include #include +#include using namespace DFHack; using std::string; diff --git a/library/Process.cpp b/library/Process.cpp index 1ff63622f01..e6b82ccabda 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -30,7 +30,7 @@ distribution. #include "VersionInfo.h" #include "VersionInfoFactory.h" -#include "modules/Filesystem.h" +#include "Modules/Filesystem.h" #include #include From 397d95ade374048c0bbaba0df82cde7f0923e5d4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 1 May 2026 13:54:49 -0500 Subject: [PATCH 165/202] case dependent filesystems --- library/Process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Process.cpp b/library/Process.cpp index e6b82ccabda..1ff63622f01 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -30,7 +30,7 @@ distribution. #include "VersionInfo.h" #include "VersionInfoFactory.h" -#include "Modules/Filesystem.h" +#include "modules/Filesystem.h" #include #include From 3c2c40db54703d5cd58310c011c25887f9797327 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 19:45:32 +0000 Subject: [PATCH 166/202] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.37.1 → 0.37.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.37.1...0.37.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b991bfbb742..91a34bdee25 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.1 + rev: 0.37.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From ebb18ee3afe1ea81325b3490ef389a46eeab3b79 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 10 May 2026 09:54:51 +0200 Subject: [PATCH 167/202] Handle units without current soul in `Units::getFocusPenalty` --- docs/changelog.txt | 1 + library/modules/Units.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index cfff16e5a46..84b897421d7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -66,6 +66,7 @@ Template for new versions: ## API - add flexible casting to ``enum_field`` to enable explicit casting to more types +- Handle units without current soul in ``Units::getFocusPenalty`` ## Lua diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index d8bfebdc976..707437ea347 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -2027,6 +2027,9 @@ int32_t Units::getFocusPenalty(df::unit* unit, need_type_set need_types) { CHECK_NULL_POINTER(unit); int max_penalty = INT_MAX; + if (!unit->status.current_soul) { + return max_penalty; + } auto& needs = unit->status.current_soul->personality.needs; for (auto const need : needs) { if (need_types.test(need->id)) { From 6b65dce6a7e26d7cef2bad497945190a85694563 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 10 May 2026 11:58:30 +0200 Subject: [PATCH 168/202] exclude `fmtlib` build artifacts --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 60a0e1b6b2b..8ee4eb5ff19 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ build/compile_commands.json build/dfhack_setarch.txt build/ImportExecutables.cmake build/Testing +build/_deps # Python binding binaries *.pyc From a35a0c333262868144b35f6d0f23baa1f24029c4 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 10 May 2026 15:09:57 +0200 Subject: [PATCH 169/202] improve `autofarm` documentation --- docs/plugins/autofarm.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 8896c3117c9..0fe98f2b8b8 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -24,9 +24,7 @@ Usage Sets thresholds of individual plant types. You can find the identifiers for the crop types in your world by running the -following command:: - - lua "for _,plant in ipairs(df.global.world.raws.plants.all) do if plant.flags.SEED then print(plant.id) end end" +following command: ``getplants -f`` Examples -------- From 9a1d5b86fdcaf329dbc5be81356e15eb419cd968 Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Wed, 13 May 2026 16:07:06 +0000 Subject: [PATCH 170/202] Auto-update structures ref for 53.13 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index cab90ab30c4..ab0b7648862 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit cab90ab30c4f52796aec9f83ce21c2fa5974494b +Subproject commit ab0b7648862b4718de07bc1e30a5ac0d9d1b1c89 From eec022051369f45e227c371bed9ac8ea2ea8c6af Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 13 May 2026 11:19:18 -0500 Subject: [PATCH 171/202] update CMakeLists for 53.13 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebc0ffe3102..815a2df89a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) # set up versioning. -set(DF_VERSION "53.12") +set(DF_VERSION "53.13") set(DFHACK_RELEASE "r1") set(DFHACK_PRERELEASE FALSE) From 3c9e10d46e4738e26068c4a74a70fd67924a3470 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 13 May 2026 17:09:09 +0000 Subject: [PATCH 172/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index ab0b7648862..392d6522cd2 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ab0b7648862b4718de07bc1e30a5ac0d9d1b1c89 +Subproject commit 392d6522cd2f88fdc36c978944e508f17cb43ad3 From 5611803aaf10b32fdd13187026ea994d863c9f68 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 13 May 2026 17:50:45 +0000 Subject: [PATCH 173/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 392d6522cd2..ee2b7c73806 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 392d6522cd2f88fdc36c978944e508f17cb43ad3 +Subproject commit ee2b7c73806f9652d6524d3324673600c04f6481 From 66dddaaea5832149bc3f4a21ba7b00ecc2dc352e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 13 May 2026 13:55:17 -0500 Subject: [PATCH 174/202] Changelog and structures for 53.13-r1 --- docs/changelog.txt | 19 +++++++++++++++++++ library/xml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 84b897421d7..6b87efe40d6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -64,6 +64,25 @@ Template for new versions: ## Documentation +## API + +## Lua + +## Removed + +# 53.13-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation +- updated documentation for ``autofarm`` for more clarity + ## API - add flexible casting to ``enum_field`` to enable explicit casting to more types - Handle units without current soul in ``Units::getFocusPenalty`` diff --git a/library/xml b/library/xml index ee2b7c73806..82e62e90f0f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ee2b7c73806f9652d6524d3324673600c04f6481 +Subproject commit 82e62e90f0f50f143dcb3eca1698344ff39c45dd From 4a48d49429941ece57085a814ac9b202554f1429 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 14 May 2026 19:08:10 -0500 Subject: [PATCH 175/202] remove announcement culling from `Gui` module --- docs/changelog.txt | 1 + library/modules/Gui.cpp | 39 +++------------------------------------ 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6b87efe40d6..fa85b98865e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,6 +59,7 @@ Template for new versions: ## New Features ## Fixes +- ``Gui::makeAnnoucement``, ``Gui::showPopupAnnouncement`, and ``Gui::autoDFAnnouncement`` will no longer attempt to cull the DF announcement vector ## Misc Improvements diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 68d63e68de1..06de785a390 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -105,10 +105,9 @@ using std::string; using std::vector; using namespace DFHack; -const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size -const int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE -const int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 2 sec at 50 GFPS -const int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 40 sec at 50 GFPS +static constexpr int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE +static constexpr int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 2 sec at 50 GFPS +static constexpr int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 40 sec at 50 GFPS namespace DFHack { @@ -1928,18 +1927,6 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce world->status.display_timer = ANNOUNCE_DISPLAY_TIME; } - // Delete excess reports - while (reports.size() > MAX_REPORTS_SIZE) - { // Report destructor - if (reports[0] != NULL) - { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; - } - reports.erase(reports.begin()); - } - return world->status.reports.size() - 1; } @@ -2032,14 +2019,6 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright) auto &popups = world->status.popups; popups.push_back(popup); - // Delete excess popups - while (popups.size() > MAX_REPORTS_SIZE) - { - if (popups[0] != NULL) - delete popups[0]; - popups.erase(popups.begin()); - } - Gui::MTB_clean(&world->status.mega_text); Gui::MTB_parse(&world->status.mega_text, popups[0]->text); Gui::MTB_set_width(&world->status.mega_text); @@ -2268,18 +2247,6 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) world->status.display_timer = info.display_timer; } - // Delete excess reports - while (reports.size() > MAX_REPORTS_SIZE) - { // Report destructor - if (reports[0] != NULL) - { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; - } - reports.erase(reports.begin()); - } - if (*gamemode == game_mode::DWARF || // Did dwarf announcement or UCR (*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || // Did adventure announcement (a_flags.bits.DO_MEGA && !adv_unconscious)) // Did popup From 2d5ae9a7c012d5c66b60ae55bce3877be37bcc9b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 15 May 2026 09:48:30 +0000 Subject: [PATCH 176/202] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 82e62e90f0f..79749f5bb15 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 82e62e90f0f50f143dcb3eca1698344ff39c45dd +Subproject commit 79749f5bb15a50f7b4a065aae05a1fc53a6ab462 From e4dc45e36ebc6de37dbed02b40a7ed1e26f5b96a Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 16 May 2026 07:54:48 -0500 Subject: [PATCH 177/202] CMakelist & changelog for 53.13-r2 --- CMakeLists.txt | 2 +- docs/changelog.txt | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 815a2df89a2..6120219faed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.13") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index fa85b98865e..bf0171b3184 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,6 +54,25 @@ Template for new versions: # Future + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.13-r2 + ## New Tools ## New Features From e53187c8b975f71c5c356d9a587563009684bc89 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 16 May 2026 07:57:00 -0500 Subject: [PATCH 178/202] structures for 53.13-r2 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 79749f5bb15..5ac7a801297 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 79749f5bb15a50f7b4a065aae05a1fc53a6ab462 +Subproject commit 5ac7a801297a604ff2a58d7235291b33535a64a0 From 6c7a24c910fea5220ee462a238040a06c8822bd7 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 May 2026 10:24:50 +0000 Subject: [PATCH 179/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index af457f9b5c8..e320f138ffa 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit af457f9b5c86fc041a064ef5bdfbcab38f38a50f +Subproject commit e320f138ffa762f6f7533451d8adcdfb769cf1a1 From fa1165bce8c0c5630d93a32efea5f2fb29c903ad Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 May 2026 13:18:43 +0000 Subject: [PATCH 180/202] Auto-update submodules library/xml: master scripts: master plugins/stonesense: master --- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index 5ac7a801297..37bd2498d42 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5ac7a801297a604ff2a58d7235291b33535a64a0 +Subproject commit 37bd2498d42f26ba904abe04b38341c625f5f3b3 diff --git a/plugins/stonesense b/plugins/stonesense index c8ddd2c5238..32471bc1e76 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit c8ddd2c52387d32f06d7c99d83b9303e3038b47b +Subproject commit 32471bc1e76d29ca0051d61a1fba4fc104f240e9 diff --git a/scripts b/scripts index e320f138ffa..66c1ff627b4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e320f138ffa762f6f7533451d8adcdfb769cf1a1 +Subproject commit 66c1ff627b4bf195e0576eaa2e4dbe2146ba6fb8 From 108dd8c1e842f1c3e97f9d1ab9a1d37fe38ebdc9 Mon Sep 17 00:00:00 2001 From: ab9rf <1445859+ab9rf@users.noreply.github.com> Date: Wed, 20 May 2026 16:07:56 +0000 Subject: [PATCH 181/202] Auto-update structures ref for 53.14 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 37bd2498d42..01aae95cacd 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 37bd2498d42f26ba904abe04b38341c625f5f3b3 +Subproject commit 01aae95cacd98850e4f477c45a4b75f800bacecc From ac13e275a2b7da55566118972762798dcae64d79 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 20 May 2026 11:16:18 -0500 Subject: [PATCH 182/202] Update version to 53.14-r1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6120219faed..62f55d0b4f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) # set up versioning. -set(DF_VERSION "53.13") -set(DFHACK_RELEASE "r2") +set(DF_VERSION "53.14") +set(DFHACK_RELEASE "r1") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From a58f695ff3b521b94fba64ddd6fdeddd60542e32 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 20 May 2026 11:22:14 -0500 Subject: [PATCH 183/202] Changelog for 53.14 --- docs/changelog.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index bf0171b3184..113fc16ba9c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,10 +54,28 @@ Template for new versions: # Future +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.14-r1 ## New Tools ## New Features +- Compatibility with Dwarf Fortress 53.14 ## Fixes From 128014165b230b7b7e437d5e3435fbf6c6cc9137 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 May 2026 17:03:39 +0000 Subject: [PATCH 184/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 66c1ff627b4..3ce62a5ade3 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 66c1ff627b4bf195e0576eaa2e4dbe2146ba6fb8 +Subproject commit 3ce62a5ade38c3ddb266294017e9a151772a09c9 From c2f6e92abc1a18b9edf377a180b768d0d4eff76c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 21 May 2026 11:26:37 -0500 Subject: [PATCH 185/202] protect DF pooled objects from being deleted --- docs/changelog.txt | 1 + library/include/DataDefs.h | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 113fc16ba9c..735ddddf4ed 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -61,6 +61,7 @@ Template for new versions: ## Fixes ## Misc Improvements +- Core: attempts to delete a pool-allocated DF object will now throw an exception instead of corrupting the heap ## Documentation diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 88c4a93651d..aabcfdd9dbf 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -24,14 +24,15 @@ distribution. #pragma once +#include #include #include #include +#include #include #include #include #include -#include #include "BitArray.h" #include "Export.h" @@ -572,15 +573,21 @@ namespace df * */ + template concept pooled_object = requires () { { T::pool_id } -> std::convertible_to; }; + template concept copy_assignable = std::assignable_from && std::assignable_from; template void *allocator_fn(void *out, const void *in) { - if (out) + // unerase type + T* _out = out ? reinterpret_cast(out) : nullptr; + const T* _in = in ? reinterpret_cast(in) : nullptr; + + if (_out) { if constexpr (copy_assignable) { - *(T*)out = *(const T*)in; + *_out = *_in; return out; } else @@ -588,13 +595,20 @@ namespace df return nullptr; } } - else if (in) + else if (_in) { + if constexpr (pooled_object) + { + if (_in->pool_id != -1) + { + throw std::runtime_error("Pool-allocated type cannot be deallocated with allocator_fn"); + } + } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" - delete (T*)in; + delete _in; #pragma GCC diagnostic pop - return (T*)in; + return const_cast(in); } else return new T(); From ab59c374923cba0ccba5c0a32154d37cf49f6c4f Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 21 May 2026 11:39:03 -0500 Subject: [PATCH 186/202] `pool_id`s are `size_t` --- library/include/DataDefs.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index aabcfdd9dbf..37892391a48 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -573,12 +573,14 @@ namespace df * */ - template concept pooled_object = requires () { { T::pool_id } -> std::convertible_to; }; + using df_pool_id_t = size_t; + template concept pooled_object = requires () { { T::pool_id } -> std::convertible_to; }; template concept copy_assignable = std::assignable_from && std::assignable_from; template void *allocator_fn(void *out, const void *in) { + constexpr df_pool_id_t invalid_pool_id = static_cast(-1); // unerase type T* _out = out ? reinterpret_cast(out) : nullptr; const T* _in = in ? reinterpret_cast(in) : nullptr; @@ -599,7 +601,7 @@ namespace df { if constexpr (pooled_object) { - if (_in->pool_id != -1) + if (_in->pool_id != invalid_pool_id) { throw std::runtime_error("Pool-allocated type cannot be deallocated with allocator_fn"); } From d86cf1928ae2a3c4574eb50531ca939dfc102bb8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 10:51:08 -0500 Subject: [PATCH 187/202] Remove ``logcleaner`` --- docs/changelog.txt | 1 + docs/plugins/logcleaner.rst | 62 ---------- plugins/CMakeLists.txt | 1 - plugins/logcleaner/logcleaner.cpp | 190 ------------------------------ plugins/lua/logcleaner.lua | 85 ------------- 5 files changed, 1 insertion(+), 338 deletions(-) delete mode 100644 docs/plugins/logcleaner.rst delete mode 100644 plugins/logcleaner/logcleaner.cpp delete mode 100644 plugins/lua/logcleaner.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index 735ddddf4ed..8d723a4c014 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -70,6 +70,7 @@ Template for new versions: ## Lua ## Removed +- ``logcleaner``: Removed (cannot be safely implemented at this time) # 53.14-r1 diff --git a/docs/plugins/logcleaner.rst b/docs/plugins/logcleaner.rst deleted file mode 100644 index 6e4cba07d6b..00000000000 --- a/docs/plugins/logcleaner.rst +++ /dev/null @@ -1,62 +0,0 @@ -logcleaner -========== -.. dfhack-tool:: - :summary: Automatically clear combat, sparring, and hunting reports. - :tags: fort auto units - -This plugin prevents spam from cluttering your announcement history and filling -the 3000-item reports buffer. It runs approximately every 100 ticks and clears -selected report types from both the global reports buffer and per-unit logs. - -Usage ------ - -Basic commands -~~~~~~~~~~~~~~ - -``logcleaner`` - Show the current status of the plugin. -``logcleaner enable`` - Enable the plugin (persists per save). -``logcleaner disable`` - Disable the plugin. - -Configuring filters -~~~~~~~~~~~~~~~~~~~ - -``logcleaner combat`` - Clear combat reports (also enables the plugin if disabled). -``logcleaner sparring`` - Clear sparring reports. -``logcleaner hunting`` - Clear hunting reports. -``logcleaner combat,sparring`` - Clear multiple report types (comma-separated). -``logcleaner all`` - Enable all three filter types. -``logcleaner none`` - Disable all filter types. - -Examples -~~~~~~~~ - -Clear only sparring reports:: - - logcleaner sparring - -Clear combat and hunting, but not sparring:: - - logcleaner combat,hunting - -Overlay UI ----------- - -Run ``gui/logcleaner`` to open the settings overlay, or access it from the -control panel under the Gameplay tab. - -The overlay provides: - -- **Enable toggle**: Turn the plugin on or off (``Shift+E``) -- **Combat toggle**: Clear combat reports (``Shift+C``) -- **Sparring toggle**: Clear sparring reports (``Shift+S``) -- **Hunting toggle**: Clear hunting reports (``Shift+H``) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4a4423f48f2..355cc0ac4c0 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -66,7 +66,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(createitem createitem.cpp) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) - dfhack_plugin(logcleaner logcleaner/logcleaner.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp LINK_LIBRARIES lua) diff --git a/plugins/logcleaner/logcleaner.cpp b/plugins/logcleaner/logcleaner.cpp deleted file mode 100644 index 89f84cf32f8..00000000000 --- a/plugins/logcleaner/logcleaner.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "LuaTools.h" -#include "PluginManager.h" -#include "PluginLua.h" - -#include "modules/Persistence.h" -#include "modules/World.h" - -#include -#include -#include -#include - -using namespace DFHack; - -DFHACK_PLUGIN("logcleaner"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -REQUIRE_GLOBAL(world); - -static const std::string CONFIG_KEY = std::string(plugin_name) + "/config"; -static PersistentDataItem config; - -enum ConfigValues { - CONFIG_IS_ENABLED = 0, - CONFIG_CLEAR_COMBAT = 1, - CONFIG_CLEAR_SPARING = 2, - CONFIG_CLEAR_HUNTING = 3, -}; - -static bool clear_combat = false; -static bool clear_sparring = true; -static bool clear_hunting = false; - -static constexpr int32_t CYCLE_TICKS = 97; -static int32_t cycle_timestamp = 0; - -static void cleanupLogs(); -static command_result do_command(color_ostream& out, std::vector& params); - -// Getter functions for Lua -static bool logcleaner_getCombat() { return clear_combat; } -static bool logcleaner_getSparring() { return clear_sparring; } -static bool logcleaner_getHunting() { return clear_hunting; } - -// Setter functions for Lua (also persist to config) -static void logcleaner_setCombat(bool val) { - clear_combat = val; - config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); -} - -static void logcleaner_setSparring(bool val) { - clear_sparring = val; - config.set_bool(CONFIG_CLEAR_SPARING, clear_sparring); -} - -static void logcleaner_setHunting(bool val) { - clear_hunting = val; - config.set_bool(CONFIG_CLEAR_HUNTING, clear_hunting); -} - -DFhackCExport command_result plugin_init(color_ostream& out, std::vector& commands) { - commands.push_back(PluginCommand( - plugin_name, - "Prevent report buffer from filling up by clearing selected report types (combat, sparring, hunting).", - do_command)); - - return CR_OK; -} - -static command_result do_command(color_ostream& out, std::vector& params) { - if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot use {} without a loaded fort.\n", plugin_name); - return CR_FAILURE; - } - - bool show_help = false; - if (!Lua::CallLuaModuleFunction(out, "plugins.logcleaner", "parse_commandline", params, - 1, [&](lua_State *L) { - show_help = !lua_toboolean(L, -1); - })) { - return CR_FAILURE; - } - - return show_help ? CR_WRONG_USAGE : CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { - if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); - return CR_FAILURE; - } - - if (enable != is_enabled) { - is_enabled = enable; - config.set_bool(CONFIG_IS_ENABLED, is_enabled); - } - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown(color_ostream& out) { - return CR_OK; -} - -DFhackCExport command_result plugin_load_site_data(color_ostream& out) { - config = World::GetPersistentSiteData(CONFIG_KEY); - - if (!config.isValid()) { - config = World::AddPersistentSiteData(CONFIG_KEY); - config.set_bool(CONFIG_IS_ENABLED, is_enabled); - config.set_bool(CONFIG_CLEAR_COMBAT, clear_combat); - config.set_bool(CONFIG_CLEAR_SPARING, clear_sparring); - config.set_bool(CONFIG_CLEAR_HUNTING, clear_hunting); - } - - is_enabled = config.get_bool(CONFIG_IS_ENABLED); - clear_combat = config.get_bool(CONFIG_CLEAR_COMBAT); - clear_sparring = config.get_bool(CONFIG_CLEAR_SPARING); - clear_hunting = config.get_bool(CONFIG_CLEAR_HUNTING); - - cycle_timestamp = 0; - return CR_OK; -} - -DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event) { - if (event == DFHack::SC_WORLD_UNLOADED && is_enabled) { - is_enabled = false; - } - return CR_OK; -} - -static void cleanupLogs() { - if (!is_enabled || !world) - return; - - if (!clear_combat && !clear_sparring && !clear_hunting) - return; - - // Collect all report IDs from unit combat/sparring/hunting logs - std::unordered_set report_ids_to_remove; - bool log_types[] = {clear_combat, clear_sparring, clear_hunting}; - - for (auto unit : world->units.all) { - for (int log_idx = 0; log_idx < 3; log_idx++) { - if (log_types[log_idx]) { - auto& log = unit->reports.log[log_idx]; - for (auto report_id : log) { - report_ids_to_remove.insert(report_id); - } - log.clear(); - } - } - } - - if (report_ids_to_remove.empty()) - return; - - // Remove collected reports from global buffers - auto& reports = world->status.reports; - - std::erase_if(reports, [&](df::report* report) { - if (!report || !report_ids_to_remove.contains(report->id)) - return false; - if (report->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, report->id); - delete report; - return true; - }); -} - -DFhackCExport command_result plugin_onupdate(color_ostream& out, state_change_event event) { - if (!is_enabled || !world) - return CR_OK; - else if (world->frame_counter - cycle_timestamp < CYCLE_TICKS) - return CR_OK; - - cycle_timestamp = world->frame_counter; - cleanupLogs(); - - return CR_OK; -} - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(logcleaner_getCombat), - DFHACK_LUA_FUNCTION(logcleaner_getSparring), - DFHACK_LUA_FUNCTION(logcleaner_getHunting), - DFHACK_LUA_FUNCTION(logcleaner_setCombat), - DFHACK_LUA_FUNCTION(logcleaner_setSparring), - DFHACK_LUA_FUNCTION(logcleaner_setHunting), - DFHACK_LUA_END -}; diff --git a/plugins/lua/logcleaner.lua b/plugins/lua/logcleaner.lua deleted file mode 100644 index 104c23ab88f..00000000000 --- a/plugins/lua/logcleaner.lua +++ /dev/null @@ -1,85 +0,0 @@ -local _ENV = mkmodule('plugins.logcleaner') - -local function print_status() - print(('logcleaner is %s'):format(isEnabled() and "enabled" or "disabled")) - print(' Combat: ' .. (logcleaner_getCombat() and 'enabled' or 'disabled')) - print(' Sparring: ' .. (logcleaner_getSparring() and 'enabled' or 'disabled')) - print(' Hunting: ' .. (logcleaner_getHunting() and 'enabled' or 'disabled')) -end - -function parse_commandline(...) - local args = {...} - local command = args[1] - - -- Show status if no command or "status" - if not command or command == 'status' then - print_status() - return true - end - - -- Handle enable/disable commands - if command == 'enable' then - if isEnabled() then - print('logcleaner is already enabled') - else - dfhack.run_command('enable', 'logcleaner') - print('logcleaner enabled') - end - return true - end - - if command == 'disable' then - if not isEnabled() then - print('logcleaner is already disabled') - else - dfhack.run_command('disable', 'logcleaner') - print('logcleaner disabled') - end - return true - end - - -- Start with all disabled, enable only what's specified - local new_combat, new_sparring, new_hunting = false, false, false - local has_filter = false - - if command == 'all' then - new_combat, new_sparring, new_hunting = true, true, true - has_filter = true - elseif command == 'none' then - new_combat, new_sparring, new_hunting = false, false, false - else - -- Split by comma for multiple options in one parameter - for token in command:gmatch('([^,]+)') do - if token == 'combat' then - new_combat = true - has_filter = true - elseif token == 'sparring' then - new_sparring = true - has_filter = true - elseif token == 'hunting' then - new_hunting = true - has_filter = true - else - dfhack.printerr('Unknown option: ' .. token) - return false - end - end - end - - -- Auto-enable plugin when filters are being configured - if has_filter and not isEnabled() then - dfhack.run_command('enable', 'logcleaner') - print('logcleaner enabled') - end - - logcleaner_setCombat(new_combat) - logcleaner_setSparring(new_sparring) - logcleaner_setHunting(new_hunting) - - print('Log cleaning config updated:') - print_status() - - return true -end - -return _ENV From bdbb52f45f88f0895b55f72de677216114260869 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 11:14:21 -0500 Subject: [PATCH 188/202] tombstone `logcleaner` documentation --- docs/about/Removed.rst | 12 ++++++++++++ docs/changelog.txt | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 5f73b86be38..9ef7781ebf1 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -258,6 +258,11 @@ gui/hack-wish ============= Replaced by `gui/create-item`. +gui/log-cleaner +=============== +Removed because changes to Dwarf Fortress internals made the functionality +impossible to implement safely. + .. _gui/manager-quantity: gui/manager-quantity @@ -278,6 +283,13 @@ Tool that warned the user when the ``dfhack.init`` file did not exist. Now that ``dfhack.init`` is autogenerated in ``dfhack-config/init``, this warning is no longer necessary. +.. _logcleaner: + +logcleaner +=============== +Removed because changes to Dwarf Fortress internals made the functionality +impossible to implement safely. + .. _masspit: masspit diff --git a/docs/changelog.txt b/docs/changelog.txt index 8d723a4c014..cddef2d75c3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -70,7 +70,7 @@ Template for new versions: ## Lua ## Removed -- ``logcleaner``: Removed (cannot be safely implemented at this time) +- `logcleaner`: Removed (cannot be safely implemented at this time) # 53.14-r1 From 4f31fff8290487c3f5bbcf93c8d741ea83f1587d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 11:53:38 -0500 Subject: [PATCH 189/202] Update Removed.rst --- docs/about/Removed.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 9ef7781ebf1..b0a7f469a18 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -258,7 +258,7 @@ gui/hack-wish ============= Replaced by `gui/create-item`. -gui/log-cleaner +gui/logcleaner =============== Removed because changes to Dwarf Fortress internals made the functionality impossible to implement safely. From 6e85d8551316c8bd826b1665c2d8afb8f8d32120 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 12:23:59 -0500 Subject: [PATCH 190/202] Update Removed.rst --- docs/about/Removed.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index b0a7f469a18..cf9bf95b090 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -258,6 +258,7 @@ gui/hack-wish ============= Replaced by `gui/create-item`. +.. _gui/logcleaner gui/logcleaner =============== Removed because changes to Dwarf Fortress internals made the functionality From 360d0f76d79b111bc755c27a784e75c3018adaf6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 12:39:10 -0500 Subject: [PATCH 191/202] Update Removed.rst sigh --- docs/about/Removed.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index cf9bf95b090..96eaa3c1423 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -258,7 +258,8 @@ gui/hack-wish ============= Replaced by `gui/create-item`. -.. _gui/logcleaner +.. _gui/logcleaner: + gui/logcleaner =============== Removed because changes to Dwarf Fortress internals made the functionality From 7db509ea6529b078b3615d58f8f31050896703df Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 22 May 2026 18:17:30 +0000 Subject: [PATCH 192/202] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3ce62a5ade3..364dd46ab03 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3ce62a5ade38c3ddb266294017e9a151772a09c9 +Subproject commit 364dd46ab037521a34ade84bb7bf3ce3638723f4 From 2e2b92130d9f0c1bddbdd20cec262ae5cdb050e7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 22 May 2026 13:58:23 -0500 Subject: [PATCH 193/202] update version, changelog and scripts for 53.14-r2 --- CMakeLists.txt | 2 +- docs/changelog.txt | 18 ++++++++++++++++++ scripts | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62f55d0b4f8..dff1b7fac32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.14") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index cddef2d75c3..70e55aff58b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -60,6 +60,24 @@ Template for new versions: ## Fixes +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.14-r2 + +## New Tools + +## New Features + +## Fixes + ## Misc Improvements - Core: attempts to delete a pool-allocated DF object will now throw an exception instead of corrupting the heap diff --git a/scripts b/scripts index 364dd46ab03..92aec15dd7e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 364dd46ab037521a34ade84bb7bf3ce3638723f4 +Subproject commit 92aec15dd7eff76b76ff74efb3164e31b7f0e5bd From 91458a222a7adee32901f05422535c6733f85f26 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 23 May 2026 00:16:26 -0500 Subject: [PATCH 194/202] remove dead `Graphic` module This module has been without purpose for at least a decade; nothing uses it I don't think it's had a purpose since DFHack became an in-process toolset --- library/CMakeLists.txt | 2 - library/Core.cpp | 2 - library/include/Core.h | 5 -- library/include/modules/DFSDL.h | 11 ---- library/include/modules/Graphic.h | 59 -------------------- library/modules/Graphic.cpp | 93 ------------------------------- 6 files changed, 172 deletions(-) delete mode 100644 library/include/modules/Graphic.h delete mode 100644 library/modules/Graphic.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index f91afef2e2a..6da49099681 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -157,7 +157,6 @@ set(MODULE_HEADERS include/modules/Designations.h include/modules/EventManager.h include/modules/Filesystem.h - include/modules/Graphic.h include/modules/Gui.h include/modules/GuiHooks.h include/modules/Hotkey.h @@ -189,7 +188,6 @@ set(MODULE_SOURCES modules/Designations.cpp modules/EventManager.cpp modules/Filesystem.cpp - modules/Graphic.cpp modules/Gui.cpp modules/Hotkey.cpp modules/Items.cpp diff --git a/library/Core.cpp b/library/Core.cpp index c1ab8812b33..62a280a187b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -52,7 +52,6 @@ distribution. #include "modules/DFSteam.h" #include "modules/EventManager.h" #include "modules/Filesystem.h" -#include "modules/Graphic.h" #include "modules/Gui.h" #include "modules/Hotkey.h" #include "modules/Persistence.h" @@ -2203,4 +2202,3 @@ TYPE * Core::get##TYPE() \ } MODULE_GETTER(Materials); -MODULE_GETTER(Graphic); diff --git a/library/include/Core.h b/library/include/Core.h index 9ccf0ca8710..ee50a83080a 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -29,8 +29,6 @@ distribution. #include "Export.h" #include "Hooks.h" -#include "modules/Graphic.h" - #include #include #include @@ -166,8 +164,6 @@ namespace DFHack /// get the materials module Materials * getMaterials(); - /// get the graphic module - Graphic * getGraphic(); command_result runCommand(color_ostream &out, const std::string &command, std::vector ¶meters, bool no_autocomplete = false); command_result runCommand(color_ostream& out, const std::string& command); @@ -300,7 +296,6 @@ namespace DFHack struct { Materials * pMaterials; - Graphic * pGraphic; } s_mods; std::vector> allModules; DFHack::PluginManager *plug_mgr; diff --git a/library/include/modules/DFSDL.h b/library/include/modules/DFSDL.h index 953686deed6..7d53242ad03 100644 --- a/library/include/modules/DFSDL.h +++ b/library/include/modules/DFSDL.h @@ -16,17 +16,6 @@ struct SDL_Window; union SDL_Event; using SDL_Keycode = int32_t; -namespace DFHack -{ - struct DFTileSurface - { - bool paintOver; // draw over original tile? - SDL_Surface* surface; // from where it should be drawn - SDL_Rect* rect; // from which coords (NULL to draw whole surface) - SDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) - }; -} - /** * The DFSDL module - provides access to SDL functions without actually * requiring build-time linkage to SDL diff --git a/library/include/modules/Graphic.h b/library/include/modules/Graphic.h deleted file mode 100644 index 5e30d898054..00000000000 --- a/library/include/modules/Graphic.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -/******************************************************************************* - GRAPHIC - Changing tile cache -*******************************************************************************/ -#pragma once -#ifndef CL_MOD_GRAPHIC -#define CL_MOD_GRAPHIC - -#include "Export.h" -#include "Module.h" - -#include - -namespace DFHack -{ - // forward declaration used here instead of including DFSDL.h to reduce inclusion loading - struct DFTileSurface; - - class DFHACK_EXPORT Graphic : public Module - { - public: - bool Finish() - { - return true; - } - bool Register(DFTileSurface* (*func)(int,int)); - bool Unregister(DFTileSurface* (*func)(int,int)); - DFTileSurface* Call(int x, int y); - - private: - std::vector funcs; - }; -} - -#endif diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp deleted file mode 100644 index b55ee83ed4e..00000000000 --- a/library/modules/Graphic.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -#include -#include -using namespace std; - -#include "modules/Graphic.h" -#include "Error.h" -#include "VersionInfo.h" -#include "MemAccess.h" -#include "MiscUtils.h" -#include "ModuleFactory.h" -#include "Core.h" - -using namespace DFHack; - -std::unique_ptr DFHack::createGraphic() -{ - return std::make_unique(); -} - -bool Graphic::Register(DFTileSurface* (*func)(int,int)) -{ - funcs.push_back(func); - return true; -} - -bool Graphic::Unregister(DFTileSurface* (*func)(int,int)) -{ - if ( funcs.empty() ) return false; - - vector::iterator it = funcs.begin(); - while ( it != funcs.end() ) - { - if ( *it == func ) - { - funcs.erase(it); - return true; - } - it++; - } - - return false; -} - -// This will return first DFTileSurface it can get (or NULL if theres none) -DFTileSurface* Graphic::Call(int x, int y) -{ - if ( funcs.empty() ) return NULL; - - DFTileSurface* temp = NULL; - - vector::iterator it = funcs.begin(); - while ( it != funcs.end() ) - { - temp = (*it)(x,y); - if ( temp != NULL ) - { - return temp; - } - it++; - } - - return NULL; -} From b799dd6366a4dc5a61ed3d51a2ce4079c94758de Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 23 May 2026 02:42:46 -0500 Subject: [PATCH 195/202] remove use of `Materials` module from `probe` also remove UB use of a union and clean up headers (at least somewhat) --- plugins/probe.cpp | 96 +++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index f49e167867b..9df22004063 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -1,11 +1,16 @@ +#include +#include +#include + #include "LuaTools.h" +#include "MiscUtils.h" #include "PluginManager.h" #include "TileTypes.h" #include "modules/Gui.h" -#include "modules/Materials.h" #include "modules/MapCache.h" #include "modules/Maps.h" +#include "modules/Materials.h" #include "df/block_square_event_grassst.h" #include "df/block_square_event_world_constructionst.h" @@ -15,6 +20,8 @@ #include "df/civzone_type.h" #include "df/construction_type.h" #include "df/furnace_type.h" +#include "df/global_objects.h" +#include "df/inorganic_raw.h" #include "df/item.h" #include "df/map_block.h" #include "df/region_map_entry.h" @@ -86,10 +93,8 @@ static void describeTile(color_ostream &out, df::tiletype tiletype) { } static command_result df_probe(color_ostream &out, vector & parameters) { - DFHack::Materials *Materials = Core::getInstance().getMaterials(); - vector inorganic; - bool hasmats = Materials->CopyInorganicMaterials(inorganic); + auto& inorganic = world->raws.inorganics.all; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -173,29 +178,25 @@ static command_result df_probe(color_ostream &out, vector & parameters) out << "geolayer: " << des.bits.geolayer_index << std::endl; int16_t base_rock = mc.layerMaterialAt(cursor); - if (base_rock != -1) { + if (base_rock != -1) + { out << "Layer material: " << std::dec << base_rock; - if(hasmats) - out << " / " << inorganic[base_rock].id - << " / " - << inorganic[base_rock].name - << std::endl; - else - out << std::endl; + out << " / " << inorganic[base_rock]->id + << " / " + << inorganic[base_rock]->material.stone_name + << std::endl; } int16_t vein_rock = mc.veinMaterialAt(cursor); - if (vein_rock != -1) { + if (vein_rock != -1) + { out << "Vein material (final): " << std::dec << vein_rock; - if(hasmats) - out << " / " << inorganic[vein_rock].id - << " / " - << inorganic[vein_rock].name - << " (" - << ENUM_KEY_STR(inclusion_type,b->veinTypeAt(cursor)) - << ")" - << std::endl; - else - out << std::endl; + out << " / " << inorganic[vein_rock]->id + << " / " + << inorganic[vein_rock]->material.stone_name + << " (" + << ENUM_KEY_STR(inclusion_type, b->veinTypeAt(cursor)) + << ")" + << std::endl; } MaterialInfo minfo(mc.baseMaterialAt(cursor)); if (minfo.isValid()) @@ -309,17 +310,6 @@ static command_result df_probe(color_ostream &out, vector & parameters) return CR_OK; } -union Subtype { - int16_t subtype; - df::civzone_type civzone_type; - df::furnace_type furnace_type; - df::workshop_type workshop_type; - df::construction_type construction_type; - df::shop_type shop_type; - df::siegeengine_type siegeengine_type; - df::trap_type trap_type; -}; - static command_result df_bprobe(color_ostream &out, vector & parameters) { auto bld = Gui::getSelectedBuilding(out); if (!bld) @@ -329,7 +319,7 @@ static command_result df_bprobe(color_ostream &out, vector & parameters) bld->getName(&name); auto bld_type = bld->getType(); - Subtype subtype{bld->getSubtype()}; + int16_t subtype{bld->getSubtype()}; int32_t custom = bld->getCustomType(); out.print("Building {:<4}, \"{}\", type {} ({})", @@ -342,46 +332,46 @@ static command_result df_bprobe(color_ostream &out, vector & parameters) switch (bld_type) { case df::building_type::Civzone: out.print(", subtype {} ({})", - ENUM_KEY_STR(civzone_type, subtype.civzone_type), - subtype.subtype); + ENUM_KEY_STR(civzone_type, static_cast(subtype)), + subtype); break; case df::building_type::Furnace: out.print(", subtype {} ({})", - ENUM_KEY_STR(furnace_type, subtype.furnace_type), - subtype.subtype); - if (subtype.furnace_type == df::furnace_type::Custom) + ENUM_KEY_STR(furnace_type, static_cast(subtype)), + subtype); + if (static_cast(subtype) == df::furnace_type::Custom) out.print(", custom type {} ({})", world->raws.buildings.all[custom]->code, custom); break; case df::building_type::Workshop: out.print(", subtype {} ({})", - ENUM_KEY_STR(workshop_type, subtype.workshop_type), - subtype.subtype); - if (subtype.workshop_type == df::workshop_type::Custom) + ENUM_KEY_STR(workshop_type, static_cast(subtype)), + subtype); + if (subtype == static_cast(df::workshop_type::Custom)) out.print(", custom type {} ({})", world->raws.buildings.all[custom]->code, custom); break; case df::building_type::Construction: out.print(", subtype {} ({})", - ENUM_KEY_STR(construction_type, subtype.construction_type), - subtype.subtype); + ENUM_KEY_STR(construction_type, static_cast(subtype)), + subtype); break; case df::building_type::Shop: out.print(", subtype {} ({})", - ENUM_KEY_STR(shop_type, subtype.shop_type), - subtype.subtype); + ENUM_KEY_STR(shop_type, static_cast(subtype)), + subtype); break; case df::building_type::SiegeEngine: out.print(", subtype {} ({})", - ENUM_KEY_STR(siegeengine_type, subtype.siegeengine_type), - subtype.subtype); + ENUM_KEY_STR(siegeengine_type, static_cast(subtype)), + subtype); break; case df::building_type::Trap: out.print(", subtype {} ({})", - ENUM_KEY_STR(trap_type, subtype.trap_type), - subtype.subtype); + ENUM_KEY_STR(trap_type, static_cast(subtype)), + subtype); break; case df::building_type::NestBox: { @@ -391,8 +381,8 @@ static command_result df_bprobe(color_ostream &out, vector & parameters) break; } default: - if (subtype.subtype != -1) - out.print(", subtype {}", subtype.subtype); + if (subtype != -1) + out.print(", subtype {}", subtype); break; } From faea23b361913eefc25c10f1bcf7be98768bf575 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 24 May 2026 04:17:39 +0000 Subject: [PATCH 196/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 32471bc1e76..992957670fd 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 32471bc1e76d29ca0051d61a1fba4fc104f240e9 +Subproject commit 992957670fd99b55d10196cf2850a4554bb15374 From d29682533d2f7a9d8cebc1d1a8c63a7046542bc1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 23 May 2026 23:37:31 -0500 Subject: [PATCH 197/202] Remove `Materials` module from `prospector` was unused - the plugin only obtained the handle and then disposed it without using it for anything --- plugins/prospector.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 035d6cdee12..5dd712e872e 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -610,9 +610,7 @@ static command_result embark_prospector(color_ostream &out, } if (options.hidden) { - DFHack::Materials *mats = Core::getInstance().getMaterials(); printVeins(out, veinMats, options); - mats->Finish(); } out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " "; @@ -635,8 +633,6 @@ static command_result map_prospector(color_ostream &con, Maps::getSize(x_max, y_max, z_max); MapExtras::MapCache map; - DFHack::Materials *mats = Core::getInstance().getMaterials(); - DFHack::t_feature blockFeatureGlobal; DFHack::t_feature blockFeatureLocal; @@ -894,9 +890,6 @@ static command_result map_prospector(color_ostream &con, printMats(con, treeMats, world->raws.plants.all, options); } - // Cleanup - mats->Finish(); - return CR_OK; } From e82a2e1c8f45f027e8b7b77ba35f8a1f652d4bb2 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 24 May 2026 00:30:19 -0500 Subject: [PATCH 198/202] Remove `Materials` module --- library/Core.cpp | 2 - library/include/Core.h | 5 - library/include/modules/Materials.h | 134 +++++----- library/modules/Materials.cpp | 383 +++++----------------------- 4 files changed, 131 insertions(+), 393 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 62a280a187b..50c6fe5f8bd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2200,5 +2200,3 @@ TYPE * Core::get##TYPE() \ }\ return s_mods.p##TYPE;\ } - -MODULE_GETTER(Materials); diff --git a/library/include/Core.h b/library/include/Core.h index ee50a83080a..085dd33cf1a 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -61,7 +61,6 @@ namespace DFHack class Process; class Module; - class Materials; struct VersionInfo; class VersionInfoFactory; class PluginManager; @@ -162,9 +161,6 @@ namespace DFHack /// Is everything OK? bool isValid(void) { return !errorstate; } - /// get the materials module - Materials * getMaterials(); - command_result runCommand(color_ostream &out, const std::string &command, std::vector ¶meters, bool no_autocomplete = false); command_result runCommand(color_ostream& out, const std::string& command); @@ -295,7 +291,6 @@ namespace DFHack // Module storage struct { - Materials * pMaterials; } s_mods; std::vector> allModules; DFHack::PluginManager *plug_mgr; diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 2c3be4e7318..9689c7790cc 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -35,7 +35,8 @@ distribution. #include "df/craft_material_class.h" -namespace df { +namespace df +{ struct item; struct plant_raw; struct creature_raw; @@ -54,28 +55,32 @@ namespace df { namespace DFHack { - struct t_matpair { + struct t_matpair + { int16_t mat_type; int32_t mat_index; t_matpair(int16_t type = -1, int32_t index = -1) - : mat_type(type), mat_index(index) {} + : mat_type(type), mat_index(index) + {} }; - struct DFHACK_EXPORT MaterialInfo { + struct DFHACK_EXPORT MaterialInfo + { static const int NUM_BUILTIN = 19; static const int GROUP_SIZE = 200; static const int CREATURE_BASE = NUM_BUILTIN; static const int FIGURE_BASE = NUM_BUILTIN + GROUP_SIZE; - static const int PLANT_BASE = NUM_BUILTIN + GROUP_SIZE*2; - static const int END_BASE = NUM_BUILTIN + GROUP_SIZE*3; + static const int PLANT_BASE = NUM_BUILTIN + GROUP_SIZE * 2; + static const int END_BASE = NUM_BUILTIN + GROUP_SIZE * 3; int16_t type; int32_t index; - df::material *material; + df::material* material; - enum Mode { + enum Mode + { None, Builtin, Inorganic, @@ -85,16 +90,16 @@ namespace DFHack Mode mode; int16_t subtype; - df::inorganic_raw *inorganic; - df::creature_raw *creature; - df::plant_raw *plant; + df::inorganic_raw* inorganic; + df::creature_raw* creature; + df::plant_raw* plant; - df::historical_figure *figure; + df::historical_figure* figure; public: MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); } - MaterialInfo(const t_matpair &mp) { decode(mp.mat_type, mp.mat_index); } - template MaterialInfo(T *ptr) { decode(ptr); } + MaterialInfo(const t_matpair& mp) { decode(mp.mat_type, mp.mat_index); } + template MaterialInfo(T* ptr) { decode(ptr); } bool isValid() const { return material != NULL; } @@ -108,25 +113,27 @@ namespace DFHack bool isInorganicWildcard() const { return isAnyInorganic() && isBuiltin(); } bool decode(int16_t type, int32_t index = -1); - bool decode(df::item *item); - bool decode(const df::material_vec_ref &vr, int idx); - bool decode(const t_matpair &mp) { return decode(mp.mat_type, mp.mat_index); } + bool decode(df::item* item); + bool decode(const df::material_vec_ref& vr, int idx); + bool decode(const t_matpair& mp) { return decode(mp.mat_type, mp.mat_index); } - template bool decode(T *ptr) { + template bool decode(T* ptr) + { // Assume and exploit a certain naming convention return ptr ? decode(ptr->mat_type, ptr->mat_index) : decode(-1); } - bool find(const std::string &token); - bool find(const std::vector &tokens); + bool find(const std::string& token); + bool find(const std::vector& tokens); - bool findBuiltin(const std::string &token); - bool findInorganic(const std::string &token); - bool findPlant(const std::string &token, const std::string &subtoken); - bool findCreature(const std::string &token, const std::string &subtoken); + bool findBuiltin(const std::string& token); + bool findInorganic(const std::string& token); + bool findPlant(const std::string& token, const std::string& subtoken); + bool findCreature(const std::string& token, const std::string& subtoken); - bool findProduct(df::material *material, const std::string &name); - bool findProduct(const MaterialInfo &info, const std::string &name) { + bool findProduct(df::material* material, const std::string& name); + bool findProduct(const MaterialInfo& info, const std::string& name) + { return findProduct(info.material, name); } @@ -135,35 +142,38 @@ namespace DFHack bool isAnyCloth() const; - void getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const; - void getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const; - void getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const; + void getMatchBits(df::job_item_flags1& ok, df::job_item_flags1& mask) const; + void getMatchBits(df::job_item_flags2& ok, df::job_item_flags2& mask) const; + void getMatchBits(df::job_item_flags3& ok, df::job_item_flags3& mask) const; df::craft_material_class getCraftClass(); - bool matches(const MaterialInfo &mat) const + bool matches(const MaterialInfo& mat) const { if (!mat.isValid()) return true; return (type == mat.type) && - (mat.index == -1 || index == mat.index); + (mat.index == -1 || index == mat.index); } - bool matches(const df::job_material_category &cat) const; - bool matches(const df::dfhack_material_category &cat) const; - bool matches(const df::job_item &jitem, - df::item_type itype = df::item_type::NONE) const; + bool matches(const df::job_material_category& cat) const; + bool matches(const df::dfhack_material_category& cat) const; + bool matches(const df::job_item& jitem, + df::item_type itype = df::item_type::NONE) const; }; - DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); - DFHACK_EXPORT bool parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token); + DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category* cat, const std::string& token); + DFHACK_EXPORT bool parseJobMaterialCategory(df::dfhack_material_category* cat, const std::string& token); - inline bool operator== (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator== (const MaterialInfo& a, const MaterialInfo& b) + { return a.type == b.type && a.index == b.index; } - inline bool operator!= (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator!= (const MaterialInfo& a, const MaterialInfo& b) + { return a.type != b.type || a.index != b.index; } - inline bool operator< (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator< (const MaterialInfo& a, const MaterialInfo& b) + { return a.type < b.type || (a.type == b.type && a.index < b.index); } @@ -345,38 +355,18 @@ namespace DFHack t_materialIndex mat_index; uint32_t flags; }; - /** - * The Materials module - * \ingroup grp_modules - * \ingroup grp_materials - */ - class DFHACK_EXPORT Materials : public Module + + + namespace Materials { - public: - Materials(); - ~Materials(); - bool Finish(); - - std::vector race; - std::vector raceEx; - std::vector color; - std::vector other; - std::vector alldesc; - - bool CopyInorganicMaterials (std::vector & inorganic); - bool CopyOrganicMaterials (std::vector & organic); - bool CopyWoodMaterials (std::vector & tree); - bool CopyPlantMaterials (std::vector & plant); - - bool ReadCreatureTypes (void); - bool ReadCreatureTypesEx (void); - bool ReadDescriptorColors(void); - bool ReadOthers (void); - - bool ReadAllMaterials(void); - - std::string getType(const t_material & mat); - std::string getDescription(const t_material & mat); - }; + /** + * The Materials module + * \ingroup grp_modules + * \ingroup grp_materials + */ + + std::string getType(const t_material& mat); + std::string getDescription(const t_material& mat); + } } #endif diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index d5358f6139f..3351726d57e 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -68,7 +68,7 @@ using namespace df::enums; using df::global::world; using df::global::plotinfo; -bool MaterialInfo::decode(df::item *item) +bool MaterialInfo::decode(df::item* item) { if (!item) return decode(-1); @@ -76,7 +76,7 @@ bool MaterialInfo::decode(df::item *item) return decode(item->getActualMaterial(), item->getActualMaterialIndex()); } -bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) +bool MaterialInfo::decode(const df::material_vec_ref& vr, int idx) { if (size_t(idx) >= vr.mat_type.size() || size_t(idx) >= vr.mat_index.size()) return decode(-1); @@ -94,14 +94,15 @@ bool MaterialInfo::decode(int16_t type, int32_t index) inorganic = NULL; plant = NULL; creature = NULL; figure = NULL; - if (type < 0) { + if (type < 0) + { mode = None; return false; } - auto &raws = world->raws; + auto& raws = world->raws; - if (size_t(type) >= sizeof(raws.mat_table.builtin)/sizeof(void*)) + if (size_t(type) >= sizeof(raws.mat_table.builtin) / sizeof(void*)) return false; if (index < 0) @@ -123,7 +124,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < FIGURE_BASE) { mode = Creature; - subtype = type-CREATURE_BASE; + subtype = type - CREATURE_BASE; creature = df::creature_raw::find(index); if (!creature || size_t(subtype) >= creature->material.size()) return false; @@ -132,7 +133,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < PLANT_BASE) { mode = Creature; - subtype = type-FIGURE_BASE; + subtype = type - FIGURE_BASE; figure = df::historical_figure::find(index); if (!figure) return false; @@ -144,7 +145,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < END_BASE) { mode = Plant; - subtype = type-PLANT_BASE; + subtype = type - PLANT_BASE; plant = df::plant_raw::find(index); if (!plant || size_t(subtype) >= plant->material.size()) return false; @@ -158,24 +159,24 @@ bool MaterialInfo::decode(int16_t type, int32_t index) return (material != NULL); } -bool MaterialInfo::find(const std::string &token) +bool MaterialInfo::find(const std::string& token) { std::vector items; split_string(&items, token, ":"); return find(items); } -bool MaterialInfo::find(const std::vector &items) +bool MaterialInfo::find(const std::vector& items) { if (items.empty()) return false; if (items[0] == "INORGANIC" && items.size() > 1) - return findInorganic(vector_get(items,1)); + return findInorganic(vector_get(items, 1)); if (items[0] == "CREATURE_MAT" || items[0] == "CREATURE") - return findCreature(vector_get(items,1), vector_get(items,2)); + return findCreature(vector_get(items, 1), vector_get(items, 2)); if (items[0] == "PLANT_MAT" || items[0] == "PLANT") - return findPlant(vector_get(items,1), vector_get(items,2)); + return findPlant(vector_get(items, 1), vector_get(items, 2)); if (items.size() == 1) { @@ -188,7 +189,8 @@ bool MaterialInfo::find(const std::vector &items) } else if (items.size() == 2) { - if (items[0] == "COAL" && findBuiltin(items[0])) { + if (items[0] == "COAL" && findBuiltin(items[0])) + { if (items[1] == "COKE") this->index = 0; else if (items[1] == "CHARCOAL") @@ -206,17 +208,18 @@ bool MaterialInfo::find(const std::vector &items) return false; } -bool MaterialInfo::findBuiltin(const std::string &token) +bool MaterialInfo::findBuiltin(const std::string& token) { if (token.empty()) return decode(-1); - if (token == "NONE") { + if (token == "NONE") + { decode(-1); return true; } - auto &raws = world->raws; + auto& raws = world->raws; for (int i = 0; i < NUM_BUILTIN; i++) { auto obj = raws.mat_table.builtin[i]; @@ -226,34 +229,35 @@ bool MaterialInfo::findBuiltin(const std::string &token) return decode(-1); } -bool MaterialInfo::findInorganic(const std::string &token) +bool MaterialInfo::findInorganic(const std::string& token) { if (token.empty()) return decode(-1); - if (token == "NONE") { + if (token == "NONE") + { decode(0, -1); return true; } - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.inorganics.all.size(); i++) { - df::inorganic_raw *p = raws.inorganics.all[i]; + df::inorganic_raw* p = raws.inorganics.all[i]; if (p->id == token) return decode(0, i); } return decode(-1); } -bool MaterialInfo::findPlant(const std::string &token, const std::string &subtoken) +bool MaterialInfo::findPlant(const std::string& token, const std::string& subtoken) { if (token.empty()) return decode(-1); - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.plants.all.size(); i++) { - df::plant_raw *p = raws.plants.all[i]; + df::plant_raw* p = raws.plants.all[i]; if (p->id != token) continue; @@ -263,39 +267,39 @@ bool MaterialInfo::findPlant(const std::string &token, const std::string &subtok for (size_t j = 0; j < p->material.size(); j++) if (p->material[j]->id == subtoken) - return decode(PLANT_BASE+j, i); + return decode(PLANT_BASE + j, i); break; } return decode(-1); } -bool MaterialInfo::findCreature(const std::string &token, const std::string &subtoken) +bool MaterialInfo::findCreature(const std::string& token, const std::string& subtoken) { if (token.empty() || subtoken.empty()) return decode(-1); - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.creatures.all.size(); i++) { - df::creature_raw *p = raws.creatures.all[i]; + df::creature_raw* p = raws.creatures.all[i]; if (p->creature_id != token) continue; for (size_t j = 0; j < p->material.size(); j++) if (p->material[j]->id == subtoken) - return decode(CREATURE_BASE+j, i); + return decode(CREATURE_BASE + j, i); break; } return decode(-1); } -bool MaterialInfo::findProduct(df::material *material, const std::string &name) +bool MaterialInfo::findProduct(df::material* material, const std::string& name) { if (!material || name.empty()) return decode(-1); - auto &pids = material->reaction_product.id; + auto& pids = material->reaction_product.id; for (size_t i = 0; i < pids.size(); i++) if ((*pids[i]) == name) return decode(material->reaction_product.material, i); @@ -311,9 +315,11 @@ std::string MaterialInfo::getToken() const if (!material) return fmt::format("INVALID:{}:{}", type, index); - switch (mode) { + switch (mode) + { case Builtin: - if (material->id == "COAL") { + if (material->id == "COAL") + { if (index == 0) return "COAL:COKE"; else if (index == 1) @@ -382,10 +388,10 @@ bool MaterialInfo::isAnyCloth() const material->flags.is_set(THREAD_PLANT) || material->flags.is_set(SILK) || material->flags.is_set(YARN) - ); + ); } -bool MaterialInfo::matches(const df::job_material_category &cat) const +bool MaterialInfo::matches(const df::job_material_category& cat) const { if (!material) return false; @@ -414,7 +420,7 @@ bool MaterialInfo::matches(const df::job_material_category &cat) const return false; } -bool MaterialInfo::matches(const df::dfhack_material_category &cat) const +bool MaterialInfo::matches(const df::dfhack_material_category& cat) const { if (!material) return false; @@ -443,7 +449,7 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) const #undef TEST -bool MaterialInfo::matches(const df::job_item &jitem, df::item_type itype) const +bool MaterialInfo::matches(const df::job_item& jitem, df::item_type itype) const { if (!isValid()) return false; @@ -460,11 +466,11 @@ bool MaterialInfo::matches(const df::job_item &jitem, df::item_type itype) const mask2.whole &= ~xmask2.whole; return bits_match(jitem.flags1.whole, ok1.whole, mask1.whole) && - bits_match(jitem.flags2.whole, ok2.whole, mask2.whole) && - bits_match(jitem.flags3.whole, ok3.whole, mask3.whole); + bits_match(jitem.flags2.whole, ok2.whole, mask2.whole) && + bits_match(jitem.flags3.whole, ok3.whole, mask3.whole); } -void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags1& ok, df::job_item_flags1& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -486,10 +492,10 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma TEST(processable_to_vial, structural && FLAG(plant, plant_raw_flags::EXTRACT_VIAL)); TEST(processable_to_barrel, structural && FLAG(plant, plant_raw_flags::EXTRACT_BARREL)); TEST(solid, !(MAT_FLAG(ALCOHOL_PLANT) || - MAT_FLAG(ALCOHOL_CREATURE) || - MAT_FLAG(LIQUID_MISC_PLANT) || - MAT_FLAG(LIQUID_MISC_CREATURE) || - MAT_FLAG(LIQUID_MISC_OTHER))); + MAT_FLAG(ALCOHOL_CREATURE) || + MAT_FLAG(LIQUID_MISC_PLANT) || + MAT_FLAG(LIQUID_MISC_CREATURE) || + MAT_FLAG(LIQUID_MISC_OTHER))); TEST(tameable_vermin, false); TEST(sharpenable, MAT_FLAG(IS_STONE)); TEST(milk, linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0); @@ -497,7 +503,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma //04000000 - "milkable" - vtable[107],1,1 } -void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags2& ok, df::job_item_flags2& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -511,15 +517,15 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(glass_making, MAT_FLAG(CRYSTAL_GLASSABLE)); TEST(fire_safe, material->heat.melting_point > 11000 - && material->heat.boiling_point > 11000 - && material->heat.ignite_point > 11000 - && material->heat.heatdam_point > 11000 - && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 11000)); + && material->heat.boiling_point > 11000 + && material->heat.ignite_point > 11000 + && material->heat.heatdam_point > 11000 + && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 11000)); TEST(magma_safe, material->heat.melting_point > 12000 - && material->heat.boiling_point > 12000 - && material->heat.ignite_point > 12000 - && material->heat.heatdam_point > 12000 - && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 12000)); + && material->heat.boiling_point > 12000 + && material->heat.ignite_point > 12000 + && material->heat.heatdam_point > 12000 + && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 12000)); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); TEST(non_economic, !inorganic || !(plotinfo && vector_get(plotinfo->economic_stone, index))); @@ -537,7 +543,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(yarn, MAT_FLAG(YARN)); } -void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags3& ok, df::job_item_flags3& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -549,7 +555,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &ma #undef FLAG #undef TEST -bool DFHack::parseJobMaterialCategory(df::job_material_category *cat, const std::string &token) +bool DFHack::parseJobMaterialCategory(df::job_material_category* cat, const std::string& token) { cat->whole = 0; @@ -565,7 +571,7 @@ bool DFHack::parseJobMaterialCategory(df::job_material_category *cat, const std: return true; } -bool DFHack::parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token) +bool DFHack::parseJobMaterialCategory(df::dfhack_material_category* cat, const std::string& token) { cat->whole = 0; @@ -600,32 +606,14 @@ bool DFHack::isStoneInorganic(int material) return true; } -std::unique_ptr DFHack::createMaterials() -{ - return std::make_unique(); -} - -Materials::Materials() -{ -} - -Materials::~Materials() -{ -} - -bool Materials::Finish() -{ - return true; -} - t_matgloss::t_matgloss() { - fore = 0; - back = 0; - bright = 0; + fore = 0; + back = 0; + bright = 0; - value = 0; - wall_tile = 0; + value = 0; + wall_tile = 0; boulder_tile = 0; } @@ -643,240 +631,7 @@ bool t_matglossInorganic::isGem() return is_gem; } -bool Materials::CopyInorganicMaterials (std::vector & inorganic) -{ - size_t size = world->raws.inorganics.all.size(); - inorganic.clear(); - inorganic.reserve (size); - for (size_t i = 0; i < size;i++) - { - df::inorganic_raw *orig = world->raws.inorganics.all[i]; - t_matglossInorganic mat; - mat.id = orig->id; - mat.name = orig->material.stone_name; - - mat.ore_types = orig->metal_ore.mat_index; - mat.ore_chances = orig->metal_ore.probability; - mat.strand_types = orig->thread_metal.mat_index; - mat.strand_chances = orig->thread_metal.probability; - mat.value = orig->material.material_value; - mat.wall_tile = orig->material.tile; - mat.boulder_tile = orig->material.item_symbol; - mat.fore = orig->material.basic_color[0]; - mat.bright = orig->material.basic_color[1]; - mat.is_gem = orig->material.flags.is_set(material_flags::IS_GEM); - inorganic.push_back(mat); - } - return true; -} - -bool Materials::CopyOrganicMaterials (std::vector & organic) -{ - size_t size = world->raws.plants.all.size(); - organic.clear(); - organic.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.all[i]->id; - organic.push_back(mat); - } - return true; -} - -bool Materials::CopyWoodMaterials (std::vector & tree) -{ - size_t size = world->raws.plants.trees.size(); - tree.clear(); - tree.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.trees[i]->id; - tree.push_back(mat); - } - return true; -} - -bool Materials::CopyPlantMaterials (std::vector & plant) -{ - size_t size = world->raws.plants.bushes.size(); - plant.clear(); - plant.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.bushes[i]->id; - plant.push_back(mat); - } - return true; -} - -bool Materials::ReadCreatureTypes (void) -{ - size_t size = world->raws.creatures.all.size(); - race.clear(); - race.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.creatures.all[i]->creature_id; - race.push_back(mat); - } - return true; -} - -bool Materials::ReadOthers(void) -{ - other.clear(); - FOR_ENUM_ITEMS(builtin_mats, i) - { - t_matglossOther mat; - mat.id = world->raws.mat_table.builtin[i]->id; - other.push_back(mat); - } - return true; -} - -bool Materials::ReadDescriptorColors (void) -{ - size_t size = world->raws.descriptors.colors.size(); - - color.clear(); - if(size == 0) - return false; - color.reserve(size); - for (size_t i = 0; i < size;i++) - { - df::descriptor_color *c = world->raws.descriptors.colors[i]; - t_descriptor_color col; - col.id = c->id; - col.name = c->name; - col.red = c->red; - col.green = c->green; - col.blue = c->blue; - color.push_back(col); - } - - size = world->raws.descriptors.patterns.size(); - alldesc.clear(); - alldesc.reserve(size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.descriptors.patterns[i]->id; - alldesc.push_back(mat); - } - return true; -} - -bool Materials::ReadCreatureTypesEx (void) -{ - size_t size = world->raws.creatures.all.size(); - raceEx.clear(); - raceEx.reserve (size); - for (size_t i = 0; i < size; i++) - { - df::creature_raw *cr = world->raws.creatures.all[i]; - t_creaturetype mat; - mat.id = cr->creature_id; - mat.tile_character = cr->creature_tile; - mat.tilecolor.fore = cr->color[0]; - mat.tilecolor.back = cr->color[1]; - mat.tilecolor.bright = cr->color[2]; - - size_t sizecas = cr->caste.size(); - for (size_t j = 0; j < sizecas;j++) - { - df::caste_raw *ca = cr->caste[j]; - /* caste name */ - t_creaturecaste caste; - caste.id = ca->caste_id; - caste.singular = ca->caste_name[0]; - caste.plural = ca->caste_name[1]; - caste.adjective = ca->caste_name[2]; - - // color mod reading - // Caste + offset > color mod vector - auto & colorings = ca->color_modifiers; - size_t sizecolormod = colorings.size(); - caste.ColorModifier.resize(sizecolormod); - for(size_t k = 0; k < sizecolormod;k++) - { - // color mod [0] -> color list - auto & indexes = colorings[k]->pattern_index; - size_t sizecolorlist = indexes.size(); - caste.ColorModifier[k].colorlist.resize(sizecolorlist); - for(size_t l = 0; l < sizecolorlist; l++) - caste.ColorModifier[k].colorlist[l] = indexes[l]; - // color mod [color_modifier_part_offset] = string part - caste.ColorModifier[k].part = colorings[k]->part; - caste.ColorModifier[k].startdate = colorings[k]->start_date; - caste.ColorModifier[k].enddate = colorings[k]->end_date; - } - - // body parts - caste.bodypart.clear(); - size_t sizebp = ca->body_info.body_parts.size(); - for (size_t k = 0; k < sizebp; k++) - { - df::body_part_raw *bp = ca->body_info.body_parts[k]; - t_bodypart part; - part.id = bp->token; - part.category = bp->category; - caste.bodypart.push_back(part); - } - using namespace df::enums::mental_attribute_type; - using namespace df::enums::physical_attribute_type; - for (int32_t k = 0; k < 7; k++) - { - auto & physical = ca->attributes.phys_att_range; - caste.strength[k] = physical[STRENGTH][k]; - caste.agility[k] = physical[AGILITY][k]; - caste.toughness[k] = physical[TOUGHNESS][k]; - caste.endurance[k] = physical[ENDURANCE][k]; - caste.recuperation[k] = physical[RECUPERATION][k]; - caste.disease_resistance[k] = physical[DISEASE_RESISTANCE][k]; - - auto & mental = ca->attributes.ment_att_range; - caste.analytical_ability[k] = mental[ANALYTICAL_ABILITY][k]; - caste.focus[k] = mental[FOCUS][k]; - caste.willpower[k] = mental[WILLPOWER][k]; - caste.creativity[k] = mental[CREATIVITY][k]; - caste.intuition[k] = mental[INTUITION][k]; - caste.patience[k] = mental[PATIENCE][k]; - caste.memory[k] = mental[MEMORY][k]; - caste.linguistic_ability[k] = mental[LINGUISTIC_ABILITY][k]; - caste.spatial_sense[k] = mental[SPATIAL_SENSE][k]; - caste.musicality[k] = mental[MUSICALITY][k]; - caste.kinesthetic_sense[k] = mental[KINESTHETIC_SENSE][k]; - caste.empathy[k] = mental[EMPATHY][k]; - caste.social_awareness[k] = mental[SOCIAL_AWARENESS][k]; - } - mat.castes.push_back(caste); - } - for (size_t j = 0; j < world->raws.creatures.all[i]->material.size(); j++) - { - t_creatureextract extract; - extract.id = world->raws.creatures.all[i]->material[j]->id; - mat.extract.push_back(extract); - } - raceEx.push_back(mat); - } - return true; -} - -bool Materials::ReadAllMaterials(void) -{ - bool ok = true; - ok &= this->ReadCreatureTypes(); - ok &= this->ReadCreatureTypesEx(); - ok &= this->ReadDescriptorColors(); - ok &= this->ReadOthers(); - return ok; -} - -std::string Materials::getDescription(const t_material & mat) +std::string Materials::getDescription(const t_material& mat) { MaterialInfo mi(mat.mat_type, mat.mat_index); if (mi.creature) @@ -889,7 +644,7 @@ std::string Materials::getDescription(const t_material & mat) // type of material only so we know which vector to retrieve // This is completely worthless now -std::string Materials::getType(const t_material & mat) +std::string Materials::getType(const t_material& mat) { MaterialInfo mi(mat.mat_type, mat.mat_index); switch (mi.mode) From a527e8cdcbd954ff1c66cfd58c9f7f957eefe1a4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 24 May 2026 03:26:59 -0500 Subject: [PATCH 199/202] sunset the old Modules implementation not relevant since we move to in-process --- library/CMakeLists.txt | 2 - library/Core.cpp | 21 ---------- library/include/Core.h | 5 --- library/include/Module.h | 59 ----------------------------- library/include/ModuleFactory.h | 38 ------------------- library/include/modules/Materials.h | 1 - library/modules/Buildings.cpp | 1 - library/modules/Gui.cpp | 1 - library/modules/Items.cpp | 1 - library/modules/Kitchen.cpp | 1 - library/modules/MapCache.cpp | 1 - library/modules/Maps.cpp | 1 - library/modules/Materials.cpp | 1 - library/modules/Random.cpp | 1 - library/modules/Screen.cpp | 1 - library/modules/Units.cpp | 1 - 16 files changed, 136 deletions(-) delete mode 100644 library/include/Module.h delete mode 100644 library/include/ModuleFactory.h diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6da49099681..d87e20531e0 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -66,10 +66,8 @@ set(MAIN_HEADERS include/MemAccess.h include/Memory.h include/MiscUtils.h - include/Module.h include/MemAccess.h include/MemoryPatcher.h - include/ModuleFactory.h include/PluginLua.h include/PluginManager.h include/PluginStatics.h diff --git a/library/Core.cpp b/library/Core.cpp index 50c6fe5f8bd..ec105a6c6bd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -40,8 +40,6 @@ distribution. #include "MemoryPatcher.h" #include "MiscUtils.h" #include "MiscUtils.h" -#include "Module.h" -#include "ModuleFactory.h" #include "PluginManager.h" #include "RemoteServer.h" #include "RemoteTools.h" @@ -1047,7 +1045,6 @@ Core::Core() : plug_mgr = nullptr; errorstate = false; vinfo = 0; - memset(&(s_mods), 0, sizeof(s_mods)); // set up hotkey capture suppress_duplicate_keyboard_events = true; @@ -1949,11 +1946,9 @@ int Core::Shutdown ( void ) plug_mgr = nullptr; } // invalidate all modules - allModules.clear(); Textures::cleanup(); DFSDL::cleanup(); DFSteam::cleanup(getConsole()); - memset(&(s_mods), 0, sizeof(s_mods)); d.reset(); return -1; } @@ -2184,19 +2179,3 @@ std::string Core::GetAliasCommand(const std::string &name, bool ignore_params) return join_strings(" ", aliases[name]); } -/******************************************************************************* - M O D U L E S -*******************************************************************************/ - -#define MODULE_GETTER(TYPE) \ -TYPE * Core::get##TYPE() \ -{ \ - if(errorstate) return nullptr;\ - if(!s_mods.p##TYPE)\ - {\ - std::unique_ptr mod = create##TYPE();\ - s_mods.p##TYPE = (TYPE *) mod.get();\ - allModules.push_back(std::move(mod));\ - }\ - return s_mods.p##TYPE;\ -} diff --git a/library/include/Core.h b/library/include/Core.h index 085dd33cf1a..8b78e580970 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -288,11 +288,6 @@ namespace DFHack // FIXME: shouldn't be kept around like this std::unique_ptr vif; - // Module storage - struct - { - } s_mods; - std::vector> allModules; DFHack::PluginManager *plug_mgr; // Hotkey Manager diff --git a/library/include/Module.h b/library/include/Module.h deleted file mode 100644 index 7770e13970b..00000000000 --- a/library/include/Module.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once - -#ifndef MODULE_H_INCLUDED -#define MODULE_H_INCLUDED - -#include "Export.h" -namespace DFHack -{ - /** - * The parent class for all DFHack modules - * \ingroup grp_modules - */ - class DFHACK_EXPORT Module - { - public: - virtual ~Module(){}; - virtual bool Start(){return true;};// default start... - virtual bool Finish() = 0;// everything should have a Finish() - /* - // should Context call Finish when Resume is called? - virtual bool OnResume() - { - Finish(); - return true; - }; - // Finish when map change is detected? - // TODO: implement - virtual bool OnMapChange() - { - return false; - }; - */ - }; -} -#endif //MODULE_H_INCLUDED diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h deleted file mode 100644 index c99e7b32898..00000000000 --- a/library/include/ModuleFactory.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once - -#ifndef MODULE_FACTORY_H_INCLUDED -#define MODULE_FACTORY_H_INCLUDED - -#include - -namespace DFHack -{ - class Module; - std::unique_ptr createMaterials(); - std::unique_ptr createGraphic(); -} -#endif diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 9689c7790cc..1f87f548ffc 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -30,7 +30,6 @@ distribution. * @ingroup grp_modules */ #include "Export.h" -#include "Module.h" #include "DataDefs.h" #include "df/craft_material_class.h" diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index e5a0af116b3..ddedbbc1b66 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -29,7 +29,6 @@ distribution. #include "MemAccess.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "TileTypes.h" #include "MiscUtils.h" diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 06de785a390..e7c8233ba17 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -30,7 +30,6 @@ distribution. #include "VersionInfo.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "Debug.h" #include "PluginManager.h" diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index adce8ba8a6d..6b5b3ee1d28 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -28,7 +28,6 @@ distribution. #include "Internal.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "Types.h" #include "VersionInfo.h" diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index be1415d90d5..c9d4c7d5c67 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -14,7 +14,6 @@ using namespace std; #include "Types.h" #include "Error.h" #include "modules/Kitchen.h" -#include "ModuleFactory.h" #include "Core.h" using namespace DFHack; diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index c01f5bf5769..603d1d0da29 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -30,7 +30,6 @@ distribution. #include "Error.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "VersionInfo.h" #include "modules/Buildings.h" diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 11499a42701..2376936054b 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -31,7 +31,6 @@ distribution. #include "Error.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "VersionInfo.h" #include "modules/Buildings.h" diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 3351726d57e..dba8a781324 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -28,7 +28,6 @@ distribution. #include "VersionInfo.h" #include "MemAccess.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "MiscUtils.h" diff --git a/library/modules/Random.cpp b/library/modules/Random.cpp index f0d2054c9e3..d6d682d9216 100644 --- a/library/modules/Random.cpp +++ b/library/modules/Random.cpp @@ -35,7 +35,6 @@ using namespace std; #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" -#include "ModuleFactory.h" #include "Core.h" #include "Error.h" #include "VTableInterpose.h" diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index d629c4b8cf4..b16a3e9b0e3 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -28,7 +28,6 @@ distribution. #include "VersionInfo.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "PluginManager.h" #include "LuaTools.h" diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 707437ea347..00821c11c0c 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -27,7 +27,6 @@ distribution. #include "Internal.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "Types.h" #include "VersionInfo.h" From 2c164932af0c2a401e10dec54086db6c13b1a716 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 24 May 2026 03:44:51 -0500 Subject: [PATCH 200/202] missed one --- library/modules/Translation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 38ae2c51ba3..097de85cc9f 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -27,7 +27,6 @@ distribution. #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" -#include "ModuleFactory.h" #include "Core.h" #include "Error.h" #include "DataDefs.h" From 5a8c9f3559122b0719c3d3ecc4ae9b9508a677a8 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 24 May 2026 03:45:45 -0500 Subject: [PATCH 201/202] Update Core.cpp --- library/Core.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index ec105a6c6bd..00419bd7aef 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2178,4 +2178,3 @@ std::string Core::GetAliasCommand(const std::string &name, bool ignore_params) return aliases[name][0]; return join_strings(" ", aliases[name]); } - From 9d86dc2f5a8959fb298cc0f6c80187c6d92596a3 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 18 Jun 2026 11:08:51 +0000 Subject: [PATCH 202/202] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 992957670fd..8791e2c2669 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 992957670fd99b55d10196cf2850a4554bb15374 +Subproject commit 8791e2c26693cea552c42700e38c87503b7ac7da