From e0dd34dcb89aaa3f37a9a52bcac4491c75c8b11a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 02:29:01 -0700 Subject: [PATCH 1/3] implement basic burrow mode functionality --- changelog.txt | 1 + internal/quickfort/burrow.lua | 167 ++++++++++++++++++++++++++++++++++ internal/quickfort/parse.lua | 1 + quickfort.lua | 1 + 4 files changed, 170 insertions(+) create mode 100644 internal/quickfort/burrow.lua diff --git a/changelog.txt b/changelog.txt index 15fe6d24c7..c40e9e4ce5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,7 @@ Template for new versions: ## New Features - `gui/design`: show selected dimensions next to the mouse cursor when designating with vanilla tools, for example when painting a burrow or designating digging +- `quickfort`: new blueprint mode for designating burrows ## Fixes - `gui/unit-syndromes`: show the syndrome names properly in the UI diff --git a/internal/quickfort/burrow.lua b/internal/quickfort/burrow.lua new file mode 100644 index 0000000000..7157b59ee6 --- /dev/null +++ b/internal/quickfort/burrow.lua @@ -0,0 +1,167 @@ +-- burrow-related data and logic for the quickfort script +--@ module = true + +if not dfhack_flags.module then + qerror('this script cannot be called directly') +end + +local quickfort_common = reqscript('internal/quickfort/common') +local quickfort_map = reqscript('internal/quickfort/map') +local quickfort_parse = reqscript('internal/quickfort/parse') +local quickfort_preview = reqscript('internal/quickfort/preview') +local utils = require('utils') + +local log = quickfort_common.log +local logfn = quickfort_common.logfn + +local burrow_db = { + a={label='Add', add=true}, + e={label='Erase', add=false}, +} + +local function custom_burrow(_, keys) + local token_and_label, props_start_pos = quickfort_parse.parse_token_and_label(keys, 1, '%w') + if not token_and_label or not rawget(burrow_db, token_and_label.token) then return nil end + local db_entry = copyall(burrow_db[token_and_label.token]) + local props, next_token_pos = quickfort_parse.parse_properties(keys, props_start_pos) + if props.name then + db_entry.name = props.name + props.name = nil + end + if db_entry.add and props.create == 'true' then + db_entry.create = true + props.create = nil + end + + for k,v in pairs(props) do + dfhack.printerr(('unhandled property for symbol "%s": "%s"="%s"'):format( + token_and_label.token, k, v)) + end + + return db_entry +end + +setmetatable(burrow_db, {__index=custom_burrow}) + +local burrows = df.global.plotinfo.burrows + +local function do_burrow(ctx, db_entry, pos) + local stats = ctx.stats + local b + if db_entry.name then + b = dfhack.burrows.findByName(db_entry.name, true) + end + if not b and db_entry.add and db_entry.create then + b = df.burrow:new() + b.id = burrows.next_id + burrows.next_id = burrows.next_id + 1 + if db_entry.name then + b.name = db_entry.name + end + b.symbol_index = math.random(0, 22) + b.texture_r = math.random(0, 255) + b.texture_g = math.random(0, 255) + b.texture_b = math.random(0, 255) + b.texture_br = 255 - b.texture_r + b.texture_bg = 255 - b.texture_g + b.texture_bb = 255 - b.texture_b + burrows.list:insert('#', b) + stats.burrow_created.value = stats.burrow_created.value + 1 + end + if not b and db_entry.add then + log('could not find burrow to add to') + return + end + if b then + dfhack.burrows.setAssignedTile(b, pos, db_entry.add) + stats['burrow_tiles_'..(db_entry.add and 'added' or 'removed')].value = + stats['burrow_tiles_'..(db_entry.add and 'added' or 'removed')].value + 1 + if not db_entry.add and db_entry.create and #dfhack.burrows.listBlocks(b) == 0 then + dfhack.burrows.clearTiles(b) + local _, _, idx = utils.binsearch(burrows.list, b.id, 'id') + if idx then + burrows.list:erase(idx) + b:delete() + stats.burrow_destroyed.value = stats.burrow_destroyed.value + 1 + end + end + elseif not db_entry.add then + for _,b in ipairs(burrows.list) do + dfhack.burrows.setAssignedTile(b, pos, false) + end + stats.burrow_tiles_removed.value = stats.burrow_tiles_removed.value + 1 + end +end + +function do_run_impl(zlevel, grid, ctx, invert) + local stats = ctx.stats + stats.burrow_created = stats.burrow_created or + {label='Burrows created', value=0} + stats.burrow_destroyed = stats.burrow_destroyed or + {label='Burrows destroyed', value=0} + stats.burrow_tiles_added = stats.burrow_tiles_added or + {label='Burrow tiles added', value=0} + stats.burrow_tiles_removed = stats.burrow_tiles_removed or + {label='Burrow tiles removed', value=0} + + ctx.bounds = ctx.bounds or quickfort_map.MapBoundsChecker{} + for y, row in pairs(grid) do + for x, cell_and_text in pairs(row) do + local cell, text = cell_and_text.cell, cell_and_text.text + local pos = xyz2pos(x, y, zlevel) + log('applying spreadsheet cell %s with text "%s" to map' .. + ' coordinates (%d, %d, %d)', cell, text, pos.x, pos.y, pos.z) + local db_entry = nil + local keys, extent = quickfort_parse.parse_cell(ctx, text) + if keys then db_entry = burrow_db[keys] end + if not db_entry then + dfhack.printerr(('invalid key sequence: "%s" in cell %s') + :format(text, cell)) + stats.invalid_keys.value = stats.invalid_keys.value + 1 + goto continue + end + if invert then + db_entry = copyall(db_entry) + db_entry.add = not db_entry.add + end + if extent.specified then + -- shift pos to the upper left corner of the extent and convert + -- the extent dimensions to positive, simplifying the logic below + pos.x = math.min(pos.x, pos.x + extent.width + 1) + pos.y = math.min(pos.y, pos.y + extent.height + 1) + end + for extent_x=1,math.abs(extent.width) do + for extent_y=1,math.abs(extent.height) do + local extent_pos = xyz2pos( + pos.x+extent_x-1, + pos.y+extent_y-1, + pos.z) + if not ctx.bounds:is_on_map(extent_pos) then + log('coordinates out of bounds; skipping (%d, %d, %d)', + extent_pos.x, extent_pos.y, extent_pos.z) + stats.out_of_bounds.value = + stats.out_of_bounds.value + 1 + else + quickfort_preview.set_preview_tile(ctx, extent_pos, true) + if not ctx.dry_run then + do_burrow(ctx, db_entry, extent_pos) + end + end + end + end + ::continue:: + end + end +end + +function do_run(zlevel, grid, ctx) + do_run_impl(zlevel, grid, ctx, false) +end + +function do_orders() + log('nothing to do for blueprints in mode: burrow') +end + +function do_undo(zlevel, grid, ctx) + do_run_impl(zlevel, grid, ctx, true) +end diff --git a/internal/quickfort/parse.lua b/internal/quickfort/parse.lua index 1e29010e7f..4cf10482d4 100644 --- a/internal/quickfort/parse.lua +++ b/internal/quickfort/parse.lua @@ -15,6 +15,7 @@ valid_modes = utils.invert({ 'build', 'place', 'zone', + 'burrow', 'meta', 'notes', 'ignore', diff --git a/quickfort.lua b/quickfort.lua index 0835689318..2e2c1ac03d 100644 --- a/quickfort.lua +++ b/quickfort.lua @@ -19,6 +19,7 @@ function refresh_scripts() reqscript('internal/quickfort/api') reqscript('internal/quickfort/build') reqscript('internal/quickfort/building') + reqscript('internal/quickfort/burrow') reqscript('internal/quickfort/command') reqscript('internal/quickfort/common') reqscript('internal/quickfort/dig') From 25db4b6837d2ed37bfe7494624b464b30769320a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 02:50:42 -0700 Subject: [PATCH 2/3] support setting the civalert burrow from quickfort --- gui/civ-alert.lua | 15 +++++++++++++++ internal/quickfort/burrow.lua | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/gui/civ-alert.lua b/gui/civ-alert.lua index f043f4c222..1c6cb17519 100644 --- a/gui/civ-alert.lua +++ b/gui/civ-alert.lua @@ -35,6 +35,21 @@ local function clear_alarm() df.global.plotinfo.alerts.civ_alert_idx = 0 end +function set_civalert_burrow_if_unset(burrow) + local burrows = get_civ_alert().burrows + if #burrows == 0 then + burrows:insert('#', burrow.id) + end +end + +function unset_civalert_burrow_if_set(burrow) + local burrows = get_civ_alert().burrows + if #burrows > 0 and burrows[0] == burrow.id then + burrows:resize(0) + clear_alarm() + end +end + local function toggle_civalert_burrow(id) local burrows = get_civ_alert().burrows if #burrows == 0 then diff --git a/internal/quickfort/burrow.lua b/internal/quickfort/burrow.lua index 7157b59ee6..2091e831c1 100644 --- a/internal/quickfort/burrow.lua +++ b/internal/quickfort/burrow.lua @@ -5,6 +5,7 @@ if not dfhack_flags.module then qerror('this script cannot be called directly') end +local civalert = reqscript('gui/civ-alert') local quickfort_common = reqscript('internal/quickfort/common') local quickfort_map = reqscript('internal/quickfort/map') local quickfort_parse = reqscript('internal/quickfort/parse') @@ -32,6 +33,10 @@ local function custom_burrow(_, keys) db_entry.create = true props.create = nil end + if db_entry.add and props.civalert == 'true' then + db_entry.civalert = true + props.civalert = nil + end for k,v in pairs(props) do dfhack.printerr(('unhandled property for symbol "%s": "%s"="%s"'):format( @@ -76,6 +81,13 @@ local function do_burrow(ctx, db_entry, pos) dfhack.burrows.setAssignedTile(b, pos, db_entry.add) stats['burrow_tiles_'..(db_entry.add and 'added' or 'removed')].value = stats['burrow_tiles_'..(db_entry.add and 'added' or 'removed')].value + 1 + if db_entry.civalert then + if db_entry.add then + civalert.set_civalert_burrow_if_unset(b) + else + civalert.unset_civalert_burrow_if_set(b) + end + end if not db_entry.add and db_entry.create and #dfhack.burrows.listBlocks(b) == 0 then dfhack.burrows.clearTiles(b) local _, _, idx = utils.binsearch(burrows.list, b.id, 'id') From 5f4ee323600274c87614a676f5d43773fbe77a67 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 3 Nov 2023 11:34:26 -0700 Subject: [PATCH 3/3] support autochop integration for burrows blueprints --- internal/quickfort/burrow.lua | 66 +++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/internal/quickfort/burrow.lua b/internal/quickfort/burrow.lua index 2091e831c1..bfb7e0958b 100644 --- a/internal/quickfort/burrow.lua +++ b/internal/quickfort/burrow.lua @@ -24,7 +24,7 @@ local function custom_burrow(_, keys) local token_and_label, props_start_pos = quickfort_parse.parse_token_and_label(keys, 1, '%w') if not token_and_label or not rawget(burrow_db, token_and_label.token) then return nil end local db_entry = copyall(burrow_db[token_and_label.token]) - local props, next_token_pos = quickfort_parse.parse_properties(keys, props_start_pos) + local props = quickfort_parse.parse_properties(keys, props_start_pos) if props.name then db_entry.name = props.name props.name = nil @@ -37,6 +37,14 @@ local function custom_burrow(_, keys) db_entry.civalert = true props.civalert = nil end + if db_entry.add and props.autochop_clear == 'true' then + db_entry.autochop_clear = true + props.autochop_clear = nil + end + if db_entry.add and props.autochop_chop == 'true' then + db_entry.autochop_chop = true + props.autochop_chop = nil + end for k,v in pairs(props) do dfhack.printerr(('unhandled property for symbol "%s": "%s"="%s"'):format( @@ -50,32 +58,38 @@ setmetatable(burrow_db, {__index=custom_burrow}) local burrows = df.global.plotinfo.burrows +local function create_burrow(name) + local b = df.burrow:new() + b.id = burrows.next_id + burrows.next_id = burrows.next_id + 1 + if name then + b.name = name + end + b.symbol_index = math.random(0, 22) + b.texture_r = math.random(0, 255) + b.texture_g = math.random(0, 255) + b.texture_b = math.random(0, 255) + b.texture_br = 255 - b.texture_r + b.texture_bg = 255 - b.texture_g + b.texture_bb = 255 - b.texture_b + burrows.list:insert('#', b) + return b +end + local function do_burrow(ctx, db_entry, pos) local stats = ctx.stats local b if db_entry.name then b = dfhack.burrows.findByName(db_entry.name, true) end - if not b and db_entry.add and db_entry.create then - b = df.burrow:new() - b.id = burrows.next_id - burrows.next_id = burrows.next_id + 1 - if db_entry.name then - b.name = db_entry.name - end - b.symbol_index = math.random(0, 22) - b.texture_r = math.random(0, 255) - b.texture_g = math.random(0, 255) - b.texture_b = math.random(0, 255) - b.texture_br = 255 - b.texture_r - b.texture_bg = 255 - b.texture_g - b.texture_bb = 255 - b.texture_b - burrows.list:insert('#', b) - stats.burrow_created.value = stats.burrow_created.value + 1 - end if not b and db_entry.add then - log('could not find burrow to add to') - return + if db_entry.create then + b = create_burrow(db_entry.name) + stats.burrow_created.value = stats.burrow_created.value + 1 + else + log('could not find burrow to add to') + return + end end if b then dfhack.burrows.setAssignedTile(b, pos, db_entry.add) @@ -88,6 +102,14 @@ local function do_burrow(ctx, db_entry, pos) civalert.unset_civalert_burrow_if_set(b) end end + if db_entry.autochop_clear or db_entry.autochop_chop then + if db_entry.autochop_chop then + dfhack.run_command('autochop', (db_entry.add and '' or 'no')..'chop', tostring(b.id)) + end + if db_entry.autochop_clear then + dfhack.run_command('autochop', (db_entry.add and '' or 'no')..'clear', tostring(b.id)) + end + end if not db_entry.add and db_entry.create and #dfhack.burrows.listBlocks(b) == 0 then dfhack.burrows.clearTiles(b) local _, _, idx = utils.binsearch(burrows.list, b.id, 'id') @@ -98,8 +120,8 @@ local function do_burrow(ctx, db_entry, pos) end end elseif not db_entry.add then - for _,b in ipairs(burrows.list) do - dfhack.burrows.setAssignedTile(b, pos, false) + for _,burrow in ipairs(burrows.list) do + dfhack.burrows.setAssignedTile(burrow, pos, false) end stats.burrow_tiles_removed.value = stats.burrow_tiles_removed.value + 1 end