From 143098d3bd04676f31addcb542ba39efaae3f2f9 Mon Sep 17 00:00:00 2001 From: shevernitskiy Date: Fri, 1 Sep 2023 18:22:37 +0300 Subject: [PATCH 01/54] use reserved range for tileset --- unsuspend.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unsuspend.lua b/unsuspend.lua index 43d41d1adc..2f38fe1252 100644 --- a/unsuspend.lua +++ b/unsuspend.lua @@ -12,7 +12,7 @@ if not ok then buildingplan = nil end -local textures = dfhack.textures.loadTileset('hack/data/art/unsuspend.png', 32, 32) +local textures = dfhack.textures.loadTileset('hack/data/art/unsuspend.png', 32, 32, true) SuspendOverlay = defclass(SuspendOverlay, overlay.OverlayWidget) SuspendOverlay.ATTRS{ From 5a89438bf81dbeb498bc47fe7695401fbd713f71 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 10 Sep 2023 01:09:07 -0700 Subject: [PATCH 02/54] trade non-liquid/powder goods inside of barrels and pots --- changelog.txt | 1 + internal/caravan/movegoods.lua | 56 +++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/changelog.txt b/changelog.txt index 03dc537e9d..0f0fcde242 100644 --- a/changelog.txt +++ b/changelog.txt @@ -34,6 +34,7 @@ Template for new versions: ## Misc Improvements - `autofish`: changed ``--raw`` argument format to allow explicit setting to on or off +- `caravan`: move goods to depot screen can now see/search/trade items inside of barrels and pots ## Removed diff --git a/internal/caravan/movegoods.lua b/internal/caravan/movegoods.lua index 96c8ae6417..fa39d219b3 100644 --- a/internal/caravan/movegoods.lua +++ b/internal/caravan/movegoods.lua @@ -14,9 +14,10 @@ local widgets = require('gui.widgets') MoveGoods = defclass(MoveGoods, widgets.Window) MoveGoods.ATTRS { frame_title='Move goods to/from depot', - frame={w=84, h=46}, + frame={w=85, h=46}, resizable=true, resize_min={h=35}, + frame_inset={l=1, t=1, b=1, r=0}, pending_item_ids=DEFAULT_NIL, depot=DEFAULT_NIL, } @@ -177,7 +178,7 @@ function MoveGoods:init() on_change=function() self:refresh_list() end, }, widgets.Panel{ - frame={t=4, l=40, r=0, h=12}, + frame={t=4, l=40, r=1, h=12}, subviews=common.get_info_widgets(self, get_export_agreements(), self.predicate_context), }, widgets.Panel{ @@ -241,7 +242,7 @@ function MoveGoods:init() widgets.Label{ frame={l=0, b=4, h=1, r=0}, text={ - 'Total value of trade items:', + 'Total value of items marked for trade:', {gap=1, text=function() return common.obfuscate_value(self.value_pending) end}, }, @@ -266,9 +267,9 @@ function MoveGoods:init() on_change=function() self:refresh_list() end, }, widgets.ToggleHotkeyLabel{ - view_id='inside_bins', - frame={l=51, b=2, w=28}, - label='See inside bins:', + view_id='inside_containers', + frame={l=51, b=2, w=30}, + label='Inside containers:', key='CUSTOM_CTRL_I', options={ {label='Yes', value=true, pen=COLOR_GREEN}, @@ -309,6 +310,13 @@ function MoveGoods:refresh_list(sort_widget, sort_fn) list:setFilter(saved_filter) end +local function is_container(item) + return item and ( + df.item_binst:is_instance(item) or + item:isFoodStorage() + ) +end + local function is_tradeable_item(item, depot) if item.flags.hostile or item.flags.removed or @@ -329,8 +337,7 @@ local function is_tradeable_item(item, depot) if item.flags.in_inventory then local gref = dfhack.items.getGeneralRef(item, df.general_ref_type.CONTAINED_IN_ITEM) if not gref then return false end - local container = df.item.find(gref.item_id) - if not container or not df.item_binst:is_instance(container) then + if not is_container(df.item.find(gref.item_id)) or item:isLiquidPowder() then return false end end @@ -406,7 +413,7 @@ local function is_ethical_product(item, animal_ethics, wood_ethics) (not wood_ethics or not common.has_wood(item)) end -local function make_bin_search_key(item, desc) +local function make_container_search_key(item, desc) local words = {} common.add_words(words, desc) for _, contained_item in ipairs(dfhack.items.getContainedItems(item)) do @@ -415,15 +422,22 @@ local function make_bin_search_key(item, desc) return table.concat(words, ' ') end -local function get_cache_index(group_items, inside_bins) +local function get_cache_index(group_items, inside_containers) local val = 1 if group_items then val = val + 1 end - if inside_bins then val = val + 2 end + if inside_containers then val = val + 2 end return val end -function MoveGoods:cache_choices(group_items, inside_bins) - local cache_idx = get_cache_index(group_items, inside_bins) +local function contains_non_liquid_powder(container) + for _, item in ipairs(dfhack.items.getContainedItems(container)) do + if not item:isLiquidPowder() then return true end + end + return false +end + +function MoveGoods:cache_choices(group_items, inside_containers) + local cache_idx = get_cache_index(group_items, inside_containers) if self.choices_cache[cache_idx] then return self.choices_cache[cache_idx] end local pending = self.pending_item_ids @@ -431,11 +445,9 @@ function MoveGoods:cache_choices(group_items, inside_bins) for _, item in ipairs(df.global.world.items.all) do local item_id = item.id if not item or not is_tradeable_item(item, self.depot) then goto continue end - if inside_bins and df.item_binst:is_instance(item) and - dfhack.items.getGeneralRef(item, df.general_ref_type.CONTAINS_ITEM) - then + if inside_containers and is_container(item) and contains_non_liquid_powder(item) then goto continue - elseif not inside_bins and item.flags.in_inventory then + elseif not inside_containers and item.flags.in_inventory then goto continue end local value = common.get_perceived_value(item) @@ -479,8 +491,8 @@ function MoveGoods:cache_choices(group_items, inside_bins) dirty=false, } local search_key - if not inside_bins and df.item_binst:is_instance(item) then - search_key = make_bin_search_key(item, desc) + if not inside_containers and is_container(item) then + search_key = make_container_search_key(item, desc) else search_key = common.make_search_key(desc) end @@ -510,14 +522,14 @@ function MoveGoods:cache_choices(group_items, inside_bins) self.value_pending = self.value_pending + (data.per_item_value * data.selected) end - self.choices_cache[get_cache_index(true, inside_bins)] = group_choices - self.choices_cache[get_cache_index(false, inside_bins)] = nogroup_choices + self.choices_cache[get_cache_index(true, inside_containers)] = group_choices + self.choices_cache[get_cache_index(false, inside_containers)] = nogroup_choices return self.choices_cache[cache_idx] end function MoveGoods:get_choices() local raw_choices = self:cache_choices(self.subviews.group_items:getOptionValue(), - self.subviews.inside_bins:getOptionValue()) + self.subviews.inside_containers:getOptionValue()) local choices = {} local include_forbidden = not self.subviews.hide_forbidden:getOptionValue() local banned = self.subviews.banned:getOptionValue() From 2b81eab62e0695faf54725914ccc974e6250a2c8 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 10 Sep 2023 03:42:20 -0700 Subject: [PATCH 03/54] add missing tags for gui/design --- docs/gui/design.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gui/design.rst b/docs/gui/design.rst index 5bbbce4ecc..2c2e2feb8b 100644 --- a/docs/gui/design.rst +++ b/docs/gui/design.rst @@ -4,7 +4,7 @@ gui/design .. dfhack-tool:: :summary: Design designation utility with shapes. - + :tags: fort design productivity map This tool provides a point and click interface to make designating shapes and patterns easier. Supports both digging designations and placing constructions. From ea4e5602b66e5642dd147588d52971908b8b7899 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 10 Sep 2023 03:44:17 -0700 Subject: [PATCH 04/54] show tagged tools as autocomplete options when a tag is typed --- changelog.txt | 1 + gui/launcher.lua | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0f0fcde242..7491c9e391 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ Template for new versions: ## Misc Improvements - `autofish`: changed ``--raw`` argument format to allow explicit setting to on or off - `caravan`: move goods to depot screen can now see/search/trade items inside of barrels and pots +- `gui/launcher`: show tagged tools in the autocomplete list when a tag name is typed ## Removed diff --git a/gui/launcher.lua b/gui/launcher.lua index 72fb848a6a..cfb8d38efc 100644 --- a/gui/launcher.lua +++ b/gui/launcher.lua @@ -414,7 +414,7 @@ function HelpPanel:add_output(output) if text_len > SCROLLBACK_CHARS then text = text:sub(-SCROLLBACK_CHARS) local text_diff = text_len - #text - HelpPanel_update_label(label, label.text_to_wrap:sub(text_len - #text)) + HelpPanel_update_label(label, label.text_to_wrap:sub(text_diff)) text_height = label:getTextHeight() label:scroll('end') line_num = label.start_line_num @@ -699,30 +699,36 @@ local function add_top_related_entries(entries, entry, n) end function LauncherUI:update_autocomplete(firstword) + local includes = {{str=firstword, types='command'}} local excludes + if helpdb.is_tag(firstword) then + table.insert(includes, {tag=firstword, types='command'}) + end if not dev_mode then excludes = {tag={'dev', 'unavailable'}} - if dfhack.getHideArmokTools() then + if dfhack.getHideArmokTools() and firstword ~= 'armok' then table.insert(excludes.tag, 'armok') end end - local entries = helpdb.search_entries({str=firstword, types='command'}, excludes) + local entries = helpdb.search_entries(includes, excludes) -- if firstword is in the list, extract it so we can add it to the top later -- even if it's not in the list, add it back anyway if it's a valid db entry -- (e.g. if it's a dev script that we masked out) to show that it's a valid -- command - local found = extract_entry(entries,firstword) or helpdb.is_entry(firstword) + local found = extract_entry(entries, firstword) or helpdb.is_entry(firstword) sort_by_freq(entries) - if found then + if helpdb.is_tag(firstword) then + self.subviews.autocomplete_label:setText("Tagged tools") + elseif found then table.insert(entries, 1, firstword) - self.subviews.autocomplete_label:setText("Similar scripts") + self.subviews.autocomplete_label:setText("Similar tools") add_top_related_entries(entries, firstword, 20) else self.subviews.autocomplete_label:setText("Suggestions") end if #firstword == 0 then - self.subviews.autocomplete_label:setText("All scripts") + self.subviews.autocomplete_label:setText("All tools") end self.subviews.autocomplete:set_options(entries, found) From 9324d299cf5369426ef97efd8f57d7aec6d80585 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 13 Sep 2023 23:50:10 -0700 Subject: [PATCH 05/54] fix gui/suspendmanager formatting and tags --- docs/gui/suspendmanager.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gui/suspendmanager.rst b/docs/gui/suspendmanager.rst index 4940dc7c30..24b924b6a7 100644 --- a/docs/gui/suspendmanager.rst +++ b/docs/gui/suspendmanager.rst @@ -3,13 +3,13 @@ gui/suspendmanager .. dfhack-tool:: :summary: Intelligently suspend and unsuspend jobs. - + :tags: fort jobs This is the graphical configuration interface for the `suspendmanager` automation tool. Usage -===== +----- :: From 2ecd256e7a15fd9134e0a27fbcacae419cf94a23 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 13 Sep 2023 23:50:51 -0700 Subject: [PATCH 06/54] fix gui/autofish formatting --- docs/gui/autofish.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gui/autofish.rst b/docs/gui/autofish.rst index 739f137d7a..c216c55a16 100644 --- a/docs/gui/autofish.rst +++ b/docs/gui/autofish.rst @@ -11,7 +11,7 @@ should also count your raw fish. You can also check whether or not autofish is currently fishing or not. Usage -===== +----- :: From 0f457b56595f0e7eaba8300a6870af406b1154f6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 14 Sep 2023 12:37:08 -0700 Subject: [PATCH 07/54] update changelog for 50.09-r4 --- changelog.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7491c9e391..30e25f696f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -32,13 +32,17 @@ Template for new versions: ## Fixes +## Misc Improvements + +## Removed + +# 50.09-r4 + ## Misc Improvements - `autofish`: changed ``--raw`` argument format to allow explicit setting to on or off - `caravan`: move goods to depot screen can now see/search/trade items inside of barrels and pots - `gui/launcher`: show tagged tools in the autocomplete list when a tag name is typed -## Removed - # 50.09-r3 ## New Tools From 8016747ae999147c147ddd62c3b1d8a9a701b0b3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 16 Sep 2023 05:21:16 -0500 Subject: [PATCH 08/54] update fix/general-strike make less aggressive should close #3779 --- changelog.txt | 1 + fix/general-strike.lua | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 30e25f696f..08e8ff9f89 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,7 @@ Template for new versions: ## New Features ## Fixes +- 'fix/general-strike: make less aggressive about trying to fix problems that don't exist yet ## Misc Improvements diff --git a/fix/general-strike.lua b/fix/general-strike.lua index 9807112b22..c3b3b10b66 100644 --- a/fix/general-strike.lua +++ b/fix/general-strike.lua @@ -6,10 +6,16 @@ local argparse = require('argparse') local function fix_seeds(quiet) local count = 0 for _,v in ipairs(df.global.world.items.other.SEEDS) do - if not v.flags.in_building then + if (not v.flags.in_job) and (not v.flags.in_building) then local bld = dfhack.items.getHolderBuilding(v) if bld and bld:isFarmPlot() then v.flags.in_building = true + for _,i in ipairs(bld.contained_items) do + print (('%d %d'):format(i.item.id, v.id)) + if i.item.id == v.id then + i.use_mode = 2 + end + end count = count + 1 end end From 9373e8547679767a7f7c7d138eeb405d4c7d3088 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 16 Sep 2023 05:22:30 -0500 Subject: [PATCH 09/54] remove errant debugging print --- fix/general-strike.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/fix/general-strike.lua b/fix/general-strike.lua index c3b3b10b66..5081251c2d 100644 --- a/fix/general-strike.lua +++ b/fix/general-strike.lua @@ -11,7 +11,6 @@ local function fix_seeds(quiet) if bld and bld:isFarmPlot() then v.flags.in_building = true for _,i in ipairs(bld.contained_items) do - print (('%d %d'):format(i.item.id, v.id)) if i.item.id == v.id then i.use_mode = 2 end From e0fdd68f8824a2af050bf4f71769a50d0dd3bfcb Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 Sep 2023 14:45:01 +0300 Subject: [PATCH 10/54] Added orders-reevaluate option to request manager orders conditions recheck once per month. --- gui/control-panel.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index 24689c0c6b..eb3e67b05a 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -127,6 +127,9 @@ local REPEATS = { ['orders-sort']={ desc='Sort manager orders by repeat frequency so one-time orders can be completed.', command={'--time', '1', '--timeUnits', 'days', '--command', '[', 'orders', 'sort', ']'}}, + ['orders-reevaluate']={ + desc='Invalidates manager orders making it necessary to recheck conditions.', + command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'reset', ']'}}, ['warn-starving']={ desc='Show a warning dialog when units are starving or dehydrated.', command={'--time', '10', '--timeUnits', 'days', '--command', '[', 'warn-starving', ']'}}, From e0591830b72cdfaec5c9bdb1bf713a74fe744788 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 17 Sep 2023 23:51:58 -0700 Subject: [PATCH 11/54] bump changelog to 50.10-r1 --- changelog.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 08e8ff9f89..4970b5eac7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,12 +31,16 @@ Template for new versions: ## New Features ## Fixes -- 'fix/general-strike: make less aggressive about trying to fix problems that don't exist yet ## Misc Improvements ## Removed +# 50.10-r1 + +## Fixes +- 'fix/general-strike: fix issue where too many seeds were getting planted in farm plots + # 50.09-r4 ## Misc Improvements From c7456909bc0ed5c9d0248f1762921b09a57a3f87 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 11:44:32 +0300 Subject: [PATCH 12/54] Added 'once a month' clarification to orders-reevaluate. --- gui/control-panel.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index eb3e67b05a..8a39294c50 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -128,7 +128,7 @@ local REPEATS = { desc='Sort manager orders by repeat frequency so one-time orders can be completed.', command={'--time', '1', '--timeUnits', 'days', '--command', '[', 'orders', 'sort', ']'}}, ['orders-reevaluate']={ - desc='Invalidates manager orders making it necessary to recheck conditions.', + desc='Invalidates manager orders once a month making it necessary to recheck conditions.', command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'reset', ']'}}, ['warn-starving']={ desc='Show a warning dialog when units are starving or dehydrated.', From bec158e26692100d8a3f77619ae769223d4b4d0e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 13:42:10 +0300 Subject: [PATCH 13/54] Changed 'reset' to 'recheck'. --- gui/control-panel.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index 8a39294c50..bdc5a980cb 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -129,7 +129,7 @@ local REPEATS = { command={'--time', '1', '--timeUnits', 'days', '--command', '[', 'orders', 'sort', ']'}}, ['orders-reevaluate']={ desc='Invalidates manager orders once a month making it necessary to recheck conditions.', - command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'reset', ']'}}, + command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'recheck', ']'}}, ['warn-starving']={ desc='Show a warning dialog when units are starving or dehydrated.', command={'--time', '10', '--timeUnits', 'days', '--command', '[', 'warn-starving', ']'}}, From d8acfef36d108bbea0f68a481f54769b6d7873bc Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 18:48:44 +0300 Subject: [PATCH 14/54] Removed workorder-recheck.lua. Work order manager enhancement branch moves it's code to orders.lua plugin. --- gui/control-panel.lua | 2 +- workorder-recheck.lua | 103 ------------------------------------------ 2 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 workorder-recheck.lua diff --git a/gui/control-panel.lua b/gui/control-panel.lua index bdc5a980cb..fb248e5cc4 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -128,7 +128,7 @@ local REPEATS = { desc='Sort manager orders by repeat frequency so one-time orders can be completed.', command={'--time', '1', '--timeUnits', 'days', '--command', '[', 'orders', 'sort', ']'}}, ['orders-reevaluate']={ - desc='Invalidates manager orders once a month making it necessary to recheck conditions.', + desc='Invalidates work orders once a month forcing manager to recheck conditions.', command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'recheck', ']'}}, ['warn-starving']={ desc='Show a warning dialog when units are starving or dehydrated.', diff --git a/workorder-recheck.lua b/workorder-recheck.lua deleted file mode 100644 index 7115cdd09c..0000000000 --- a/workorder-recheck.lua +++ /dev/null @@ -1,103 +0,0 @@ --- Resets the selected work order to the `Checking` state - ---@ module = true - -local widgets = require('gui.widgets') -local overlay = require('plugins.overlay') - -local function set_current_inactive() - local scrConditions = df.global.game.main_interface.info.work_orders.conditions - if scrConditions.open then - local order = scrConditions.wq - order.status.active = false - else - qerror("Order conditions is not open") - end -end - -local function is_current_active() - local scrConditions = df.global.game.main_interface.info.work_orders.conditions - local order = scrConditions.wq - return order.status.active -end - --- ------------------- --- RecheckOverlay --- - -local focusString = 'dwarfmode/Info/WORK_ORDERS/Conditions' - -RecheckOverlay = defclass(RecheckOverlay, overlay.OverlayWidget) -RecheckOverlay.ATTRS{ - default_pos={x=6,y=8}, - default_enabled=true, - viewscreens=focusString, - -- width is the sum of lengths of `[` + `Ctrl+A` + `: ` + button.label + `]` - frame={w=1 + 6 + 2 + 16 + 1, h=3}, -} - -local function areTabsInTwoRows() - -- get the tile above the order status icon - local pen = dfhack.screen.readTile(7, 7, false) - -- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures") - -- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top) - return (pen.ch ~= 0 and pen.ch ~= 32) -end - -function RecheckOverlay:updateTextButtonFrame() - local twoRows = areTabsInTwoRows() - if (self._twoRows == twoRows) then return false end - - self._twoRows = twoRows - local frame = twoRows - and {b=0, l=0, r=0, h=1} - or {t=0, l=0, r=0, h=1} - self.subviews.button.frame = frame - - return true -end - -function RecheckOverlay:init() - self:addviews{ - widgets.TextButton{ - view_id = 'button', - -- frame={t=0, l=0, r=0, h=1}, -- is set in `updateTextButtonFrame()` - label='request re-check', - key='CUSTOM_CTRL_A', - on_activate=set_current_inactive, - enabled=is_current_active, - }, - } - - self:updateTextButtonFrame() -end - -function RecheckOverlay:onRenderBody(dc) - if (self.frame_rect.y1 == 7) then - -- only apply this logic if the overlay is on the same row as - -- originally thought: just above the order status icon - - if self:updateTextButtonFrame() then - self:updateLayout() - end - end - - RecheckOverlay.super.onRenderBody(self, dc) -end - --- ------------------- - -OVERLAY_WIDGETS = { - recheck=RecheckOverlay, -} - -if dfhack_flags.module then - return -end - --- Check if on the correct screen and perform the action if so -if not dfhack.gui.matchFocusString(focusString) then - qerror('workorder-recheck must be run from the manager order conditions view') -end - -set_current_inactive() From 3fb36edc030f487a063243348bc306fe0a473134 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 19:03:55 +0300 Subject: [PATCH 15/54] Removed workorder-recheck.rst. --- docs/workorder-recheck.rst | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 docs/workorder-recheck.rst diff --git a/docs/workorder-recheck.rst b/docs/workorder-recheck.rst deleted file mode 100644 index 9685823351..0000000000 --- a/docs/workorder-recheck.rst +++ /dev/null @@ -1,25 +0,0 @@ -workorder-recheck -================= - -.. dfhack-tool:: - :summary: Recheck start conditions for a manager workorder. - :tags: fort workorders - -Sets the status to ``Checking`` (from ``Active``) of the selected work order. -This makes the manager reevaluate its conditions. This is especially useful -for an order that had its conditions met when it was started, but the requisite -items have since disappeared and the workorder is now generating job cancellation -spam. - -Usage ------ - -:: - - workorder-recheck - -Overlay -------- - -The position of the "request re-check" text that appears when a workorder -conditions window is open is configurable via `gui/overlay`. From a10400f78fd8fdb600602ee9ac4bb1c536942915 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 24 Sep 2023 13:47:06 -0700 Subject: [PATCH 16/54] show total grid sizes --- changelog.txt | 1 + devel/inspect-screen.lua | 52 ++++++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4970b5eac7..f4898dcd82 100644 --- a/changelog.txt +++ b/changelog.txt @@ -33,6 +33,7 @@ Template for new versions: ## Fixes ## Misc Improvements +- `devel/inspect-screen`: display total grid size for UI and map layers ## Removed diff --git a/devel/inspect-screen.lua b/devel/inspect-screen.lua index 586166c398..3510cf0468 100644 --- a/devel/inspect-screen.lua +++ b/devel/inspect-screen.lua @@ -1,7 +1,7 @@ -- Read from the screen and display info about the tiles local gui = require('gui') -local utils = require('utils') +local guidm = require('gui.dwarfmode') local widgets = require('gui.widgets') local overlay = require('plugins.overlay') @@ -19,32 +19,42 @@ function Inspect:init() self:addviews{ widgets.Label{ frame={t=0, l=0}, - text={'Current screen: ', {text=scr_name, pen=COLOR_CYAN}}}, + text={'Current screen: ', {text=scr_name, pen=COLOR_CYAN}}, + }, widgets.CycleHotkeyLabel{ view_id='layer', frame={t=2, l=0}, key='CUSTOM_CTRL_A', label='Inspect layer:', options={{label='UI', value='ui'}, 'map'}, - enabled=self:callback('is_unfrozen')}, + enabled=self:callback('is_unfrozen'), + }, widgets.CycleHotkeyLabel{ view_id='empties', frame={t=3, l=0}, key='CUSTOM_CTRL_E', label='Empty elements:', - options={'hide', 'show'}}, + options={'hide', 'show'}, + }, widgets.ToggleHotkeyLabel{ view_id='freeze', frame={t=4, l=0}, key='CUSTOM_CTRL_F', label='Freeze current tile:', - initial_option=false}, + initial_option=false, + }, widgets.Label{ frame={t=6}, - text={{text=self:callback('get_mouse_pos')}}}, + text={{text=self:callback('get_grid_size')}}, + }, + widgets.Label{ + frame={t=7}, + text={{text=self:callback('get_mouse_pos')}}, + }, widgets.Label{ view_id='report', - frame={t=8},}, + frame={t=9}, + }, } end @@ -56,6 +66,15 @@ function Inspect:do_refresh() return self:is_unfrozen() and not self:getMouseFramePos() end +function Inspect:get_grid_size() + if self.subviews.layer:getOptionValue() == 'ui' then + local width, height = dfhack.screen.getWindowSize() + return ('UI grid size: %d x %d'):format(width, height) + end + local layout = guidm.getPanelLayout() + return ('Map grid size: %d x %d'):format(layout.map.width, layout.map.height) +end + local cur_mouse_pos = {x=-1, y=-1} function Inspect:get_mouse_pos() local pos, text = cur_mouse_pos, '' @@ -294,13 +313,12 @@ local function get_map_report(show_empty) end function Inspect:onRenderBody() - if self:do_refresh() then - local show_empty = self.subviews.empties:getOptionValue() == 'show' - local report = self.subviews.layer:getOptionValue() == 'ui' and - get_ui_report(show_empty) or get_map_report(show_empty) - self.subviews.report:setText(report) - self:updateLayout() - end + if not self:do_refresh() then return end + local show_empty = self.subviews.empties:getOptionValue() == 'show' + local report = self.subviews.layer:getOptionValue() == 'ui' and + get_ui_report(show_empty) or get_map_report(show_empty) + self.subviews.report:setText(report) + self:updateLayout() end function Inspect:onInput(keys) @@ -313,18 +331,16 @@ function Inspect:onInput(keys) end end -InspectScreen = defclass(InspectScreen, gui.ZScreen) +InspectScreen = defclass(InspectScreen, gui.ZScreenModal) InspectScreen.ATTRS{ focus_string='inspect-screen', - force_pause=true, - pass_mouse_clicks=false, } function InspectScreen:init() -- prevent hotspot widgets from reacting overlay.register_trigger_lock_screen(self) - self:addviews{Inspect{view_id='main'}} + self:addviews{Inspect{}} end function InspectScreen:onDismiss() From f06180326506b8d2b9ee99d0f88dc299482fdf8a Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 15:25:16 +0100 Subject: [PATCH 17/54] suspendmanager now checks for unsupported tiles --- suspendmanager.lua | 209 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index 4f650f8b27..16c0d6927d 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -36,6 +36,7 @@ REASON = { ERASE_DESIGNATION = 4, --- Blocks a dead end (either a corridor or on top of a wall) DEADEND = 5, + UNSUPPORTED = 6, } REASON_TEXT = { @@ -44,6 +45,7 @@ REASON_TEXT = { [REASON.RISK_BLOCKING] = 'blocking', [REASON.ERASE_DESIGNATION] = 'designation', [REASON.DEADEND] = 'dead end', + [REASON.UNSUPPORTED] = 'unsupported', } --- Description of suspension @@ -52,7 +54,8 @@ REASON_TEXT = { REASON_DESCRIPTION = { [REASON.RISK_BLOCKING] = 'May block another build job', [REASON.ERASE_DESIGNATION] = 'Waiting for carve/smooth/engrave', - [REASON.DEADEND] = 'Blocks another build job' + [REASON.DEADEND] = 'Blocks another build job', + [REASON.UNSUPPORTED] = 'Construction is unsupported' } --- Suspension reasons from an external source @@ -161,6 +164,77 @@ local CONSTRUCTION_IMPASSABLE = utils.invert{ df.construction_type.Fortification, } +local CONSTRUCTION_WALL_SUPPORT = utils.invert{ + df.construction_type.Wall, + df.construction_type.Fortification, + df.construction_type.UpStair, + df.construction_type.UpDownStair, +} + +local CONSTRUCTION_FLOOR_SUPPORT = utils.invert{ + df.construction_type.FLOOR, + df.construction_type.DownStair, + df.construction_type.Ramp, + df.construction_type.TrackN, + df.construction_type.TrackS, + df.construction_type.TrackE, + df.construction_type.TrackW, + df.construction_type.TrackNS, + df.construction_type.TrackNE, + df.construction_type.TrackSE, + df.construction_type.TrackSW, + df.construction_type.TrackEW, + df.construction_type.TrackNSE, + df.construction_type.TrackNSW, + df.construction_type.TrackNEW, + df.construction_type.TrackSEW, + df.construction_type.TrackNSEW, + df.construction_type.TrackRampN, + df.construction_type.TrackRampS, + df.construction_type.TrackRampE, + df.construction_type.TrackRampW, + df.construction_type.TrackRampNS, + df.construction_type.TrackRampNE, + df.construction_type.TrackRampNW, + df.construction_type.TrackRampSE, + df.construction_type.TrackRampSW, + df.construction_type.TrackRampEW, + df.construction_type.TrackRampNSE, + df.construction_type.TrackRampNSW, + df.construction_type.TrackRampNEW, + df.construction_type.TrackRampSEW, + df.construction_type.TrackRampNSEW, +} + +-- all the tiletype shapes which provide support as if a wall +-- note that these shapes act as if there is a floor above them, +-- (including an up stair with no down stair above) which then connects +-- orthogonally at that level. +-- see: https://dwarffortresswiki.org/index.php/DF2014:Cave-in +local TILETYPE_SHAPE_WALL_SUPPORT = utils.invert{ + df.tiletype_shape.WALL, + df.tiletype_shape.FORTIFICATION, + df.tiletype_shape.STAIR_UP, + df.tiletype_shape.STAIR_UPDOWN, +} + +-- all the tiletype shapes which provide support as if it were a floor. +-- Tested as of v50.10 - YES, twigs do provide orthogonal support like a floor. +local TILETYPE_SHAPE_FLOOR_SUPPORT = utils.invert{ + df.tiletype_shape.FLOOR, + df.tiletype_shape.STAIR_DOWN, + df.tiletype_shape.RAMP, + df.tiletype_shape.BOULDER, + df.tiletype_shape.PEBBLES, + df.tiletype_shape.SAPLING, + df.tiletype_shape.BROOK_BED, + df.tiletype_shape.BROOK_TOP, + df.tiletype_shape.SHRUB, + df.tiletype_shape.TWIG, + df.tiletype_shape.BRANCH, + df.tiletype_shape.TRUNK_BRANCH, +} + local BUILDING_IMPASSABLE = utils.invert{ df.building_type.Floodgate, df.building_type.Statue, @@ -266,6 +340,134 @@ local function neighbours(pos) } end +--- list neighbour coordinates of pos which if is a Wall, will support a Wall at pos +---@param pos coord +---@return table +local function neighboursWallSupportsWall(pos) + return { + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z-1}, + {x=pos.x+1, y=pos.y, z=pos.z-1}, + {x=pos.x, y=pos.y-1, z=pos.z-1}, + {x=pos.x, y=pos.y+1, z=pos.z-1}, + {x=pos.x-1, y=pos.y, z=pos.z+1}, + {x=pos.x+1, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y-1, z=pos.z+1}, + {x=pos.x, y=pos.y+1, z=pos.z+1}, + {x=pos.x, y=pos.y, z=pos.z-1}, + {x=pos.x, y=pos.y, z=pos.z+1}, + } +end + +--- list neighbour coordinates of pos which if is a Floor, will support a Wall at pos +---@param pos coord +---@return table +local function neighboursFloorSupportsWall(pos) + return { + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x, y=pos.y, z=pos.z+1}, + {x=pos.x-1, y=pos.y, z=pos.z+1}, + {x=pos.x+1, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y-1, z=pos.z+1}, + {x=pos.x, y=pos.y+1, z=pos.z+1}, + } +end + +--- list neighbour coordinates of pos which if is a Wall, will support a Floor at pos +---@param pos coord +---@return table +local function neighboursWallSupportsFloor(pos) + return { + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + } +end + +--- list neighbour coordinates of pos which if is a Floor, will support a Floor at pos +---@param pos coord +---@return table +local function neighboursFloorSupportsFloor(pos) + return { + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x, y=pos.y, z=pos.z+1}, + {x=pos.x-1, y=pos.y, z=pos.z+1}, + {x=pos.x+1, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y-1, z=pos.z+1}, + {x=pos.x, y=pos.y+1, z=pos.z+1}, + } +end + +local function tileHasSupportBuilding(pos) + local bld = dfhack.buildings.findAtTile(pos) + if bld then + return bld:getType() == df.building_type.Support and bld.flags.exists + end + return false +end + +--- +local function constructionIsUnsupported(job) + if job.job_type ~= df.job_type.ConstructBuilding then return false end + + local building = dfhack.job.getHolder(job) + if not building or building:getType() ~= df.building_type.Construction then return false end + + local pos = {x=building.centerx, y=building.centery,z=building.z} + + -- find out what type of construction + local constr_type = building:getSubtype() + if CONSTRUCTION_FLOOR_SUPPORT[constr_type] then + for _,n in pairs(neighboursWallSupportsFloor(pos)) do + local tt = dfhack.maps.getTileType(n) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_WALL_SUPPORT[attrs.shape] then return false end + end + end + for _,n in pairs(neighboursFloorSupportsFloor(pos)) do + local tt = dfhack.maps.getTileType(n) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return false end + end + end + -- check for a support building below the tile + if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z-1}) then return false end + return true + elseif CONSTRUCTION_WALL_SUPPORT[constr_type] then + for _,n in pairs(neighboursWallSupportsWall(pos)) do + local tt = dfhack.maps.getTileType(n) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_WALL_SUPPORT[attrs.shape] then return false end + end + end + for _,n in pairs(neighboursFloorSupportsWall(pos)) do + local tt = dfhack.maps.getTileType(n) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return false end + end + end + -- check for a support building below and above the tile + if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z-1}) then return false end + if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z+1}) then return false end + return true + end + return false +end + --- Get the amount of risk a tile is to be blocked --- -1: There is a nearby walkable area with no plan to build a wall --- >=0: Surrounded by either unwalkable tiles, or tiles that will be constructed @@ -433,6 +635,11 @@ function SuspendManager:refresh() end end + -- Check for construction jobs which may be unsupported + if constructionIsUnsupported(job) then + self.suspensions[job.id]=REASON.UNSUPPORTED + end + if not self.preventBlocking then goto continue end -- Internal reasons to suspend a job From f761683bebad7f77c1f50c64ea3e78f94ff48aa3 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 15:47:57 +0100 Subject: [PATCH 18/54] add suspendmanager unsupported condition improvement to changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index f4898dcd82..5efb93672f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -34,6 +34,7 @@ Template for new versions: ## Misc Improvements - `devel/inspect-screen`: display total grid size for UI and map layers +- `suspendmanager`: now suspends constructions that would cave-in immediately on completion ## Removed From e49605726d470f72916c3deea591b6e64d4f4e34 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 16:29:10 +0100 Subject: [PATCH 19/54] cleaned up the control flow of function constructionIsUnsupported(job) for reading clarity --- suspendmanager.lua | 76 ++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index 16c0d6927d..bd26c7ee1b 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -408,6 +408,23 @@ local function neighboursFloorSupportsFloor(pos) } end +local function tileHasSupportWall(pos) + local tt = dfhack.maps.getTileType(pos) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_WALL_SUPPORT[attrs.shape] then return true end + end + return false +end + +local function tileHasSupportFloor(pos) + local tt = dfhack.maps.getTileType(pos) + if tt then + local attrs = df.tiletype.attrs[tt] + if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return true end + end +end + local function tileHasSupportBuilding(pos) local bld = dfhack.buildings.findAtTile(pos) if bld then @@ -427,45 +444,32 @@ local function constructionIsUnsupported(job) -- find out what type of construction local constr_type = building:getSubtype() + local wall_would_support = {} + local floor_would_support = {} + local supportbld_would_support = {} + if CONSTRUCTION_FLOOR_SUPPORT[constr_type] then - for _,n in pairs(neighboursWallSupportsFloor(pos)) do - local tt = dfhack.maps.getTileType(n) - if tt then - local attrs = df.tiletype.attrs[tt] - if TILETYPE_SHAPE_WALL_SUPPORT[attrs.shape] then return false end - end - end - for _,n in pairs(neighboursFloorSupportsFloor(pos)) do - local tt = dfhack.maps.getTileType(n) - if tt then - local attrs = df.tiletype.attrs[tt] - if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return false end - end - end - -- check for a support building below the tile - if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z-1}) then return false end - return true + wall_would_support = neighboursWallSupportsFloor(pos) + floor_would_support = neighboursFloorSupportsFloor(pos) + supportbld_would_support = {{x=pos.x, y=pos.y, z=pos.z-1}} elseif CONSTRUCTION_WALL_SUPPORT[constr_type] then - for _,n in pairs(neighboursWallSupportsWall(pos)) do - local tt = dfhack.maps.getTileType(n) - if tt then - local attrs = df.tiletype.attrs[tt] - if TILETYPE_SHAPE_WALL_SUPPORT[attrs.shape] then return false end - end - end - for _,n in pairs(neighboursFloorSupportsWall(pos)) do - local tt = dfhack.maps.getTileType(n) - if tt then - local attrs = df.tiletype.attrs[tt] - if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return false end - end - end - -- check for a support building below and above the tile - if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z-1}) then return false end - if tileHasSupportBuilding({x=pos.x, y=pos.y, z=pos.z+1}) then return false end - return true + wall_would_support = neighboursWallSupportsWall(pos) + floor_would_support = neighboursFloorSupportsWall(pos) + supportbld_would_support = {{x=pos.x, y=pos.y, z=pos.z-1}, {x=pos.x, y=pos.y, z=pos.z+1}} + else return false -- some unknown construction - don't suspend end - return false + + for _,n in pairs(wall_would_support) do + if tileHasSupportWall(n) then return false end + end + for _,n in pairs(floor_would_support) do + if tileHasSupportFloor(n) then return false end + end + -- check for a support building below the tile + for _,n in pairs(supportbld_would_support) do + if tileHasSupportBuilding(n) then return false end + end + return true end --- Get the amount of risk a tile is to be blocked From 72f6b743bf6e41e35a0233e1b6a16db1460715d5 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 16:49:49 +0100 Subject: [PATCH 20/54] fixed typo df.construction_type.FLOOR -> ...Floor --- suspendmanager.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index bd26c7ee1b..f407ad0761 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -172,7 +172,7 @@ local CONSTRUCTION_WALL_SUPPORT = utils.invert{ } local CONSTRUCTION_FLOOR_SUPPORT = utils.invert{ - df.construction_type.FLOOR, + df.construction_type.Floor, df.construction_type.DownStair, df.construction_type.Ramp, df.construction_type.TrackN, @@ -447,7 +447,7 @@ local function constructionIsUnsupported(job) local wall_would_support = {} local floor_would_support = {} local supportbld_would_support = {} - + if CONSTRUCTION_FLOOR_SUPPORT[constr_type] then wall_would_support = neighboursWallSupportsFloor(pos) floor_would_support = neighboursFloorSupportsFloor(pos) From db0f4a3c79171d250f1b375a3e82d3e8e1adb9b0 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 17:04:33 +0100 Subject: [PATCH 21/54] SuspendManager:refresh() moved constructionIsUnsupported() check after self.preventBlocking check --- suspendmanager.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index f407ad0761..f33273ff43 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -639,10 +639,7 @@ function SuspendManager:refresh() end end - -- Check for construction jobs which may be unsupported - if constructionIsUnsupported(job) then - self.suspensions[job.id]=REASON.UNSUPPORTED - end + if not self.preventBlocking then goto continue end @@ -651,6 +648,11 @@ function SuspendManager:refresh() self.suspensions[job.id]=REASON.RISK_BLOCKING end + -- Check for construction jobs which may be unsupported + if constructionIsUnsupported(job) then + self.suspensions[job.id]=REASON.UNSUPPORTED + end + -- If this job is a dead end, mark jobs leading to it as dead end self:suspendDeadend(job) From 2f4a6e5f22771ccc239b7a24ddf876a78cf6141b Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 17:10:27 +0100 Subject: [PATCH 22/54] suspendmanager: added comment and updated doc --- docs/suspendmanager.rst | 2 ++ suspendmanager.lua | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/suspendmanager.rst b/docs/suspendmanager.rst index f36c8e3c9c..fc26461833 100644 --- a/docs/suspendmanager.rst +++ b/docs/suspendmanager.rst @@ -14,6 +14,8 @@ This tool will watch your active jobs and: - suspend construction jobs on top of a smoothing, engraving or track carving designation. This prevents the construction job from being completed first, which would erase the designation. +- suspend construction jobs that would cave in immediately on completion, + such as when building walls or floors next to grates/bars. Usage ----- diff --git a/suspendmanager.lua b/suspendmanager.lua index f33273ff43..458425b1d1 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -36,6 +36,7 @@ REASON = { ERASE_DESIGNATION = 4, --- Blocks a dead end (either a corridor or on top of a wall) DEADEND = 5, + --- Would cave in immediately on completion UNSUPPORTED = 6, } From 01481c6ec1273f054cce09c098e0d8765216d38b Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 17:26:39 +0100 Subject: [PATCH 23/54] fixed directions for supporting connections --- suspendmanager.lua | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index 458425b1d1..a37cd28dee 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -346,20 +346,20 @@ end ---@return table local function neighboursWallSupportsWall(pos) return { - {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, -- orthogonal same level {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}, - {x=pos.x-1, y=pos.y, z=pos.z-1}, + {x=pos.x-1, y=pos.y, z=pos.z-1}, -- orthogonal level below {x=pos.x+1, y=pos.y, z=pos.z-1}, {x=pos.x, y=pos.y-1, z=pos.z-1}, {x=pos.x, y=pos.y+1, z=pos.z-1}, - {x=pos.x-1, y=pos.y, z=pos.z+1}, + {x=pos.x-1, y=pos.y, z=pos.z+1}, -- orthogonal level above {x=pos.x+1, y=pos.y, z=pos.z+1}, {x=pos.x, y=pos.y-1, z=pos.z+1}, {x=pos.x, y=pos.y+1, z=pos.z+1}, - {x=pos.x, y=pos.y, z=pos.z-1}, - {x=pos.x, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y, z=pos.z-1}, -- directly below + {x=pos.x, y=pos.y, z=pos.z+1}, -- directly above } end @@ -368,12 +368,12 @@ end ---@return table local function neighboursFloorSupportsWall(pos) return { - {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, -- orthogonal same level {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}, - {x=pos.x, y=pos.y, z=pos.z+1}, - {x=pos.x-1, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y, z=pos.z+1}, -- directly above + {x=pos.x-1, y=pos.y, z=pos.z+1}, --orthogonal level above {x=pos.x+1, y=pos.y, z=pos.z+1}, {x=pos.x, y=pos.y-1, z=pos.z+1}, {x=pos.x, y=pos.y+1, z=pos.z+1}, @@ -385,10 +385,15 @@ end ---@return table local function neighboursWallSupportsFloor(pos) return { - {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, -- orthogonal same level {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z-1}, -- orthogonal level below + {x=pos.x+1, y=pos.y, z=pos.z-1}, + {x=pos.x, y=pos.y-1, z=pos.z-1}, + {x=pos.x, y=pos.y+1, z=pos.z-1}, + {x=pos.x, y=pos.y, z=pos.z-1}, -- directly below } end @@ -397,15 +402,10 @@ end ---@return table local function neighboursFloorSupportsFloor(pos) return { - {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, -- orthogonal same level {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}, - {x=pos.x, y=pos.y, z=pos.z+1}, - {x=pos.x-1, y=pos.y, z=pos.z+1}, - {x=pos.x+1, y=pos.y, z=pos.z+1}, - {x=pos.x, y=pos.y-1, z=pos.z+1}, - {x=pos.x, y=pos.y+1, z=pos.z+1}, } end From eab1120473c4844aeffc319c15388cc8c7bcdf6b Mon Sep 17 00:00:00 2001 From: plule <630159+plule@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:35:56 +0200 Subject: [PATCH 24/54] Restore the "kept suspended" color --- unsuspend.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unsuspend.lua b/unsuspend.lua index 2f38fe1252..2a56f5dea9 100644 --- a/unsuspend.lua +++ b/unsuspend.lua @@ -154,7 +154,7 @@ function SuspendOverlay:render_marker(dc, bld, screen_pos) if buildingplan and buildingplan.isPlannedBuilding(bld) then color, ch, texpos = COLOR_GREEN, 'P', tp(4) elseif suspendmanager and suspendmanager.isKeptSuspended(job) then - color, ch, texpos = COLOR_WHITE, 'x', tp(1) + color, ch, texpos = COLOR_WHITE, 'x', tp(3) elseif data.suspend_count > 1 then color, ch, texpos = COLOR_RED, 'X', tp(1) end From 7c924218d3772b38f894a42da65a755155efacdb Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 18:06:21 +0100 Subject: [PATCH 25/54] constructionIsUnsupported() - early return if unreachable to reduce spam --- suspendmanager.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/suspendmanager.lua b/suspendmanager.lua index a37cd28dee..2de83aecec 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -409,6 +409,13 @@ local function neighboursFloorSupportsFloor(pos) } end +local function hasWalkableNeighbour(pos) + for _,n in pairs(neighbours(pos)) do + if (walkable(n)) then return true end + end + return false +end + local function tileHasSupportWall(pos) local tt = dfhack.maps.getTileType(pos) if tt then @@ -443,6 +450,10 @@ local function constructionIsUnsupported(job) local pos = {x=building.centerx, y=building.centery,z=building.z} + -- if no neighbour is walkable it can't be constructed now anyways, + -- this early return helps reduce "spam" + if not hasWalkableNeighbour(pos) then return false end + -- find out what type of construction local constr_type = building:getSubtype() local wall_would_support = {} From 4ac36ac4e8b5b796ae1ff5c54d2504e9d1b3ab4c Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 18:19:51 +0100 Subject: [PATCH 26/54] suspendmanager - clarified 'unsupported' reason description --- suspendmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index 2de83aecec..f799e119f5 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -56,7 +56,7 @@ REASON_DESCRIPTION = { [REASON.RISK_BLOCKING] = 'May block another build job', [REASON.ERASE_DESIGNATION] = 'Waiting for carve/smooth/engrave', [REASON.DEADEND] = 'Blocks another build job', - [REASON.UNSUPPORTED] = 'Construction is unsupported' + [REASON.UNSUPPORTED] = 'Would collapse immediately' } --- Suspension reasons from an external source From 6d3bd57dbc7a2bebe9cb047f67354358eeab41e3 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Mon, 25 Sep 2023 19:11:04 +0100 Subject: [PATCH 27/54] reverted walkable neighbour early return --- suspendmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index f799e119f5..9f97fd4127 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -452,7 +452,7 @@ local function constructionIsUnsupported(job) -- if no neighbour is walkable it can't be constructed now anyways, -- this early return helps reduce "spam" - if not hasWalkableNeighbour(pos) then return false end + -- if not hasWalkableNeighbour(pos) then return false end -- commented out pending `walkable()` fix -- find out what type of construction local constr_type = building:getSubtype() From eb3b1fceb6380276a479359ebb51c061db4ec24b Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Tue, 26 Sep 2023 08:49:26 +0100 Subject: [PATCH 28/54] walkable() now correct and new function to ignore case where that tile is tree branch --- suspendmanager.lua | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/suspendmanager.lua b/suspendmanager.lua index 9f97fd4127..a3d751d349 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -308,25 +308,19 @@ end --- Check if the tile can be walked on ---@param pos coord local function walkable(pos) + return dfhack.maps.getTileBlock(pos).walkable[pos.x % 16][pos.y % 16] > 0 +end + +--- Check if the tile is suitable tile to stand on for construction (walkable & not a tree branch) +---@param pos coord +local function isSuitableAccess(pos) local tt = dfhack.maps.getTileType(pos) - if not tt then - return false - end local attrs = df.tiletype.attrs[tt] - if attrs.shape == df.tiletype_shape.BRANCH or attrs.shape == df.tiletype_shape.TRUNK_BRANCH then -- Branches can be walked on, but most of the time we can assume that it's not a suitable access. return false end - - local shape_attrs = df.tiletype_shape.attrs[attrs.shape] - - if not shape_attrs.walkable then - return false - end - - local building = dfhack.buildings.findAtTile(pos) - return not building or not building.flags.exists or not isImpassable(building) + return walkable(pos) end --- List neighbour coordinates of a position @@ -452,7 +446,7 @@ local function constructionIsUnsupported(job) -- if no neighbour is walkable it can't be constructed now anyways, -- this early return helps reduce "spam" - -- if not hasWalkableNeighbour(pos) then return false end -- commented out pending `walkable()` fix + if not hasWalkableNeighbour(pos) then return false end -- find out what type of construction local constr_type = building:getSubtype() @@ -516,7 +510,7 @@ local function riskBlocking(job) local pos = {x=building.centerx,y=building.centery,z=building.z} -- The construction is on a non walkable tile, it can't get worst - if not walkable(pos) then return false end + if not isSuitableAccess(pos) then return false end --- Get self risk of being blocked local risk = riskOfStuckConstructionAt(pos) @@ -544,7 +538,7 @@ function SuspendManager:suspendDeadend(start_job) ---@type building? local exit = nil for _,neighbourPos in pairs(neighbours(pos)) do - if not walkable(neighbourPos) then + if not isSuitableAccess(neighbourPos) then -- non walkable neighbour, not an exit goto continue end From 263cfcff29865965c09187b8e12f87e45429fcf5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 03:56:40 -0700 Subject: [PATCH 29/54] clean up dump-offsets --- devel/dump-offsets.lua | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/devel/dump-offsets.lua b/devel/dump-offsets.lua index 0e3b0d2f0e..5f49ce1662 100644 --- a/devel/dump-offsets.lua +++ b/devel/dump-offsets.lua @@ -1,26 +1,5 @@ -- Dump all global addresses ---[====[ - -devel/dump-offsets -================== - -.. warning:: - - THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - - Running this script on a new DF version will NOT - MAKE IT RUN CORRECTLY if any data structures - changed, thus possibly leading to CRASHES AND/OR - PERMANENT SAVE CORRUPTION. - -This dumps the contents of the table of global addresses (new in 0.44.01). - -Passing global names as arguments calls setAddress() to set those globals' -addresses in-game. Passing "all" does this for all globals. - -]====] - GLOBALS = {} for k, v in pairs(df.global._fields) do GLOBALS[v.original_name] = k @@ -50,13 +29,11 @@ else search = {0x12345678, 0x87654321, 0x89abcdef} end -local addrs = {} function save_addr(name, addr) print((""):format(name, addr)) if iargs[name] or iargs.all then ms.found_offset(name, addr) end - addrs[name] = addr end local extended = false From 58e24f9bbc908050e6074d05794af58dbad96175 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 04:03:55 -0700 Subject: [PATCH 30/54] clean up points code --- points.lua | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/points.lua b/points.lua index b7d2647039..40dd9c3ce7 100644 --- a/points.lua +++ b/points.lua @@ -1,20 +1,7 @@ --- Set available points at the embark screen --- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005 ---[====[ - -points -====== -Sets available points at the embark screen to the specified number. Eg. -``points 1000000`` would allow you to buy everything, or ``points 0`` would -make life quite difficult. - -]====] - if dfhack.isWorldLoaded() then df.global.world.worldgen.worldgen_parms.embark_points = tonumber(...) - local scr = dfhack.gui.getCurViewscreen() + local scr = dfhack.gui.getDFViewscreen() if df.viewscreen_setupdwarfgamest:is_instance(scr) then - local scr = scr --as:df.viewscreen_setupdwarfgamest scr.points_remaining = tonumber(...) end else From 572c3abc0b72eb1e7f05c01130e9a87f3adfaef8 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 04:05:30 -0700 Subject: [PATCH 31/54] adjust to new mouse event semantics --- devel/inspect-screen.lua | 2 +- exportlegends.lua | 4 ++-- gui/autochop.lua | 2 +- gui/autodump.lua | 4 ++-- gui/blueprint.lua | 4 ++-- gui/control-panel.lua | 8 ++++---- gui/design.lua | 6 +++--- gui/gm-editor.lua | 2 +- gui/gm-unit.lua | 2 +- gui/liquids.lua | 6 +++--- gui/mass-remove.lua | 4 ++-- gui/masspit.lua | 2 +- gui/overlay.lua | 4 ++-- gui/quickfort.lua | 6 +++--- gui/sandbox.lua | 12 ++++++------ gui/seedwatch.lua | 2 +- gui/unit-syndromes.lua | 2 +- hide-tutorials.lua | 2 +- internal/caravan/trade.lua | 6 +++--- internal/gm-unit/editor_body.lua | 2 +- test/gui/blueprint.lua | 2 +- 21 files changed, 42 insertions(+), 42 deletions(-) diff --git a/devel/inspect-screen.lua b/devel/inspect-screen.lua index 3510cf0468..36d42564be 100644 --- a/devel/inspect-screen.lua +++ b/devel/inspect-screen.lua @@ -325,7 +325,7 @@ function Inspect:onInput(keys) if Inspect.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then self.subviews.freeze:cycle() return true end diff --git a/exportlegends.lua b/exportlegends.lua index 69633a6320..22373fd7a5 100644 --- a/exportlegends.lua +++ b/exportlegends.lua @@ -1070,7 +1070,7 @@ function LegendsOverlay:init() end function LegendsOverlay:onInput(keys) - if keys._MOUSE_L_DOWN and progress_percent < 0 and + if keys._MOUSE_L and progress_percent < 0 and self.subviews.button_mask:getMousePos() and self.subviews.do_export:getOptionValue() then @@ -1102,7 +1102,7 @@ end function DoneMaskOverlay:onInput(keys) if progress_percent >= 0 then - if keys.LEAVESCREEN or (keys._MOUSE_L_DOWN and self:getMousePos()) then + if keys.LEAVESCREEN or (keys._MOUSE_L and self:getMousePos()) then return true end end diff --git a/gui/autochop.lua b/gui/autochop.lua index 12122875c8..5fc6a858db 100644 --- a/gui/autochop.lua +++ b/gui/autochop.lua @@ -101,7 +101,7 @@ function BurrowSettings:commit() end function BurrowSettings:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self:hide() return true end diff --git a/gui/autodump.lua b/gui/autodump.lua index a0ab71f686..bb81432b0b 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -267,11 +267,11 @@ end function Autodump:onInput(keys) if Autodump.super.onInput(self, keys) then return true end - if keys._MOUSE_R_DOWN and self.mark then + if keys._MOUSE_R and self.mark then self.mark = nil self:updateLayout() return true - elseif keys._MOUSE_L_DOWN then + elseif keys._MOUSE_L then if self:getMouseFramePos() then return true end local pos = dfhack.gui.getMousePos() if not pos then diff --git a/gui/blueprint.lua b/gui/blueprint.lua index 0335e1b19f..f05d4bada5 100644 --- a/gui/blueprint.lua +++ b/gui/blueprint.lua @@ -459,7 +459,7 @@ end function Blueprint:onInput(keys) if Blueprint.super.onInput(self, keys) then return true end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then if self:is_setting_start_pos() then self.subviews.startpos.option_idx = 1 self.saved_cursor = nil @@ -474,7 +474,7 @@ function Blueprint:onInput(keys) end local pos = nil - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then pos = dfhack.gui.getMousePos() if pos then guidm.setCursorPos(pos) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index 24689c0c6b..bd7a49ee48 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -250,7 +250,7 @@ end function ConfigPanel:onInput(keys) local handled = ConfigPanel.super.onInput(self, keys) - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then local list = self.subviews.list.list local idx = list:getIdxUnderMouse() if idx then @@ -603,7 +603,7 @@ function IntegerInputDialog:onInput(keys) if keys.SELECT then self:hide(self.subviews.input_edit.text) return true - elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + elseif keys.LEAVESCREEN or keys._MOUSE_R then self:hide() return true end @@ -638,7 +638,7 @@ end function Preferences:onInput(keys) -- call grandparent's onInput since we don't want ConfigPanel's processing local handled = Preferences.super.super.onInput(self, keys) - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then local list = self.subviews.list.list local idx = list:getIdxUnderMouse() if idx then @@ -787,7 +787,7 @@ end function RepeatAutostart:onInput(keys) -- call grandparent's onInput since we don't want ConfigPanel's processing local handled = RepeatAutostart.super.super.onInput(self, keys) - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then local list = self.subviews.list.list local idx = list:getIdxUnderMouse() if idx then diff --git a/gui/design.lua b/gui/design.lua index 66ef48ddb3..a0a215d4ad 100644 --- a/gui/design.lua +++ b/gui/design.lua @@ -1386,7 +1386,7 @@ function Design:onInput(keys) -- return -- end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then -- Close help window if open if view.help_window.visible then self:dismiss_help() return true end @@ -1438,7 +1438,7 @@ function Design:onInput(keys) local pos = nil - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then pos = getMousePoint() if not pos then return true end guidm.setCursorPos(dfhack.gui.getMousePos()) @@ -1446,7 +1446,7 @@ function Design:onInput(keys) pos = Point(guidm.getCursorPos()) end - if keys._MOUSE_L_DOWN and pos then + if keys._MOUSE_L and pos then -- TODO Refactor this a bit if self.shape.max_points and #self.marks == self.shape.max_points and self.placing_mark.active then self.marks[self.placing_mark.index] = pos diff --git a/gui/gm-editor.lua b/gui/gm-editor.lua index 0814d84d83..380e9cdd3c 100644 --- a/gui/gm-editor.lua +++ b/gui/gm-editor.lua @@ -514,7 +514,7 @@ end function GmEditorUi:onInput(keys) if GmEditorUi.super.onInput(self, keys) then return true end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then if dfhack.internal.getModifiers().shift then return false end diff --git a/gui/gm-unit.lua b/gui/gm-unit.lua index f37ec6f546..b83ed49cf9 100644 --- a/gui/gm-unit.lua +++ b/gui/gm-unit.lua @@ -147,7 +147,7 @@ end function Editor_Unit:onInput(keys) local pages = self.subviews.pages if pages:getSelected() == 1 or - (not keys.LEAVESCREEN and not keys._MOUSE_R_DOWN) then + (not keys.LEAVESCREEN and not keys._MOUSE_R) then return Editor_Unit.super.onInput(self, keys) end local page = pages:getSelectedPage() diff --git a/gui/liquids.lua b/gui/liquids.lua index 4eda71242a..f5d90483fb 100644 --- a/gui/liquids.lua +++ b/gui/liquids.lua @@ -210,7 +210,7 @@ function SpawnLiquid:onRenderFrame(dc, rect) local mouse_pos = dfhack.gui.getMousePos() if self.is_dragging then - if df.global.enabler.mouse_lbut == 0 then + if df.global.enabler.mouse_lbut_down == 0 then self.is_dragging = false elseif mouse_pos and not self:getMouseFramePos() then self:spawn(mouse_pos) @@ -228,7 +228,7 @@ end function SpawnLiquid:onInput(keys) if SpawnLiquid.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then local mouse_pos = dfhack.gui.getMousePos() if self.paint_mode == SpawnLiquidPaintMode.CLICK and mouse_pos then @@ -258,7 +258,7 @@ function SpawnLiquid:onInput(keys) end -- TODO: Holding the mouse down causes event spam. - if keys._MOUSE_L and not self:getMouseFramePos() then + if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then if self.paint_mode == SpawnLiquidPaintMode.DRAG then self.is_dragging = true return true diff --git a/gui/mass-remove.lua b/gui/mass-remove.lua index 3c2781a380..4be9fb338a 100644 --- a/gui/mass-remove.lua +++ b/gui/mass-remove.lua @@ -175,7 +175,7 @@ end function MassRemove:onInput(keys) if MassRemove.super.onInput(self, keys) then return true end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then if self.mark then self.mark = nil self:updateLayout() @@ -185,7 +185,7 @@ function MassRemove:onInput(keys) end local pos = nil - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then pos = dfhack.gui.getMousePos() end if not pos then return false end diff --git a/gui/masspit.lua b/gui/masspit.lua index 464701781d..f7d77176bb 100644 --- a/gui/masspit.lua +++ b/gui/masspit.lua @@ -186,7 +186,7 @@ end function Masspit:onInput(keys) if Masspit.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then if self.subviews.pages:getSelected() == 1 then local building = dfhack.buildings.findAtTile(dfhack.gui.getMousePos()) diff --git a/gui/overlay.lua b/gui/overlay.lua index 017c66738c..a0c5719495 100644 --- a/gui/overlay.lua +++ b/gui/overlay.lua @@ -57,7 +57,7 @@ DraggablePanel.ATTRS{ } function DraggablePanel:onInput(keys) - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then local rect = self.frame_rect local x,y = self:getMousePos(gui.ViewRect{rect=rect}) if x then @@ -281,7 +281,7 @@ function OverlayConfig:onInput(keys) return true end end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() return true end diff --git a/gui/quickfort.lua b/gui/quickfort.lua index 13014a2c75..2d713c7b3e 100644 --- a/gui/quickfort.lua +++ b/gui/quickfort.lua @@ -52,7 +52,7 @@ end function BlueprintDetails:onInput(keys) if keys.CUSTOM_CTRL_D or keys.SELECT - or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + or keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() end end @@ -211,7 +211,7 @@ function BlueprintDialog:onInput(keys) details:show() -- for testing self._details = details - elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + elseif keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() if self.on_cancel then self.on_cancel() @@ -606,7 +606,7 @@ function Quickfort:onInput(keys) return true end - if keys._MOUSE_L_DOWN and not self:getMouseFramePos() then + if keys._MOUSE_L and not self:getMouseFramePos() then local pos = dfhack.gui.getMousePos() if pos then self:commit() diff --git a/gui/sandbox.lua b/gui/sandbox.lua index ff74ceb31a..b491e1b899 100644 --- a/gui/sandbox.lua +++ b/gui/sandbox.lua @@ -121,7 +121,7 @@ function Sandbox:init() key='CUSTOM_SHIFT_U', label="Spawn unit", on_activate=function() - df.global.enabler.mouse_lbut = 0 + df.global.enabler.mouse_lbut_down = 0 clear_arena_action() view:sendInputToParent{ARENA_CREATE_CREATURE=true} df.global.game.main_interface.arena_unit.editing_filter = true @@ -162,7 +162,7 @@ function Sandbox:init() key='CUSTOM_SHIFT_T', label="Spawn tree", on_activate=function() - df.global.enabler.mouse_lbut = 0 + df.global.enabler.mouse_lbut_down = 0 clear_arena_action() view:sendInputToParent{ARENA_CREATE_TREE=true} df.global.game.main_interface.arena_tree.editing_filter = true @@ -189,11 +189,11 @@ function Sandbox:init() end function Sandbox:onInput(keys) - if keys._MOUSE_R_DOWN and self:getMouseFramePos() then + if keys._MOUSE_R and self:getMouseFramePos() then clear_arena_action() return false end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then if is_arena_action_in_progress() then clear_arena_action() return true @@ -204,7 +204,7 @@ function Sandbox:onInput(keys) if Sandbox.super.onInput(self, keys) then return true end - if keys._MOUSE_L then + if keys._MOUSE_L_DOWN then if self:getMouseFramePos() then return true end for _,mask_panel in ipairs(self.interface_masks) do if mask_panel:getMousePos() then return true end @@ -252,7 +252,7 @@ InterfaceMask.ATTRS{ } function InterfaceMask:onInput(keys) - return keys._MOUSE_L and self:getMousePos() + return keys._MOUSE_L_DOWN and self:getMousePos() end --------------------- diff --git a/gui/seedwatch.lua b/gui/seedwatch.lua index 30ae9dc8d0..612b42ee48 100644 --- a/gui/seedwatch.lua +++ b/gui/seedwatch.lua @@ -78,7 +78,7 @@ function SeedSettings:commit() end function SeedSettings:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self:hide() return true end diff --git a/gui/unit-syndromes.lua b/gui/unit-syndromes.lua index 93a3476a7c..994f967c08 100644 --- a/gui/unit-syndromes.lua +++ b/gui/unit-syndromes.lua @@ -441,7 +441,7 @@ function UnitSyndromes:push_state() end function UnitSyndromes:onInput(keys) - if keys._MOUSE_R_DOWN then + if keys._MOUSE_R then self:previous_page() return true end diff --git a/hide-tutorials.lua b/hide-tutorials.lua index 6887a77df4..f31a6072f2 100644 --- a/hide-tutorials.lua +++ b/hide-tutorials.lua @@ -29,7 +29,7 @@ function skip_tutorial_prompt(scr) df.global.gps.mouse_y = 18 df.global.enabler.mouse_lbut = 1 df.global.enabler.mouse_lbut_down = 1 - gui.simulateInput(scr, '_MOUSE_L_DOWN') + gui.simulateInput(scr, '_MOUSE_L') end end diff --git a/internal/caravan/trade.lua b/internal/caravan/trade.lua index 47401f272a..9c8650eef3 100644 --- a/internal/caravan/trade.lua +++ b/internal/caravan/trade.lua @@ -525,7 +525,7 @@ end function TradeScreen:onInput(keys) if self.reset_pending then return false end local handled = TradeScreen.super.onInput(self, keys) - if keys._MOUSE_L_DOWN and not self.trade_window:getMouseFramePos() then + if keys._MOUSE_L and not self.trade_window:getMouseFramePos() then -- "trade" or "offer" buttons may have been clicked and we need to reset the cache self.reset_pending = true end @@ -780,7 +780,7 @@ end function TradeOverlay:onInput(keys) if TradeOverlay.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then if dfhack.internal.getModifiers().shift then handle_shift_click_on_render = true copyGoodflagState() @@ -819,7 +819,7 @@ end function TradeBannerOverlay:onInput(keys) if TradeBannerOverlay.super.onInput(self, keys) then return true end - if keys._MOUSE_R_DOWN or keys.LEAVESCREEN then + if keys._MOUSE_R or keys.LEAVESCREEN then if view then view:dismiss() end diff --git a/internal/gm-unit/editor_body.lua b/internal/gm-unit/editor_body.lua index 9c250d3828..b6e8935308 100644 --- a/internal/gm-unit/editor_body.lua +++ b/internal/gm-unit/editor_body.lua @@ -163,7 +163,7 @@ function Editor_Body_Modifier:init(args) end function Editor_Body_Modifier:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self:setFocus(false) self.visible = false else diff --git a/test/gui/blueprint.lua b/test/gui/blueprint.lua index 55651719da..fcf45a932f 100644 --- a/test/gui/blueprint.lua +++ b/test/gui/blueprint.lua @@ -297,7 +297,7 @@ function test.set_with_mouse() mock.patch(dfhack.gui, 'getMousePos', mock.func(pos), function() local view = load_ui() - view:onInput({_MOUSE_L_DOWN=true}) + view:onInput({_MOUSE_L=true}) expect.table_eq(pos, view.mark, comment) send_keys('LEAVESCREEN') -- cancel selection send_keys('LEAVESCREEN') -- cancel out of UI From 2dcf6d903e7f3ba069a7f65d8c3cc1c73e32e774 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 04:19:42 -0700 Subject: [PATCH 32/54] reinstate startdwarf and add scrollbar overlay --- changelog.txt | 2 + docs/startdwarf.rst | 27 +++++++++---- startdwarf.lua | 86 ++++++++++++++++++++++++++++++++++------ test/startdwarf.lua | 96 ++++----------------------------------------- 4 files changed, 103 insertions(+), 108 deletions(-) diff --git a/changelog.txt b/changelog.txt index 5efb93672f..c0604214da 100644 --- a/changelog.txt +++ b/changelog.txt @@ -27,8 +27,10 @@ Template for new versions: # Future ## New Tools +- `startdwarf`: (reinstated) set number of starting dwarves ## New Features +- `startdwarf`: overlay scrollbar so you can scroll through your starting dwarves if they don't all fit on the screen ## Fixes diff --git a/docs/startdwarf.rst b/docs/startdwarf.rst index 4f63442a99..339ceed4bd 100644 --- a/docs/startdwarf.rst +++ b/docs/startdwarf.rst @@ -2,15 +2,16 @@ startdwarf ========== .. dfhack-tool:: - :summary: Increase the number of dwarves you embark with. - :tags: unavailable embark fort armok + :summary: Change the number of dwarves you embark with. + :tags: embark fort armok -You must use this tool before embarking (e.g. at the site selection screen or -any time before) to change the number of dwarves you embark with from the -default of 7. +You must use this tool before you get to the embark preparation screen (e.g. at +the site selection screen or any time before) to change the number of dwarves +you embark with from the default of 7. The value that you set will remain in +effect until DF is restarted (or you use `startdwarf` to set a new value). -Note that the game requires that you embark with no fewer than 7 dwarves, so -this tool can only increase the starting dwarf count, not decrease it. +The maximum number of dwarves you can have is 32,767, but that is far more than +the game can handle. Usage ----- @@ -24,6 +25,18 @@ Examples ``startdwarf 10`` Start with a few more warm bodies to help you get started. +``startdwarf 1`` + Hermit fort! (also see the `hermit` tool for keeping it that way) ``startdwarf 500`` Start with a teeming army of dwarves (leading to immediate food shortage and FPS issues). + +Overlay +------- + +The vanilla DF screen doesn't provide a way to scroll through the starting +dwarves, so if you start with more dwarves than can fit on your screen, this +tool provides a scrollbar that you can use to scroll through them. The vanilla +list was *not* designed for scrolling, so there is some odd behavior. When you +click on a dwarf to set skills, the list will jump so that the dwarf you +clicked on will be at the top of the page. diff --git a/startdwarf.lua b/startdwarf.lua index 143dce23ff..5f779a5933 100644 --- a/startdwarf.lua +++ b/startdwarf.lua @@ -1,17 +1,79 @@ --- change number of dwarves on initial embark +--@ module=true -local addr = dfhack.internal.getAddress('start_dwarf_count') -if not addr then - qerror('start_dwarf_count address not available - cannot patch') +local argparse = require('argparse') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +StartDwarfOverlay = defclass(StartDwarfOverlay, overlay.OverlayWidget) +StartDwarfOverlay.ATTRS{ + default_pos={x=5, y=9}, + default_enabled=true, + viewscreens='setupdwarfgame/Dwarves', + frame={w=5, h=10}, +} + +function StartDwarfOverlay:init() + self:addviews{ + widgets.Scrollbar{ + view_id='scrollbar', + frame={r=0, t=0, w=2, b=0}, + on_scroll=self:callback('on_scrollbar'), + }, + } +end + +function StartDwarfOverlay:on_scrollbar(scroll_spec) + local scr = dfhack.gui.getDFViewscreen(true) + local _, sh = dfhack.screen.getWindowSize() + local list_height = sh - 17 + local num_units = #scr.s_unit + local units_per_page = list_height // 3 + + local v = 0 + if tonumber(scroll_spec) then + v = tonumber(scroll_spec) - 1 + elseif scroll_spec == 'down_large' then + v = scr.selected_u + units_per_page // 2 + elseif scroll_spec == 'up_large' then + v = scr.selected_u - units_per_page // 2 + elseif scroll_spec == 'down_small' then + v = scr.selected_u + 1 + elseif scroll_spec == 'up_small' then + v = scr.selected_u - 1 + end + + scr.selected_u = math.max(0, math.min(num_units-1, v)) +end + +function StartDwarfOverlay:render(dc) + local sw, sh = dfhack.screen.getWindowSize() + local list_height = sh - 17 + local scr = dfhack.gui.getDFViewscreen(true) + local num_units = #scr.s_unit + local units_per_page = list_height // 3 + local scrollbar = self.subviews.scrollbar + self.frame.w = sw // 2 - 4 + self.frame.h = list_height + self:updateLayout() + + local top = math.min(scr.selected_u + 1, num_units - units_per_page + 1) + scrollbar:update(top, units_per_page, num_units) + + StartDwarfOverlay.super.render(self, dc) +end + +OVERLAY_WIDGETS = { + overlay=StartDwarfOverlay, +} + +if dfhack_flags.module then + return end -local num = tonumber(({...})[1]) -if not num or num < 7 then - qerror('argument must be a number no less than 7') +local num = argparse.positiveInt(({...})[1]) +if num > 32767 then + qerror(('value must be no more than 32,767: %d'):format(num)) end +df.global.start_dwarf_count = num -dfhack.with_temp_object(df.new('uint32_t'), function(temp) - temp.value = num - local temp_size, temp_addr = temp:sizeof() - dfhack.internal.patchMemory(addr, temp_addr, temp_size) -end) +print(('starting dwarf count set to %d. good luck!'):format(num)) diff --git a/test/startdwarf.lua b/test/startdwarf.lua index af58ce9198..511b325d35 100644 --- a/test/startdwarf.lua +++ b/test/startdwarf.lua @@ -1,104 +1,22 @@ -local utils = require('utils') - -local function with_patches(callback, custom_mocks) - dfhack.with_temp_object(df.new('uint32_t'), function(temp_out) - local originalPatchMemory = dfhack.internal.patchMemory - local function safePatchMemory(target, source, length) - -- only allow patching the expected address - otherwise a buggy - -- script could corrupt the test environment - if target ~= utils.addressof(temp_out) then - return expect.fail(('attempted to patch invalid address 0x%x: expected 0x%x'):format(target, utils.addressof(temp_out))) - end - return originalPatchMemory(target, source, length) - end - local mocks = { - getAddress = mock.func(utils.addressof(temp_out)), - patchMemory = mock.observe_func(safePatchMemory), - } - if custom_mocks then - for k, v in pairs(custom_mocks) do - mocks[k] = v - end - end - mock.patch({ - {dfhack.internal, 'getAddress', mocks.getAddress}, - {dfhack.internal, 'patchMemory', mocks.patchMemory}, - }, function() - callback(mocks, temp_out) - end) - end) -end +config.target = 'startdwarf' local function run_startdwarf(...) return dfhack.run_script('startdwarf', ...) end -local function test_early_error(args, expected_message, custom_mocks) - with_patches(function(mocks, temp_out) - temp_out.value = 12345 - - expect.error_match(expected_message, function() - run_startdwarf(table.unpack(args)) - end) - - expect.eq(mocks.getAddress.call_count, 1, 'getAddress was not called') - expect.table_eq(mocks.getAddress.call_args[1], {'start_dwarf_count'}) - - expect.eq(mocks.patchMemory.call_count, 0, 'patchMemory was called unexpectedly') - - -- make sure the script didn't attempt to write in some other way - expect.eq(temp_out.value, 12345, 'memory was changed unexpectedly') - end, custom_mocks) -end - -local function test_invalid_args(args, expected_message) - test_early_error(args, expected_message) -end - -local function test_patch_successful(expected_value) - with_patches(function(mocks, temp_out) - run_startdwarf(tostring(expected_value)) - expect.eq(temp_out.value, expected_value) - - expect.eq(mocks.getAddress.call_count, 1, 'getAddress was not called') - expect.table_eq(mocks.getAddress.call_args[1], {'start_dwarf_count'}) - - expect.eq(mocks.patchMemory.call_count, 1, 'patchMemory was not called') - expect.eq(mocks.patchMemory.call_args[1][1], utils.addressof(temp_out), - 'patchMemory called with wrong destination') - -- skip checking source (arg 2) because it has already been freed by the script - expect.eq(mocks.patchMemory.call_args[1][3], df.sizeof(temp_out), - 'patchMemory called with wrong length') - end) -end - function test.no_arg() - test_invalid_args({}, 'must be a number') + expect.error_match('expected positive integer', run_startdwarf) end function test.not_number() - test_invalid_args({'a'}, 'must be a number') + expect.error_match('expected positive integer', curry(run_startdwarf, 'a')) end function test.too_small() - test_invalid_args({'4'}, 'less than 7') - test_invalid_args({'6'}, 'less than 7') - test_invalid_args({'-1'}, 'less than 7') -end - -function test.missing_address() - test_early_error({}, 'address not available', {getAddress = mock.func(nil)}) - test_early_error({'8'}, 'address not available', {getAddress = mock.func(nil)}) -end - -function test.exactly_7() - test_patch_successful(7) -end - -function test.above_7() - test_patch_successful(10) + expect.error_match('expected positive integer', curry(run_startdwarf, '0')) + expect.error_match('expected positive integer', curry(run_startdwarf, '-1')) end -function test.uint8_overflow() - test_patch_successful(257) +function test.too_big() + expect.error_match('value must be no more than', curry(run_startdwarf, '32768')) end From 1cc37bc82f51db0eb24875f298ce57a65210ad1d Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Wed, 27 Sep 2023 10:35:43 +0100 Subject: [PATCH 33/54] update changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 5efb93672f..5fb2bd346e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,7 @@ Template for new versions: ## New Features ## Fixes +- `suspendmanager`: fixed a bug where floor grates, bars, bridges etc. wouldn't be recognised as walkable, leading to unnecessary suspensions in certain cases. ## Misc Improvements - `devel/inspect-screen`: display total grid size for UI and map layers From 26643dc55f15e0ddcaf3933ab3315266f6c2c22d Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Sep 2023 19:57:32 -0400 Subject: [PATCH 34/54] sc: skip temp_save vectors --- devel/sc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devel/sc.lua b/devel/sc.lua index 3d0b286f22..1f91a1a8e7 100644 --- a/devel/sc.lua +++ b/devel/sc.lua @@ -127,7 +127,7 @@ local function check_container(obj, path) if df.isvalid(v) == 'ref' then local s, a = v:sizeof() - if v and v._kind == 'container' and k ~= 'bad' then + if v and v._kind == 'container' and k ~= 'bad' and k ~= 'temp_save' then if tostring(v._type):sub(1,6) == 'vector' and check_vectors and not is_valid_vector(a) then local key = tostring(obj._type) .. '.' .. k if not checkedp[key] then From 99872eda34bebc56d59ec58b85823c25070afa65 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Sep 2023 19:57:43 -0400 Subject: [PATCH 35/54] sc: log path to mis-sized objects --- devel/sc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devel/sc.lua b/devel/sc.lua index 1f91a1a8e7..6cf8aab27b 100644 --- a/devel/sc.lua +++ b/devel/sc.lua @@ -162,7 +162,7 @@ local function check_container(obj, path) --print (' OK') else bold (t) - err (' NOT OK '.. s .. ' ' .. s2) + err (' NOT OK '.. s .. ' ' .. s2 .. ' at ' .. path .. '.' .. k) end end From 85e61428786db2a010080967a76e0ceeac92f29c Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 27 Sep 2023 23:01:04 -0700 Subject: [PATCH 36/54] Update gui/control-panel.lua --- gui/control-panel.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index fb248e5cc4..2ee28bd60f 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -128,7 +128,7 @@ local REPEATS = { desc='Sort manager orders by repeat frequency so one-time orders can be completed.', command={'--time', '1', '--timeUnits', 'days', '--command', '[', 'orders', 'sort', ']'}}, ['orders-reevaluate']={ - desc='Invalidates work orders once a month forcing manager to recheck conditions.', + desc='Invalidates work orders once a month, allowing conditions to be rechecked.', command={'--time', '1', '--timeUnits', 'months', '--command', '[', 'orders', 'recheck', ']'}}, ['warn-starving']={ desc='Show a warning dialog when units are starving or dehydrated.', From a4bdfe0aedacc4c7fa735af82068efb6e139564d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 29 Sep 2023 20:55:58 -0700 Subject: [PATCH 37/54] first draft of the display furniture selection --- caravan.lua | 2 + internal/caravan/pedestal.lua | 670 ++++++++++++++++++++++++++++++++++ 2 files changed, 672 insertions(+) create mode 100644 internal/caravan/pedestal.lua diff --git a/caravan.lua b/caravan.lua index 26d704b023..de8c4e32f9 100644 --- a/caravan.lua +++ b/caravan.lua @@ -2,6 +2,7 @@ --@ module = true local movegoods = reqscript('internal/caravan/movegoods') +local pedestal = reqscript('internal/caravan/pedestal') local trade = reqscript('internal/caravan/trade') local tradeagreement = reqscript('internal/caravan/tradeagreement') @@ -20,6 +21,7 @@ OVERLAY_WIDGETS = { tradeagreement=tradeagreement.TradeAgreementOverlay, movegoods=movegoods.MoveGoodsOverlay, assigntrade=movegoods.AssignTradeOverlay, + displayitemselector=pedestal.PedestalOverlay, } INTERESTING_FLAGS = { diff --git a/internal/caravan/pedestal.lua b/internal/caravan/pedestal.lua new file mode 100644 index 0000000000..755706621f --- /dev/null +++ b/internal/caravan/pedestal.lua @@ -0,0 +1,670 @@ +--@ module=true + +-- TODO: this should be moved to stocks once the item filter code is moved there + +local common = reqscript('internal/caravan/common') +local gui = require('gui') +local overlay = require('plugins.overlay') +local utils = require('utils') +local widgets = require('gui.widgets') + +local STATUS = { + NONE={label='Unknown', value=0}, + ASSIGNED_HERE={label='Assigned here', value=1}, + ASSIGNED_THERE={label='Assigned elsewhere', value=2}, + AVAILABLE={label='', value=3}, +} +local STATUS_REVMAP = {} +for k, v in pairs(STATUS) do + STATUS_REVMAP[v.value] = k +end + +-- ------------------- +-- AssignItems +-- + +AssignItems = defclass(AssignItems, widgets.Window) +AssignItems.ATTRS { + frame_title='Assign items for display', + frame={w=80, h=46}, + resizable=true, + resize_min={h=25}, + frame_inset={l=1, t=1, b=1, r=0}, +} + +local STATUS_COL_WIDTH = 18 +local VALUE_COL_WIDTH = 9 + +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 + +local function sort_base(a, b) + return a.data.desc < b.data.desc +end + +local function sort_by_name_desc(a, b) + if a.search_key == b.search_key then + return sort_base(a, b) + end + return a.search_key < b.search_key +end + +local function sort_by_name_asc(a, b) + if a.search_key == b.search_key then + return sort_base(a, b) + end + return a.search_key > b.search_key +end + +local function sort_by_value_desc(a, b) + if a.data.value == b.data.value then + return sort_by_name_desc(a, b) + end + return a.data.value > b.data.value +end + +local function sort_by_value_asc(a, b) + if a.data.value == b.data.value then + return sort_by_name_desc(a, b) + end + return a.data.value < b.data.value +end + +local function sort_by_status_desc(a, b) + if a.data.status == b.data.status then + return sort_by_value_desc(a, b) + end + return a.data.status < b.data.status +end + +local function sort_by_status_asc(a, b) + if a.data.status == b.data.status then + return sort_by_value_desc(a, b) + end + return a.data.status > b.data.status +end + +local function get_assigned_value(display_bld) + local value = 0 + for _, item_id in ipairs(display_bld.displayed_items) do + local item = df.item.find(item_id) + if item then + value = value + common.get_perceived_value(item) + end + end + return value +end + +local function get_containing_temple_or_guildhall(display_bld) + local loc_id = nil + for _, relation in ipairs(display_bld.relations) do + if relation.location_id > -1 then + loc_id = relation.location_id + end + end + if not loc_id then return end + local site = df.global.world.world_data.active_site[0] + local location = utils.binsearch(site.buildings, loc_id, 'id') + if not location then return end + local loc_type = location:getType() + if loc_type ~= df.abstract_building_type.GUILDHALL and loc_type ~= df.abstract_building_type.TEMPLE then + return + end + return location +end + +local function to_title_case(str) + str = str:gsub('(%a)([%w_]*)', + function (first, rest) return first:upper()..rest:lower() end) + str = str:gsub('_', ' ') + return str +end + +-- returns the value of items assigned to the display but not yet in the display +local function get_pending_value(display_bld) + local value = get_assigned_value(display_bld) + for _, contained_item in ipairs(display_bld.contained_items) do + if contained_item.use_mode ~= 0 or + not contained_item.item.flags.in_building + then + goto continue + end + value = value - common.get_perceived_value(contained_item.item) + ::continue:: + end + return value +end + +local difficulty = df.global.plotinfo.main.custom_difficulty +local function get_expected_location_tier(display_bld) + local location = get_containing_temple_or_guildhall(display_bld) + if not location then return '' end + local loc_type = to_title_case(df.abstract_building_type[location:getType()]) + local pending_value = get_pending_value(display_bld) // #display_bld.relations + local value = location.contents.location_value + pending_value + if loc_type == 'Guildhall' then + if value >= difficulty.grand_guildhall_value then + return 'Grand Guildhall' + elseif value >= difficulty.guildhall_value then + return loc_type + end + else + if value >= difficulty.temple_complex_value then + return 'Temple Complex' + elseif value >= difficulty.temple_value then + return loc_type + end + end + return 'Meeting Hall' +end + +function AssignItems:init() + self.bld = dfhack.gui.getSelectedBuilding(true) + if not self.bld or not df.building_display_furniturest:is_instance(self.bld) then + qerror('No display furniture selected') + end + + self.choices_cache = {} + + self:addviews{ + widgets.CycleHotkeyLabel{ + view_id='sort', + frame={l=0, t=0, w=21}, + label='Sort by:', + key='CUSTOM_SHIFT_S', + options={ + {label='status'..common.CH_DN, value=sort_by_status_desc}, + {label='status'..common.CH_UP, value=sort_by_status_asc}, + {label='value'..common.CH_DN, value=sort_by_value_desc}, + {label='value'..common.CH_UP, value=sort_by_value_asc}, + {label='name'..common.CH_DN, value=sort_by_name_desc}, + {label='name'..common.CH_UP, value=sort_by_name_asc}, + }, + initial_option=sort_by_status_desc, + on_change=self:callback('refresh_list', 'sort'), + }, + widgets.EditField{ + view_id='search', + frame={l=26, t=0}, + label_text='Search: ', + on_char=function(ch) return ch:match('[%l -]') end, + }, + widgets.Panel{ + frame={t=2, l=0, w=38, h=4}, + subviews={ + widgets.CycleHotkeyLabel{ + view_id='min_quality', + frame={l=0, t=0, w=18}, + label='Min quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Z', + key='CUSTOM_SHIFT_X', + options={ + {label='Ordinary', value=0}, + {label='-Well Crafted-', value=1}, + {label='+Finely Crafted+', value=2}, + {label='*Superior*', value=3}, + {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, + {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, + {label='Artifact', value=6}, + }, + initial_option=0, + on_change=function(val) + if self.subviews.max_quality:getOptionValue() < val then + self.subviews.max_quality:setOption(val) + end + self:refresh_list() + end, + }, + widgets.CycleHotkeyLabel{ + view_id='max_quality', + frame={r=1, t=0, w=18}, + label='Max quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Q', + key='CUSTOM_SHIFT_W', + options={ + {label='Ordinary', value=0}, + {label='-Well Crafted-', value=1}, + {label='+Finely Crafted+', value=2}, + {label='*Superior*', value=3}, + {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, + {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, + {label='Artifact', value=6}, + }, + initial_option=6, + on_change=function(val) + if self.subviews.min_quality:getOptionValue() > val then + self.subviews.min_quality:setOption(val) + end + self:refresh_list() + end, + }, + widgets.RangeSlider{ + frame={l=0, t=3}, + num_stops=7, + get_left_idx_fn=function() + return self.subviews.min_quality:getOptionValue() + 1 + end, + get_right_idx_fn=function() + return self.subviews.max_quality:getOptionValue() + 1 + end, + on_left_change=function(idx) self.subviews.min_quality:setOption(idx-1, true) end, + on_right_change=function(idx) self.subviews.max_quality:setOption(idx-1, true) end, + }, + }, + }, + widgets.ToggleHotkeyLabel{ + view_id='hide_forbidden', + frame={t=2, l=40, w=28}, + label='Hide forbidden items:', + key='CUSTOM_SHIFT_F', + options={ + {label='Yes', value=true, pen=COLOR_GREEN}, + {label='No', value=false} + }, + initial_option=false, + on_change=function() self:refresh_list() end, + }, + widgets.Panel{ + frame={t=7, l=0, r=0, b=7}, + subviews={ + widgets.CycleHotkeyLabel{ + view_id='sort_status', + frame={t=0, l=0, w=7}, + options={ + {label='status', value=sort_noop}, + {label='status'..common.CH_DN, value=sort_by_status_desc}, + {label='status'..common.CH_UP, value=sort_by_status_asc}, + }, + initial_option=sort_by_status_desc, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_status'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_value', + frame={t=0, l=STATUS_COL_WIDTH+2+VALUE_COL_WIDTH+1-6, w=6}, + options={ + {label='value', value=sort_noop}, + {label='value'..common.CH_DN, value=sort_by_value_desc}, + {label='value'..common.CH_UP, value=sort_by_value_asc}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_value'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_name', + frame={t=0, l=STATUS_COL_WIDTH+2+VALUE_COL_WIDTH+2, w=5}, + options={ + {label='name', value=sort_noop}, + {label='name'..common.CH_DN, value=sort_by_name_desc}, + {label='name'..common.CH_UP, value=sort_by_name_asc}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_name'), + }, + widgets.FilteredList{ + view_id='list', + frame={l=0, t=2, r=0, b=0}, + on_submit=self:callback('toggle_item'), + on_submit2=self:callback('toggle_range'), + on_select=self:callback('select_item'), + }, + } + }, + widgets.Label{ + frame={l=0, b=5, h=1, r=0}, + text={ + 'Total value of assigned items:', + {gap=1, + text=function() return common.obfuscate_value(get_assigned_value(self.bld)) end}, + }, + }, + widgets.Label{ + frame={l=0, b=4, h=1, r=0}, + text={ + {gap=7, + text='Expected location tier:'}, + {gap=1, + text=function() return get_expected_location_tier(self.bld) end}, + }, + visible=function() return get_containing_temple_or_guildhall(self.bld) end, + }, + widgets.HotkeyLabel{ + frame={l=0, b=2}, + label='Select all/none', + key='CUSTOM_CTRL_A', + on_activate=self:callback('toggle_visible'), + auto_width=true, + }, + widgets.ToggleHotkeyLabel{ + view_id='inside_containers', + frame={l=33, b=2, w=34}, + label='See inside containers:', + key='CUSTOM_CTRL_I', + options={ + {label='Yes', value=true, pen=COLOR_GREEN}, + {label='No', value=false} + }, + initial_option=true, + on_change=function() self:refresh_list() end, + }, + widgets.WrappedLabel{ + frame={b=0, l=0, r=0}, + text_to_wrap='Click to assign/unassign. Shift click to assign/unassign a range of items.', + }, + } + + -- replace the FilteredList's built-in EditField with our own + self.subviews.list.list.frame.t = 0 + self.subviews.list.edit.visible = false + self.subviews.list.edit = self.subviews.search + self.subviews.search.on_change = self.subviews.list:callback('onFilterChange') + + self.subviews.list:setChoices(self:get_choices()) +end + +function AssignItems:refresh_list(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 + for _,widget_name in ipairs{'sort', 'sort_status', 'sort_value', 'sort_name'} do + self.subviews[widget_name]:setOption(sort_fn) + end + local list = self.subviews.list + local saved_filter = list:getFilter() + list:setFilter('') + list:setChoices(self:get_choices(), list:getSelected()) + list:setFilter(saved_filter) +end + +local function is_container(item) + return item and ( + df.item_binst:is_instance(item) or + item:isFoodStorage() + ) +end + +local function is_displayable_item(item, display_bld) + if not item or + item.flags.hostile or + item.flags.removed or + item.flags.dead_dwarf or + item.flags.spider_web or + item.flags.construction or + item.flags.encased or + item.flags.unk12 or + item.flags.murder or + item.flags.trader or + item.flags.owned or + item.flags.garbage_collect or + item.flags.on_fire or + item.flags.in_chest + then + return false + end + if item.flags.in_job then + local spec_ref = dfhack.items.getSpecificRef(item, df.specific_ref_type.JOB) + if not spec_ref then return false end + if spec_ref.data.job.job_type ~= df.job_type.PutItemOnDisplay then return false end + elseif item.flags.in_inventory then + local gref = dfhack.items.getGeneralRef(item, df.general_ref_type.CONTAINED_IN_ITEM) + if not gref then return false end + if not is_container(df.item.find(gref.item_id)) or item:isLiquidPowder() then + return false + end + end + if item.flags.in_building then + local bld = dfhack.items.getHolderBuilding(item) + if not bld then return false end + for _, contained_item in ipairs(bld.contained_items) do + if contained_item.use_mode == 0 then return true end + -- building construction materials + if item == contained_item.item then return false end + end + end + return dfhack.maps.canWalkBetween(xyz2pos(dfhack.items.getPosition(item)), + xyz2pos(display_bld.centerx, display_bld.centery, display_bld.z)) +end + +local function get_display_bld_id(item) + local assigned_bld_ref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_DISPLAY_FURNITURE) + if assigned_bld_ref then return assigned_bld_ref.building_id end +end + +local function get_status(item, display_bld) + local display_bld_id = get_display_bld_id(item) + if display_bld_id == display_bld.id then + return STATUS.ASSIGNED_HERE.value + elseif display_bld_id then + return STATUS.ASSIGNED_THERE.value + end + return STATUS.AVAILABLE.value +end + +local function make_choice_text(data) + return { + {width=STATUS_COL_WIDTH, text=function() return STATUS[STATUS_REVMAP[data.status]].label end}, + {gap=2, width=VALUE_COL_WIDTH, rjustify=true, text=common.obfuscate_value(data.value)}, + {gap=2, text=data.desc}, + } +end + +local function make_container_search_key(item, desc) + local words = {} + common.add_words(words, desc) + for _, contained_item in ipairs(dfhack.items.getContainedItems(item)) do + common.add_words(words, common.get_item_description(contained_item)) + end + return table.concat(words, ' ') +end + +local function contains_non_liquid_powder(container) + for _, item in ipairs(dfhack.items.getContainedItems(container)) do + if not item:isLiquidPowder() then return true end + end + return false +end + +function AssignItems:cache_choices(inside_containers) + if self.choices_cache[inside_containers] then return self.choices_cache[inside_containers] end + + local choices = {} + for _, item in ipairs(df.global.world.items.all) do + if not is_displayable_item(item, self.bld) then goto continue end + if inside_containers and is_container(item) and contains_non_liquid_powder(item) then + goto continue + elseif not inside_containers and item.flags.in_inventory then + goto continue + end + local value = common.get_perceived_value(item) + local desc = common.get_item_description(item) + local status = get_status(item, self.bld) + local data = { + item=item, + desc=desc, + value=value, + status=status, + quality=item.flags.artifact and 6 or item:getQuality(), + } + local search_key + if not inside_containers and is_container(item) then + search_key = make_container_search_key(item, desc) + else + search_key = common.make_search_key(desc) + end + local entry = { + search_key=search_key, + text=make_choice_text(data), + data=data, + } + table.insert(choices, entry) + ::continue:: + end + + self.choices_cache[inside_containers] = choices + return choices +end + +function AssignItems:get_choices() + local raw_choices = self:cache_choices(self.subviews.inside_containers:getOptionValue()) + local choices = {} + local include_forbidden = not self.subviews.hide_forbidden:getOptionValue() + local min_quality = self.subviews.min_quality:getOptionValue() + local max_quality = self.subviews.max_quality:getOptionValue() + for _,choice in ipairs(raw_choices) do + local data = choice.data + if not include_forbidden then + if data.item.flags.forbid then + goto continue + end + end + if min_quality > data.quality then goto continue end + if max_quality < data.quality then goto continue end + table.insert(choices, choice) + ::continue:: + end + table.sort(choices, self.subviews.sort:getOptionValue()) + return choices +end + +local function unassign_item(bld, item) + if not bld then return end + local _, found, idx = utils.binsearch(bld.displayed_items, item.id) + if found then + bld.displayed_items:erase(idx) + end +end + +local function detach_item(item) + if item.flags.in_job then + local spec_ref = dfhack.items.getSpecificRef(item, df.specific_ref_type.JOB) + if spec_ref then + dfhack.job.removeJob(spec_ref.data.job) + end + end + local display_bld_id = get_display_bld_id(item) + if not display_bld_id then return end + for idx = #item.general_refs-1, 0, -1 do + local ref = item.general_refs[idx] + if df.general_ref_building_display_furniturest:is_instance(ref) then + unassign_item(df.building.find(ref.building_id), item) + item.general_refs:erase(idx) + ref:delete() + end + end +end + +local function attach_item(item, display_bld) + local ref = df.new(df.general_ref_building_display_furniturest) + ref.building_id = display_bld.id + item.general_refs:insert('#', ref) + utils.insert_sorted(display_bld.displayed_items, item.id) + item.flags.forbid = false + item.flags.in_building = false +end + +function AssignItems:toggle_item_base(choice, target_value) + local true_value = STATUS.ASSIGNED_HERE.value + + if target_value == nil then + target_value = choice.data.status ~= true_value + end + + if target_value and choice.data.status == true_value then + return target_value + end + if not target_value and choice.data.status ~= true_value then + return target_value + end + + local item = choice.data.item + detach_item(item) + + if target_value then + attach_item(item, self.bld) + end + + choice.data.status = get_status(item, self.bld) + + return target_value +end + +function AssignItems:select_item(idx, choice) + if not dfhack.internal.getModifiers().shift then + self.prev_list_idx = self.subviews.list.list:getSelected() + end +end + +function AssignItems:toggle_item(idx, choice) + self:toggle_item_base(choice) +end + +function AssignItems:toggle_range(idx, choice) + if not self.prev_list_idx then + self:toggle_item(idx, choice) + return + end + local choices = self.subviews.list:getVisibleChoices() + local list_idx = self.subviews.list.list:getSelected() + local target_value + for i = list_idx, self.prev_list_idx, list_idx < self.prev_list_idx and 1 or -1 do + target_value = self:toggle_item_base(choices[i], target_value) + end + self.prev_list_idx = list_idx +end + +function AssignItems:toggle_visible() + local target_value + for _, choice in ipairs(self.subviews.list:getVisibleChoices()) do + target_value = self:toggle_item_base(choice, target_value) + end +end + +-- ------------------- +-- AssignItemsModal +-- + +AssignItemsModal = defclass(AssignItemsModal, gui.ZScreenModal) +AssignItemsModal.ATTRS { + focus_path='pedestal/assignitems', +} + +function AssignItemsModal:init() + self:addviews{AssignItems{}} +end + +-- ------------------- +-- PedestalOverlay +-- + +PedestalOverlay = defclass(PedestalOverlay, overlay.OverlayWidget) +PedestalOverlay.ATTRS{ + default_pos={x=-40, y=34}, + default_enabled=true, + viewscreens='dwarfmode/ViewSheets/BUILDING/DisplayFurniture', + frame={w=23, h=1}, + frame_background=gui.CLEAR_PEN, +} + +local function is_valid_building() + local bld = dfhack.gui.getSelectedBuilding(true) +return bld and bld:getBuildStage() == bld:getMaxBuildStage() +end + +function PedestalOverlay:init() + self:addviews{ + widgets.TextButton{ + frame={t=0, l=0}, + label='DFHack assign items', + key='CUSTOM_CTRL_T', + visible=is_valid_building, + on_activate=function() AssignItemsModal{}:show() end, + }, + } +end From 9c3c9a1a06fc836dd06512034c79fdb64659ecb1 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 29 Sep 2023 21:02:32 -0700 Subject: [PATCH 38/54] frames --- internal/caravan/pedestal.lua | 148 ++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/internal/caravan/pedestal.lua b/internal/caravan/pedestal.lua index 755706621f..b6f2bf6231 100644 --- a/internal/caravan/pedestal.lua +++ b/internal/caravan/pedestal.lua @@ -26,7 +26,7 @@ end AssignItems = defclass(AssignItems, widgets.Window) AssignItems.ATTRS { frame_title='Assign items for display', - frame={w=80, h=46}, + frame={w=74, h=46}, resizable=true, resize_min={h=25}, frame_inset={l=1, t=1, b=1, r=0}, @@ -192,84 +192,90 @@ function AssignItems:init() on_char=function(ch) return ch:match('[%l -]') end, }, widgets.Panel{ - frame={t=2, l=0, w=38, h=4}, + frame={t=2, l=0, w=70, h=6}, + frame_style=gui.FRAME_INTERIOR, subviews={ - widgets.CycleHotkeyLabel{ - view_id='min_quality', - frame={l=0, t=0, w=18}, - label='Min quality:', - label_below=true, - key_back='CUSTOM_SHIFT_Z', - key='CUSTOM_SHIFT_X', - options={ - {label='Ordinary', value=0}, - {label='-Well Crafted-', value=1}, - {label='+Finely Crafted+', value=2}, - {label='*Superior*', value=3}, - {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, - {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, - {label='Artifact', value=6}, + widgets.Panel{ + frame={t=0, l=0, w=38, h=4}, + subviews={ + widgets.CycleHotkeyLabel{ + view_id='min_quality', + frame={l=0, t=0, w=18}, + label='Min quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Z', + key='CUSTOM_SHIFT_X', + options={ + {label='Ordinary', value=0}, + {label='-Well Crafted-', value=1}, + {label='+Finely Crafted+', value=2}, + {label='*Superior*', value=3}, + {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, + {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, + {label='Artifact', value=6}, + }, + initial_option=0, + on_change=function(val) + if self.subviews.max_quality:getOptionValue() < val then + self.subviews.max_quality:setOption(val) + end + self:refresh_list() + end, + }, + widgets.CycleHotkeyLabel{ + view_id='max_quality', + frame={r=1, t=0, w=18}, + label='Max quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Q', + key='CUSTOM_SHIFT_W', + options={ + {label='Ordinary', value=0}, + {label='-Well Crafted-', value=1}, + {label='+Finely Crafted+', value=2}, + {label='*Superior*', value=3}, + {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, + {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, + {label='Artifact', value=6}, + }, + initial_option=6, + on_change=function(val) + if self.subviews.min_quality:getOptionValue() > val then + self.subviews.min_quality:setOption(val) + end + self:refresh_list() + end, + }, + widgets.RangeSlider{ + frame={l=0, t=3}, + num_stops=7, + get_left_idx_fn=function() + return self.subviews.min_quality:getOptionValue() + 1 + end, + get_right_idx_fn=function() + return self.subviews.max_quality:getOptionValue() + 1 + end, + on_left_change=function(idx) self.subviews.min_quality:setOption(idx-1, true) end, + on_right_change=function(idx) self.subviews.max_quality:setOption(idx-1, true) end, + }, }, - initial_option=0, - on_change=function(val) - if self.subviews.max_quality:getOptionValue() < val then - self.subviews.max_quality:setOption(val) - end - self:refresh_list() - end, }, - widgets.CycleHotkeyLabel{ - view_id='max_quality', - frame={r=1, t=0, w=18}, - label='Max quality:', - label_below=true, - key_back='CUSTOM_SHIFT_Q', - key='CUSTOM_SHIFT_W', + widgets.ToggleHotkeyLabel{ + view_id='hide_forbidden', + frame={t=0, l=40, w=28}, + label='Hide forbidden items:', + key='CUSTOM_SHIFT_F', options={ - {label='Ordinary', value=0}, - {label='-Well Crafted-', value=1}, - {label='+Finely Crafted+', value=2}, - {label='*Superior*', value=3}, - {label=common.CH_EXCEPTIONAL..'Exceptional'..common.CH_EXCEPTIONAL, value=4}, - {label=common.CH_MONEY..'Masterful'..common.CH_MONEY, value=5}, - {label='Artifact', value=6}, + {label='Yes', value=true, pen=COLOR_GREEN}, + {label='No', value=false} }, - initial_option=6, - on_change=function(val) - if self.subviews.min_quality:getOptionValue() > val then - self.subviews.min_quality:setOption(val) - end - self:refresh_list() - end, - }, - widgets.RangeSlider{ - frame={l=0, t=3}, - num_stops=7, - get_left_idx_fn=function() - return self.subviews.min_quality:getOptionValue() + 1 - end, - get_right_idx_fn=function() - return self.subviews.max_quality:getOptionValue() + 1 - end, - on_left_change=function(idx) self.subviews.min_quality:setOption(idx-1, true) end, - on_right_change=function(idx) self.subviews.max_quality:setOption(idx-1, true) end, + initial_option=false, + on_change=function() self:refresh_list() end, }, }, }, - widgets.ToggleHotkeyLabel{ - view_id='hide_forbidden', - frame={t=2, l=40, w=28}, - label='Hide forbidden items:', - key='CUSTOM_SHIFT_F', - options={ - {label='Yes', value=true, pen=COLOR_GREEN}, - {label='No', value=false} - }, - initial_option=false, - on_change=function() self:refresh_list() end, - }, widgets.Panel{ - frame={t=7, l=0, r=0, b=7}, + frame={t=9, l=0, r=0, b=7}, subviews={ widgets.CycleHotkeyLabel{ view_id='sort_status', @@ -353,7 +359,7 @@ function AssignItems:init() }, widgets.WrappedLabel{ frame={b=0, l=0, r=0}, - text_to_wrap='Click to assign/unassign. Shift click to assign/unassign a range of items.', + text_to_wrap='Click to assign/unassign. Shift click to assign/unassign a range.', }, } From 044bd5c9dc2f2344b2dd17003a8b8e8680103f2a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 29 Sep 2023 21:14:05 -0700 Subject: [PATCH 39/54] add docs for the new pedestal screen --- docs/caravan.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/caravan.rst b/docs/caravan.rst index 7e0fdf15de..d45432619d 100644 --- a/docs/caravan.rst +++ b/docs/caravan.rst @@ -94,3 +94,15 @@ Trade agreement A small panel is shown with a hotkey (``Ctrl-A``) for selecting all/none in the currently shown category. + +Display furniture +````````````````` + +A button is added to the screen when you are viewing display furniture +(pedestals and display cases) where you can launch an item assignment GUI. + +The dialog allows you to sort by name, value, or where the item is currently +assigned for display. + +You can search by name, and you can filter by item quality and by whether the +item is forbidden. From c26b63b727dbc2855e9faeccb6e5ff2212f482d6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 29 Sep 2023 21:15:37 -0700 Subject: [PATCH 40/54] update changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 288eba9479..ada35e9866 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,7 @@ Template for new versions: ## New Features - `startdwarf`: overlay scrollbar so you can scroll through your starting dwarves if they don't all fit on the screen +- A new searchable, sortable, filterable dialog for selecting items for display on pedestals and display cases ## Fixes - `suspendmanager`: fixed a bug where floor grates, bars, bridges etc. wouldn't be recognised as walkable, leading to unnecessary suspensions in certain cases. From 44fd440bdce7e174e47a92dc59de42e12ec2364d Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Sat, 30 Sep 2023 14:53:52 +0100 Subject: [PATCH 41/54] added preserve-tombs to fort services list --- gui/control-panel.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/control-panel.lua b/gui/control-panel.lua index 31e025918f..1ca6ed3b21 100644 --- a/gui/control-panel.lua +++ b/gui/control-panel.lua @@ -30,6 +30,7 @@ local FORT_SERVICES = { 'hermit', 'misery', 'nestboxes', + 'preserve-tombs', 'prioritize', 'seedwatch', 'starvingdead', From 55d0463cdb67165f746a3f2c0f4dd87c6ef86eec Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 30 Sep 2023 23:54:57 -0700 Subject: [PATCH 42/54] use common dialog input handler code so mouse clicks don't bleed through --- gui/quickfort.lua | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/gui/quickfort.lua b/gui/quickfort.lua index 2d713c7b3e..ca27ca6c51 100644 --- a/gui/quickfort.lua +++ b/gui/quickfort.lua @@ -211,13 +211,7 @@ function BlueprintDialog:onInput(keys) details:show() -- for testing self._details = details - elseif keys.LEAVESCREEN or keys._MOUSE_R then - self:dismiss() - if self.on_cancel then - self.on_cancel() - end - else - self:inputToSubviews(keys) + elseif BlueprintDialog.super.onInput(self, keys) then local prev_filter_text = filter_text -- save the filter if it was updated so we always have the most recent -- text for the next invocation of the dialog @@ -229,6 +223,7 @@ function BlueprintDialog:onInput(keys) -- otherwise, save the new selected item save_selection(self.subviews.list) end + return true end end From 43a22b312666e6ca0478987942676b86b1d77631 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 01:14:39 -0700 Subject: [PATCH 43/54] get any citizen if no unit is specified --- modtools/create-item.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modtools/create-item.lua b/modtools/create-item.lua index b6325657c4..031f5c56b1 100644 --- a/modtools/create-item.lua +++ b/modtools/create-item.lua @@ -274,7 +274,7 @@ local function createItem(mat, itemType, quality, creator, description, amount) end local function get_first_citizen() - local citizens = dfhack.units.getCitizens() + local citizens = dfhack.units.getCitizens(true) if not citizens or not citizens[1] then qerror('Could not choose a creator unit. Please select one in the UI') end From 8259d95e9ee9d88c88f32b22608d0d8c977aac18 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 01:34:12 -0700 Subject: [PATCH 44/54] align gui/sandbox with mouse button changes --- gui/sandbox.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/gui/sandbox.lua b/gui/sandbox.lua index b491e1b899..854173d836 100644 --- a/gui/sandbox.lua +++ b/gui/sandbox.lua @@ -121,8 +121,8 @@ function Sandbox:init() key='CUSTOM_SHIFT_U', label="Spawn unit", on_activate=function() - df.global.enabler.mouse_lbut_down = 0 clear_arena_action() + gui.markMouseClicksHandled{_MOUSE_L=true} view:sendInputToParent{ARENA_CREATE_CREATURE=true} df.global.game.main_interface.arena_unit.editing_filter = true end, @@ -162,8 +162,8 @@ function Sandbox:init() key='CUSTOM_SHIFT_T', label="Spawn tree", on_activate=function() - df.global.enabler.mouse_lbut_down = 0 clear_arena_action() + gui.markMouseClicksHandled{_MOUSE_L=true} view:sendInputToParent{ARENA_CREATE_TREE=true} df.global.game.main_interface.arena_tree.editing_filter = true end, @@ -204,7 +204,7 @@ function Sandbox:onInput(keys) if Sandbox.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then if self:getMouseFramePos() then return true end for _,mask_panel in ipairs(self.interface_masks) do if mask_panel:getMousePos() then return true end @@ -251,10 +251,6 @@ InterfaceMask.ATTRS{ frame_background=gui.TRANSPARENT_PEN, } -function InterfaceMask:onInput(keys) - return keys._MOUSE_L_DOWN and self:getMousePos() -end - --------------------- -- SandboxScreen -- From 4250c075d237b4da1efed8a9fd4af302b3f0c8c4 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 10:17:27 -0700 Subject: [PATCH 45/54] add some color and ability to include unreachable items but still exclude hidden items like unseen demon slabs --- internal/caravan/pedestal.lua | 47 +++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/internal/caravan/pedestal.lua b/internal/caravan/pedestal.lua index b6f2bf6231..a7842891f9 100644 --- a/internal/caravan/pedestal.lua +++ b/internal/caravan/pedestal.lua @@ -26,7 +26,7 @@ end AssignItems = defclass(AssignItems, widgets.Window) AssignItems.ATTRS { frame_title='Assign items for display', - frame={w=74, h=46}, + frame={w=76, h=46}, resizable=true, resize_min={h=25}, frame_inset={l=1, t=1, b=1, r=0}, @@ -192,7 +192,7 @@ function AssignItems:init() on_char=function(ch) return ch:match('[%l -]') end, }, widgets.Panel{ - frame={t=2, l=0, w=70, h=6}, + frame={t=2, l=0, w=72, h=6}, frame_style=gui.FRAME_INTERIOR, subviews={ widgets.Panel{ @@ -260,9 +260,21 @@ function AssignItems:init() }, }, }, + widgets.ToggleHotkeyLabel{ + view_id='hide_unreachable', + frame={t=0, l=40, w=30}, + label='Hide unreachable items:', + key='CUSTOM_SHIFT_U', + options={ + {label='Yes', value=true, pen=COLOR_GREEN}, + {label='No', value=false} + }, + initial_option=true, + on_change=function() self:refresh_list() end, + }, widgets.ToggleHotkeyLabel{ view_id='hide_forbidden', - frame={t=0, l=40, w=28}, + frame={t=2, l=40, w=28}, label='Hide forbidden items:', key='CUSTOM_SHIFT_F', options={ @@ -324,7 +336,7 @@ function AssignItems:init() frame={l=0, b=5, h=1, r=0}, text={ 'Total value of assigned items:', - {gap=1, + {gap=1, pen=COLOR_GREEN, text=function() return common.obfuscate_value(get_assigned_value(self.bld)) end}, }, }, @@ -333,7 +345,7 @@ function AssignItems:init() text={ {gap=7, text='Expected location tier:'}, - {gap=1, + {gap=1, pen=COLOR_GREEN, text=function() return get_expected_location_tier(self.bld) end}, }, visible=function() return get_containing_temple_or_guildhall(self.bld) end, @@ -396,7 +408,7 @@ local function is_container(item) ) end -local function is_displayable_item(item, display_bld) +local function is_displayable_item(item) if not item or item.flags.hostile or item.flags.removed or @@ -425,6 +437,9 @@ local function is_displayable_item(item, display_bld) return false end end + if not dfhack.maps.isTileVisible(xyz2pos(dfhack.items.getPosition(item))) then + return false + end if item.flags.in_building then local bld = dfhack.items.getHolderBuilding(item) if not bld then return false end @@ -434,8 +449,7 @@ local function is_displayable_item(item, display_bld) if item == contained_item.item then return false end end end - return dfhack.maps.canWalkBetween(xyz2pos(dfhack.items.getPosition(item)), - xyz2pos(display_bld.centerx, display_bld.centery, display_bld.z)) + return true end local function get_display_bld_id(item) @@ -477,12 +491,12 @@ local function contains_non_liquid_powder(container) return false end -function AssignItems:cache_choices(inside_containers) +function AssignItems:cache_choices(inside_containers, display_bld) if self.choices_cache[inside_containers] then return self.choices_cache[inside_containers] end local choices = {} for _, item in ipairs(df.global.world.items.all) do - if not is_displayable_item(item, self.bld) then goto continue end + if not is_displayable_item(item) then goto continue end if inside_containers and is_container(item) and contains_non_liquid_powder(item) then goto continue elseif not inside_containers and item.flags.in_inventory then @@ -491,12 +505,15 @@ function AssignItems:cache_choices(inside_containers) local value = common.get_perceived_value(item) local desc = common.get_item_description(item) local status = get_status(item, self.bld) + local reachable = dfhack.maps.canWalkBetween(xyz2pos(dfhack.items.getPosition(item)), + xyz2pos(display_bld.centerx, display_bld.centery, display_bld.z)) local data = { item=item, desc=desc, value=value, status=status, quality=item.flags.artifact and 6 or item:getQuality(), + reachable=reachable, } local search_key if not inside_containers and is_container(item) then @@ -518,18 +535,16 @@ function AssignItems:cache_choices(inside_containers) end function AssignItems:get_choices() - local raw_choices = self:cache_choices(self.subviews.inside_containers:getOptionValue()) + local raw_choices = self:cache_choices(self.subviews.inside_containers:getOptionValue(), self.bld) local choices = {} + local include_unreachable = not self.subviews.hide_unreachable:getOptionValue() local include_forbidden = not self.subviews.hide_forbidden:getOptionValue() local min_quality = self.subviews.min_quality:getOptionValue() local max_quality = self.subviews.max_quality:getOptionValue() for _,choice in ipairs(raw_choices) do local data = choice.data - if not include_forbidden then - if data.item.flags.forbid then - goto continue - end - end + if not include_unreachable and not data.reachable then goto continue end + if not include_forbidden and data.item.flags.forbid then goto continue end if min_quality > data.quality then goto continue end if max_quality < data.quality then goto continue end table.insert(choices, choice) From 7911f758979f1a8e9bf4d2ca893a3c1d9c9a43aa Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 12:56:21 -0700 Subject: [PATCH 46/54] adjust hide-tutorials to new embark message behavior --- hide-tutorials.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hide-tutorials.lua b/hide-tutorials.lua index f31a6072f2..c1fac3a980 100644 --- a/hide-tutorials.lua +++ b/hide-tutorials.lua @@ -23,10 +23,18 @@ local function close_help() end function skip_tutorial_prompt(scr) - if help.open and help.context == df.help_context_type.EMBARK_TUTORIAL_CHOICE then + if not help.open then return end + local mouse_y = 23 + if help.context == df.help_context_type.EMBARK_TUTORIAL_CHOICE then help.context = df.help_context_type.EMBARK_MESSAGE + -- dialog behavior changes for the button click, but the button is still + -- in the "tutorial choice" button position + mouse_y = 18 + end + if help.context == df.help_context_type.EMBARK_MESSAGE then df.global.gps.mouse_x = df.global.gps.dimx // 2 - df.global.gps.mouse_y = 18 + df.global.gps.mouse_y = mouse_y + df.global.enabler.tracking_on = 1 df.global.enabler.mouse_lbut = 1 df.global.enabler.mouse_lbut_down = 1 gui.simulateInput(scr, '_MOUSE_L') From bcfbfe51ba2256b0cfe3f172f51dea29d370cd82 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 13:34:51 -0700 Subject: [PATCH 47/54] bump changelog to 50.11-r1 --- changelog.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index ada35e9866..f7a2e75445 100644 --- a/changelog.txt +++ b/changelog.txt @@ -26,6 +26,18 @@ Template for new versions: # Future +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Removed + +# 50.11-r1 + ## New Tools - `startdwarf`: (reinstated) set number of starting dwarves @@ -40,8 +52,6 @@ Template for new versions: - `devel/inspect-screen`: display total grid size for UI and map layers - `suspendmanager`: now suspends constructions that would cave-in immediately on completion -## Removed - # 50.10-r1 ## Fixes From 1d3dd5fe2a7066e99810059b15626c05a3978b63 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 16:29:10 -0700 Subject: [PATCH 48/54] use new centralized mouse handling --- gui/cp437-table.lua | 4 ---- gui/sandbox.lua | 2 -- hide-tutorials.lua | 3 --- 3 files changed, 9 deletions(-) diff --git a/gui/cp437-table.lua b/gui/cp437-table.lua index 0340c4dbda..667e028913 100644 --- a/gui/cp437-table.lua +++ b/gui/cp437-table.lua @@ -118,10 +118,6 @@ function CPDialog:submit() keys[i] = k end - -- ensure clicks on "submit" don't bleed through - df.global.enabler.mouse_lbut = 0 - df.global.enabler.mouse_lbut_down = 0 - local screen = self.parent_view local parent = screen._native.parent dfhack.screen.hideGuard(screen, function() diff --git a/gui/sandbox.lua b/gui/sandbox.lua index 854173d836..cb136f41ba 100644 --- a/gui/sandbox.lua +++ b/gui/sandbox.lua @@ -122,7 +122,6 @@ function Sandbox:init() label="Spawn unit", on_activate=function() clear_arena_action() - gui.markMouseClicksHandled{_MOUSE_L=true} view:sendInputToParent{ARENA_CREATE_CREATURE=true} df.global.game.main_interface.arena_unit.editing_filter = true end, @@ -163,7 +162,6 @@ function Sandbox:init() label="Spawn tree", on_activate=function() clear_arena_action() - gui.markMouseClicksHandled{_MOUSE_L=true} view:sendInputToParent{ARENA_CREATE_TREE=true} df.global.game.main_interface.arena_tree.editing_filter = true end, diff --git a/hide-tutorials.lua b/hide-tutorials.lua index c1fac3a980..ef855539e4 100644 --- a/hide-tutorials.lua +++ b/hide-tutorials.lua @@ -34,9 +34,6 @@ function skip_tutorial_prompt(scr) if help.context == df.help_context_type.EMBARK_MESSAGE then df.global.gps.mouse_x = df.global.gps.dimx // 2 df.global.gps.mouse_y = mouse_y - df.global.enabler.tracking_on = 1 - df.global.enabler.mouse_lbut = 1 - df.global.enabler.mouse_lbut_down = 1 gui.simulateInput(scr, '_MOUSE_L') end end From d2ad86165e89dc3b0f262eea00db8e2347cc4421 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 1 Oct 2023 23:27:35 -0700 Subject: [PATCH 49/54] don't dismiss a nil view --- internal/caravan/trade.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/caravan/trade.lua b/internal/caravan/trade.lua index 9c8650eef3..6d5f3a8d89 100644 --- a/internal/caravan/trade.lua +++ b/internal/caravan/trade.lua @@ -534,7 +534,7 @@ end function TradeScreen:onRenderFrame() if not df.global.game.main_interface.trade.open then - view:dismiss() + if view then view:dismiss() end elseif self.reset_pending then self.reset_pending = nil self.trade_window:reset_cache() From 3f8904fe1231444021fd180bd6aeb425d3076f00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 05:44:04 +0000 Subject: [PATCH 50/54] [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.26.3 → 0.27.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.26.3...0.27.0) --- .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 a76c1f8a22..ac959adc36 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.26.3 + rev: 0.27.0 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From 76ba6e9b5c272ef99af6ed5d1183073f32e03a7b Mon Sep 17 00:00:00 2001 From: plule <630159+plule@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:53:49 +0200 Subject: [PATCH 51/54] Fix nil access of tiletype and tileblock --- changelog.txt | 1 + suspendmanager.lua | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index f7a2e75445..3c4fcc1e01 100644 --- a/changelog.txt +++ b/changelog.txt @@ -31,6 +31,7 @@ Template for new versions: ## New Features ## Fixes +- `suspendmanager`: fix errors when constructing near the map edge ## Misc Improvements diff --git a/suspendmanager.lua b/suspendmanager.lua index a3d751d349..283d99234f 100644 --- a/suspendmanager.lua +++ b/suspendmanager.lua @@ -308,13 +308,20 @@ end --- Check if the tile can be walked on ---@param pos coord local function walkable(pos) - return dfhack.maps.getTileBlock(pos).walkable[pos.x % 16][pos.y % 16] > 0 + local tileblock = dfhack.maps.getTileBlock(pos) + return tileblock and tileblock.walkable[pos.x % 16][pos.y % 16] > 0 end --- Check if the tile is suitable tile to stand on for construction (walkable & not a tree branch) ---@param pos coord local function isSuitableAccess(pos) local tt = dfhack.maps.getTileType(pos) + + if not tt then + -- no tiletype, likely out of bound + return false + end + local attrs = df.tiletype.attrs[tt] if attrs.shape == df.tiletype_shape.BRANCH or attrs.shape == df.tiletype_shape.TRUNK_BRANCH then -- Branches can be walked on, but most of the time we can assume that it's not a suitable access. @@ -425,6 +432,7 @@ local function tileHasSupportFloor(pos) local attrs = df.tiletype.attrs[tt] if TILETYPE_SHAPE_FLOOR_SUPPORT[attrs.shape] then return true end end + return false end local function tileHasSupportBuilding(pos) From b8c7ff4072fc09532e84a1e89ccccb710664431e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Oct 2023 13:29:01 -0700 Subject: [PATCH 52/54] fix clicks not getting cleared on first handle --- changelog.txt | 1 + gui/sandbox.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 3c4fcc1e01..4722c3b630 100644 --- a/changelog.txt +++ b/changelog.txt @@ -32,6 +32,7 @@ Template for new versions: ## Fixes - `suspendmanager`: fix errors when constructing near the map edge +- `gui/sandbox`: fix scrollbar moving double distance on click ## Misc Improvements diff --git a/gui/sandbox.lua b/gui/sandbox.lua index cb136f41ba..3866fd3096 100644 --- a/gui/sandbox.lua +++ b/gui/sandbox.lua @@ -209,6 +209,7 @@ function Sandbox:onInput(keys) end end view:sendInputToParent(keys) + return true end function Sandbox:find_zombie_syndrome() From 71cd71885c10357cec9d264d2ea9331fe592003f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 6 Oct 2023 11:48:06 -0700 Subject: [PATCH 53/54] use capital letter for class name --- warn-starving.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/warn-starving.lua b/warn-starving.lua index 2b08042030..225d41cfd7 100644 --- a/warn-starving.lua +++ b/warn-starving.lua @@ -27,14 +27,14 @@ if args.sane then checkOnlySane = true end -warning = defclass(warning, gui.ZScreen) -warning.ATTRS = { +Warning = defclass(Warning, gui.ZScreen) +Warning.ATTRS = { focus_path='warn-starving', force_pause=true, pass_mouse_clicks=false, } -function warning:init(info) +function Warning:init(info) local main = widgets.Window{ frame={w=80, h=18}, frame_title='Warning', @@ -51,7 +51,7 @@ function warning:init(info) self:addviews{main} end -function warning:onDismiss() +function Warning:onDismiss() view = nil end @@ -111,7 +111,7 @@ function doCheck() print(dfhack.df2console(msg)) end dfhack.color() - return warning{messages=messages}:show() + return Warning{messages=messages}:show() end end From b74a2930dfc80d21295101d1c8a3d3a5fb2eb428 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 6 Oct 2023 16:12:50 -0700 Subject: [PATCH 54/54] clean up remove-wear code --- remove-wear.lua | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/remove-wear.lua b/remove-wear.lua index 58406a1b6d..90621b54c9 100644 --- a/remove-wear.lua +++ b/remove-wear.lua @@ -1,33 +1,21 @@ -- Reset items in your fort to 0 wear -- original author: Laggy, edited by expwnent -local help = [====[ - -remove-wear -=========== -Sets the wear on items in your fort to zero. Usage: - -:remove-wear all: - Removes wear from all items in your fort. -:remove-wear ID1 ID2 ...: - Removes wear from items with the given ID numbers. - -]====] local args = {...} local count = 0 -if args[1] == 'help' then - print(help) +if not args[1] or args[1] == 'help' or args[1] == '-h' or args[1] == '--help' then + print(dfhack.script_help()) return elseif args[1] == 'all' or args[1] == '-all' then for _, item in ipairs(df.global.world.items.all) do - if item.wear > 0 then --hint:df.item_actual + if item:getWear() > 0 then --hint:df.item_actual item:setWear(0) count = count + 1 end end else - for i, arg in ipairs(args) do + for _, arg in ipairs(args) do local item_id = tonumber(arg) if item_id then local item = df.item.find(item_id) @@ -43,4 +31,4 @@ else end end -print('remove-wear: removed wear from '..count..' objects') +print('remove-wear: removed wear from '..tostring(count)..' items')