From ad52919865d42e1d120bba72463f3c09cb2e2b4b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 25 Feb 2026 20:00:05 +0100 Subject: [PATCH 1/7] First working version --- .../lua/buildingplan/unlink_mechanisms.lua | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 86ae80940f..c48a4502e1 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -263,6 +263,37 @@ function MechLinkOverlay:get_button(n, ensure) return button end +function MechLinkOverlay:get_pull_button(n, label, text_pen) + local old_pull = self.subviews["pull_"..n] + + -- Create new pull button with correct label (use unique ID to avoid collision) + local unique_id = "pull_"..n.."_"..tostring(label):gsub("[^%w]", "_") + self:addviews + { + widgets.TextButton + { + view_id = unique_id, + frame = {t=0, r=17, w=9, h=1}, + label = label, + text_pen = text_pen or COLOR_WHITE, + on_activate = function() self:activate_pull_button(n) end, + visible = false, + }, + } + local pull_button = self.subviews[unique_id] + pull_button:updateLayout(self.frame_body) + + -- Update the main reference + self.subviews["pull_"..n] = pull_button + + -- Mark old button for cleanup (it will be hidden) + if old_pull and old_pull ~= pull_button then + old_pull.visible = false + end + + return pull_button +end + function MechLinkOverlay:activate_button(n) local button = self:get_button(n) local saved_mode = self.subviews.unlink_mode:getOptionValue() @@ -303,6 +334,72 @@ function MechLinkOverlay:activate_button(n) end end +function MechLinkOverlay:is_lever(b) + return b._type == df.building_trapst and b.trap_type == df.trap_type.Lever +end + +function MechLinkOverlay:has_lever_pull_job(lever) + for _, j in ipairs(lever.jobs) do + if j.job_type == df.job_type.PullLever then + return true + end + end + return false +end + +function MechLinkOverlay:queue_lever_pull_job(lever) + local ref = df.general_ref_building_holderst:new() + ref.building_id = lever.id + + local job = df.job:new() + job.job_type = df.job_type.PullLever + job.pos = { + x = lever.centerx, + y = lever.centery, + z = lever.z + } + job.flags.do_now = false + job.general_refs:insert("#", ref) + lever.jobs:insert("#", job) + + dfhack.job.linkIntoWorld(job, true) + dfhack.job.checkBuildingsNow() +end + +function MechLinkOverlay:remove_lever_pull_job(lever) + for i = #lever.jobs, 1, -1 do + local job = lever.jobs[i-1] + if job.job_type == df.job_type.PullLever then + lever.jobs:erase(i-1) + dfhack.job.removeJob(job) + return true + end + end + return false +end + +function MechLinkOverlay:activate_pull_button(n) + local pull_button = self.subviews["pull_"..n] + + local idx = self:idx_from_offset(pull_button.frame.t) + if idx > 0 and idx < #self.building.contained_items then + local item = self.building.contained_items[idx].item + local lever = get_mech_target(item) + + if lever and self:is_lever(lever) then + if self:has_lever_pull_job(lever) then + self:remove_lever_pull_job(lever) + else + self:queue_lever_pull_job(lever) + end + else + dfhack.printerr(("MechLinkOverlay: Mechanism is not linked to a lever!"):format(item)) + end + else + dfhack.printerr(("MechLinkOverlay: Invalid pull button! Offset %d"):format(pull_button.frame.t)) + end +end + function MechLinkOverlay:ask_unlink_all() local saved_mode = self.subviews.unlink_mode:getOptionValue() local message = { @@ -350,12 +447,44 @@ function MechLinkOverlay:update_buttons() local idx = self:idx_from_offset(offset) button.visible = false + local pull_button = self.subviews["pull_"..i] + if pull_button then + pull_button.visible = false + end + if idx > 0 and idx < bci_len then + local item = self.building.contained_items[idx].item + local lever = get_mech_target(item) + local show_pull = lever and self:is_lever(lever) and not self:is_lever(self.building) + + if show_pull then + local queued = self:has_lever_pull_job(lever) + local label, text_pen + + if queued then + label = lever.state == 0 and "Closing" or "Opening" + text_pen = COLOR_YELLOW + else + label = lever.state == 0 and "Close" or "Open" + text_pen = COLOR_WHITE + end + + pull_button = self:get_pull_button(i, label, text_pen) + end + button.frame.t = offset button.frame.r = h_offset + if pull_button then + pull_button.frame.t = offset + pull_button.frame.r = h_offset + 9 + pull_button.visible = show_pull + end button.visible = true end button:updateLayout() + if pull_button then + pull_button:updateLayout() + end end local b = (self.frame.h % 3) == 1 and #self.links >= self.num_buttons and 0 or 1 @@ -371,6 +500,10 @@ function MechLinkOverlay:preUpdateLayout(parent_rect) if button then button.visible = false end + local pull_button = self.subviews["pull_"..i] + if pull_button then + pull_button.visible = false + end end local h = parent_rect.height - 49 From 8ca5b2585d44d686f58790715d73a83e6d0d058b Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 25 Feb 2026 20:17:55 +0100 Subject: [PATCH 2/7] Separate overlay --- plugins/lua/buildingplan.lua | 1 + .../lua/buildingplan/unlink_mechanisms.lua | 271 +++++++++++------- 2 files changed, 175 insertions(+), 97 deletions(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index d6eb5fb9ae..bfbc627342 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -148,6 +148,7 @@ OVERLAY_WIDGETS = { mechanisms=mechanisms.MechanismOverlay, mechanism_free=unlink_mechanisms.MechItemOverlay, mechanism_unlink=unlink_mechanisms.MechLinkOverlay, + mechanism_leverpull=unlink_mechanisms.MechLeverPullOverlay, } return _ENV diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index c48a4502e1..5e974f7297 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -263,37 +263,6 @@ function MechLinkOverlay:get_button(n, ensure) return button end -function MechLinkOverlay:get_pull_button(n, label, text_pen) - local old_pull = self.subviews["pull_"..n] - - -- Create new pull button with correct label (use unique ID to avoid collision) - local unique_id = "pull_"..n.."_"..tostring(label):gsub("[^%w]", "_") - self:addviews - { - widgets.TextButton - { - view_id = unique_id, - frame = {t=0, r=17, w=9, h=1}, - label = label, - text_pen = text_pen or COLOR_WHITE, - on_activate = function() self:activate_pull_button(n) end, - visible = false, - }, - } - local pull_button = self.subviews[unique_id] - pull_button:updateLayout(self.frame_body) - - -- Update the main reference - self.subviews["pull_"..n] = pull_button - - -- Mark old button for cleanup (it will be hidden) - if old_pull and old_pull ~= pull_button then - old_pull.visible = false - end - - return pull_button -end - function MechLinkOverlay:activate_button(n) local button = self:get_button(n) local saved_mode = self.subviews.unlink_mode:getOptionValue() @@ -334,11 +303,123 @@ function MechLinkOverlay:activate_button(n) end end -function MechLinkOverlay:is_lever(b) +function MechLinkOverlay:ask_unlink_all() + local saved_mode = self.subviews.unlink_mode:getOptionValue() + local message = { + "Unlink all mechanisms?", + NEWLINE, NEWLINE, + (saved_mode % 2 == 1) and {text="These mechanisms will be freed and moved to the ground.", pen=COLOR_GREEN} or + {text="These mechanisms will be kept in the building. You can free them later.", pen=COLOR_DARKGRAY}, + NEWLINE, + (saved_mode // 2 > 0) and {text="Mechanisms in linked buildings will be freed and moved to the ground.", pen=COLOR_GREEN} or + {text="Mechanisms in linked buildings will be kept in the buildings. You can free them later.", pen=COLOR_DARKGRAY}, + } + confirm_action(self, "Unlink", message, 'always_unlink_all', function () + local free_src = (saved_mode % 2 == 1) + local free_dst = (saved_mode // 2 > 0) + + for _, idx in ipairs(self.links) do + local item = self.building.contained_items[idx].item + local target = get_mech_target(item) + + if target then + unlink_mech(self.building, item, target, free_src, free_dst) + else + dfhack.printerr(("MechLinkOverlay: Mechanism %s had no target!"):format(item)) + end + end + + if not has_link_tab(self.building) then + sheet.show_linked_buildings = false --DF will swap viewscreen + end + end) +end + +function MechLinkOverlay:update_buttons() + self:build_links_table() + local scroll_pos = sheet.scroll_position_linked_buildings + self.subviews.scroll:update(scroll_pos + 1, self.num_buttons*3, #self.links*3) + self:scroll(0) + + local bci_len = #self.building.contained_items + local h_offset = #self.links > self.num_buttons and 8 or 6 --account for scrollbar + + for i=1, self.num_buttons do + local button = self:get_button(i, true) + local offset = i*3 - 1 - ((scroll_pos + 1) % 3) + local idx = self:idx_from_offset(offset) + + button.visible = false + + if idx > 0 and idx < bci_len then + button.frame.t = offset + button.frame.r = h_offset + button.visible = true + end + button:updateLayout() + end + + local b = (self.frame.h % 3) == 1 and #self.links >= self.num_buttons and 0 or 1 + self.subviews.unlink_mode.frame.b = b --avoid overlapping list + self.subviews.unlink_all.frame.b = b + self.subviews.unlink_mode:updateLayout() + self.subviews.unlink_all:updateLayout() +end + +function MechLinkOverlay:preUpdateLayout(parent_rect) + for i=1, self.num_buttons do --hide existing buttons + local button = self:get_button(i) + if button then + button.visible = false + end + end + + local h = parent_rect.height - 49 + self.frame.h = h + 1 --includes lower border + self.num_buttons = h // 3 + + self.subviews.scroll.frame.h = self.num_buttons*3 +end + +function MechLinkOverlay:onRenderFrame(dc, rect) + if self.bld_id ~= sheet.viewing_bldid then + self.bld_id = sheet.viewing_bldid + self.building = df.building.find(self.bld_id) + end + + self:update_buttons() + + MechLinkOverlay.super.onRenderFrame(self, dc, rect) +end + +-- ---------------------- +-- MechLeverPullOverlay +-- + +MechLeverPullOverlay = defclass(MechLeverPullOverlay, overlay.OverlayWidget) +MechLeverPullOverlay.ATTRS +{ + desc = "Allows queueing lever pull jobs from linked building view.", + default_enabled = true, + default_pos = {x=-41, y=-4}, + frame = {w=56, h=27}, + viewscreens = {}, +} + +for _,v in ipairs(valid_build) do + utils.insert_sorted(MechLeverPullOverlay.ATTRS.viewscreens, "dwarfmode/ViewSheets/BUILDING/"..v.."/LinkedBuildings") +end + +function MechLeverPullOverlay:init() + self.num_buttons = 0 + self.links = {} +end + +function MechLeverPullOverlay:is_lever(b) return b._type == df.building_trapst and b.trap_type == df.trap_type.Lever end -function MechLinkOverlay:has_lever_pull_job(lever) +function MechLeverPullOverlay:has_lever_pull_job(lever) for _, j in ipairs(lever.jobs) do if j.job_type == df.job_type.PullLever then return true @@ -347,7 +428,7 @@ function MechLinkOverlay:has_lever_pull_job(lever) return false end -function MechLinkOverlay:queue_lever_pull_job(lever) +function MechLeverPullOverlay:queue_lever_pull_job(lever) local ref = df.general_ref_building_holderst:new() ref.building_id = lever.id @@ -366,7 +447,7 @@ function MechLinkOverlay:queue_lever_pull_job(lever) dfhack.job.checkBuildingsNow() end -function MechLinkOverlay:remove_lever_pull_job(lever) +function MechLeverPullOverlay:remove_lever_pull_job(lever) for i = #lever.jobs, 1, -1 do local job = lever.jobs[i-1] if job.job_type == df.job_type.PullLever then @@ -378,7 +459,57 @@ function MechLinkOverlay:remove_lever_pull_job(lever) return false end -function MechLinkOverlay:activate_pull_button(n) +function MechLeverPullOverlay:build_links_table() + self.links = {} + for idx = 1, #self.building.contained_items-1 do --index 0 is always building component + local item = self.building.contained_items[idx].item + if item._type == df.item_trappartsst and item.flags.in_building and + not item.flags.in_job and get_trigger_index(item) then --item is linked mechanism + table.insert(self.links, idx) + end + end +end + +function MechLeverPullOverlay:idx_from_offset(offset) + if offset <= 0 or offset >= self.num_buttons*3 - 1 then --linked icons disappear early + return 0 --outside of list + else + return self.links[(offset + sheet.scroll_position_linked_buildings) // 3 + 1] or 0 + end +end + +function MechLeverPullOverlay:get_pull_button(n, label, text_pen) + local old_pull = self.subviews["pull_"..n] + + -- Create new pull button with correct label (use unique ID to avoid collision) + local unique_id = "pull_"..n.."_"..tostring(label):gsub("[^%w]", "_") + self:addviews + { + widgets.TextButton + { + view_id = unique_id, + frame = {t=0, r=17, w=9, h=1}, + label = label, + text_pen = text_pen or COLOR_WHITE, + on_activate = function() self:activate_pull_button(n) end, + visible = false, + }, + } + local pull_button = self.subviews[unique_id] + pull_button:updateLayout(self.frame_body) + + -- Update the main reference + self.subviews["pull_"..n] = pull_button + + -- Mark old button for cleanup (it will be hidden) + if old_pull and old_pull ~= pull_button then + old_pull.visible = false + end + + return pull_button +end + +function MechLeverPullOverlay:activate_pull_button(n) local pull_button = self.subviews["pull_"..n] local idx = self:idx_from_offset(pull_button.frame.t) @@ -393,60 +524,24 @@ function MechLinkOverlay:activate_pull_button(n) self:queue_lever_pull_job(lever) end else - dfhack.printerr(("MechLinkOverlay: Mechanism is not linked to a lever!"):format(item)) + dfhack.printerr(("MechLeverPullOverlay: Mechanism is not linked to a lever!"):format(item)) end else - dfhack.printerr(("MechLinkOverlay: Invalid pull button! Offset %d"):format(pull_button.frame.t)) + dfhack.printerr(("MechLeverPullOverlay: Invalid pull button! Offset %d"):format(pull_button.frame.t)) end end -function MechLinkOverlay:ask_unlink_all() - local saved_mode = self.subviews.unlink_mode:getOptionValue() - local message = { - "Unlink all mechanisms?", - NEWLINE, NEWLINE, - (saved_mode % 2 == 1) and {text="These mechanisms will be freed and moved to the ground.", pen=COLOR_GREEN} or - {text="These mechanisms will be kept in the building. You can free them later.", pen=COLOR_DARKGRAY}, - NEWLINE, - (saved_mode // 2 > 0) and {text="Mechanisms in linked buildings will be freed and moved to the ground.", pen=COLOR_GREEN} or - {text="Mechanisms in linked buildings will be kept in the buildings. You can free them later.", pen=COLOR_DARKGRAY}, - } - confirm_action(self, "Unlink", message, 'always_unlink_all', function () - local free_src = (saved_mode % 2 == 1) - local free_dst = (saved_mode // 2 > 0) - - for _, idx in ipairs(self.links) do - local item = self.building.contained_items[idx].item - local target = get_mech_target(item) - - if target then - unlink_mech(self.building, item, target, free_src, free_dst) - else - dfhack.printerr(("MechLinkOverlay: Mechanism %s had no target!"):format(item)) - end - end - - if not has_link_tab(self.building) then - sheet.show_linked_buildings = false --DF will swap viewscreen - end - end) -end - -function MechLinkOverlay:update_buttons() +function MechLeverPullOverlay:update_buttons() self:build_links_table() local scroll_pos = sheet.scroll_position_linked_buildings - self.subviews.scroll:update(scroll_pos + 1, self.num_buttons*3, #self.links*3) - self:scroll(0) local bci_len = #self.building.contained_items local h_offset = #self.links > self.num_buttons and 8 or 6 --account for scrollbar for i=1, self.num_buttons do - local button = self:get_button(i, true) local offset = i*3 - 1 - ((scroll_pos + 1) % 3) local idx = self:idx_from_offset(offset) - button.visible = false local pull_button = self.subviews["pull_"..i] if pull_button then pull_button.visible = false @@ -470,36 +565,20 @@ function MechLinkOverlay:update_buttons() end pull_button = self:get_pull_button(i, label, text_pen) - end - - button.frame.t = offset - button.frame.r = h_offset - if pull_button then pull_button.frame.t = offset pull_button.frame.r = h_offset + 9 pull_button.visible = show_pull end - button.visible = true end - button:updateLayout() + if pull_button then pull_button:updateLayout() end end - - local b = (self.frame.h % 3) == 1 and #self.links >= self.num_buttons and 0 or 1 - self.subviews.unlink_mode.frame.b = b --avoid overlapping list - self.subviews.unlink_all.frame.b = b - self.subviews.unlink_mode:updateLayout() - self.subviews.unlink_all:updateLayout() end -function MechLinkOverlay:preUpdateLayout(parent_rect) +function MechLeverPullOverlay:preUpdateLayout(parent_rect) for i=1, self.num_buttons do --hide existing buttons - local button = self:get_button(i) - if button then - button.visible = false - end local pull_button = self.subviews["pull_"..i] if pull_button then pull_button.visible = false @@ -509,11 +588,9 @@ function MechLinkOverlay:preUpdateLayout(parent_rect) local h = parent_rect.height - 49 self.frame.h = h + 1 --includes lower border self.num_buttons = h // 3 - - self.subviews.scroll.frame.h = self.num_buttons*3 end -function MechLinkOverlay:onRenderFrame(dc, rect) +function MechLeverPullOverlay:onRenderFrame(dc, rect) if self.bld_id ~= sheet.viewing_bldid then self.bld_id = sheet.viewing_bldid self.building = df.building.find(self.bld_id) @@ -521,7 +598,7 @@ function MechLinkOverlay:onRenderFrame(dc, rect) self:update_buttons() - MechLinkOverlay.super.onRenderFrame(self, dc, rect) + MechLeverPullOverlay.super.onRenderFrame(self, dc, rect) end -- ---------------------- From c9b5b4c416ebbefe5ed24a8570d652cfc4688c68 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 25 Feb 2026 20:56:59 +0100 Subject: [PATCH 3/7] Move to the right when unlink is disabled --- plugins/lua/buildingplan/unlink_mechanisms.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 5e974f7297..436686f904 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -538,6 +538,10 @@ function MechLeverPullOverlay:update_buttons() local bci_len = #self.building.contained_items local h_offset = #self.links > self.num_buttons and 8 or 6 --account for scrollbar + -- Check if mechanism_unlink overlay is enabled + local unlink_enabled = overlay.isOverlayEnabled('buildingplan.mechanism_unlink') + local pull_offset = unlink_enabled and 14 or 6 -- 14 when both enabled, 6 when only pull + for i=1, self.num_buttons do local offset = i*3 - 1 - ((scroll_pos + 1) % 3) local idx = self:idx_from_offset(offset) @@ -566,7 +570,7 @@ function MechLeverPullOverlay:update_buttons() pull_button = self:get_pull_button(i, label, text_pen) pull_button.frame.t = offset - pull_button.frame.r = h_offset + 9 + pull_button.frame.r = pull_offset pull_button.visible = show_pull end end From eca0e41b7d26f349378ddfbb5dd82b59e42415cc Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Wed, 25 Feb 2026 21:25:27 +0100 Subject: [PATCH 4/7] Memory leak --- .../lua/buildingplan/unlink_mechanisms.lua | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 436686f904..2c754c4d80 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -350,7 +350,6 @@ function MechLinkOverlay:update_buttons() local idx = self:idx_from_offset(offset) button.visible = false - if idx > 0 and idx < bci_len then button.frame.t = offset button.frame.r = h_offset @@ -479,31 +478,27 @@ function MechLeverPullOverlay:idx_from_offset(offset) end function MechLeverPullOverlay:get_pull_button(n, label, text_pen) - local old_pull = self.subviews["pull_"..n] + local pull_button = self.subviews["pull_"..n] - -- Create new pull button with correct label (use unique ID to avoid collision) - local unique_id = "pull_"..n.."_"..tostring(label):gsub("[^%w]", "_") - self:addviews - { - widgets.TextButton + if not pull_button then + self:addviews { - view_id = unique_id, - frame = {t=0, r=17, w=9, h=1}, - label = label, - text_pen = text_pen or COLOR_WHITE, - on_activate = function() self:activate_pull_button(n) end, - visible = false, - }, - } - local pull_button = self.subviews[unique_id] - pull_button:updateLayout(self.frame_body) - - -- Update the main reference - self.subviews["pull_"..n] = pull_button - - -- Mark old button for cleanup (it will be hidden) - if old_pull and old_pull ~= pull_button then - old_pull.visible = false + widgets.TextButton + { + view_id = "pull_"..n, + frame = {t=0, r=17, w=9, h=1}, + label = label, + text_pen = text_pen or COLOR_WHITE, + on_activate = function() self:activate_pull_button(n) end, + visible = false, + }, + } + pull_button = self.subviews["pull_"..n] + pull_button:updateLayout(self.frame_body) + else + -- Update existing button using setLabel method + pull_button:setLabel(label) + pull_button.label.text_pen = text_pen or COLOR_WHITE end return pull_button From 1a789fae82ebc50388129815a9a57c00bc25e8da Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Thu, 26 Feb 2026 19:10:42 +0100 Subject: [PATCH 5/7] Green text when job assigned --- .../lua/buildingplan/unlink_mechanisms.lua | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 2c754c4d80..44f889b019 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -414,22 +414,22 @@ function MechLeverPullOverlay:init() self.links = {} end -function MechLeverPullOverlay:is_lever(b) - return b._type == df.building_trapst and b.trap_type == df.trap_type.Lever +function MechLeverPullOverlay:is_lever(building) + return building._type == df.building_trapst and building.trap_type == df.trap_type.Lever end -function MechLeverPullOverlay:has_lever_pull_job(lever) - for _, j in ipairs(lever.jobs) do - if j.job_type == df.job_type.PullLever then - return true +function MechLeverPullOverlay:get_lever_pull_job(lever) + for _, job in ipairs(lever.jobs) do + if job.job_type == df.job_type.PullLever then + return job end end - return false + return nil end function MechLeverPullOverlay:queue_lever_pull_job(lever) - local ref = df.general_ref_building_holderst:new() - ref.building_id = lever.id + local building_ref = df.general_ref_building_holderst:new() + building_ref.building_id = lever.id local job = df.job:new() job.job_type = df.job_type.PullLever @@ -439,7 +439,7 @@ function MechLeverPullOverlay:queue_lever_pull_job(lever) z = lever.z } job.flags.do_now = false - job.general_refs:insert("#", ref) + job.general_refs:insert("#", building_ref) lever.jobs:insert("#", job) dfhack.job.linkIntoWorld(job, true) @@ -477,23 +477,23 @@ function MechLeverPullOverlay:idx_from_offset(offset) end end -function MechLeverPullOverlay:get_pull_button(n, label, text_pen) - local pull_button = self.subviews["pull_"..n] +function MechLeverPullOverlay:get_button(button_idx, label, text_pen) + local pull_button = self.subviews["pull_"..button_idx] if not pull_button then self:addviews { widgets.TextButton { - view_id = "pull_"..n, + view_id = "pull_"..button_idx, frame = {t=0, r=17, w=9, h=1}, label = label, text_pen = text_pen or COLOR_WHITE, - on_activate = function() self:activate_pull_button(n) end, + on_activate = function() self:activate_button(button_idx) end, visible = false, }, } - pull_button = self.subviews["pull_"..n] + pull_button = self.subviews["pull_"..button_idx] pull_button:updateLayout(self.frame_body) else -- Update existing button using setLabel method @@ -504,8 +504,8 @@ function MechLeverPullOverlay:get_pull_button(n, label, text_pen) return pull_button end -function MechLeverPullOverlay:activate_pull_button(n) - local pull_button = self.subviews["pull_"..n] +function MechLeverPullOverlay:activate_button(button_idx) + local pull_button = self.subviews["pull_"..button_idx] local idx = self:idx_from_offset(pull_button.frame.t) if idx > 0 and idx < #self.building.contained_items then @@ -513,16 +513,14 @@ function MechLeverPullOverlay:activate_pull_button(n) local lever = get_mech_target(item) if lever and self:is_lever(lever) then - if self:has_lever_pull_job(lever) then + if self:get_lever_pull_job(lever) then self:remove_lever_pull_job(lever) else self:queue_lever_pull_job(lever) end else - dfhack.printerr(("MechLeverPullOverlay: Mechanism is not linked to a lever!"):format(item)) + dfhack.printerr("MechLeverPullOverlay: Mechanism is not linked to a lever") end - else - dfhack.printerr(("MechLeverPullOverlay: Invalid pull button! Offset %d"):format(pull_button.frame.t)) end end @@ -533,9 +531,8 @@ function MechLeverPullOverlay:update_buttons() local bci_len = #self.building.contained_items local h_offset = #self.links > self.num_buttons and 8 or 6 --account for scrollbar - -- Check if mechanism_unlink overlay is enabled local unlink_enabled = overlay.isOverlayEnabled('buildingplan.mechanism_unlink') - local pull_offset = unlink_enabled and 14 or 6 -- 14 when both enabled, 6 when only pull + local pull_offset = unlink_enabled and 14 or 6 for i=1, self.num_buttons do local offset = i*3 - 1 - ((scroll_pos + 1) % 3) @@ -552,18 +549,19 @@ function MechLeverPullOverlay:update_buttons() local show_pull = lever and self:is_lever(lever) and not self:is_lever(self.building) if show_pull then - local queued = self:has_lever_pull_job(lever) + local job = self:get_lever_pull_job(lever) local label, text_pen - if queued then + if job then label = lever.state == 0 and "Closing" or "Opening" - text_pen = COLOR_YELLOW + local worker = dfhack.job.getWorker(job) + text_pen = worker and COLOR_GREEN or COLOR_YELLOW else label = lever.state == 0 and "Close" or "Open" text_pen = COLOR_WHITE end - pull_button = self:get_pull_button(i, label, text_pen) + pull_button = self:get_button(i, label, text_pen) pull_button.frame.t = offset pull_button.frame.r = pull_offset pull_button.visible = show_pull @@ -584,9 +582,9 @@ function MechLeverPullOverlay:preUpdateLayout(parent_rect) end end - local h = parent_rect.height - 49 - self.frame.h = h + 1 --includes lower border - self.num_buttons = h // 3 + local height = parent_rect.height - 49 + self.frame.h = height + 1 --includes lower border + self.num_buttons = height // 3 end function MechLeverPullOverlay:onRenderFrame(dc, rect) From a96063e71a4ef3c9871f1c1ebba59c081eaa028e Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Thu, 26 Feb 2026 19:31:08 +0100 Subject: [PATCH 6/7] Change open/close to pull. Sadly, can't tell if it's open or closed based on the state without considering the building type --- plugins/lua/buildingplan/unlink_mechanisms.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 44f889b019..58f782acc0 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -486,7 +486,7 @@ function MechLeverPullOverlay:get_button(button_idx, label, text_pen) widgets.TextButton { view_id = "pull_"..button_idx, - frame = {t=0, r=17, w=9, h=1}, + frame = {t=0, r=17, w=11, h=1}, label = label, text_pen = text_pen or COLOR_WHITE, on_activate = function() self:activate_button(button_idx) end, @@ -551,13 +551,14 @@ function MechLeverPullOverlay:update_buttons() if show_pull then local job = self:get_lever_pull_job(lever) local label, text_pen + local state_char = lever.state == 0 and "/" or "\\" if job then - label = lever.state == 0 and "Closing" or "Opening" + label = "Pulling " .. state_char local worker = dfhack.job.getWorker(job) text_pen = worker and COLOR_GREEN or COLOR_YELLOW else - label = lever.state == 0 and "Close" or "Open" + label = "Pull " .. state_char text_pen = COLOR_WHITE end From b13bf9e3f3658d6decaab9b520a4e238ba9d9e29 Mon Sep 17 00:00:00 2001 From: pajawojciech Date: Thu, 26 Feb 2026 19:36:20 +0100 Subject: [PATCH 7/7] Changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 3d78dd8335..b29d445e1b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -58,6 +58,7 @@ Template for new versions: - ``logcleaner``: New plugin for time-triggered clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI. ## New Features +- `buildingplan`: added overlay to queue lever pull jobs from linked building view - `orders`: added search overlay to find and navigate to matching manager orders with arrow indicators - `sort`: added ``Uniformed`` filter to squad assignment screen to filter dwarves with mining, woodcutting, or hunting labors - `sort`: Add death cause button to dead/missing tab in the creatures screen