From 435fcd066dcd6ee0e271a67fbd2e4d1dbfec376c Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sun, 23 Mar 2025 05:35:01 -0500 Subject: [PATCH 1/8] confirm: only show pause option for pausable confirmations Prevent dialogs.showYesNoPrompt from showing the pause option by passing a nil on_pause argument when a confirmation is not pausable (e.g., trade-cancel, depot-remove). --- changelog.txt | 1 + confirm.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 49a241e338..c9b0c8ac22 100644 --- a/changelog.txt +++ b/changelog.txt @@ -37,6 +37,7 @@ Template for new versions: ## Fixes - `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session - `starvingdead`: ensure undead decay does not happen faster than the declared decay rate when saving and loading the game +- `confirm`: only show pause option for pausable confirmations ## Misc Improvements diff --git a/confirm.lua b/confirm.lua index fb0a108ed6..9fb114e9b3 100644 --- a/confirm.lua +++ b/confirm.lua @@ -131,8 +131,9 @@ function ConfirmOverlay:onInput(keys) gui.simulateInput(scr, keys) self.simulating = false end + local pause_fn = conf.pausable and curry(propagate_fn, true) or nil dialogs.showYesNoPrompt(conf.title, utils.getval(conf.message):wrap(45), COLOR_YELLOW, - propagate_fn, nil, curry(propagate_fn, true), curry(dfhack.run_script, 'gui/confirm', tostring(conf.id))) + propagate_fn, nil, pause_fn, curry(dfhack.run_script, 'gui/confirm', tostring(conf.id))) return true end end From 2a73f93541404ab181abaf06fb9f392c02c65a7a Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sun, 23 Mar 2025 05:58:55 -0500 Subject: [PATCH 2/8] confirm: handle LEAVESCREEN in uniform-discard-changes Match the _MOUSE_R handling: prompt if there are uniform changes. --- changelog.txt | 1 + internal/confirm/specs.lua | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index c9b0c8ac22..ad4e2676d7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,6 +38,7 @@ Template for new versions: - `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session - `starvingdead`: ensure undead decay does not happen faster than the declared decay rate when saving and loading the game - `confirm`: only show pause option for pausable confirmations +- `confirm`: when editing a uniform, confirm discard of changes when exiting with Escape ## Misc Improvements diff --git a/internal/confirm/specs.lua b/internal/confirm/specs.lua index 73b9179e41..03d55fae82 100644 --- a/internal/confirm/specs.lua +++ b/internal/confirm/specs.lua @@ -325,13 +325,13 @@ ConfirmSpec{ id='uniform-discard-changes', title='Discard uniform changes', message='Are you sure you want to discard changes to this uniform?', - intercept_keys={'_MOUSE_L', '_MOUSE_R'}, + intercept_keys={'LEAVESCREEN', '_MOUSE_L', '_MOUSE_R'}, -- sticks out the left side so it can move with the panel -- when the screen is resized too narrow intercept_frame={r=32, t=19, w=101, b=3}, context='dwarfmode/Squads/Equipment/Customizing/Default', predicate=function(keys, mouse_offset) - if keys._MOUSE_R then + if keys.LEAVESCREEN or keys._MOUSE_R then return uniform_has_changes() end if clicked_on_confirm_button(mouse_offset) then From fe09c126e4811ab43a5e7e1eaf1b2a954d3c11a8 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Wed, 9 Apr 2025 06:52:09 -0500 Subject: [PATCH 3/8] confirm: use interface rect for order-remove calculations When the DF interface percentage is not 100, the interface width can be smaller than the window width. For certain sizes (depending on the interface percentage), the following condition can hold: - interface width <= 154 < window width In this situation, the info window tab row will not have "unwrapped" (from four to two UI rows), but the previous order index calculation code would assume that it had (due to using the window width). This two UI row discrepancy caused the calculated order index to be one too high (and possibly out of bounds) when clicking on either of the bottom two UI rows of an order remove button. The incorrect index caused the confirmation to display the description of the following order. --- changelog.txt | 1 + internal/confirm/specs.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index ad4e2676d7..02bd55ccd1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -39,6 +39,7 @@ Template for new versions: - `starvingdead`: ensure undead decay does not happen faster than the declared decay rate when saving and loading the game - `confirm`: only show pause option for pausable confirmations - `confirm`: when editing a uniform, confirm discard of changes when exiting with Escape +- `confirm`: when removing a manager order, show correct order description when using non-100% interface setting ## Misc Improvements diff --git a/internal/confirm/specs.lua b/internal/confirm/specs.lua index 03d55fae82..9147674330 100644 --- a/internal/confirm/specs.lua +++ b/internal/confirm/specs.lua @@ -7,6 +7,7 @@ local json = require('json') local trade_internal = reqscript('internal/caravan/trade') +local gui = require('gui') local CONFIG_FILE = 'dfhack-config/confirm.json' @@ -457,7 +458,7 @@ ConfirmSpec{ message=function() local order_desc = '' local scroll_pos = mi.info.work_orders.scroll_position_work_orders - local y_offset = dfhack.screen.getWindowSize() > 154 and 8 or 10 + local y_offset = gui.get_interface_rect().width > 154 and 8 or 10 local _, y = dfhack.screen.getMousePos() if y then local order_idx = scroll_pos + (y - y_offset) // 3 From ded125a055f0d7b452b7628156ca88ce68c44f03 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Mon, 24 Mar 2025 02:38:37 -0500 Subject: [PATCH 4/8] confirm: order-remove: work around stale scroll position The scroll_position_work_orders value is being used here as the index of the first displayed order. DF seems to maintain this when the list view is updated via scrolling, but DF does not update it in at least two important situations: - when an order is removed, and - when the order list view grows enough to display additional orders (e.g., by increasing the height of the DF window). When the order list view is not scrolled all the way to the bottom, DF handles these actions by pushing orders into view at the bottom of the list view. Since the same order is still at the top of the list view, this does not require an update of the reported scroll position value. However, when the order list view is scrolled all the way to the bottom, DF handles these actions by pushing orders into view at the *top* of the list view. Since a new order is now at the top of the list view, we would expect that DF should have updated the reported scroll position, but it does not. The lack of scroll position update breaks our expectation that the reported scroll position should match the index of the first displayed order. If N orders have been removed and M order rows have been added to the order list view (after having scrolled to the bottom of the order list), our calculated order_idx will be N+M too high (causing mismatched descriptions in the confirmation dialogs, and out-of-bounds errors that entirely prevent confirmation when acting on the last N+M orders!). When there are at least as many orders as the height of the orders list view, DF seems to always keep the bottom of the list view populated. Assume this is will be case and adjust our order_idx calculation when the reported scroll position would put the effective index of the last order out of bounds. If DF's order list view ever changes to displaying empty order rows even when there are orders that could be "pushed in" from the top, this will need to be revisited. --- changelog.txt | 1 + internal/confirm/specs.lua | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 02bd55ccd1..d373953650 100644 --- a/changelog.txt +++ b/changelog.txt @@ -40,6 +40,7 @@ Template for new versions: - `confirm`: only show pause option for pausable confirmations - `confirm`: when editing a uniform, confirm discard of changes when exiting with Escape - `confirm`: when removing a manager order, show correct order description when using non-100% interface setting +- `confirm`: when removing a manager order, show correct order description after prior order removal or window resize (when scrolled to bottom of order list) ## Misc Improvements diff --git a/internal/confirm/specs.lua b/internal/confirm/specs.lua index 9147674330..4526daa6bb 100644 --- a/internal/confirm/specs.lua +++ b/internal/confirm/specs.lua @@ -458,7 +458,16 @@ ConfirmSpec{ message=function() local order_desc = '' local scroll_pos = mi.info.work_orders.scroll_position_work_orders - local y_offset = gui.get_interface_rect().width > 154 and 8 or 10 + local ir = gui.get_interface_rect() + local y_offset = ir.width > 154 and 8 or 10 + local order_rows = (ir.height - y_offset - 9) // 3 + local max_scroll_pos = math.max(0, #orders - order_rows) -- DF keeps list view "full" (no empty rows at bottom), if possible + if scroll_pos > max_scroll_pos then + -- sometimes, DF does not adjust scroll_position_work_orders (when + -- scrolled to bottom: order removed, or list view height grew); + -- compensate to keep order_idx in sync (and in bounds) + scroll_pos = max_scroll_pos + end local _, y = dfhack.screen.getMousePos() if y then local order_idx = scroll_pos + (y - y_offset) // 3 From fc3e30038ffd16d5a9c6a67b30ae57371cfdb607 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Tue, 25 Mar 2025 05:36:01 -0500 Subject: [PATCH 5/8] confirm: rework order-remove description generation Handle possibly out-of-bounds order index. Factor out call to material description generation. --- internal/confirm/specs.lua | 52 +++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/internal/confirm/specs.lua b/internal/confirm/specs.lua index 4526daa6bb..5abd2aa58a 100644 --- a/internal/confirm/specs.lua +++ b/internal/confirm/specs.lua @@ -431,7 +431,7 @@ ConfirmSpec{ end, } -local function make_order_desc(order, noun) +local function make_order_material_desc(order, noun) local desc = '' if order.mat_type >= 0 then local matinfo = dfhack.matinfo.decode(order.mat_type, order.mat_index) @@ -452,6 +452,34 @@ end local orders = df.global.world.manager_orders.all local itemdefs = df.global.world.raws.itemdefs local reactions = df.global.world.raws.reactions.reactions + +local function make_order_desc(order) + if order.job_type == df.job_type.CustomReaction then + for _, reaction in ipairs(reactions) do + if reaction.code == order.reaction_name then + return reaction.name + end + end + return '' + end + local noun + if order.job_type == df.job_type.MakeArmor then + noun = itemdefs.armor[order.item_subtype].name + elseif order.job_type == df.job_type.MakeWeapon then + noun = itemdefs.weapons[order.item_subtype].name + elseif order.job_type == df.job_type.MakePants then + noun = itemdefs.pants[order.item_subtype].name + elseif order.job_type == df.job_type.MakeTool then + noun = itemdefs.tools[order.item_subtype].name + elseif order.job_type == df.job_type.SmeltOre then + noun = 'ore' + else + -- caption is usually "verb noun(-phrase)" + noun = df.job_type.attrs[order.job_type].caption + end + return make_order_material_desc(order, noun) +end + ConfirmSpec{ id='order-remove', title='Remove manger order', @@ -471,25 +499,9 @@ ConfirmSpec{ local _, y = dfhack.screen.getMousePos() if y then local order_idx = scroll_pos + (y - y_offset) // 3 - local order = orders[order_idx] - if order.job_type == df.job_type.CustomReaction then - for _, reaction in ipairs(reactions) do - if reaction.code == order.reaction_name then - order_desc = reaction.name - end - end - elseif order.job_type == df.job_type.MakeArmor then - order_desc = make_order_desc(order, itemdefs.armor[order.item_subtype].name) - elseif order.job_type == df.job_type.MakeWeapon then - order_desc = make_order_desc(order, itemdefs.weapons[order.item_subtype].name) - elseif order.job_type == df.job_type.MakePants then - order_desc = make_order_desc(order, itemdefs.pants[order.item_subtype].name) - elseif order.job_type == df.job_type.SmeltOre then - order_desc = make_order_desc(order, 'ore') - elseif order.job_type == df.job_type.MakeTool then - order_desc = make_order_desc(order, itemdefs.tools[order.item_subtype].name) - else - order_desc = make_order_desc(order, df.job_type.attrs[order.job_type].caption) + local order = safe_index(orders, order_idx) + if order then + order_desc = make_order_desc(order) end end return ('Are you sure you want to remove this manager order?\n\n%s'):format(dfhack.capitalizeStringWords(order_desc)) From 4bce233fb8f11664c7fb5f69d4ba571e6a90b9d1 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Tue, 25 Mar 2025 05:44:44 -0500 Subject: [PATCH 6/8] confirm: more specific order-remove descriptions Let order removal confirmation show the specific variety of - shield (shield, buckler), and - helm (helm, cap, hood), - gloves (gauntlets, gloves, mittens), - shoes (shoes, high boots, low boots, socks), - ammo (bolt), - trap component (axe blade, corkscrew, ball, disc, spike), and - meal (easy, fine, lavish). Most of these were previously described using the "generic" variety ("Gloves" for gauntlets, "Shoes" for socks, etc.), but meals looked particularly odd since they were previously described as "Coral", "Green Glass", and "Clear Glass" "Prepare Meal". `itemdefs.food` is not used because those list the final item names (biscuits, stew, roast), not the meal "type" (easy, fine, lavish). --- changelog.txt | 1 + internal/confirm/specs.lua | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/changelog.txt b/changelog.txt index d373953650..603dac8b5d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -41,6 +41,7 @@ Template for new versions: - `confirm`: when editing a uniform, confirm discard of changes when exiting with Escape - `confirm`: when removing a manager order, show correct order description when using non-100% interface setting - `confirm`: when removing a manager order, show correct order description after prior order removal or window resize (when scrolled to bottom of order list) +- `confirm`: when removing a manager order, show specific item/job type for ammo, shield, helm, gloves, shoes, trap component, and meal orders ## Misc Improvements diff --git a/internal/confirm/specs.lua b/internal/confirm/specs.lua index 5abd2aa58a..8daffa92d4 100644 --- a/internal/confirm/specs.lua +++ b/internal/confirm/specs.lua @@ -453,6 +453,12 @@ local orders = df.global.world.manager_orders.all local itemdefs = df.global.world.raws.itemdefs local reactions = df.global.world.raws.reactions.reactions +local meal_type_by_ingredient_count = { + [2] = 'easy', + [3] = 'fine', + [4] = 'lavish', +} + local function make_order_desc(order) if order.job_type == df.job_type.CustomReaction then for _, reaction in ipairs(reactions) do @@ -461,16 +467,35 @@ local function make_order_desc(order) end end return '' + elseif order.job_type == df.job_type.PrepareMeal then + -- DF uses mat_type as ingredient count? + local meal_type = meal_type_by_ingredient_count[order.mat_type] + if meal_type then + return 'prepare ' .. meal_type .. ' meal' + end + return 'prepare meal' end local noun if order.job_type == df.job_type.MakeArmor then noun = itemdefs.armor[order.item_subtype].name elseif order.job_type == df.job_type.MakeWeapon then noun = itemdefs.weapons[order.item_subtype].name + elseif order.job_type == df.job_type.MakeShield then + noun = itemdefs.shields[order.item_subtype].name + elseif order.job_type == df.job_type.MakeAmmo then + noun = itemdefs.ammo[order.item_subtype].name + elseif order.job_type == df.job_type.MakeHelm then + noun = itemdefs.helms[order.item_subtype].name + elseif order.job_type == df.job_type.MakeGloves then + noun = itemdefs.gloves[order.item_subtype].name elseif order.job_type == df.job_type.MakePants then noun = itemdefs.pants[order.item_subtype].name + elseif order.job_type == df.job_type.MakeShoes then + noun = itemdefs.shoes[order.item_subtype].name elseif order.job_type == df.job_type.MakeTool then noun = itemdefs.tools[order.item_subtype].name + elseif order.job_type == df.job_type.MakeTrapComponent then + noun = itemdefs.trapcomps[order.item_subtype].name elseif order.job_type == df.job_type.SmeltOre then noun = 'ore' else From fb25d4d2e7d0b34cbdbb9c1f175573729cb5ae02 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sun, 23 Mar 2025 06:13:55 -0500 Subject: [PATCH 7/8] confirm: only pause specific confirmations Pausing a confirmation currently pauses all confirmations, not just other instances of the current confirmation. For example, when trading, pausing a Mark All confirmation will also skip confirmation of the Seize action. The prompt in showYesNoPrompt is "Pause this confirmation". To better match that description, only pause new occurrences of the current confirmation. --- changelog.txt | 1 + confirm.lua | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 603dac8b5d..6b3d279ae1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,6 +42,7 @@ Template for new versions: - `confirm`: when removing a manager order, show correct order description when using non-100% interface setting - `confirm`: when removing a manager order, show correct order description after prior order removal or window resize (when scrolled to bottom of order list) - `confirm`: when removing a manager order, show specific item/job type for ammo, shield, helm, gloves, shoes, trap component, and meal orders +- `confirm`: the pause option now only pauses future instances of the current confirmation instead of all confirmations in the current context ## Misc Improvements diff --git a/confirm.lua b/confirm.lua index 9fb114e9b3..5d1944d29b 100644 --- a/confirm.lua +++ b/confirm.lua @@ -108,12 +108,15 @@ function ConfirmOverlay:matches_conf(conf, keys, scr) end function ConfirmOverlay:onInput(keys) - if self.paused_conf or self.simulating then + if self.simulating then return false end local scr = dfhack.gui.getDFViewscreen(true) for id, conf in pairs(specs.REGISTRY) do if specs.config.data[id].enabled and self:matches_conf(conf, keys, scr) then + if conf == self.paused_conf then + return false + end local mouse_pos = xy2pos(dfhack.screen.getMousePos()) local propagate_fn = function(pause) if conf.on_propagate then From d73e2dc1afd205272d5ade3a47b72d9bf5998e6d Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sat, 3 May 2025 21:23:04 -0500 Subject: [PATCH 8/8] confirm: allow multiple confirmations to be paused Allowing only a single, mutually exclusive, paused confirmation was likely to be confusing. Per review from lethosor. --- changelog.txt | 2 +- confirm.lua | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6b3d279ae1..6d14f32e86 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,7 +42,7 @@ Template for new versions: - `confirm`: when removing a manager order, show correct order description when using non-100% interface setting - `confirm`: when removing a manager order, show correct order description after prior order removal or window resize (when scrolled to bottom of order list) - `confirm`: when removing a manager order, show specific item/job type for ammo, shield, helm, gloves, shoes, trap component, and meal orders -- `confirm`: the pause option now only pauses future instances of the current confirmation instead of all confirmations in the current context +- `confirm`: the pause option now pauses individual confirmation types, allowing multiple different confirmations to be paused independently ## Misc Improvements diff --git a/confirm.lua b/confirm.lua index 5d1944d29b..5142013d1f 100644 --- a/confirm.lua +++ b/confirm.lua @@ -63,6 +63,7 @@ function ConfirmOverlay:init() } end end + self.paused_confs = {} end function ConfirmOverlay:preUpdateLayout() @@ -77,11 +78,14 @@ function ConfirmOverlay:preUpdateLayout() end function ConfirmOverlay:overlay_onupdate() - if self.paused_conf and - not dfhack.gui.matchFocusString(self.paused_conf.context, + for conf in pairs(self.paused_confs) do + if not dfhack.gui.matchFocusString(conf.context, dfhack.gui.getDFViewscreen(true)) - then - self.paused_conf = nil + then + self.paused_confs[conf] = nil + end + end + if not next(self.paused_confs) then self.overlay_onupdate_max_freq_seconds = 300 end end @@ -114,7 +118,7 @@ function ConfirmOverlay:onInput(keys) local scr = dfhack.gui.getDFViewscreen(true) for id, conf in pairs(specs.REGISTRY) do if specs.config.data[id].enabled and self:matches_conf(conf, keys, scr) then - if conf == self.paused_conf then + if self.paused_confs[conf] then return false end local mouse_pos = xy2pos(dfhack.screen.getMousePos()) @@ -123,7 +127,7 @@ function ConfirmOverlay:onInput(keys) conf.on_propagate() end if pause then - self.paused_conf = conf + self.paused_confs[conf] = true self.overlay_onupdate_max_freq_seconds = 0 end if keys._MOUSE_L then