From 5d806e28a3591a7e0d603c2918f16eb3f6e6c744 Mon Sep 17 00:00:00 2001 From: unboundlopez Date: Fri, 30 May 2025 18:27:14 -0500 Subject: [PATCH 1/7] 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/7] 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 0928f29d27222073c07de6c6b7c4cf6f9e1b9c8c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 23:33:38 +0000 Subject: [PATCH 3/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- zUniform.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20c5249563b41294481905dc133caa84ddebcad0 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:21:17 -0500 Subject: [PATCH 4/7] 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 67b4233bfead706179059e6b3dd78b40c0119ac9 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:21:40 -0500 Subject: [PATCH 5/7] 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 68f0b2a835acaa2af9e4eea8a11adfe3603a368e Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:34:26 -0500 Subject: [PATCH 6/7] Update zForceWorkshopJobsNow.rst Changed the hotkeys as the user may want to use the o or i key when typing. For example when user is choosing a material --- docs/zForceWorkshopJobsNow.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zForceWorkshopJobsNow.rst b/docs/zForceWorkshopJobsNow.rst index 1d64fd455..1fbdcf6e4 100644 --- a/docs/zForceWorkshopJobsNow.rst +++ b/docs/zForceWorkshopJobsNow.rst @@ -28,8 +28,8 @@ 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. +- Press ``ctrl+o`` to prioritize all jobs on all stations. +- Press ``ctrl+i`` to unprioritize all jobs on all stations. **Console Command** From 0acc961417058e8cd17208ef838c74187481570b Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 4 Jun 2025 09:35:45 -0500 Subject: [PATCH 7/7] Update zForceWorkshopJobsNow.lua updated hotkey as the user may want to use o or f keys when choosing a material or when using the workstation work order option. --- zForceWorkshopJobsNow.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zForceWorkshopJobsNow.lua b/zForceWorkshopJobsNow.lua index 9ef19fd7e..59ea730ad 100644 --- a/zForceWorkshopJobsNow.lua +++ b/zForceWorkshopJobsNow.lua @@ -99,8 +99,8 @@ ForceJobsOverlay.ATTRS { 'dwarfmode/ViewSheets/BUILDING/Workshop/Quern/Items', }, default_enabled = true, - default_pos = {x = -41, y = 9}, - frame = {w = 18, h = 3, transparent = true}, + default_pos = {x = -39, y = 9}, + frame = {w = 22, h = 3, transparent = true}, } function ForceJobsOverlay:init() @@ -112,13 +112,13 @@ function ForceJobsOverlay:init() frame_background = gui.CLEAR_PEN, subviews = { widgets.Label{ - frame = {l = 1, t = 0}, + frame = {l = 0, t = 0}, text = 'Prioritize All:', }, widgets.HotkeyLabel{ - frame = {l = 1, t = 2}, + frame = {l = 0, t = 2}, label = 'ON', - key = 'CUSTOM_O', + key = 'CUSTOM_CTRL_O', auto_width = true, on_activate = function() self.toggle_state = true @@ -126,9 +126,9 @@ function ForceJobsOverlay:init() end, }, widgets.HotkeyLabel{ - frame = {l = 9, t = 2}, + frame = {l = 11, t = 2}, label = 'OFF', - key = 'CUSTOM_F', + key = 'CUSTOM_CTRL_I', auto_width = true, on_activate = function() self.toggle_state = false