From 5d806e28a3591a7e0d603c2918f16eb3f6e6c744 Mon Sep 17 00:00:00 2001 From: unboundlopez Date: Fri, 30 May 2025 18:27:14 -0500 Subject: [PATCH 1/8] Add zUniform squad uniform management script and documentation --- docs/zUniform.rst | 23 +++++ zUniform.lua | 249 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 docs/zUniform.rst create mode 100644 zUniform.lua diff --git a/docs/zUniform.rst b/docs/zUniform.rst new file mode 100644 index 000000000..6aa17429c --- /dev/null +++ b/docs/zUniform.rst @@ -0,0 +1,23 @@ +squad-uniform +============= + +An overlay UI that allows importing and exporting squad uniforms. + +Usage +----- + +To use this overlay: + +1. Press `q` to open the squad sidebar. +2. Select a squad by clicking its checkbox. +3. Click the `Equip` button. +4. Either: + - Press `Add uniform` to create a new one, **or** + - Click a unit’s `Details` button to customize their equipment. +5. The `[Import]` and `[Export]` buttons will now appear in the **bottom-right** corner of the screen. + - You can also use the hotkeys: + - `Ctrl+I` to import a uniform + - `Ctrl+E` to export the current uniform + +Uniforms are saved to and loaded from the following folder: +Dwarf Fortress\dfhack-config\squad_uniform diff --git a/zUniform.lua b/zUniform.lua new file mode 100644 index 000000000..bb21d85b2 --- /dev/null +++ b/zUniform.lua @@ -0,0 +1,249 @@ +--@ module=true + +local gui = require('gui') +local widgets = require('gui.widgets') +local overlay = require('plugins.overlay') +local dialogs = require('gui.dialogs') +local json = require('json') + +local UNIFORM_DIR = dfhack.getDFPath() .. '/dfhack-config/squad_uniform/' + +local function ensure_uniform_dir() + if not dfhack.filesystem.isdir(UNIFORM_DIR) then + dfhack.filesystem.mkdir(UNIFORM_DIR) + end +end + +local function is_valid_name(name) + return name and #name > 0 and not name:find('[^%w%._%s]') +end + +local function get_uniform_files() + ensure_uniform_dir() + local files = {} + local list = dfhack.filesystem.listdir(UNIFORM_DIR) + if list then + for _, file in ipairs(list) do + if file:match('%.dfuniform$') then + table.insert(files, file) + end + end + table.sort(files) + end + return files +end + +local function import_uniform_file(filepath) + ensure_uniform_dir() + local file, err = io.open(filepath, 'r') + if not file then + return false, 'Failed to open file for reading: ' .. tostring(err) + end + + local text = file:read('*a') + file:close() + + local ok, data = pcall(json.decode, text) + if not ok or type(data) ~= 'table' then + return false, 'Failed to decode uniform file or invalid format.' + end + + local uniform_data = data.uniform + if type(uniform_data) ~= 'table' then + return false, 'Uniform data is missing or invalid.' + end + + local nickname = data.nickname + if not nickname or nickname == '' then + nickname = filepath:match('([^/\\]+)%.dfuniform$') or 'ImportedUniform' + end + + local panel = df.global.game.main_interface and df.global.game.main_interface.squad_equipment + if not panel then + return false, 'Squad equipment panel is not available. Please open the Military > Equipment screen.' + end + + local n = #uniform_data + panel.cs_cat:resize(n) + panel.cs_it_spec_item_id:resize(n) + panel.cs_it_type:resize(n) + panel.cs_it_subtype:resize(n) + panel.cs_civ_mat:resize(n) + panel.cs_spec_mat:resize(n) + panel.cs_spec_matg:resize(n) + panel.cs_color_pattern_index:resize(n) + panel.cs_icp_flag:resize(n) + panel.cs_assigned_item_number:resize(n) + panel.cs_assigned_item_id:resize(n) + + panel.open = true + panel.customizing_equipment = true + panel.customizing_squad_entering_uniform_nickname = true + panel.customizing_squad_uniform_nickname = nickname + + for i, slot in ipairs(uniform_data) do + local idx = i - 1 + panel.cs_cat[idx] = slot.cat or -1 + panel.cs_it_spec_item_id[idx] = slot.spec_item_id or -1 + panel.cs_it_type[idx] = slot.it_type or -1 + panel.cs_it_subtype[idx] = slot.it_subtype or -1 + panel.cs_civ_mat[idx] = slot.civ_mat or -1 + panel.cs_spec_mat[idx] = slot.spec_mat or -1 + panel.cs_spec_matg[idx] = slot.spec_matg or -1 + panel.cs_color_pattern_index[idx] = slot.color_pattern_index or -1 + panel.cs_icp_flag[idx] = slot.icp_flag or 0 + panel.cs_assigned_item_number[idx] = slot.assigned_item_number or -1 + panel.cs_assigned_item_id[idx] = slot.assigned_item_id or -1 + end + + panel.cs_uniform_flag = data.uniform_flag or 2 + + return true, 'Uniform successfully imported!' +end + +local function export_uniform_file(filepath) + ensure_uniform_dir() + local panel = df.global.game.main_interface and df.global.game.main_interface.squad_equipment + if not panel then + return false, 'Squad equipment panel is not available. Please open the Military > Equipment screen.' + end + + local n = #panel.cs_cat + local uniform_data = {} + for i = 0, n - 1 do + table.insert(uniform_data, { + cat = panel.cs_cat[i], + spec_item_id = panel.cs_it_spec_item_id[i], + it_type = panel.cs_it_type[i], + it_subtype = panel.cs_it_subtype[i], + civ_mat = panel.cs_civ_mat[i], + spec_mat = panel.cs_spec_mat[i], + spec_matg = panel.cs_spec_matg[i], + color_pattern_index = panel.cs_color_pattern_index[i], + icp_flag = panel.cs_icp_flag[i], + assigned_item_number = panel.cs_assigned_item_number[i], + assigned_item_id = panel.cs_assigned_item_id[i], + }) + end + + local nickname = panel.customizing_squad_uniform_nickname or '' + local uniform_flag = panel.cs_uniform_flag or 2 + + local file, err = io.open(filepath, 'w') + if not file then return false, 'Failed to open file for writing: ' .. tostring(err) end + file:write(json.encode({ + nickname = nickname, + uniform = uniform_data, + uniform_flag = uniform_flag + })) + file:close() + return true, 'Uniform saved to ' .. filepath +end + +local function ExportUniformDialog() + dialogs.InputBox{ + frame_title = 'Export Squad Uniform', + text = 'Enter file name (no extension):', + on_input = function(name) + if not is_valid_name(name) then + dialogs.showMessage("Invalid Name", "Name can only contain letters, numbers, underscores, periods, and spaces.") + return + end + local fname = UNIFORM_DIR .. name .. '.dfuniform' + local ok, msg = export_uniform_file(fname) + if ok then + dfhack.println('Exported to: ' .. fname) + else + dfhack.printerr(msg) + end + end + }:show() +end + +local function get_uniform_choices() + local files = get_uniform_files() + local choices = {} + for _, f in ipairs(files) do + table.insert(choices, {text = f}) + end + return choices +end + +local function ImportUniformDialog() + ensure_uniform_dir() + local dlg + local function get_dlg() return dlg end + + dlg = dialogs.ListBox{ + frame_title = 'Import/Delete Squad Uniform', + with_filter = true, + choices = get_uniform_choices(), + on_select = function(_, choice) + dfhack.timeout(2, 'frames', function() + local fname = UNIFORM_DIR .. choice.text + local ok, msg = import_uniform_file(fname) + if ok then + dfhack.println('Imported from: ' .. fname) + else + dfhack.printerr(msg) + end + end) + end, + dismiss_on_select2 = false, + on_select2 = function(_, choice) + local fname = UNIFORM_DIR .. choice.text + if not dfhack.filesystem.isfile(fname) then return end + + dialogs.showYesNoPrompt('Delete uniform file?', + 'Are you sure you want to delete "' .. fname .. '"?', nil, + function() + os.remove(fname) + dfhack.println('Deleted: ' .. fname) + local list = get_dlg().subviews.list + local filter = list:getFilter() + list:setChoices(get_uniform_choices(), list:getSelected()) + list:setFilter(filter) + end) + end, + select2_hint = 'Delete file', + }:show() +end + +local UniformOverlay = defclass(UniformOverlay, overlay.OverlayWidget) +UniformOverlay.ATTRS{ + desc = 'Manage squad uniforms.', + viewscreens = 'dwarfmode/Squads/Equipment/Customizing/Default', + default_enabled = true, + default_pos = {x = -33, y = -5}, + frame = {w = 40, h = 3}, +} + +function UniformOverlay:init() + self:addviews{ + widgets.Panel{ + frame = {t = 0, l = 0, w = 40, h = 3}, + frame_style = gui.MEDIUM_FRAME, + frame_background = gui.CLEAR_PEN, + subviews = { + widgets.HotkeyLabel{ + frame = {l = 0, t = 0}, + label = '[Import]', + key = 'CUSTOM_CTRL_I', + auto_width = true, + on_activate = ImportUniformDialog, + }, + widgets.HotkeyLabel{ + frame = {l = 20, t = 0}, + label = '[Export]', + key = 'CUSTOM_CTRL_E', + auto_width = true, + on_activate = ExportUniformDialog, + }, + }, + }, + } +end + +OVERLAY_WIDGETS = { + uniform_overlay = UniformOverlay, +} \ No newline at end of file From 8b1819140b61a6038d4179e73b9896c38792402d Mon Sep 17 00:00:00 2001 From: unboundlopez Date: Fri, 30 May 2025 18:32:06 -0500 Subject: [PATCH 2/8] Add zForceWorkshopJobsNow script and documentation --- docs/zForceWorkshopJobsNow.rst | 42 +++++++++ zForceWorkshopJobsNow.lua | 157 +++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 docs/zForceWorkshopJobsNow.rst create mode 100644 zForceWorkshopJobsNow.lua diff --git a/docs/zForceWorkshopJobsNow.rst b/docs/zForceWorkshopJobsNow.rst new file mode 100644 index 000000000..1d64fd455 --- /dev/null +++ b/docs/zForceWorkshopJobsNow.rst @@ -0,0 +1,42 @@ +zForceWorkshopJobsNow +===================== + +A DFHack plugin that force-starts or unforces all jobs in workshops and furnaces across the fortress. + +Overview +-------- + +This plugin provides both an in-game overlay and a command-line interface to control whether queued jobs in workshops and furnaces should be executed immediately (`do_now = true`) or not (`do_now = false`). It is useful for players who want more control over job execution prioritization. + +Features +-------- + +- Force all current jobs in all workshops and furnaces to start immediately. +- Toggle job forcing directly from the workshop task viewscreen using a small overlay panel. +- Simple hotkeys: ``o`` (ON) and ``f`` (OFF). +- Compatible with most standard and custom workshops and furnaces. + +Usage +----- + +**In-Game Overlay** + +Navigate to any workshop or furnace job screen. A small UI will appear with: + +:: + + Prioritize All: + ON OFF + +- Press ``o`` to force all jobs. +- Press ``f`` to disable forced jobs. + +**Console Command** + +:: + + zForceWorkshopJobsNow [ON|OFF] + +- Running with no argument is equivalent to ``ON``. +- ``ON``: Set ``do_now = true`` for all applicable jobs. +- ``OFF``: Set ``do_now = false`` for all applicable jobs. diff --git a/zForceWorkshopJobsNow.lua b/zForceWorkshopJobsNow.lua new file mode 100644 index 000000000..9ef19fd7e --- /dev/null +++ b/zForceWorkshopJobsNow.lua @@ -0,0 +1,157 @@ +--@ module=true + +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') +local gui = require('gui') + +local ForceJobs = {} + +function ForceJobs.prioritize_all_jobs() + local count, changed = 0, 0 + for _, bld in ipairs(df.global.world.buildings.other.IN_PLAY) do + local t = bld:getType() + if t == df.building_type.Workshop or t == df.building_type.Furnace then + count = count + 1 + for _, job in ipairs(bld.jobs or {}) do + if job and not job.flags.do_now then + job.flags.do_now = true + changed = changed + 1 + end + end + end + end + dfhack.println(('ForceJobsNow: Buildings scanned: %d | Jobs changed: %d'):format(count, changed)) +end + +function ForceJobs.disable_all_jobs() + local count, changed = 0, 0 + for _, bld in ipairs(df.global.world.buildings.other.IN_PLAY) do + local t = bld:getType() + if t == df.building_type.Workshop or t == df.building_type.Furnace then + count = count + 1 + for _, job in ipairs(bld.jobs or {}) do + if job and job.flags.do_now then + job.flags.do_now = false + changed = changed + 1 + end + end + end + end + dfhack.println(('ForceJobsNow: Buildings scanned: %d | Jobs disabled: %d'):format(count, changed)) +end + +local ForceJobsOverlay = defclass(ForceJobsOverlay, overlay.OverlayWidget) +ForceJobsOverlay.ATTRS { + desc = 'Force-start jobs in workshops/furnaces.', + viewscreens = { + + 'dwarfmode/ViewSheets/BUILDING/Workshop/Masons/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Masons/Items', + 'dwarfmode/ViewSheets/BUILDING/Furnace/Smelter/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Furnace/Smelter/Items', + 'dwarfmode/ViewSheets/BUILDING/Furnace/WoodFurnace/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Furnace/WoodFurnace/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Bowyers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Bowyers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Mechanics/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Mechanics/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Jewelers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Jewelers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Ashery/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Ashery/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/SOAP_MAKER/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Siege/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Siege/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Loom/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Loom/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Clothiers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Clothiers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Dyers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Dyers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Leatherworks/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Leatherworks/Items', + 'dwarfmode/ViewSheets/BUILDING/Furnace/Kiln/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Furnace/Kiln/Items', + 'dwarfmode/ViewSheets/BUILDING/Furnace/GlassFurnace/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Furnace/GlassFurnace/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Carpenters/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Carpenters/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/MetalsmithsForge/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/MetalsmithsForge/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Still/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Still/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Farmers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Farmers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Butchers/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Butchers/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Kitchen/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Kitchen/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Fishery/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Fishery/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Tanners/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Tanners/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/SCREW_PRESS/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/Items', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Quern/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Quern/Items', + }, + default_enabled = true, + default_pos = {x = -41, y = 9}, + frame = {w = 18, h = 3, transparent = true}, +} + +function ForceJobsOverlay:init() + self.toggle_state = true -- default to ON + + self:addviews{ + widgets.Panel{ + frame = {b = 0, r = 0, w = 40, h = 5}, + frame_background = gui.CLEAR_PEN, + subviews = { + widgets.Label{ + frame = {l = 1, t = 0}, + text = 'Prioritize All:', + }, + widgets.HotkeyLabel{ + frame = {l = 1, t = 2}, + label = 'ON', + key = 'CUSTOM_O', + auto_width = true, + on_activate = function() + self.toggle_state = true + ForceJobs.prioritize_all_jobs() + end, + }, + widgets.HotkeyLabel{ + frame = {l = 9, t = 2}, + label = 'OFF', + key = 'CUSTOM_F', + auto_width = true, + on_activate = function() + self.toggle_state = false + ForceJobs.disable_all_jobs() + end, + }, + }, + }, + } +end + +OVERLAY_WIDGETS = { + force_jobs_overlay = ForceJobsOverlay, +} + +-- Run manually from DFHack console +if not dfhack_flags.module then + local cmd = ... + if cmd == nil or cmd:upper() == 'ON' then + ForceJobs.prioritize_all_jobs() + elseif cmd:upper() == 'OFF' then + ForceJobs.disable_all_jobs() + else + qerror("Usage: zForceWorkshopJobsNow [ON|OFF]") + end +end From eb51287459453ca73fb15fac3e8a89e06adab4a2 Mon Sep 17 00:00:00 2001 From: unboundlopez Date: Sat, 31 May 2025 17:39:41 -0500 Subject: [PATCH 3/8] Add zAutowheelbarrow script and documentation --- docs/zAutowheelbarrow.rst | 24 ++++++++++ zAutowheelbarrow.lua | 92 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 docs/zAutowheelbarrow.rst create mode 100644 zAutowheelbarrow.lua diff --git a/docs/zAutowheelbarrow.rst b/docs/zAutowheelbarrow.rst new file mode 100644 index 000000000..1e8ee6d28 --- /dev/null +++ b/docs/zAutowheelbarrow.rst @@ -0,0 +1,24 @@ +zAutowheelbarrow +================ + +Automatically manages wheelbarrow assignments for your fortress stockpiles. + +This script evaluates all stockpiles and assigns wheelbarrows to those that +would benefit the most (e.g., stone, furniture, or corpse stockpiles) based on +their size. It also clears stale or invalid wheelbarrow assignments. + +Overview +-------- + +This script performs the following tasks: + +1. **Scans all stockpiles** in the fortress. +2. **Calculates desired wheelbarrows** for each applicable stockpile (one per 3 tiles). +3. **Assigns wheelbarrows** only to stone, furniture, or corpse stockpiles. +4. **Clears existing wheelbarrow assignments** if they are no longer valid. +5. **Provides a summary** of total stockpiles, total wheelbarrows, and how many more are needed or excess. + +Usage +----- + +Run the script from the DFHack console: \ No newline at end of file diff --git a/zAutowheelbarrow.lua b/zAutowheelbarrow.lua new file mode 100644 index 000000000..52dbee955 --- /dev/null +++ b/zAutowheelbarrow.lua @@ -0,0 +1,92 @@ +local stockpiles = df.global.world.buildings.other.STOCKPILE +local tool_items = df.global.world.items.other.TOOL + +local function get_total_stockpiles() + local count = 0 + for _ in ipairs(stockpiles) do + count = count + 1 + end + return count +end + +local function get_total_wheelbarrows() + local count = 0 + for _, item in ipairs(tool_items) do + if df.item_toolst:is_instance(item) and item.subtype and item.subtype.id == "ITEM_TOOL_WHEELBARROW" then + count = count + 1 + end + end + return count +end + +local function log_summary(total_stockpiles, total_wheelbarrows, needed_wheelbarrows) + print(string.format("Total stockpiles in fortress: %d", total_stockpiles)) + print(string.format("Total wheelbarrows in fortress: %d", total_wheelbarrows)) + + if needed_wheelbarrows > total_wheelbarrows then + print(string.format("You need to craft %d more wheelbarrows.", needed_wheelbarrows - total_wheelbarrows)) + elseif needed_wheelbarrows < total_wheelbarrows then + print(string.format("You have %d excess wheelbarrows.", total_wheelbarrows - needed_wheelbarrows)) + else + print("You have exactly the number of wheelbarrows needed.") + end +end + +local function assign_wheelbarrows_to_stockpiles() + local total_needed = 0 + + print("\n--- Stockpiles Assigned Wheelbarrows ---") + for _, bld in ipairs(stockpiles) do + local width = bld.x2 - bld.x1 + 1 + local height = bld.y2 - bld.y1 + 1 + local area = width * height + local desired_wheelbarrows = math.floor(area / 3) + + local flags = bld.settings and bld.settings.flags + if flags and (flags.stone or flags.furniture or flags.corpses) then + bld.storage.max_wheelbarrows = desired_wheelbarrows + total_needed = total_needed + desired_wheelbarrows + print(string.format("Stockpile #%d: size %d x %d = %d tiles, needs %d wheelbarrows", bld.id, width, height, area, desired_wheelbarrows)) + end + end + + local skipped_count = 0 + for _, bld in ipairs(stockpiles) do + local flags = bld.settings and bld.settings.flags + if not (flags and (flags.stone or flags.furniture or flags.corpses)) then + bld.storage.max_wheelbarrows = 0 + skipped_count = skipped_count + 1 + end + end + print("\n--- Skipped Stockpiles ---") + print(string.format("Skipped %d stockpiles (not stone, furniture, or corpses) \n", skipped_count)) + + return total_needed +end + +local function clear_wheelbarrow_assignments() + for _, item in ipairs(tool_items) do + if df.item_toolst:is_instance(item) + and item.subtype + and item.subtype.id == "ITEM_TOOL_WHEELBARROW" then + + if #item.specific_refs > 0 or #item.general_refs > 0 then + print(string.format("Wheelbarrow ID %d in use, skipping", item.id)) + elseif item.stockpile then + item.stockpile.id = -1 + item.stockpile.x = -30000 + item.stockpile.y = -30000 + end + end + end +end + +local total_stockpiles = get_total_stockpiles() +local total_wheelbarrows = get_total_wheelbarrows() +local needed_wheelbarrows = assign_wheelbarrows_to_stockpiles() + +log_summary(total_stockpiles, total_wheelbarrows, needed_wheelbarrows) + +print("\n> zAutowheelbarrow\n") + +clear_wheelbarrow_assignments() \ No newline at end of file From b7f74142e91b6b31377cb9ab18d72e7049b3def3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 22:45:32 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/zAutowheelbarrow.rst | 2 +- zAutowheelbarrow.lua | 2 +- zUniform.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zAutowheelbarrow.rst b/docs/zAutowheelbarrow.rst index 1e8ee6d28..a2f955acc 100644 --- a/docs/zAutowheelbarrow.rst +++ b/docs/zAutowheelbarrow.rst @@ -21,4 +21,4 @@ This script performs the following tasks: Usage ----- -Run the script from the DFHack console: \ No newline at end of file +Run the script from the DFHack console: diff --git a/zAutowheelbarrow.lua b/zAutowheelbarrow.lua index 52dbee955..5cef67945 100644 --- a/zAutowheelbarrow.lua +++ b/zAutowheelbarrow.lua @@ -89,4 +89,4 @@ log_summary(total_stockpiles, total_wheelbarrows, needed_wheelbarrows) print("\n> zAutowheelbarrow\n") -clear_wheelbarrow_assignments() \ No newline at end of file +clear_wheelbarrow_assignments() diff --git a/zUniform.lua b/zUniform.lua index bb21d85b2..148b389be 100644 --- a/zUniform.lua +++ b/zUniform.lua @@ -246,4 +246,4 @@ end OVERLAY_WIDGETS = { uniform_overlay = UniformOverlay, -} \ No newline at end of file +} From 593fc2624334508b937bf9cc6e3728f322d03c1e Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:07:48 -0500 Subject: [PATCH 5/8] Delete docs/zForceWorkshopJobsNow.rst --- docs/zForceWorkshopJobsNow.rst | 42 ---------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 docs/zForceWorkshopJobsNow.rst diff --git a/docs/zForceWorkshopJobsNow.rst b/docs/zForceWorkshopJobsNow.rst deleted file mode 100644 index 1d64fd455..000000000 --- a/docs/zForceWorkshopJobsNow.rst +++ /dev/null @@ -1,42 +0,0 @@ -zForceWorkshopJobsNow -===================== - -A DFHack plugin that force-starts or unforces all jobs in workshops and furnaces across the fortress. - -Overview --------- - -This plugin provides both an in-game overlay and a command-line interface to control whether queued jobs in workshops and furnaces should be executed immediately (`do_now = true`) or not (`do_now = false`). It is useful for players who want more control over job execution prioritization. - -Features --------- - -- Force all current jobs in all workshops and furnaces to start immediately. -- Toggle job forcing directly from the workshop task viewscreen using a small overlay panel. -- Simple hotkeys: ``o`` (ON) and ``f`` (OFF). -- Compatible with most standard and custom workshops and furnaces. - -Usage ------ - -**In-Game Overlay** - -Navigate to any workshop or furnace job screen. A small UI will appear with: - -:: - - Prioritize All: - ON OFF - -- Press ``o`` to force all jobs. -- Press ``f`` to disable forced jobs. - -**Console Command** - -:: - - zForceWorkshopJobsNow [ON|OFF] - -- Running with no argument is equivalent to ``ON``. -- ``ON``: Set ``do_now = true`` for all applicable jobs. -- ``OFF``: Set ``do_now = false`` for all applicable jobs. From 800d3809d01280629af182049cbc5daf64b1feb8 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:08:15 -0500 Subject: [PATCH 6/8] Delete docs/zUniform.rst --- docs/zUniform.rst | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 docs/zUniform.rst diff --git a/docs/zUniform.rst b/docs/zUniform.rst deleted file mode 100644 index 6aa17429c..000000000 --- a/docs/zUniform.rst +++ /dev/null @@ -1,23 +0,0 @@ -squad-uniform -============= - -An overlay UI that allows importing and exporting squad uniforms. - -Usage ------ - -To use this overlay: - -1. Press `q` to open the squad sidebar. -2. Select a squad by clicking its checkbox. -3. Click the `Equip` button. -4. Either: - - Press `Add uniform` to create a new one, **or** - - Click a unit’s `Details` button to customize their equipment. -5. The `[Import]` and `[Export]` buttons will now appear in the **bottom-right** corner of the screen. - - You can also use the hotkeys: - - `Ctrl+I` to import a uniform - - `Ctrl+E` to export the current uniform - -Uniforms are saved to and loaded from the following folder: -Dwarf Fortress\dfhack-config\squad_uniform From a7771f86c69710dc513795a85adb33b8893fd876 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:08:29 -0500 Subject: [PATCH 7/8] Delete zUniform.lua --- zUniform.lua | 249 --------------------------------------------------- 1 file changed, 249 deletions(-) delete mode 100644 zUniform.lua diff --git a/zUniform.lua b/zUniform.lua deleted file mode 100644 index 148b389be..000000000 --- a/zUniform.lua +++ /dev/null @@ -1,249 +0,0 @@ ---@ module=true - -local gui = require('gui') -local widgets = require('gui.widgets') -local overlay = require('plugins.overlay') -local dialogs = require('gui.dialogs') -local json = require('json') - -local UNIFORM_DIR = dfhack.getDFPath() .. '/dfhack-config/squad_uniform/' - -local function ensure_uniform_dir() - if not dfhack.filesystem.isdir(UNIFORM_DIR) then - dfhack.filesystem.mkdir(UNIFORM_DIR) - end -end - -local function is_valid_name(name) - return name and #name > 0 and not name:find('[^%w%._%s]') -end - -local function get_uniform_files() - ensure_uniform_dir() - local files = {} - local list = dfhack.filesystem.listdir(UNIFORM_DIR) - if list then - for _, file in ipairs(list) do - if file:match('%.dfuniform$') then - table.insert(files, file) - end - end - table.sort(files) - end - return files -end - -local function import_uniform_file(filepath) - ensure_uniform_dir() - local file, err = io.open(filepath, 'r') - if not file then - return false, 'Failed to open file for reading: ' .. tostring(err) - end - - local text = file:read('*a') - file:close() - - local ok, data = pcall(json.decode, text) - if not ok or type(data) ~= 'table' then - return false, 'Failed to decode uniform file or invalid format.' - end - - local uniform_data = data.uniform - if type(uniform_data) ~= 'table' then - return false, 'Uniform data is missing or invalid.' - end - - local nickname = data.nickname - if not nickname or nickname == '' then - nickname = filepath:match('([^/\\]+)%.dfuniform$') or 'ImportedUniform' - end - - local panel = df.global.game.main_interface and df.global.game.main_interface.squad_equipment - if not panel then - return false, 'Squad equipment panel is not available. Please open the Military > Equipment screen.' - end - - local n = #uniform_data - panel.cs_cat:resize(n) - panel.cs_it_spec_item_id:resize(n) - panel.cs_it_type:resize(n) - panel.cs_it_subtype:resize(n) - panel.cs_civ_mat:resize(n) - panel.cs_spec_mat:resize(n) - panel.cs_spec_matg:resize(n) - panel.cs_color_pattern_index:resize(n) - panel.cs_icp_flag:resize(n) - panel.cs_assigned_item_number:resize(n) - panel.cs_assigned_item_id:resize(n) - - panel.open = true - panel.customizing_equipment = true - panel.customizing_squad_entering_uniform_nickname = true - panel.customizing_squad_uniform_nickname = nickname - - for i, slot in ipairs(uniform_data) do - local idx = i - 1 - panel.cs_cat[idx] = slot.cat or -1 - panel.cs_it_spec_item_id[idx] = slot.spec_item_id or -1 - panel.cs_it_type[idx] = slot.it_type or -1 - panel.cs_it_subtype[idx] = slot.it_subtype or -1 - panel.cs_civ_mat[idx] = slot.civ_mat or -1 - panel.cs_spec_mat[idx] = slot.spec_mat or -1 - panel.cs_spec_matg[idx] = slot.spec_matg or -1 - panel.cs_color_pattern_index[idx] = slot.color_pattern_index or -1 - panel.cs_icp_flag[idx] = slot.icp_flag or 0 - panel.cs_assigned_item_number[idx] = slot.assigned_item_number or -1 - panel.cs_assigned_item_id[idx] = slot.assigned_item_id or -1 - end - - panel.cs_uniform_flag = data.uniform_flag or 2 - - return true, 'Uniform successfully imported!' -end - -local function export_uniform_file(filepath) - ensure_uniform_dir() - local panel = df.global.game.main_interface and df.global.game.main_interface.squad_equipment - if not panel then - return false, 'Squad equipment panel is not available. Please open the Military > Equipment screen.' - end - - local n = #panel.cs_cat - local uniform_data = {} - for i = 0, n - 1 do - table.insert(uniform_data, { - cat = panel.cs_cat[i], - spec_item_id = panel.cs_it_spec_item_id[i], - it_type = panel.cs_it_type[i], - it_subtype = panel.cs_it_subtype[i], - civ_mat = panel.cs_civ_mat[i], - spec_mat = panel.cs_spec_mat[i], - spec_matg = panel.cs_spec_matg[i], - color_pattern_index = panel.cs_color_pattern_index[i], - icp_flag = panel.cs_icp_flag[i], - assigned_item_number = panel.cs_assigned_item_number[i], - assigned_item_id = panel.cs_assigned_item_id[i], - }) - end - - local nickname = panel.customizing_squad_uniform_nickname or '' - local uniform_flag = panel.cs_uniform_flag or 2 - - local file, err = io.open(filepath, 'w') - if not file then return false, 'Failed to open file for writing: ' .. tostring(err) end - file:write(json.encode({ - nickname = nickname, - uniform = uniform_data, - uniform_flag = uniform_flag - })) - file:close() - return true, 'Uniform saved to ' .. filepath -end - -local function ExportUniformDialog() - dialogs.InputBox{ - frame_title = 'Export Squad Uniform', - text = 'Enter file name (no extension):', - on_input = function(name) - if not is_valid_name(name) then - dialogs.showMessage("Invalid Name", "Name can only contain letters, numbers, underscores, periods, and spaces.") - return - end - local fname = UNIFORM_DIR .. name .. '.dfuniform' - local ok, msg = export_uniform_file(fname) - if ok then - dfhack.println('Exported to: ' .. fname) - else - dfhack.printerr(msg) - end - end - }:show() -end - -local function get_uniform_choices() - local files = get_uniform_files() - local choices = {} - for _, f in ipairs(files) do - table.insert(choices, {text = f}) - end - return choices -end - -local function ImportUniformDialog() - ensure_uniform_dir() - local dlg - local function get_dlg() return dlg end - - dlg = dialogs.ListBox{ - frame_title = 'Import/Delete Squad Uniform', - with_filter = true, - choices = get_uniform_choices(), - on_select = function(_, choice) - dfhack.timeout(2, 'frames', function() - local fname = UNIFORM_DIR .. choice.text - local ok, msg = import_uniform_file(fname) - if ok then - dfhack.println('Imported from: ' .. fname) - else - dfhack.printerr(msg) - end - end) - end, - dismiss_on_select2 = false, - on_select2 = function(_, choice) - local fname = UNIFORM_DIR .. choice.text - if not dfhack.filesystem.isfile(fname) then return end - - dialogs.showYesNoPrompt('Delete uniform file?', - 'Are you sure you want to delete "' .. fname .. '"?', nil, - function() - os.remove(fname) - dfhack.println('Deleted: ' .. fname) - local list = get_dlg().subviews.list - local filter = list:getFilter() - list:setChoices(get_uniform_choices(), list:getSelected()) - list:setFilter(filter) - end) - end, - select2_hint = 'Delete file', - }:show() -end - -local UniformOverlay = defclass(UniformOverlay, overlay.OverlayWidget) -UniformOverlay.ATTRS{ - desc = 'Manage squad uniforms.', - viewscreens = 'dwarfmode/Squads/Equipment/Customizing/Default', - default_enabled = true, - default_pos = {x = -33, y = -5}, - frame = {w = 40, h = 3}, -} - -function UniformOverlay:init() - self:addviews{ - widgets.Panel{ - frame = {t = 0, l = 0, w = 40, h = 3}, - frame_style = gui.MEDIUM_FRAME, - frame_background = gui.CLEAR_PEN, - subviews = { - widgets.HotkeyLabel{ - frame = {l = 0, t = 0}, - label = '[Import]', - key = 'CUSTOM_CTRL_I', - auto_width = true, - on_activate = ImportUniformDialog, - }, - widgets.HotkeyLabel{ - frame = {l = 20, t = 0}, - label = '[Export]', - key = 'CUSTOM_CTRL_E', - auto_width = true, - on_activate = ExportUniformDialog, - }, - }, - }, - } -end - -OVERLAY_WIDGETS = { - uniform_overlay = UniformOverlay, -} From a8cc710a5b3feeeeb53db39d4feae9ce283ed979 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:08:46 -0500 Subject: [PATCH 8/8] Delete zForceWorkshopJobsNow.lua --- zForceWorkshopJobsNow.lua | 157 -------------------------------------- 1 file changed, 157 deletions(-) delete mode 100644 zForceWorkshopJobsNow.lua diff --git a/zForceWorkshopJobsNow.lua b/zForceWorkshopJobsNow.lua deleted file mode 100644 index 9ef19fd7e..000000000 --- a/zForceWorkshopJobsNow.lua +++ /dev/null @@ -1,157 +0,0 @@ ---@ module=true - -local overlay = require('plugins.overlay') -local widgets = require('gui.widgets') -local gui = require('gui') - -local ForceJobs = {} - -function ForceJobs.prioritize_all_jobs() - local count, changed = 0, 0 - for _, bld in ipairs(df.global.world.buildings.other.IN_PLAY) do - local t = bld:getType() - if t == df.building_type.Workshop or t == df.building_type.Furnace then - count = count + 1 - for _, job in ipairs(bld.jobs or {}) do - if job and not job.flags.do_now then - job.flags.do_now = true - changed = changed + 1 - end - end - end - end - dfhack.println(('ForceJobsNow: Buildings scanned: %d | Jobs changed: %d'):format(count, changed)) -end - -function ForceJobs.disable_all_jobs() - local count, changed = 0, 0 - for _, bld in ipairs(df.global.world.buildings.other.IN_PLAY) do - local t = bld:getType() - if t == df.building_type.Workshop or t == df.building_type.Furnace then - count = count + 1 - for _, job in ipairs(bld.jobs or {}) do - if job and job.flags.do_now then - job.flags.do_now = false - changed = changed + 1 - end - end - end - end - dfhack.println(('ForceJobsNow: Buildings scanned: %d | Jobs disabled: %d'):format(count, changed)) -end - -local ForceJobsOverlay = defclass(ForceJobsOverlay, overlay.OverlayWidget) -ForceJobsOverlay.ATTRS { - desc = 'Force-start jobs in workshops/furnaces.', - viewscreens = { - - 'dwarfmode/ViewSheets/BUILDING/Workshop/Masons/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Masons/Items', - 'dwarfmode/ViewSheets/BUILDING/Furnace/Smelter/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Furnace/Smelter/Items', - 'dwarfmode/ViewSheets/BUILDING/Furnace/WoodFurnace/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Furnace/WoodFurnace/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Bowyers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Bowyers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Mechanics/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Mechanics/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Jewelers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Jewelers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Ashery/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Ashery/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/SOAP_MAKER/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Siege/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Siege/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Loom/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Loom/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Clothiers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Clothiers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Dyers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Dyers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Leatherworks/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Leatherworks/Items', - 'dwarfmode/ViewSheets/BUILDING/Furnace/Kiln/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Furnace/Kiln/Items', - 'dwarfmode/ViewSheets/BUILDING/Furnace/GlassFurnace/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Furnace/GlassFurnace/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Carpenters/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Carpenters/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/MetalsmithsForge/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/MetalsmithsForge/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Still/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Still/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Farmers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Farmers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Butchers/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Butchers/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Kitchen/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Kitchen/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Fishery/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Fishery/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Tanners/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Tanners/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/SCREW_PRESS/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Custom/Items', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Quern/Tasks', - 'dwarfmode/ViewSheets/BUILDING/Workshop/Quern/Items', - }, - default_enabled = true, - default_pos = {x = -41, y = 9}, - frame = {w = 18, h = 3, transparent = true}, -} - -function ForceJobsOverlay:init() - self.toggle_state = true -- default to ON - - self:addviews{ - widgets.Panel{ - frame = {b = 0, r = 0, w = 40, h = 5}, - frame_background = gui.CLEAR_PEN, - subviews = { - widgets.Label{ - frame = {l = 1, t = 0}, - text = 'Prioritize All:', - }, - widgets.HotkeyLabel{ - frame = {l = 1, t = 2}, - label = 'ON', - key = 'CUSTOM_O', - auto_width = true, - on_activate = function() - self.toggle_state = true - ForceJobs.prioritize_all_jobs() - end, - }, - widgets.HotkeyLabel{ - frame = {l = 9, t = 2}, - label = 'OFF', - key = 'CUSTOM_F', - auto_width = true, - on_activate = function() - self.toggle_state = false - ForceJobs.disable_all_jobs() - end, - }, - }, - }, - } -end - -OVERLAY_WIDGETS = { - force_jobs_overlay = ForceJobsOverlay, -} - --- Run manually from DFHack console -if not dfhack_flags.module then - local cmd = ... - if cmd == nil or cmd:upper() == 'ON' then - ForceJobs.prioritize_all_jobs() - elseif cmd:upper() == 'OFF' then - ForceJobs.disable_all_jobs() - else - qerror("Usage: zForceWorkshopJobsNow [ON|OFF]") - end -end