Skip to content

confirm fixes: pausing, Escape from uniform, order-remove descriptions #1446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ 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
- `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
- `confirm`: the pause option now only pauses future instances of the current confirmation instead of all confirmations in the current context

## Misc Improvements

Expand Down
8 changes: 6 additions & 2 deletions confirm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this now only allow pausing one confirmation per screen instead of all of them?

Incidentally, I agree the pause behavior should not affect all confirmations on the same screen - that's how the original implementation worked too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that change does make it so that only one confirmation can effectively be paused at a time: pausing a confirmation would reenable any previously paused confirmation.

Would you prefer it be changed to allow multiple simultaneous (individually) paused confirmations? For example, during trading, "unmark all fort goods" and "offer as gift" could both be paused, inhibiting those confirmations, without inhibiting confirmations for "seize", "trade", or other trade actions.

Or I could tighten up my description (commit and changelog) to indicate that pauses are mutually exclusive.

Copy link
Member

@lethosor lethosor May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think pauses should be mutually exclusive. If someone wants to stop seeing more than one confirmation temporarily, they should be able to do so. This is how the feature was originally implemented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I might be confusing this with the original "settings" screen, which let you quickly disable the currently-visible confirmation, not pause it. It does appear that the old "pause" logic only paused the confirmation for the lifetime of the screen - this is a little harder to track now that DF doesn't use full screens for much.

I still feel like the behavior where pausing a second confirmation unpauses the first one is unintuitive, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having multiple, independently paused confirmations does seem like it would be less surprising.

The initial change was a minimal code change to keep one paused confirmation from affecting all confirmations in the same context. I failed to explore better functionality that could be arranged with a bit more code.

Thanks!

return false
end
local mouse_pos = xy2pos(dfhack.screen.getMousePos())
local propagate_fn = function(pause)
if conf.on_propagate then
Expand All @@ -131,8 +134,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
Expand Down
93 changes: 70 additions & 23 deletions internal/confirm/specs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -325,13 +326,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
Expand Down Expand Up @@ -430,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)
Expand All @@ -451,35 +452,81 @@ 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 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
if reaction.code == order.reaction_name then
return reaction.name
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
-- 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',
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 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
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))
Expand Down