From 43020a6dd886c499cebe065b564434d1ba40037b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Aug 2024 07:05:32 -0700 Subject: [PATCH 01/17] reduce chattiness, disable widget when not applicable --- idle-crafting.lua | 70 +++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index 784222b7c..4f74ae724 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -134,7 +134,7 @@ end local function checkForWorkshop() if not next(allowed) then - print('no available workshops, disabling') + -- print('no available workshops, disabling') stop() end end @@ -191,10 +191,10 @@ local function processUnit(workshop, idx, unit_id) watched[idx][unit_id] = nil return false elseif not canAccessWorkshop(unit, workshop) then - dfhack.print('-') + -- dfhack.print('-') return false elseif not unitIsAvailable(unit) then - dfhack.print('.') + -- dfhack.print('.') return false end -- We have an available unit @@ -205,10 +205,9 @@ local function processUnit(workshop, idx, unit_id) if not success and workshop.profile.blocked_labors[BONE_CARVE] == false then success = makeBoneCraft(unit, workshop) end - local name = (dfhack.TranslateName(dfhack.units.getVisibleName(unit))) if success then -- Why is the encoding still wrong, even when using df2console? - print(' assigned ' .. dfhack.df2console(name)) + print('idle-crafting: assigned crafting job to ' .. dfhack.df2console(dfhack.units.getReadableName(unit))) watched[idx][unit_id] = nil allowed[workshop.id] = df.global.world.frame_counter else @@ -239,7 +238,7 @@ local function unit_loop() local workshop = locateWorkshop(workshop_id) -- workshop may have been destroyed, assigned a master, or does not allow crafting if not workshop or invalidProfile(workshop) then - print('workshop destroyed or has invalid profile') + -- print('workshop destroyed or has invalid profile') allowed[workshop_id] = nil --clearing during iteration is permitted goto next_workshop end @@ -251,14 +250,14 @@ local function unit_loop() -- check that we didn't schedule a job on the last iteration if (last_job_frame >= 0) and (current_frame < last_job_frame + 60) then - print(('idle-crafting: disabling failing workshop (%d) until the next run of main loop'): - format(workshop_id)) + -- print(('idle-crafting: disabling failing workshop (%d) until the next run of main loop'): + -- format(workshop_id)) failing[workshop_id] = true goto next_workshop end - dfhack.print(('idle-crafting: locating crafter for %s (%d)'): - format(dfhack.buildings.getName(workshop), workshop_id)) + -- dfhack.print(('idle-crafting: locating crafter for %s (%d)'): + -- format(dfhack.buildings.getName(workshop), workshop_id)) -- workshop is free to use, try to find a unit for idx, _ in ipairs(thresholds) do @@ -267,10 +266,10 @@ local function unit_loop() goto next_workshop end end - dfhack.print('/') + -- dfhack.print('/') end - print('no unit found') + -- print('no unit found') ::next_workshop:: end -- disable loop if there are no more units @@ -283,7 +282,7 @@ local function unit_loop() end local function main_loop() - print('idle crafting: running main loop') + -- print('idle crafting: running main loop') checkForWorkshop() if not enabled then return @@ -315,9 +314,9 @@ local function main_loop() end ::continue:: end - print(('watching %s dwarfs with crafting needs'):format( - table.concat(num_watched, '/') - )) + -- print(('watching %s dwarfs with crafting needs'):format( + -- table.concat(num_watched, '/') + -- )) if watching then repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY .. 'unit', 53, 'ticks', unit_loop) @@ -361,23 +360,32 @@ IdleCraftingOverlay.ATTRS { viewscreens = { 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Workers', }, - frame = { w = 55, h = 1 }, + frame = { w = 54, h = 1 }, } function IdleCraftingOverlay:init() self:addviews { - widgets.CycleHotkeyLabel { - view_id = 'leisure_toggle', - frame = { l = 0, t = 0 }, - label = 'Allow idle dwarves to satisfy crafting needs:', - key = 'CUSTOM_I', - options = { - { label = 'yes', value = true, pen = COLOR_GREEN }, - { label = 'no', value = false }, + widgets.BannerPanel{ + subviews={ + widgets.CycleHotkeyLabel { + view_id = 'leisure_toggle', + frame = { t=0, l = 1, r = 1 }, + label = 'Allow idle dwarves to satisfy crafting needs:', + key = 'CUSTOM_I', + options = { + { label = 'yes', value = true, pen = COLOR_GREEN }, + { label = 'no', value = false }, + }, + initial_option = 'no', + on_change = self:callback('onClick'), + enabled = function() + local bld = dfhack.gui.getSelectedBuilding(true) + if not bld then return end + return not invalidProfile(bld) + end, + } }, - initial_option = 'no', - on_change = self:callback('onClick'), - } + }, } end @@ -414,14 +422,12 @@ if dfhack_flags.module then end if df.global.gamemode ~= df.game_mode.DWARF then - print('this tool requires a loaded fort') - return + qerror('this tool requires a loaded fort') end if dfhack_flags.enable then if dfhack_flags.enable_state then - print('This tool is enabled by permitting idle crafting at a Craftsdarf\'s workshop') - return + qerror('This tool is enabled by permitting idle crafting at a Craftsdarf\'s workshop') else allowed = {} stop() From 42d6284bde014742ac5c88727779cf6f688aa25f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Aug 2024 07:20:24 -0700 Subject: [PATCH 02/17] fix invalid field ref in activity --- timestream.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timestream.lua b/timestream.lua index bf69f2a47..542ed93ff 100644 --- a/timestream.lua +++ b/timestream.lua @@ -319,7 +319,7 @@ local function adjust_activities(timeskip) elseif df.activity_event_writest:is_instance(ev) then decrement_counter(ev, 'timer', timeskip) elseif df.activity_event_copy_written_contentst:is_instance(ev) then - decrement_counter(ev, 'time_left', timeskip) + decrement_counter(ev, 'timer', timeskip) elseif df.activity_event_make_believest:is_instance(ev) then decrement_counter(ev, 'time_left', timeskip) elseif df.activity_event_play_with_toyst:is_instance(ev) then From df1cd3865cdf23a7464b3dc55cdfab217217ed8f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Aug 2024 12:04:46 -0700 Subject: [PATCH 03/17] highlight tailor's no dump option --- changelog.txt | 1 + internal/control-panel/registry.lua | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 124b4cd6d..dcabce6ce 100644 --- a/changelog.txt +++ b/changelog.txt @@ -44,6 +44,7 @@ Template for new versions: ## Misc Improvements - `gui/sitemap`: show whether a unit is friendly, hostile, or wildlife - `gui/sitemap`: show whether a unit is caged +- `gui/control-panel`: include option for turning off dumping of old clothes for `tailor`, for players who have magma pit dumps and want to save old clothes from being dumped into the magma ## Removed diff --git a/internal/control-panel/registry.lua b/internal/control-panel/registry.lua index 83c4ac8b8..c7b591f8e 100644 --- a/internal/control-panel/registry.lua +++ b/internal/control-panel/registry.lua @@ -42,7 +42,7 @@ COMMANDS_BY_IDX = { conflicts={'cleanowned-nodump'}, params={'--time', '1', '--timeUnits', 'months', '--command', '[', 'cleanowned', 'X', ']'}}, {command='cleanowned-nodump', group='automation', mode='repeat', - desc='Encourage dwarves to drop tattered clothing on the floor when there is new available clothing.', + desc='Drop tattered clothing, but don\'t mark it for dumping. Pairs well with tailor and tailor confiscate false.', conflicts={'cleanowned'}, params={'--time', '1', '--timeUnits', 'months', '--command', '[', 'cleanowned', 'X', 'nodump', ']'}}, {command='gui/settings-manager load-standing-orders', group='automation', mode='run', @@ -59,6 +59,8 @@ COMMANDS_BY_IDX = { {command='seedwatch', group='automation', mode='enable'}, {command='suspendmanager', group='automation', mode='enable'}, {command='tailor', group='automation', mode='enable'}, + {command='tailor confiscate false', group='automation', mode='run', + desc='Enable if you don\'t want old clothes to be dumped. Pairs well with cleanowned-nodump.'}, -- bugfix tools {command='adamantine-cloth-wear', help_command='tweak', group='bugfix', mode='tweak', default=true, From 07759c2f7d1cc21d4c4a876842b089c91c5aacd3 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Aug 2024 19:16:20 -0700 Subject: [PATCH 04/17] integrate quickfort with preserve-rooms --- changelog.txt | 1 + gui/quickfort.lua | 4 ++-- internal/quickfort/zone.lua | 26 ++++++++------------------ 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/changelog.txt b/changelog.txt index dcabce6ce..35629b614 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ Template for new versions: - `caravan`: DFHack dialogs for trade screens (both ``Bring goods to depot`` and the ``Trade`` barter screen) can now filter by item origins (foreign vs. fort-made) and can filter bins by whether they have a mix of ethically acceptable and unacceptable items in them - `caravan`: If you have managed to select an item that is ethically unacceptable to the merchant, an "Ethics warning" badge will now appear next to the "Trade" button. Clicking on the badge will show you which items that you have selected are problematic. The dialog has a button that you can click to deselect the problematic items in the trade list. - `confirm`: If you have ethically unacceptable items selected for trade, the "Are you sure you want to trade" confirmation will warn you about them +- `quickfort`: ``#zone`` blueprints now integrated with `preserve-rooms` so you can create a zone and automatically assign it to a noble or administrative role ## Fixes - `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped over diff --git a/gui/quickfort.lua b/gui/quickfort.lua index 2742ca774..eae4c701f 100644 --- a/gui/quickfort.lua +++ b/gui/quickfort.lua @@ -63,7 +63,7 @@ function BlueprintDialog:init() text_pen=COLOR_GREY, }, widgets.ToggleHotkeyLabel{ - frame={t=0, l=12}, + frame={t=0, l=12, w=20}, key='CUSTOM_ALT_L', label='Library:', options=options, @@ -72,7 +72,7 @@ function BlueprintDialog:init() on_change=self:callback('update_setting', 'show_library') }, widgets.ToggleHotkeyLabel{ - frame={t=0, l=35}, + frame={t=0, l=35, w=19}, key='CUSTOM_ALT_H', label='Hidden:', options=options, diff --git a/internal/quickfort/zone.lua b/internal/quickfort/zone.lua index 0c46eb5ca..dbb2522cf 100644 --- a/internal/quickfort/zone.lua +++ b/internal/quickfort/zone.lua @@ -6,10 +6,11 @@ if not dfhack_flags.module then end require('dfhack.buildings') -- loads additional functions into dfhack.buildings -local utils = require('utils') +local preserve_rooms = require('plugins.preserve-rooms') local quickfort_common = reqscript('internal/quickfort/common') local quickfort_building = reqscript('internal/quickfort/building') local quickfort_parse = reqscript('internal/quickfort/parse') +local utils = require('utils') local log = quickfort_common.log local logfn = quickfort_common.logfn @@ -227,12 +228,6 @@ local function parse_location_props(props) return location_data end -local function get_noble_unit(noble) - local unit = dfhack.units.getUnitByNobleRole(noble) - if not unit then log('could not find a noble position for: "%s"', noble) end - return unit -end - local function parse_zone_config(c, props) if not rawget(zone_db_raw, c) then return 'Invalid', nil @@ -250,13 +245,7 @@ local function parse_zone_config(c, props) props.name = nil end if props.assigned_unit then - zone_data.assigned_unit = get_noble_unit(props.assigned_unit) - if not zone_data.assigned_unit and props.assigned_unit:lower() == 'sheriff' then - zone_data.assigned_unit = get_noble_unit('captain_of_the_guard') - end - if not zone_data.assigned_unit then - log('could not find a unit assigned to noble position: "%s"', props.assigned_unit) - end + zone_data.assigned_unit = props.assigned_unit props.assigned_unit = nil end if db_entry.props_fn then db_entry.props_fn(zone_data, props) end @@ -387,11 +376,12 @@ local function create_zone(zone, data, ctx) set_location(bld, data.location, ctx) data.location = nil end - if data.assigned_unit then - dfhack.buildings.setOwner(bld, data.assigned_unit) - data.assigned_unit = nil - end + local assigned_unit = data.assigned_unit + data.assigned_unit = nil utils.assign(bld, data) + if assigned_unit then + preserve_rooms.assignToRole(assigned_unit, bld) + end return ntiles end From d845ac9f4fa3aac0773b36bbaa77f5785fc044de Mon Sep 17 00:00:00 2001 From: Kevin Donnelly Date: Fri, 30 Aug 2024 16:43:38 -0400 Subject: [PATCH 05/17] Adding fix/dry-buckets to the control panel at one week --- internal/control-panel/registry.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/control-panel/registry.lua b/internal/control-panel/registry.lua index c7b591f8e..fc2ff0ae3 100644 --- a/internal/control-panel/registry.lua +++ b/internal/control-panel/registry.lua @@ -71,6 +71,9 @@ COMMANDS_BY_IDX = { {command='fix/dead-units', group='bugfix', mode='repeat', default=true, desc='Fix units still being assigned to burrows after death.', params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dead-units', '--burrow', '-q', ']'}}, + {command='fix/dry-buckets', group='bugfix', mode='repeat', default=true, + desc='Allow discarded water buckets to be used again.', + params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dry-buckets', ']'}}, {command='fix/empty-wheelbarrows', group='bugfix', mode='repeat', default=true, desc='Make abandoned full wheelbarrows usable again.', params={'--time', '1', '--timeUnits', 'days', '--command', '[', 'fix/empty-wheelbarrows', '-q', ']'}}, From ed203d0b8263165fb59cd35bf9c0e31ec7bae6f6 Mon Sep 17 00:00:00 2001 From: Kevin Donnelly Date: Fri, 30 Aug 2024 16:55:41 -0400 Subject: [PATCH 06/17] Added --quiet / -q flag to fix/dry-buckets --- fix/dry-buckets.lua | 16 +++++++++++++--- internal/control-panel/registry.lua | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/fix/dry-buckets.lua b/fix/dry-buckets.lua index b49e5b161..27834abb8 100644 --- a/fix/dry-buckets.lua +++ b/fix/dry-buckets.lua @@ -1,7 +1,15 @@ +local argparse = require("argparse") + +local quiet = false + local emptied = 0 local in_building = 0 local water_type = dfhack.matinfo.find('WATER').type +argparse.processArgsGetopt({...}, { + {'q', 'quiet', handler=function() quiet = true end}, +}) + for _,item in ipairs(df.global.world.items.other.IN_PLAY) do local container = dfhack.items.getContainer(item) if container @@ -19,7 +27,9 @@ for _,item in ipairs(df.global.world.items.other.IN_PLAY) do end end -print('Emptied '..emptied..' buckets.') -if emptied > 0 then - print(('Unclogged %d wells.'):format(in_building)) +if not quiet then + print('Emptied '..emptied..' buckets.') + if emptied > 0 then + print(('Unclogged %d wells.'):format(in_building)) + end end diff --git a/internal/control-panel/registry.lua b/internal/control-panel/registry.lua index fc2ff0ae3..93cb2d632 100644 --- a/internal/control-panel/registry.lua +++ b/internal/control-panel/registry.lua @@ -73,7 +73,7 @@ COMMANDS_BY_IDX = { params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dead-units', '--burrow', '-q', ']'}}, {command='fix/dry-buckets', group='bugfix', mode='repeat', default=true, desc='Allow discarded water buckets to be used again.', - params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dry-buckets', ']'}}, + params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dry-buckets', '-q', ']'}}, {command='fix/empty-wheelbarrows', group='bugfix', mode='repeat', default=true, desc='Make abandoned full wheelbarrows usable again.', params={'--time', '1', '--timeUnits', 'days', '--command', '[', 'fix/empty-wheelbarrows', '-q', ']'}}, From ffd05f36f3327818fcded1936f734a20a1b143ef Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 30 Aug 2024 15:54:20 -0700 Subject: [PATCH 07/17] tweak description --- internal/control-panel/registry.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/control-panel/registry.lua b/internal/control-panel/registry.lua index 93cb2d632..5f20954cc 100644 --- a/internal/control-panel/registry.lua +++ b/internal/control-panel/registry.lua @@ -72,7 +72,7 @@ COMMANDS_BY_IDX = { desc='Fix units still being assigned to burrows after death.', params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dead-units', '--burrow', '-q', ']'}}, {command='fix/dry-buckets', group='bugfix', mode='repeat', default=true, - desc='Allow discarded water buckets to be used again.', + desc='Allow discarded water buckets and clogged wells to be used again.', params={'--time', '7', '--timeUnits', 'days', '--command', '[', 'fix/dry-buckets', '-q', ']'}}, {command='fix/empty-wheelbarrows', group='bugfix', mode='repeat', default=true, desc='Make abandoned full wheelbarrows usable again.', From 4ea47c9ed1903b8ad2367b6da337293fe0f435ae Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 30 Aug 2024 16:14:05 -0700 Subject: [PATCH 08/17] formatting --- docs/gui/seedwatch.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/gui/seedwatch.rst b/docs/gui/seedwatch.rst index 95ae306c2..6ee1723de 100644 --- a/docs/gui/seedwatch.rst +++ b/docs/gui/seedwatch.rst @@ -5,11 +5,11 @@ gui/seedwatch :summary: Manages seed and plant cooking based on seed stock levels. :tags: fort auto plants -This is the configuration interface for the `seedwatch` plugin. You can configure -a target stock amount for each seed type. If the number of seeds of that type falls -below the target, then the plants and seeds of that type will be protected from -cookery. If the number rises above the target + 20, then cooking will be allowed -again. +This is the configuration interface for the `seedwatch` plugin. You can +configure a target stock amount for each seed type. If the number of seeds of +that type falls below the target, then the plants and seeds of that type will +be protected from cookery. If the number rises above the target + 20, then +cooking will be allowed again. Usage ----- From 4bf22dbca5c83ea437842185ad933f66b90d388b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 30 Aug 2024 18:23:01 -0700 Subject: [PATCH 09/17] rework gui/seedwatch UI to support sorting and to not imply that the "all" threshold is saved somewhere --- gui/seedwatch.lua | 476 ++++++++++++++++++++++++---------------------- 1 file changed, 248 insertions(+), 228 deletions(-) diff --git a/gui/seedwatch.lua b/gui/seedwatch.lua index 612b42ee4..ad40e175b 100644 --- a/gui/seedwatch.lua +++ b/gui/seedwatch.lua @@ -1,270 +1,290 @@ --- config ui for seedwatch - +local dlg = require('gui.dialogs') local gui = require('gui') -local widgets = require('gui.widgets') local plugin = require('plugins.seedwatch') +local widgets = require('gui.widgets') -local PROPERTIES_HEADER = ' Quantity Target ' -local REFRESH_MS = 10000 -local MAX_TARGET = 2147483647 --- --- SeedSettings --- -SeedSettings = defclass(SeedSettings, widgets.Window) -SeedSettings.ATTRS{ - frame={l=5, t=5, w=35, h=9}, +local CH_UP = string.char(30) +local CH_DN = string.char(31) + +Seedwatch = defclass(Seedwatch, widgets.Window) +Seedwatch.ATTRS{ + frame_title='Seedwatch', + frame={w=58, h=25}, + frame_inset={t=1}, + resizable=true, } -function SeedSettings:init() - self:addviews{ - widgets.Label{ - frame={t=0, l=0}, - text='Seed: ', - }, - widgets.Label{ - view_id='name', - frame={t=0, l=6}, - text_pen=COLOR_GREEN, - }, - widgets.Label{ - frame={t=1, l=0}, - text='Quantity: ', - }, - widgets.Label{ - view_id='quantity', - frame={t=1, l=10}, - text_pen=COLOR_GREEN, - }, - widgets.EditField{ - view_id='target', - frame={t=2, l=0}, - label_text='Target: ', - key='CUSTOM_CTRL_T', - on_char=function(ch) return ch:match('%d') end, - on_submit=self:callback('commit'), - }, - widgets.HotkeyLabel{ - frame={t=4, l=0}, - key='SELECT', - label='Apply', - on_activate=self:callback('commit'), - }, - } +local function sort_noop(a, b) + -- this function is used as a marker and never actually gets called + error('sort_noop should not be called') end -function SeedSettings:show(choice, on_commit) - self.data = choice.data - self.on_commit = on_commit - self.subviews.name:setText(self.data.name) - self.subviews.quantity:setText(tostring(self.data.quantity)) - self.subviews.target:setText(tostring(self.data.target)) - self.visible = true - self:setFocus(true) - self:updateLayout() +local function sort_by_name_desc(a, b) + return a.data.name < b.data.name end -function SeedSettings:hide() - self:setFocus(false) - self.visible = false +local function sort_by_name_asc(a, b) + return a.data.name > b.data.name end -function SeedSettings:commit() - local target = math.tointeger(self.subviews.target.text) or 0 - target = math.min(MAX_TARGET, math.max(0, target)) - - plugin.seedwatch_setTarget(self.data.id, target) - self:hide() - self.on_commit() +local function sort_by_quantity_desc(a, b) + if a.data.quantity == b.data.quantity then + return sort_by_name_desc(a, b) + end + return a.data.quantity > b.data.quantity end -function SeedSettings:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R then - self:hide() - return true +local function sort_by_quantity_asc(a, b) + if a.data.quantity == b.data.quantity then + return sort_by_name_desc(a, b) end - SeedSettings.super.onInput(self, keys) - return true + return a.data.quantity < b.data.quantity end --- --- Seedwatch --- -Seedwatch = defclass(Seedwatch, widgets.Window) -Seedwatch.ATTRS { - frame_title='Seedwatch', - frame={w=60, h=27}, - resizable=true, - resize_min={h=25}, -} - -function Seedwatch:init() - local minimal = false - local saved_frame = {w=50, h=6, r=2, t=18} - local saved_resize_min = {w=saved_frame.w, h=saved_frame.h} - local function toggle_minimal() - minimal = not minimal - local swap = self.frame - self.frame = saved_frame - saved_frame = swap - swap = self.resize_min - self.resize_min = saved_resize_min - saved_resize_min = swap - self:updateLayout() - self:refresh_data() - end - local function is_minimal() - return minimal +local function sort_by_target_desc(a, b) + if a.data.target == b.data.target then + return sort_by_name_desc(a, b) end - local function is_not_minimal() - return not minimal + return a.data.target > b.data.target +end + +local function sort_by_target_asc(a, b) + if a.data.target == b.data.target then + return sort_by_name_desc(a, b) end + return a.data.target < b.data.target +end +function Seedwatch:init() self:addviews{ - widgets.ToggleHotkeyLabel{ - view_id='enable_toggle', - frame={t=0, l=0, w=31}, - label='Seedwatch is', - key='CUSTOM_CTRL_E', - options={{value=true, label='Enabled', pen=COLOR_GREEN}, - {value=false, label='Disabled', pen=COLOR_RED}}, - on_change=function(val) plugin.setEnabled(val) end, - }, - widgets.EditField{ - view_id='all', - frame={t=1, l=0}, - label_text='Target for all: ', - key='CUSTOM_CTRL_A', - on_char=function(ch) return ch:match('%d') end, - on_submit=function(text) - local target = math.tointeger(text) - if not target or target == '' then - target = 0 - elseif target > MAX_TARGET then - target = MAX_TARGET - end - plugin.seedwatch_setTarget('all', target) - self.subviews.list:setFilter('') - self:refresh_data() - self:update_choices() - end, - visible=is_not_minimal, - text='30', + widgets.CycleHotkeyLabel{ + view_id='sort', + frame={l=1, t=0, w=31}, + label='Sort by:', + key='CUSTOM_SHIFT_S', + options={ + {label='Name'..CH_DN, value=sort_by_name_desc}, + {label='Name'..CH_UP, value=sort_by_name_asc}, + {label='Quantity'..CH_DN, value=sort_by_quantity_desc}, + {label='Quantity'..CH_UP, value=sort_by_quantity_asc}, + {label='Target'..CH_DN, value=sort_by_target_desc}, + {label='Target'..CH_UP, value=sort_by_target_asc}, + }, + initial_option=sort_by_name_desc, + on_change=self:callback('refresh', 'sort'), }, - - widgets.HotkeyLabel{ - frame={r=0, t=0, w=10}, - key='CUSTOM_ALT_M', - label=string.char(31)..string.char(30), - on_activate=toggle_minimal}, - widgets.Label{ - view_id='minimal_summary', - frame={t=1, l=0, h=1}, - auto_height=false, - visible=is_minimal, - }, - widgets.Label{ - frame={t=3, l=0}, - text='Seed', - auto_width=true, - visible=is_not_minimal, - }, - widgets.Label{ - frame={t=3, r=0}, - text=PROPERTIES_HEADER, - auto_width=true, - visible=is_not_minimal, - }, - widgets.FilteredList{ - view_id='list', - frame={t=5, l=0, r=0, b=3}, - on_submit=self:callback('configure_seed'), - visible=is_not_minimal, - edit_key = 'CUSTOM_S', + widgets.ToggleHotkeyLabel{ + view_id='hide_nostock', + frame={t=0, l=24, w=31}, + key='CUSTOM_CTRL_H', + label='Show only in stock:', + on_change=self:callback('refresh', 'sort'), }, - widgets.Label{ - view_id='summary', - frame={b=0, l=0}, - visible=is_not_minimal, + widgets.Panel{ + view_id='list_panel', + frame={t=2, l=0, r=0, b=4}, + frame_style=gui.FRAME_INTERIOR, + subviews={ + widgets.CycleHotkeyLabel{ + view_id='sort_name', + frame={t=0, l=0, w=5}, + options={ + {label='Name', value=sort_noop}, + {label='Name'..CH_DN, value=sort_by_name_desc}, + {label='Name'..CH_UP, value=sort_by_name_asc}, + }, + initial_option=sort_by_name_desc, + option_gap=0, + on_change=self:callback('refresh', 'sort_name'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_quantity', + frame={t=0, r=12, w=9}, + options={ + {label='Quantity', value=sort_noop}, + {label='Quantity'..CH_DN, value=sort_by_quantity_desc}, + {label='Quantity'..CH_UP, value=sort_by_quantity_asc}, + }, + option_gap=0, + on_change=self:callback('refresh', 'sort_quantity'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_target', + frame={t=0, r=3, w=7}, + options={ + {label='Target', value=sort_noop}, + {label='Target'..CH_DN, value=sort_by_target_desc}, + {label='Target'..CH_UP, value=sort_by_target_asc}, + }, + option_gap=0, + on_change=self:callback('refresh', 'sort_target'), + }, + widgets.Label{ + view_id='disabled_warning', + visible=function() return not plugin.isEnabled() end, + frame={t=3, h=1}, + auto_width=true, + text={"Please enable seedwatch to change settings"}, + text_pen=COLOR_YELLOW + }, + widgets.List{ + view_id='list', + frame={t=2, b=0}, + visible=plugin.isEnabled, + on_double_click=self:callback('prompt_for_new_target'), + }, + }, }, - SeedSettings{ - view_id='seed_settings', - visible=false, + widgets.Panel{ + view_id='footer', + frame={l=1, r=1, b=0, h=3}, + subviews={ + widgets.Label{ + frame={t=0, l=0}, + text={ + 'Double click on a row or hit ', + {text='Enter', pen=COLOR_LIGHTGREEN}, + ' to set the target.' + }, + }, + widgets.ToggleHotkeyLabel{ + view_id='enable_toggle', + frame={t=2, l=0, w=29}, + label='Seedwatch is', + key='CUSTOM_CTRL_E', + options={{value=true, label='Enabled', pen=COLOR_GREEN}, + {value=false, label='Disabled', pen=COLOR_RED}}, + on_change=function(val) + plugin.setEnabled(val) + self:refresh() + end, + }, + widgets.HotkeyLabel{ + frame={t=2, l=31}, + label='Set all targets', + key='CUSTOM_CTRL_A', + auto_width=true, + on_activate=self:callback('prompt_for_all_targets'), + }, + }, }, - } - - self:refresh_data() end -function Seedwatch:configure_seed(idx, choice) - self.subviews.seed_settings:show(choice, function() - self:refresh_data() - self:update_choices() - end) +function Seedwatch:render(dc) + self.subviews.enable_toggle:setOption(plugin.isEnabled()) + Seedwatch.super.render(self, dc) end -function Seedwatch:update_choices() - local list = self.subviews.list - local name_width = list.frame_body.width - #PROPERTIES_HEADER - local fmt = '%-'..tostring(name_width)..'s %10d %10d ' - local choices = {} - local prior_search=self.subviews.list.edit.text - for k, v in pairs(self.data.seeds) do - local text = (fmt):format(v.name:sub(1,name_width), v.quantity or 0, v.target or 0) - table.insert(choices, {text=text, data=v}) +function Seedwatch:onInput(keys) + if keys.SELECT then + self:prompt_for_new_target(self.subviews.list:getSelected()) end + return Seedwatch.super.onInput(self, keys) +end - self.subviews.list:setChoices(choices) - if prior_search then self.subviews.list:setFilter(prior_search) end - self.subviews.list:updateLayout() +function Seedwatch:postUpdateLayout() + self:refresh() end -function Seedwatch:refresh_data() - self.subviews.enable_toggle:setOption(plugin.isEnabled()) - local watch_map, seed_counts = plugin.seedwatch_getData() - self.data = {} - self.data.sum = 0 - self.data.seeds_qty = 0 - self.data.seeds_watched = 0 - self.data.seeds = {} - for k,v in pairs(seed_counts) do - local seed = {} - seed.id = df.global.world.raws.plants.all[k].id - seed.name = df.global.world.raws.plants.all[k].seed_singular - seed.quantity = v - seed.target = watch_map[k] or 0 - self.data.seeds[k] = seed - if self.data.seeds[k].target > 0 then - self.data.seeds_watched = self.data.seeds_watched + 1 - end - self.data.seeds_qty = self.data.seeds_qty + v +local SORT_WIDGETS = { + 'sort', + 'sort_name', + 'sort_quantity', + 'sort_target', +} + +local function make_row_text(name, quantity, target, row_width) + return { + {text=name, width=row_width-22, pad_char=' '}, + ' ', {text=quantity, width=7, rjustify=true, pad_char=' '}, + ' ', {text=target, width=7, rjustify=true, pad_char=' '}, + } +end + +local plants_all = df.global.world.raws.plants.all + +function Seedwatch:refresh(sort_widget, sort_fn) + sort_widget = sort_widget or 'sort' + sort_fn = sort_fn or self.subviews.sort:getOptionValue() + if sort_fn == sort_noop then + self.subviews[sort_widget]:cycle() + return end - if self.subviews.all.text == '' then - self.subviews.all:setText('0') + for _,widget_name in ipairs(SORT_WIDGETS) do + self.subviews[widget_name]:setOption(sort_fn) end - local summary_text = ('Seeds quantity: %d watched: %d\n'):format(tostring(self.data.seeds_qty),tostring(self.data.seeds_watched)) - self.subviews.summary:setText(summary_text) - local minimal_summary_text = summary_text - self.subviews.minimal_summary:setText(minimal_summary_text) - self.next_refresh_ms = dfhack.getTickCount() + REFRESH_MS + local watch_map, seed_counts = plugin.seedwatch_getData() + local hide_nostock = self.subviews.hide_nostock:getOptionValue() -end + local list = self.subviews.list + local row_width = list.frame_body.width + local choices = {} + for idx,target in pairs(watch_map) do + if hide_nostock and not seed_counts[idx] then goto continue end + local name = plants_all[idx].seed_singular + local quantity = seed_counts[idx] or 0 + table.insert(choices, { + text=make_row_text(name, quantity, target, row_width), + data={ + id=plants_all[idx].id, + name=name, + quantity=quantity, + target=target, + }, + }) + ::continue:: + end -function Seedwatch:postUpdateLayout() - self:update_choices() + table.sort(choices, self.subviews.sort:getOptionValue()) + local selected = list:getSelected() + list:setChoices(choices, selected) end --- refreshes data every 10 seconds or so -function Seedwatch:onRenderBody() - if self.next_refresh_ms <= dfhack.getTickCount() - and self.subviews.seed_settings.visible == false - and not self.subviews.all.focus - and not self.subviews.list.edit.focus then - self:refresh_data() - self:update_choices() +local function check_number(target, text) + if not target then + dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) + return false end + if target < 0 then + dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) + return false + end + return true +end + +function Seedwatch:prompt_for_new_target(_, choice) + dlg.showInputPrompt( + 'Set target', + ('Enter desired target for %s:'):format(choice.data.name), + COLOR_WHITE, + tostring(choice.data.target), + function(text) + local target = tonumber(text) + if check_number(target, text) then + plugin.seedwatch_setTarget(choice.data.id, target) + self:refresh() + end + end + ) +end + +function Seedwatch:prompt_for_all_targets() + dlg.showInputPrompt( + 'Set all targets', + 'Enter desired target for all seed types', + COLOR_WHITE, + '', + function(text) + local target = tonumber(text) + if check_number(target, text) then + plugin.seedwatch_setTarget('all', target) + self:refresh() + end + end + ) end -- @@ -272,7 +292,7 @@ end -- SeedwatchScreen = defclass(SeedwatchScreen, gui.ZScreen) -SeedwatchScreen.ATTRS { +SeedwatchScreen.ATTRS{ focus_path='seedwatch', } @@ -284,8 +304,8 @@ function SeedwatchScreen:onDismiss() view = nil end -if not dfhack.isMapLoaded() then - qerror('seedwatch requires a map to be loaded') +if not dfhack.isMapLoaded() or not dfhack.world.isFortressMode() then + qerror('seedwatch requires a fort map to be loaded') end view = view and view:raise() or SeedwatchScreen{}:show() From 90365d348d04a3b48801b7850e93e9cdbc09a823 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 30 Aug 2024 18:49:31 -0700 Subject: [PATCH 10/17] changelog editing pass --- changelog.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 35629b614..5e1761acb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -27,9 +27,9 @@ Template for new versions: # Future ## New Tools -- `embark-anyone`: allows you to embark as any civilisation, including dead, and non-dwarven ones -- `idle-crafting`: Allow dwarfs to automatically satisfy their need to craft objects. -- `gui/family-affairs`: (reinstated) inspect or meddle with pregnancies, marriages, or lover relationsips +- `embark-anyone`: allows you to embark as any civilization, including dead and non-dwarven ones +- `idle-crafting`: allow dwarves to independently satisfy their need to craft objects +- `gui/family-affairs`: (reinstated) inspect or meddle with pregnancies, marriages, or lover relationships ## New Features - `caravan`: DFHack dialogs for trade screens (both ``Bring goods to depot`` and the ``Trade`` barter screen) can now filter by item origins (foreign vs. fort-made) and can filter bins by whether they have a mix of ethically acceptable and unacceptable items in them @@ -38,12 +38,12 @@ Template for new versions: - `quickfort`: ``#zone`` blueprints now integrated with `preserve-rooms` so you can create a zone and automatically assign it to a noble or administrative role ## Fixes -- `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped over -- `empty-bin`: ``--liquids`` option correctly emptying containers filled with LIQUID_MISC -- `gui/design`: Update Line & Freeform tools to not overcount tiles +- `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped +- `empty-bin`: ``--liquids`` option now correctly empties containers filled with LIQUID_MISC (like lye) +- `gui/design`: don't overcount "affected tiles" for Line & Freeform drawing tools ## Misc Improvements -- `gui/sitemap`: show whether a unit is friendly, hostile, or wildlife +- `gui/sitemap`: show whether a unit is friendly, hostile, or wild - `gui/sitemap`: show whether a unit is caged - `gui/control-panel`: include option for turning off dumping of old clothes for `tailor`, for players who have magma pit dumps and want to save old clothes from being dumped into the magma From 0160b336b6a891f351af38683a3bce017e64a251 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 2 Sep 2024 07:09:15 -0700 Subject: [PATCH 11/17] Update position.lua --- position.lua | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/position.lua b/position.lua index 42c90136f..3ca3bdd54 100644 --- a/position.lua +++ b/position.lua @@ -1,3 +1,19 @@ + +local cursor = df.global.cursor +local args = {...} +if #args > 0 then --Copy keyboard cursor to clipboard + if #args > 1 then + qerror('Too many arguments!') + elseif args[1] ~= '-c' and args[1] ~= '--copy' then + qerror('Invalid argument "'..args[1]..'"!') + elseif cursor.x < 0 then + qerror('No keyboard cursor!') + end + + dfhack.internal.setClipboardTextCp437(('%d,%d,%d'):format(cursor.x, cursor.y, cursor.z)) + return +end + local months = { 'Granite, in early Spring.', 'Slate, in mid Spring.', @@ -30,11 +46,15 @@ print('Time:') print(' The time is '..string.format('%02d:%02d:%02d', hour, minute, second)) print(' The date is '..string.format('%05d-%02d-%02d', df.global.cur_year, month, day)) print(' It is the month of '..months[month]) ---TODO: print(' It is the Age of '..age_name) + +local eras = df.global.world.history.eras +if #eras > 0 then + print(' It is the '..eras[#eras-1].title.name..'.') +end print('Place:') print(' The z-level is z='..df.global.window_z) -print(' The cursor is at x='..df.global.cursor.x..', y='..df.global.cursor.y) +print(' The cursor is at x='..cursor.x..', y='..cursor.y) print(' The window is '..df.global.gps.dimx..' tiles wide and '..df.global.gps.dimy..' tiles high') if df.global.gps.mouse_x == -1 then print(' The mouse is not in the DF window') else print(' The mouse is at x='..df.global.gps.mouse_x..', y='..df.global.gps.mouse_y..' within the window') end From dd16ed0fbd6d4a73a25b58574c9723a5494a1947 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 2 Sep 2024 07:23:44 -0700 Subject: [PATCH 12/17] Update position.rst --- docs/position.rst | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/position.rst b/docs/position.rst index fada2ec54..7a88ae04b 100644 --- a/docs/position.rst +++ b/docs/position.rst @@ -5,13 +5,30 @@ position :summary: Report cursor and mouse position, along with other info. :tags: fort inspection map -This tool reports the current date, clock time, month, and season. It also -reports the cursor position (or just the z-level if no cursor), window size, and -mouse location on the screen. +This tool reports the current date, clock time, month, season, and historical +era. It also reports the keyboard cursor position (or just the z-level if no +active cursor), window size, and mouse location on the screen. + +Can also be used to copy the current cursor position for later use. Usage ----- :: - position + position [--copy] + +Examples +-------- + +``position`` + Print various information. +``position -c`` + Copy cursor position to system clipboard. + +Options +------- + +``-c``, ``--copy`` + Copy current keyboard cursor position to the clipboard in format ``0,0,0`` + instead of reporting info. For convenience with other tools. From 1e8bf2bf59f60e37895b3cab01df958d1c8ffba3 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 2 Sep 2024 07:26:46 -0700 Subject: [PATCH 13/17] Update changelog.txt --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 5e1761acb..28a17540e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -36,6 +36,7 @@ Template for new versions: - `caravan`: If you have managed to select an item that is ethically unacceptable to the merchant, an "Ethics warning" badge will now appear next to the "Trade" button. Clicking on the badge will show you which items that you have selected are problematic. The dialog has a button that you can click to deselect the problematic items in the trade list. - `confirm`: If you have ethically unacceptable items selected for trade, the "Are you sure you want to trade" confirmation will warn you about them - `quickfort`: ``#zone`` blueprints now integrated with `preserve-rooms` so you can create a zone and automatically assign it to a noble or administrative role +- `position`: option to copy cursor position to clipboard ## Fixes - `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped @@ -46,6 +47,7 @@ Template for new versions: - `gui/sitemap`: show whether a unit is friendly, hostile, or wild - `gui/sitemap`: show whether a unit is caged - `gui/control-panel`: include option for turning off dumping of old clothes for `tailor`, for players who have magma pit dumps and want to save old clothes from being dumped into the magma +- `position`: report current historical era (e.g., "Age of Myth") ## Removed From fa8caaf0866e5e588a99c03cddd41e75ce048333 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:09:11 +0000 Subject: [PATCH 14/17] [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.29.1 → 0.29.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.1...0.29.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 2ca5c64fc..208bd78ba 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.29.1 + rev: 0.29.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From fc57ea96ea46fc8044c361eb8a1dd2aef969e358 Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 2 Sep 2024 22:57:33 -0700 Subject: [PATCH 15/17] Update docs/position.rst --- docs/position.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/position.rst b/docs/position.rst index 7a88ae04b..72ab716b5 100644 --- a/docs/position.rst +++ b/docs/position.rst @@ -9,7 +9,7 @@ This tool reports the current date, clock time, month, season, and historical era. It also reports the keyboard cursor position (or just the z-level if no active cursor), window size, and mouse location on the screen. -Can also be used to copy the current cursor position for later use. +Can also be used to copy the current keyboard cursor position for later use. Usage ----- From 97221a131f262560a37ccfa7fcf2ee4df41761da Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 3 Sep 2024 12:50:12 -0700 Subject: [PATCH 16/17] style --- internal/advtools/party.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/advtools/party.lua b/internal/advtools/party.lua index 241edc478..4ef9f7cbf 100644 --- a/internal/advtools/party.lua +++ b/internal/advtools/party.lua @@ -1,7 +1,7 @@ --@ module=true -local dialogs = require 'gui.dialogs' -local utils = require 'utils' +local dialogs = require('gui.dialogs') +local utils = require('utils') local makeown = reqscript('makeown') @@ -40,7 +40,7 @@ local function showExtraPartyPrompt() table.insert(choices, {text=name, nemesis=nemesis, search_key=dfhack.toSearchNormalized(name)}) ::continue:: end - dialogs.showListPrompt('party', "Select someone to add to your \"Core Party\" (able to assume control, able to unretire):", COLOR_WHITE, + dialogs.showListPrompt('party', 'Select someone to add to your "Core Party" (able to assume control, able to unretire):', COLOR_WHITE, choices, function(id, choice) addToCoreParty(choice.nemesis) end, nil, nil, true) From 45e3dbd4200c6b7c6a2f3dc2d2f76f2f3ab1eaef Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 3 Sep 2024 12:51:44 -0700 Subject: [PATCH 17/17] add info on bridging two landmasses --- changelog.txt | 3 +++ docs/gui/embark-anywhere.rst | 47 ++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 28a17540e..dc2e423db 100644 --- a/changelog.txt +++ b/changelog.txt @@ -49,6 +49,9 @@ Template for new versions: - `gui/control-panel`: include option for turning off dumping of old clothes for `tailor`, for players who have magma pit dumps and want to save old clothes from being dumped into the magma - `position`: report current historical era (e.g., "Age of Myth") +## Documentation +- `gui/embark-anywhere`: add information about how the game determines world tile pathability and instructions for bridging two landmasses + ## Removed # 50.13-r4 diff --git a/docs/gui/embark-anywhere.rst b/docs/gui/embark-anywhere.rst index 4583b04cd..db5c06fc3 100644 --- a/docs/gui/embark-anywhere.rst +++ b/docs/gui/embark-anywhere.rst @@ -11,8 +11,13 @@ embark in an inaccessible location on top of a mountain range? Go for it! Want to try a brief existence in the middle of the ocean? Nobody can stop you! Want to tempt fate by embarking *inside of* a necromancer tower? !!FUN!! +If you are using this tool to create a fort that will bridge two disconnected +areas of land, see `So you want to bridge a gap?`_ below for tips and caveats. + Any and all consequences of embarking in strange locations are up to you to -handle (possibly with other `armok ` tools). +handle (possibly with other `armok ` tools). In particular, +embarking in inaccessible locations will prevent migrants, caravans, and +visitors from arriving. Usage ----- @@ -21,9 +26,37 @@ Usage gui/embark-anywhere -The command will only work when you are on the screen where you can choose your -embark site. The DFHack logo is not displayed on that screen since its default -position conflicts with the vanilla embark size selection panel. Remember that -you can bring up DFHack's `context menu ` with -:kbd:`Ctrl`:kbd:`Shift`:kbd:`C` or the -`in-game command launcher ` directly with the backtick key (\`). +The command will only work when you are on the screen where you can choose the +embark site for your fort. + +So you want to bridge a gap? +---------------------------- + +A popular use case for this tool is to create a fort (or a series of forts) that +bridges two disconnected landmasses so sites on the two landmasses can reach +each other (that is, they can send raiding parties and/or engage in trade). + +However, the way this works is not entirely intuitive. + +A single large embark is not necessarily going to functionally connect the two +shores so that armies can cross the gap. You could still choose to use this +approach to build a continuous constructed bridge in fort mode for later use as +an *adventurer* in adventure mode, but it will not be usable by the other +characters/armies in the world. + +The DF world map is divided into blocks of 16x16 tiles. When you are choosing +where to embark and you move the mouse so that your embark area "shadow" moves +over a little bit -- that's one "tile". An embark area can span block +boundaries, and there is no indication on the map where those boundaries are. + +The way DF determines world pathability is to check if the ground is continuous +**or** if the enclosing 16x16 block contains the upper left tile of a fort +embark area. + +In order for a connection to be formed for armies, one fort upper left corner +must exist in each 16x16 block that contains part of the gap. + +Therefore, the simplest solution for making a "bridge" that armies can use (but +walking adventurers cannot) is to make a 1x1 fort every 16 tiles across the +water gap, starting on land on one shore and finishing on land on the opposite +shore. That will ensure that every 16x16 block in the gap is covered by a fort.