diff --git a/docs/changelog.txt b/docs/changelog.txt index cab5ec1bfa..595907c5b5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -60,9 +60,10 @@ Template for new versions: ## New Features - `autodump`: new ``undestroy`` option reverts pending item destruction while the game is still paused +- `buildingplan`: new "Do now" option marks the jobs of subsequently placed buildings as high priority +- `getplants`: new ``--dry-run`` option counts affected plants without designating, and new ``--brewable``/``--edible``/``--oil``/``--cloth``/``--dye`` trait filters restrict the selection by plant use - ``quickfix``: repair ``plotinfo.site_id`` on load when DF leaves it unassigned on reclaimed fortresses - `stocks`: the overlay's ``collapse all`` hotkey now toggles, expanding all categories again when everything is collapsed -- `getplants`: new ``--dry-run`` option counts affected plants without designating, and new ``--brewable``/``--edible``/``--oil``/``--cloth``/``--dye`` trait filters restrict the selection by plant use ## Fixes - Fix broken weather lookup in ``World::ReadCurrentWeather`` diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 01c864f7cc..e06b3586a8 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -146,6 +146,12 @@ Some building types will have other options available as well, such as a selector for how many weapons you want in weapon traps or whether you want to only build engraved slabs. +If you want placed buildings to be built as soon as possible, you can toggle +the "Do now" option (or hit :kbd:`n`). While it is on, the job for each placed +building is flagged as high priority, so dwarves will prefer it over their +regular work once the building is unsuspended. The option stays on until you +toggle it off again, and it is saved with your configuration. + Setting quality and material filters ++++++++++++++++++++++++++++++++++++ @@ -227,6 +233,9 @@ to the building. If there is a particular building that you need built ASAP, you can click on the "make top priority" button (or hit :kbd:`Ctrl`:kbd:`T`) to bump the items for this building to the front of their respective queues. +To rush the construction job itself once the building is underway, use the +"Make top priority" toggle in the `prioritize` overlay. + Note that each item type and filter configuration has its own queue, so even if an item is in queue position 1, there may be other queues that snag the needed item first. diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index d6eb5fb9ae..d094f59c9d 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -59,6 +59,19 @@ function is_suspendmanager_enabled() return require('plugins.suspendmanager').isEnabled() end +-- The flag lives on the job, which exists as soon as the building is +-- designated, so it can be set before the job is unsuspended and it persists +-- with the save like any other job flag. +function setDoNow(bld, val) + if not bld or #bld.jobs == 0 then return false end + bld.jobs[0].flags.do_now = val + return true +end + +function getDoNow(bld) + return bld ~= nil and #bld.jobs > 0 and bld.jobs[0].flags.do_now +end + function get_num_filters(btype, subtype, custom) local filters = dfhack.buildings.getFiltersByType({}, btype, subtype, custom) return filters and #filters or 0 diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index d7163bcd91..352286f626 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -818,6 +818,19 @@ function PlannerOverlay:init() buildingplan.setSpecial(uibs.building_type, uibs.building_subtype, uibs.custom_type, 'empty', val) end, }, + widgets.ToggleHotkeyLabel{ + view_id='do_now', + -- b=4 in the left column is the favorites divider row; the first + -- free row of the options block is b=3 in the right column + frame={b=3, l=24, w=25}, + key='CUSTOM_N', + label='Do now:', + initial_option=self.state.do_now or false, + on_change=function(val) + self.state.do_now = val + config:write() + end, + }, widgets.Panel{ visible=function() return #get_cur_filters() > 0 end, subviews={ @@ -1512,6 +1525,11 @@ function PlannerOverlay:place_building(placement_data, chosen_items) end end buildingplan.addPlannedBuilding(bld) + -- the job already exists at designation time; flagging it now means it + -- is posted with do_now whenever it gets unsuspended + if self.state.do_now then + buildingplan.setDoNow(bld, true) + end end buildingplan.scheduleCycle() uibs.selection_pos:clear() diff --git a/test/plugins/buildingplan.lua b/test/plugins/buildingplan.lua new file mode 100644 index 0000000000..f2f48e92f2 --- /dev/null +++ b/test/plugins/buildingplan.lua @@ -0,0 +1,70 @@ +config.mode = 'fortress' +config.target = 'buildingplan' + +local buildingplan = require('plugins.buildingplan') + +local function find_floor_pos() + for _, unit in ipairs(df.global.world.units.active) do + for dx = -4, 4 do + for dy = -4, 4 do + local pos = xyz2pos(unit.pos.x + dx, unit.pos.y + dy, unit.pos.z) + local tt = dfhack.maps.getTileType(pos) + local block = dfhack.maps.getTileBlock(pos) + if tt and block + and df.tiletype.attrs[tt].shape == df.tiletype_shape.FLOOR + and block.occupancy[pos.x % 16][pos.y % 16].building == 0 then + return pos + end + end + end + end +end + +local function place_box(pos) + return dfhack.buildings.constructBuilding{ + pos=pos, + type=df.building_type.Box, subtype=-1, custom=-1, + width=1, height=1, + filters=dfhack.buildings.getFiltersByType({}, df.building_type.Box, -1, -1), + } +end + +function test.set_get_do_now() + local pos = find_floor_pos() + expect.ne(nil, pos, 'test needs a free floor tile') + local bld = place_box(pos) + expect.ne(nil, bld, 'failed to place test building') + dfhack.with_finalize( + function() dfhack.buildings.deconstruct(bld) end, + function() + expect.false_(buildingplan.getDoNow(bld)) + expect.true_(buildingplan.setDoNow(bld, true)) + expect.true_(buildingplan.getDoNow(bld)) + expect.true_(bld.jobs[0].flags.do_now) + expect.true_(buildingplan.setDoNow(bld, false)) + expect.false_(buildingplan.getDoNow(bld)) + end) +end + +function test.do_now_invalid_input() + expect.false_(buildingplan.setDoNow(nil, true)) + expect.false_(buildingplan.getDoNow(nil)) +end + +function test.do_now_flag_survives_registration() + local pos = find_floor_pos() + expect.ne(nil, pos, 'test needs a free floor tile') + local bld = place_box(pos) + expect.ne(nil, bld, 'failed to place test building') + dfhack.with_finalize( + function() dfhack.buildings.deconstruct(bld) end, + function() + buildingplan.addPlannedBuilding(bld) + expect.true_(buildingplan.isPlannedBuilding(bld)) + expect.true_(buildingplan.setDoNow(bld, true)) + -- the flag is set on the job directly, so buildingplan's own + -- suspend/cycle handling must not clear it + buildingplan.doCycle() + expect.true_(buildingplan.getDoNow(bld)) + end) +end