From dad2cdfe973bb1cd78330a950808b4b8cef640f3 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:35:22 +0200 Subject: [PATCH 01/13] new tool: idle-crafting, allowed dwarfs to automatically satisfy their crafting needs --- changelog.txt | 2 + docs/idle-crafting.rst | 35 +++ idle-crafting.lua | 495 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 532 insertions(+) create mode 100644 docs/idle-crafting.rst create mode 100644 idle-crafting.lua diff --git a/changelog.txt b/changelog.txt index 9eb3c0b63..435d17ee1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -28,6 +28,8 @@ Template for new versions: ## New Tools +- `idle-crafting`: Allow dwarfs to automatically satisfy their need to craft objects. + ## New Features ## Fixes diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst new file mode 100644 index 000000000..3060e2277 --- /dev/null +++ b/docs/idle-crafting.rst @@ -0,0 +1,35 @@ +idle-crafting +============= + +.. dfhack-tool:: + :summary: Allow dwarfs to automatically satisfy their need to craft objects. + :tags: fort gameplay + +This script allows dwarves to automatically satisfy their crafting needs. The +script is configured through an overlay that is added to the main page of +craftsdwarf's workshops, as described below. + +Usage +----- + +:: + + idle-crafting status + +Overlay +------- + +This script provides an overlay to the "Tasks" tab of craftsdwarf's workshops, +allowing you to designate that workshop for use by idle dwarfs to satisfy their +needs to craft objects. Workshops that have a master assigned cannot be used in +this way. + +When a workshop is designated for idle crafting, this tool will create crafting +jobs and assigns them to idle dwarfs who have a need for crafting +objects. Currently, bone carving and stone crafting are supported, with bone +carving being the preferred option. This script respects linked stockpiles and +the setting for permitted general work orders from the "Workers" tab. Thus, to +designate a workshop for stone crafting only, simply disable the bone carving +labor on that tab. + + diff --git a/idle-crafting.lua b/idle-crafting.lua new file mode 100644 index 000000000..69554df59 --- /dev/null +++ b/idle-crafting.lua @@ -0,0 +1,495 @@ +--@module = true +--@enable = true + +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') +local repeatutil = require("repeat-util") + +---create a new linked job +---@return df.job +function make_job() + local job = df.job:new() + dfhack.job.linkIntoWorld(job, true) + return job +end + +---3D city metric +---@param p1 df.coord +---@param p2 df.coord +---@return number +function distance(p1, p2) + return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y) + math.abs(p1.z - p2.z) +end + +local function passesScreen(item) + return not item.flags.in_job and not item.flags.forbid +end + +---find closest item in an item vector +---@generic T : df.item +---@param pos df.coord +---@param item_vector T[] +---@param is_good? fun(item: T): boolean +---@return T? +local function findClosest(pos, item_vector, is_good) + local closest = nil + local dclosest = -1 + for _, item in ipairs(item_vector) do + if passesScreen(item) and (not is_good or is_good(item)) then + local x, y, z = dfhack.items.getPosition(item) + local ditem = distance(pos, xyz2pos(x, y, z)) + if not closest or ditem < dclosest then + closest = item + dclosest = ditem + end + end + end + return closest +end + +---find item inside workshop or linked stockpile +---@generic T : df.item +---@param workshop df.building_workshopst +---@param is_good fun(item: T): boolean +---@return T? +function findLinked(workshop, is_good) + local res = nil + -- look inside the workshop first + for _, contained_item in ipairs(workshop.contained_items) do + if contained_item.use_mode == 0 and passesScreen(contained_item.item) and is_good(contained_item.item) then + res = contained_item.item + -- print('attaching item from inside the workshop') + goto done + end + end + -- then look through the linked stockpiles + for _, stockpile in ipairs(workshop.profile.links.take_from_pile) do + for _, item in ipairs(dfhack.buildings.getStockpileContents(stockpile)) do + if passesScreen(item) and is_good(item) then + res = item + -- print('attaching item from linked stockpile') + goto done + end + end + end + ::done:: + return res +end + +---make bone crafts at specified workshop +---@param unit df.unit +---@param workshop df.building_workshopst +---@return boolean +local function makeBoneCraft(unit, workshop) + local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) + local function is_bone(item) + if df.item_corpsepiecest:is_instance(item) then + return item.corpse_flags.bone and not item.flags.dead_dwarf + else + return false + end + end + local bone = nil + if #workshop.profile.links.take_from_pile > 0 then + bone = findLinked(workshop, is_bone) + else + bone = findClosest(workshop_position, df.global.world.items.other.ANY_REFUSE, is_bone) + end + + if not bone then + print('no bones found') + return false + end + local job = make_job() + job.job_type = df.job_type.MakeCrafts + job.mat_type = -1 + job.material_category.bone = true + job.pos = workshop_position + + local jitem = df.job_item:new() + jitem.item_type = df.item_type.NONE + jitem.mat_type = -1 + jitem.mat_index = -1 + jitem.quantity = 1 + jitem.vector_id = df.job_item_vector_id.ANY_REFUSE + jitem.flags1.unrotten = true + jitem.flags2.bone = true + jitem.flags2.body_part = true + job.job_items.elements:insert('#', jitem) + + dfhack.job.addGeneralRef(job, df.general_ref_type.BUILDING_HOLDER, workshop.id) + if not dfhack.job.attachJobItem(job, bone, df.job_item_ref.T_role.Reagent, 0, -1) then + dfhack.printerr('could not attach bones') + return false + end + workshop.jobs:insert("#", job) + return dfhack.job.addWorker(job, unit) +end + +---make rock crafts at specified workshop +---@param unit df.unit +---@param workshop df.building_workshopst +---@return false +local function makeRockCraft(unit, workshop) + local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) + + + local boulder = findClosest(workshop_position, df.global.world.items.other.BOULDER) + if not boulder then + print('no boulder found') + return false + end + local job = make_job() + job.job_type = df.job_type.MakeCrafts + job.mat_type = 0 + job.pos = workshop_position + + local jitem = df.job_item:new() + jitem.item_type = df.item_type.BOULDER + jitem.mat_type = 0 + jitem.mat_index = -1 + jitem.quantity = 1 + jitem.vector_id = df.job_item_vector_id.BOULDER + jitem.flags2.non_economic = true + jitem.flags3.hard = true + job.job_items.elements:insert('#', jitem) + + dfhack.job.addGeneralRef(job, df.general_ref_type.BUILDING_HOLDER, workshop.id) + if not dfhack.job.attachJobItem(job, boulder, df.job_item_ref.T_role.Reagent, 0, -1) then + dfhack.printerr('could not attach boulder') + return false + end + workshop.jobs:insert("#", job) + return dfhack.job.addWorker(job, unit) +end + +-- script logic + +local GLOBAL_KEY = 'idle-crafting' + +enabled = enabled or false +function isEnabled() + return enabled +end + +---IDs of workshops where idle crafting is permitted +---@type table +allowed = allowed or {} + +---IDs of watched units in need of crafting items +---@type table[] +watched = watched or {} + +---priority thresholds for crafting needs +---@type integer[] +thresholds = thresholds or { 10000, 1000, 500 } + +local function persist_state() + dfhack.persistent.saveSiteData(GLOBAL_KEY, { + enabled = enabled, + allowed = allowed, + thresholds = thresholds + }) +end + +--- Load the saved state of the script +local function load_state() + -- load persistent data + local persisted_data = dfhack.persistent.getSiteData(GLOBAL_KEY, {}) + enabled = persisted_data.enabled or false + allowed = persisted_data.allowed or {} + thresholds = persisted_data.thresholds or { 10000, 1000, 500 } +end + +CraftObject = df.need_type['CraftObject'] + +---negative crafting focus penalty +---@param unit df.unit +---@return number +local function getCraftingNeed(unit) + local needs = unit.status.current_soul.personality.needs + for _, need in ipairs(needs) do + if need.id == CraftObject then + return -need.focus_level + end + end + return 0 +end + +local function stop() + enabled = false + repeatutil.cancel(GLOBAL_KEY .. 'main') + repeatutil.cancel(GLOBAL_KEY .. 'unit') +end + +local function checkForWorkshop() + if not next(allowed) then + print('no available workshops, disabling') + stop() + end +end + +---retrieve workshop by id +---@param id integer +---@return df.building_workshopst|nil +local function locateWorkshop(id) + local workshop = df.building.find(id) + if df.building_workshopst:is_instance(workshop) and workshop.type == 3 then + return workshop + else + return nil + end +end + +---checks that unit can path to workshop +---@param unit df.unit +---@param workshop df.building_workshopst +---@return boolean +function canAccessWorkshop(unit, workshop) + local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) + return dfhack.maps.canWalkBetween(unit.pos, workshop_position) +end + +---unit is ready to take jobs +---@param unit df.unit +---@return boolean +local function unitIsAvailable(unit) + if unit.job.current_job then + return false + elseif #unit.social_activities > 0 then + return false + elseif #unit.individual_drills > 0 then + return false + elseif unit.military.squad_id ~= -1 then + local squad = df.squad.find(unit.military.squad_id) + -- this lookup should never fail + ---@diagnostic disable-next-line: need-check-nil + return #squad.orders == 0 and squad.activity == -1 + end + return true +end + +---check if unit is ready and try to create a crafting job for it +---@param workshop df.building_workshopst +---@param idx integer +---@param unit_id integer +---@return boolean "proceed to next workshop" +function processUnit(workshop, idx, unit_id) + local unit = df.unit.find(unit_id) + -- check that unit is still there + if not unit then + watched[idx][unit_id] = nil + return false + elseif not canAccessWorkshop(unit, workshop) then + dfhack.print('-') + return false + elseif not unitIsAvailable(unit) then + dfhack.print('.') + return false + end + -- We have an available unit + local success = false + if workshop.profile.blocked_labors[df.unit_labor['BONE_CARVE']] == false then + success = makeBoneCraft(unit, workshop) + end + if not success and workshop.profile.blocked_labors[df.unit_labor['STONE_CRAFT']] == false then + success = makeRockCraft(unit, workshop) + end + local name = (dfhack.TranslateName(dfhack.units.getVisibleName(unit))) + if success then + -- Why is the encoding still wrong, even when using df2console? + print(' assigned ' .. dfhack.df2console(name)) + watched[idx][unit_id] = nil + else + print(' failed to attach ' .. name) + end + return true +end + +local function unit_loop() + -- print('idle crafting: running unit loop') + for workshop_id, _ in pairs(allowed) do + local workshop = locateWorkshop(workshop_id) + -- workshop may have been destroyed or assigned a master + if not workshop or #workshop.profile.permitted_workers > 0 then + allowed[workshop_id] = nil --clearing during iteration is permitted + goto next_workshop + end + -- only consider workshop if not currently in use + if #workshop.jobs > 0 then + goto next_workshop + end + dfhack.print(('idle-crafting: locating crafter for %s (%d)'):format(dfhack.buildings.getName(workshop), + workshop_id)) + -- workshop is free to use, try to find a unit + for idx, _ in ipairs(thresholds) do + for unit_id, _ in pairs(watched[idx]) do + if processUnit(workshop, idx, unit_id) then + goto next_workshop + end + end + dfhack.print('/') + end + + print('no unit found') + ::next_workshop:: + end + -- disable loop if there are no more units + if not next(watched) then + repeatutil.cancel(GLOBAL_KEY .. 'unit') + end + -- disable tool if there are no more workshops + checkForWorkshop() + persist_state() +end + +local function main_loop() + print('idle crafting: running main loop') + checkForWorkshop() + if not enabled then + return + end + local num_watched = {} + local watching = false + + ---@type table[] + watched = {} + for idx, _ in ipairs(thresholds) do + watched[idx] = {} + num_watched[idx] = 0 + end + + for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do + for idx, threshold in ipairs(thresholds) do + if getCraftingNeed(unit) > threshold then + watched[idx][unit.id] = true + num_watched[idx] = num_watched[idx] + 1 + watching = true + goto continue + end + end + ::continue:: + end + print(('watching %s dwarfs with crafting needs'):format( + table.concat(num_watched, '/') + )) + + if watching then + repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY .. 'unit', 53, 'ticks', unit_loop) + end +end + +---enable main loop +---@param enable boolean|nil +local function start(enable) + enabled = enable or enabled + if enabled then + repeatutil.scheduleUnlessAlreadyScheduled(GLOBAL_KEY .. 'main', 8419, 'ticks', main_loop) + end +end + +--- Handles automatic loading +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc == SC_MAP_UNLOADED then + enabled = false + return + end + + if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then + return + end + + load_state() + start() +end + +-- +-- Overlay to select workshops +-- + + +IdleCraftingOverlay = defclass(IdleCraftingOverlay, overlay.OverlayWidget) +IdleCraftingOverlay.ATTRS { + desc = 'Adds a UI to the Workers tab too enable idle crafting.', + default_pos = { x = -42, y = 41 }, + default_enabled = true, + viewscreens = { + 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Tasks', + }, + frame = { w = 55, h = 1 }, +} + +function IdleCraftingOverlay:init() + self:addviews { + widgets.CycleHotkeyLabel { + view_id = 'leisure_toggle', + frame = { l = 0, t = 0 }, + label = 'Allow idle dwarfs to satisfy crafting needs:', + key = 'CUSTOM_L', + options = { + { label = 'yes', value = true, pen = COLOR_GREEN }, + { label = 'no', value = false }, + }, + initial_option = 'no', + on_change = self:callback('onClick'), + } + } +end + +function IdleCraftingOverlay:onClick(new, _) + local workshop = dfhack.gui.getSelectedBuilding(true) + allowed[workshop.id] = new or nil + if new and not enabled then + start(true) + end + if not next(allowed) then + stop() + end + persist_state() +end + +function IdleCraftingOverlay:onRenderBody(painter) + local workshop = dfhack.gui.getSelectedBuilding(true) + if not workshop then + return + end + persist_state() + self.subviews.leisure_toggle:setOption(allowed[workshop.id] or false) +end + +OVERLAY_WIDGETS = { + idlecrafting = IdleCraftingOverlay +} + +-- + +if dfhack_flags.module then + return +end + +local fulfillment_level = +{ 'unfettered', 'level-headed', 'untroubled', 'not distracted', 'unfocused', 'distracted', 'badly distracted' } +local fulfillment_threshold = +{ 300, 200, 100, -999, -9999, -99999, -500000 } + + +local positionals = require('argparse').processArgsGetopt({ ... }, {}) + +if not positionals or positionals[1] == 'status' then + ---@type integer[] + stats = {} + for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do + local fulfillment = -getCraftingNeed(unit) + for i = 1, 7 do + if fulfillment >= fulfillment_threshold[i] then + stats[i] = stats[i] and stats[i] + 1 or 1 + goto continue + end + end + ::continue:: + end + print('Fulfillment levels for "craft item" needs') + for k, v in pairs(stats) do + print(('%4d %s'):format(v, fulfillment_level[k])) + end +end From f9c7a845846710518b961d4a56458ff891fdeffc Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:58:15 +0200 Subject: [PATCH 02/13] fix bugs / handle enable and disable / improved status information --- docs/idle-crafting.rst | 25 ++++++++++++++++++++-- idle-crafting.lua | 47 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 3060e2277..2685172d6 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -15,6 +15,29 @@ Usage :: idle-crafting status + idle-crafting disable + disable idle-crafting + +The ``status`` command prints statistics about the status of the tool and the +satisfaction of "craft item" needs in your fort. Both variants of the +``disable`` command disallow idle crafting at all workshops and disable the +tool. + +Examples +-------- + +``idle-crafting -t 10000,1000,500 status`` + Reset all thresholds to defaults and print status information. + +Options +------- + +``-t ``, ``--thresholds `` + Sets the threshold(s) for the "craft item" need (i.e. the negated + ``focus_level``) at which the tool starts to generate crafting jobs for a + given unit. Units meeting earlier (higher) thresholds will be + prioritized. Defaults to ``10000,1000,500``. + Overlay ------- @@ -31,5 +54,3 @@ carving being the preferred option. This script respects linked stockpiles and the setting for permitted general work orders from the "Workers" tab. Thus, to designate a workshop for stone crafting only, simply disable the bone carving labor on that tab. - - diff --git a/idle-crafting.lua b/idle-crafting.lua index 69554df59..a70c96ea0 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -123,6 +123,8 @@ local function makeBoneCraft(unit, workshop) return false end workshop.jobs:insert("#", job) + job.flags.fetching = true + job.items[0].flags.is_fetching = true return dfhack.job.addWorker(job, unit) end @@ -160,6 +162,8 @@ local function makeRockCraft(unit, workshop) return false end workshop.jobs:insert("#", job) + job.flags.fetching = true + job.items[0].flags.is_fetching = true return dfhack.job.addWorker(job, unit) end @@ -417,6 +421,9 @@ IdleCraftingOverlay.ATTRS { 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Tasks', }, frame = { w = 55, h = 1 }, + visible = function () + return #df.global.game.main_interface.building.button == 0 + end } function IdleCraftingOverlay:init() @@ -453,7 +460,6 @@ function IdleCraftingOverlay:onRenderBody(painter) if not workshop then return end - persist_state() self.subviews.leisure_toggle:setOption(allowed[workshop.id] or false) end @@ -467,15 +473,38 @@ if dfhack_flags.module then return end +if dfhack_flags.enable then + if dfhack_flags.enable_state then + print('This tool is enabled by permitting idle crafting at a Craftsdarf\'s workshop') + return + else + allowed = {} + stop() + persist_state() + return + end +end + +if df.global.gamemode ~= df.game_mode.DWARF then + print('this tool requires a loaded fort') + return +end + local fulfillment_level = { 'unfettered', 'level-headed', 'untroubled', 'not distracted', 'unfocused', 'distracted', 'badly distracted' } local fulfillment_threshold = { 300, 200, 100, -999, -9999, -99999, -500000 } +local argparse = require('argparse') -local positionals = require('argparse').processArgsGetopt({ ... }, {}) +load_state() +local positionals = argparse.processArgsGetopt({ ... }, { + { 't', 'thresholds', hasArg = true, handler = function (optarg) + thresholds = argparse.numberList(optarg, 'thresholds') + end} +}) -if not positionals or positionals[1] == 'status' then +if positionals[1] == 'status' then ---@type integer[] stats = {} for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do @@ -492,4 +521,16 @@ if not positionals or positionals[1] == 'status' then for k, v in pairs(stats) do print(('%4d %s'):format(v, fulfillment_level[k])) end + local num_workshops = 0 + for _, _ in pairs(allowed) do + num_workshops = num_workshops + 1 + end + print(('Script is %s with %d workshops configured for idle crafting'): + format(enabled and 'enabled' or 'disabled', num_workshops)) + print(('The thresholds for "craft item" needs are %s'): + format(table.concat(thresholds, '/'))) +elseif positionals[1] == 'disable' then + allowed = {} + stop() end +persist_state() From bfc2d92ed0a649e262d09bdebed367d5ab421385 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:19:18 +0200 Subject: [PATCH 03/13] idle-crafting: suspend workshops after failures until next run of main_loop --- idle-crafting.lua | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index a70c96ea0..05db72395 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -97,7 +97,6 @@ local function makeBoneCraft(unit, workshop) end if not bone then - print('no bones found') return false end local job = make_job() @@ -131,14 +130,13 @@ end ---make rock crafts at specified workshop ---@param unit df.unit ---@param workshop df.building_workshopst ----@return false +---@return boolean "" local function makeRockCraft(unit, workshop) local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) local boulder = findClosest(workshop_position, df.global.world.items.other.BOULDER) if not boulder then - print('no boulder found') return false end local job = make_job() @@ -180,6 +178,10 @@ end ---@type table allowed = allowed or {} +---IDs of workshops that have encountered failures (e.g. missing materials) +---@type table +failing = failing or {} + ---IDs of watched units in need of crafting items ---@type table[] watched = watched or {} @@ -305,14 +307,19 @@ function processUnit(workshop, idx, unit_id) print(' assigned ' .. dfhack.df2console(name)) watched[idx][unit_id] = nil else - print(' failed to attach ' .. name) + print(' failed to assign ' .. dfhack.df2console(name)) + print(' disabling failing workshop until the next run of the main loop') + failing[workshop.id] = true end return true end local function unit_loop() - -- print('idle crafting: running unit loop') for workshop_id, _ in pairs(allowed) do + -- skip workshops where job creation failed (e.g. due to missing materials) + if failing[workshop_id] then + goto next_workshop + end local workshop = locateWorkshop(workshop_id) -- workshop may have been destroyed or assigned a master if not workshop or #workshop.profile.permitted_workers > 0 then @@ -353,6 +360,9 @@ local function main_loop() if not enabled then return end + -- put failing workshops back into the loop + failing = {} + local num_watched = {} local watching = false @@ -467,12 +477,19 @@ OVERLAY_WIDGETS = { idlecrafting = IdleCraftingOverlay } +-- +-- commandline interface -- if dfhack_flags.module then return end +if df.global.gamemode ~= df.game_mode.DWARF then + print('this tool requires a loaded fort') + return +end + if dfhack_flags.enable then if dfhack_flags.enable_state then print('This tool is enabled by permitting idle crafting at a Craftsdarf\'s workshop') @@ -485,11 +502,6 @@ if dfhack_flags.enable then end end -if df.global.gamemode ~= df.game_mode.DWARF then - print('this tool requires a loaded fort') - return -end - local fulfillment_level = { 'unfettered', 'level-headed', 'untroubled', 'not distracted', 'unfocused', 'distracted', 'badly distracted' } local fulfillment_threshold = @@ -499,9 +511,12 @@ local argparse = require('argparse') load_state() local positionals = argparse.processArgsGetopt({ ... }, { - { 't', 'thresholds', hasArg = true, handler = function (optarg) - thresholds = argparse.numberList(optarg, 'thresholds') - end} + { + 't', 'thresholds', hasArg = true, + handler = function(optarg) + thresholds = argparse.numberList(optarg, 'thresholds') + end + } }) if positionals[1] == 'status' then From 790c83331bd0dca89f4b1d809ffc7a74348e1792 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 11 Aug 2024 00:07:04 +0200 Subject: [PATCH 04/13] rely on DF to attach job items --- docs/idle-crafting.rst | 12 +-- idle-crafting.lua | 169 ++++++++++++----------------------------- 2 files changed, 56 insertions(+), 125 deletions(-) diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 2685172d6..8c56419b9 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -48,9 +48,9 @@ needs to craft objects. Workshops that have a master assigned cannot be used in this way. When a workshop is designated for idle crafting, this tool will create crafting -jobs and assigns them to idle dwarfs who have a need for crafting -objects. Currently, bone carving and stone crafting are supported, with bone -carving being the preferred option. This script respects linked stockpiles and -the setting for permitted general work orders from the "Workers" tab. Thus, to -designate a workshop for stone crafting only, simply disable the bone carving -labor on that tab. +jobs and assign them to idle dwarfs who have a need for crafting +objects. Currently, bone carving and stone crafting are supported, with stone +crafting being the default option. This script respects the setting for +permitted general work orders from the "Workers" tab. Thus, to designate a +workshop for bone carving, disable the stone crafting labor on for their +workshop, will keeping bone carving enabled. diff --git a/idle-crafting.lua b/idle-crafting.lua index 05db72395..f6b5312bd 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -13,97 +13,16 @@ function make_job() return job end ----3D city metric ----@param p1 df.coord ----@param p2 df.coord ----@return number -function distance(p1, p2) - return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y) + math.abs(p1.z - p2.z) -end - -local function passesScreen(item) - return not item.flags.in_job and not item.flags.forbid -end - ----find closest item in an item vector ----@generic T : df.item ----@param pos df.coord ----@param item_vector T[] ----@param is_good? fun(item: T): boolean ----@return T? -local function findClosest(pos, item_vector, is_good) - local closest = nil - local dclosest = -1 - for _, item in ipairs(item_vector) do - if passesScreen(item) and (not is_good or is_good(item)) then - local x, y, z = dfhack.items.getPosition(item) - local ditem = distance(pos, xyz2pos(x, y, z)) - if not closest or ditem < dclosest then - closest = item - dclosest = ditem - end - end - end - return closest -end - ----find item inside workshop or linked stockpile ----@generic T : df.item ----@param workshop df.building_workshopst ----@param is_good fun(item: T): boolean ----@return T? -function findLinked(workshop, is_good) - local res = nil - -- look inside the workshop first - for _, contained_item in ipairs(workshop.contained_items) do - if contained_item.use_mode == 0 and passesScreen(contained_item.item) and is_good(contained_item.item) then - res = contained_item.item - -- print('attaching item from inside the workshop') - goto done - end - end - -- then look through the linked stockpiles - for _, stockpile in ipairs(workshop.profile.links.take_from_pile) do - for _, item in ipairs(dfhack.buildings.getStockpileContents(stockpile)) do - if passesScreen(item) and is_good(item) then - res = item - -- print('attaching item from linked stockpile') - goto done - end - end - end - ::done:: - return res -end - ---make bone crafts at specified workshop ---@param unit df.unit ---@param workshop df.building_workshopst ---@return boolean -local function makeBoneCraft(unit, workshop) - local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) - local function is_bone(item) - if df.item_corpsepiecest:is_instance(item) then - return item.corpse_flags.bone and not item.flags.dead_dwarf - else - return false - end - end - local bone = nil - if #workshop.profile.links.take_from_pile > 0 then - bone = findLinked(workshop, is_bone) - else - bone = findClosest(workshop_position, df.global.world.items.other.ANY_REFUSE, is_bone) - end - - if not bone then - return false - end +function makeBoneCraft(unit, workshop) local job = make_job() job.job_type = df.job_type.MakeCrafts job.mat_type = -1 job.material_category.bone = true - job.pos = workshop_position + job.pos = xyz2pos(workshop.centerx, workshop.centery, workshop.z) local jitem = df.job_item:new() jitem.item_type = df.item_type.NONE @@ -117,13 +36,7 @@ local function makeBoneCraft(unit, workshop) job.job_items.elements:insert('#', jitem) dfhack.job.addGeneralRef(job, df.general_ref_type.BUILDING_HOLDER, workshop.id) - if not dfhack.job.attachJobItem(job, bone, df.job_item_ref.T_role.Reagent, 0, -1) then - dfhack.printerr('could not attach bones') - return false - end workshop.jobs:insert("#", job) - job.flags.fetching = true - job.items[0].flags.is_fetching = true return dfhack.job.addWorker(job, unit) end @@ -131,18 +44,11 @@ end ---@param unit df.unit ---@param workshop df.building_workshopst ---@return boolean "" -local function makeRockCraft(unit, workshop) - local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) - - - local boulder = findClosest(workshop_position, df.global.world.items.other.BOULDER) - if not boulder then - return false - end +function makeRockCraft(unit, workshop) local job = make_job() job.job_type = df.job_type.MakeCrafts job.mat_type = 0 - job.pos = workshop_position + job.pos = xyz2pos(workshop.centerx, workshop.centery, workshop.z) local jitem = df.job_item:new() jitem.item_type = df.item_type.BOULDER @@ -155,13 +61,8 @@ local function makeRockCraft(unit, workshop) job.job_items.elements:insert('#', jitem) dfhack.job.addGeneralRef(job, df.general_ref_type.BUILDING_HOLDER, workshop.id) - if not dfhack.job.attachJobItem(job, boulder, df.job_item_ref.T_role.Reagent, 0, -1) then - dfhack.printerr('could not attach boulder') - return false - end workshop.jobs:insert("#", job) - job.flags.fetching = true - job.items[0].flags.is_fetching = true + return dfhack.job.addWorker(job, unit) end @@ -175,7 +76,7 @@ function isEnabled() end ---IDs of workshops where idle crafting is permitted ----@type table +---@type table allowed = allowed or {} ---IDs of workshops that have encountered failures (e.g. missing materials) @@ -207,7 +108,10 @@ local function load_state() thresholds = persisted_data.thresholds or { 10000, 1000, 500 } end -CraftObject = df.need_type['CraftObject'] +--frequently accessed values +local CraftObject = df.need_type['CraftObject'] +local BONE_CARVE = df.unit_labor['BONE_CARVE'] +local STONE_CRAFT = df.unit_labor['STONE_CRAFT'] ---negative crafting focus penalty ---@param unit df.unit @@ -277,7 +181,7 @@ end ---check if unit is ready and try to create a crafting job for it ---@param workshop df.building_workshopst ----@param idx integer +---@param idx integer "index of the unit's group" ---@param unit_id integer ---@return boolean "proceed to next workshop" function processUnit(workshop, idx, unit_id) @@ -295,10 +199,10 @@ function processUnit(workshop, idx, unit_id) end -- We have an available unit local success = false - if workshop.profile.blocked_labors[df.unit_labor['BONE_CARVE']] == false then + if workshop.profile.blocked_labors[BONE_CARVE] == false then success = makeBoneCraft(unit, workshop) end - if not success and workshop.profile.blocked_labors[df.unit_labor['STONE_CRAFT']] == false then + if not success and workshop.profile.blocked_labors[STONE_CRAFT] == false then success = makeRockCraft(unit, workshop) end local name = (dfhack.TranslateName(dfhack.units.getVisibleName(unit))) @@ -306,32 +210,56 @@ function processUnit(workshop, idx, unit_id) -- Why is the encoding still wrong, even when using df2console? print(' assigned ' .. dfhack.df2console(name)) watched[idx][unit_id] = nil + allowed[workshop.id] = df.global.world.frame_counter else - print(' failed to assign ' .. dfhack.df2console(name)) - print(' disabling failing workshop until the next run of the main loop') - failing[workshop.id] = true + dfhack.printerr('idle-crafting: profile allows neither bone carving nor stonecrafting, disabling workshop') end return true end + +---@param workshop df.building_workshopst +---@return boolean +function invalidProfile(workshop) + local profile = workshop.profile + return (#profile.permitted_workers > 0) or + (profile.blocked_labors[BONE_CARVE] and profile.blocked_labors[STONE_CRAFT]) +end + +-- try to catch units that currently don't have a job and send them to satisfy +-- their crafting needs. local function unit_loop() - for workshop_id, _ in pairs(allowed) do - -- skip workshops where job creation failed (e.g. due to missing materials) + local current_frame = df.global.world.frame_counter + for workshop_id, last_job_frame in pairs(allowed) do + -- skip workshops where jobs appear to have been cancelled (e.g. to missing materials) if failing[workshop_id] then goto next_workshop end + local workshop = locateWorkshop(workshop_id) - -- workshop may have been destroyed or assigned a master - if not workshop or #workshop.profile.permitted_workers > 0 then + -- workshop may have been destroyed, assigned a master, or does not allow crafting + if not workshop or invalidProfile(workshop) then + print('workshop destroyed or has invalid profile') allowed[workshop_id] = nil --clearing during iteration is permitted goto next_workshop end + -- only consider workshop if not currently in use if #workshop.jobs > 0 then goto next_workshop end - dfhack.print(('idle-crafting: locating crafter for %s (%d)'):format(dfhack.buildings.getName(workshop), - workshop_id)) + + -- check that we didn't schedule a job on the last iteration + if (last_job_frame >= 0) and (current_frame < last_job_frame + 60) then + print(('idle-crafting: disabling failing workshop (%d) until the next run of main loop'): + format(workshop_id)) + failing[workshop_id] = true + goto next_workshop + end + + dfhack.print(('idle-crafting: locating crafter for %s (%d)'): + format(dfhack.buildings.getName(workshop), workshop_id)) + -- workshop is free to use, try to find a unit for idx, _ in ipairs(thresholds) do for unit_id, _ in pairs(watched[idx]) do @@ -362,6 +290,9 @@ local function main_loop() end -- put failing workshops back into the loop failing = {} + for workshop_id, _ in pairs(allowed) do + allowed[workshop_id] = -1 + end local num_watched = {} local watching = false @@ -455,7 +386,7 @@ end function IdleCraftingOverlay:onClick(new, _) local workshop = dfhack.gui.getSelectedBuilding(true) - allowed[workshop.id] = new or nil + allowed[workshop.id] = new and -1 or nil if new and not enabled then start(true) end From 281e3ec0422e5c31da81f56abedfc7b256634df3 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:04:30 +0200 Subject: [PATCH 05/13] prioritize stone crafting / hide overlay when selecting stockpile links --- idle-crafting.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index f6b5312bd..7c6fb0817 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -199,12 +199,12 @@ function processUnit(workshop, idx, unit_id) end -- We have an available unit local success = false - if workshop.profile.blocked_labors[BONE_CARVE] == false then - success = makeBoneCraft(unit, workshop) - end - if not success and workshop.profile.blocked_labors[STONE_CRAFT] == false then + if workshop.profile.blocked_labors[STONE_CRAFT] == false then success = makeRockCraft(unit, workshop) end + if not success and workshop.profile.blocked_labors[BONE_CARVE] == false then + success = makeBoneCraft(unit, workshop) + end local name = (dfhack.TranslateName(dfhack.units.getVisibleName(unit))) if success then -- Why is the encoding still wrong, even when using df2console? @@ -363,7 +363,9 @@ IdleCraftingOverlay.ATTRS { }, frame = { w = 55, h = 1 }, visible = function () - return #df.global.game.main_interface.building.button == 0 + return + #df.global.game.main_interface.building.button == 0 and + df.global.game.main_interface.stockpile_link.adding_new_link == false end } From ea52ce9a2109e20662c73e6580b2fb4065ac0966 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:44:12 +0200 Subject: [PATCH 06/13] Apply rewordings from code review Co-authored-by: Myk --- changelog.txt | 1 - docs/idle-crafting.rst | 18 +++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7e2d023e8..d3a92733b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -28,7 +28,6 @@ Template for new versions: ## New Tools - `embark-anyone`: allows you to embark as any civilisation, including dead, and non-dwarven ones - - `idle-crafting`: Allow dwarfs to automatically satisfy their need to craft objects. ## New Features diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 8c56419b9..94277988a 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -2,12 +2,16 @@ idle-crafting ============= .. dfhack-tool:: - :summary: Allow dwarfs to automatically satisfy their need to craft objects. + :summary: Allow dwarves to independently craft objects when they have the need. :tags: fort gameplay -This script allows dwarves to automatically satisfy their crafting needs. The -script is configured through an overlay that is added to the main page of -craftsdwarf's workshops, as described below. +This script allows you to mark specific Craftsdwarf's Workshops for use as +"recreational crafting" stations. Dwarves who feel the need to craft objects +will be able to go there to independently satisfy their crafting needs without +manual intervention from the player. + +There will be a toggle on the workshop info sheet ("Tasks") tab when you +select a Craftsdwarf's Workshop in the UI. More details below. Usage ----- @@ -42,13 +46,13 @@ Options Overlay ------- -This script provides an overlay to the "Tasks" tab of craftsdwarf's workshops, -allowing you to designate that workshop for use by idle dwarfs to satisfy their +This script provides an overlay on the "Tasks" tab of Craftsdwarf's workshops, +allowing you to designate that workshop for use by idle dwarves to satisfy their needs to craft objects. Workshops that have a master assigned cannot be used in this way. When a workshop is designated for idle crafting, this tool will create crafting -jobs and assign them to idle dwarfs who have a need for crafting +jobs and assign them to idle dwarves who have a need for crafting objects. Currently, bone carving and stone crafting are supported, with stone crafting being the default option. This script respects the setting for permitted general work orders from the "Workers" tab. Thus, to designate a From a1e01c351e61264d6f8cf2609d93b10f2a431d8b Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:36:05 +0200 Subject: [PATCH 07/13] turn thresholds into a sub-command --- docs/idle-crafting.rst | 40 ++++++++++++++++------------------------ idle-crafting.lua | 18 ++++++++---------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 94277988a..68cc7f1bf 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -16,32 +16,24 @@ select a Craftsdwarf's Workshop in the UI. More details below. Usage ----- -:: +``idle-crafting [status]`` + Print statistics about the status of the tool and the satisfaction of + "craft item" needs in your fort. - idle-crafting status - idle-crafting disable - disable idle-crafting +``idle-crafting thresholds `` + Set the threshold(s) for the "craft item" need (i.e. the negated + ``focus_level``) at which the tool starts to generate crafting jobs for a + given unit. Units meeting higher thresholds will be prioritized. Defaults + to ``500,1000,10000``. -The ``status`` command prints statistics about the status of the tool and the -satisfaction of "craft item" needs in your fort. Both variants of the -``disable`` command disallow idle crafting at all workshops and disable the -tool. +``disable idle-crafting`` + Disallow idle crafting at all workshops and disable the tool. Examples -------- -``idle-crafting -t 10000,1000,500 status`` - Reset all thresholds to defaults and print status information. - -Options -------- - -``-t ``, ``--thresholds `` - Sets the threshold(s) for the "craft item" need (i.e. the negated - ``focus_level``) at which the tool starts to generate crafting jobs for a - given unit. Units meeting earlier (higher) thresholds will be - prioritized. Defaults to ``10000,1000,500``. - +``idle-crafting thresholds 500,1000,10000`` + Reset thresholds to defaults. Overlay ------- @@ -53,8 +45,8 @@ this way. When a workshop is designated for idle crafting, this tool will create crafting jobs and assign them to idle dwarves who have a need for crafting -objects. Currently, bone carving and stone crafting are supported, with stone -crafting being the default option. This script respects the setting for +objects. Currently, bone carving and stonecrafting are supported, with +stonecrafting being the default option. This script respects the setting for permitted general work orders from the "Workers" tab. Thus, to designate a -workshop for bone carving, disable the stone crafting labor on for their -workshop, will keeping bone carving enabled. +workshop for bone carving, disable the stonecrafting labor while keeping the +bone carving labor enabled. diff --git a/idle-crafting.lua b/idle-crafting.lua index 7c6fb0817..1dded2cb2 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -443,16 +443,9 @@ local fulfillment_threshold = local argparse = require('argparse') load_state() -local positionals = argparse.processArgsGetopt({ ... }, { - { - 't', 'thresholds', hasArg = true, - handler = function(optarg) - thresholds = argparse.numberList(optarg, 'thresholds') - end - } -}) +local positionals = argparse.processArgsGetopt({ ... }, {}) -if positionals[1] == 'status' then +if not positionals[1] or positionals[1] == 'status' then ---@type integer[] stats = {} for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do @@ -476,7 +469,12 @@ if positionals[1] == 'status' then print(('Script is %s with %d workshops configured for idle crafting'): format(enabled and 'enabled' or 'disabled', num_workshops)) print(('The thresholds for "craft item" needs are %s'): - format(table.concat(thresholds, '/'))) + format(table.concat(thresholds, ','))) +elseif positionals[1] == 'thresholds' then + thresholds = argparse.numberList(positionals[2], 'thresholds') + table.sort(thresholds, function (a, b) return a > b end) + print(('Thresholds for "craft item" needs set to %s'): + format(table.concat(thresholds, ','))) elseif positionals[1] == 'disable' then allowed = {} stop() From b4a5e38498bf96e4aa8d112cfe6d9d5bb5cf4b6c Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:20:32 +0200 Subject: [PATCH 08/13] move overlay two workers tab --- docs/idle-crafting.rst | 2 +- idle-crafting.lua | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 68cc7f1bf..2b3bbc451 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -38,7 +38,7 @@ Examples Overlay ------- -This script provides an overlay on the "Tasks" tab of Craftsdwarf's workshops, +This script provides an overlay on the "Workers" tab of Craftsdwarf's workshops, allowing you to designate that workshop for use by idle dwarves to satisfy their needs to craft objects. Workshops that have a master assigned cannot be used in this way. diff --git a/idle-crafting.lua b/idle-crafting.lua index 1dded2cb2..4e8acc7c2 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -359,13 +359,11 @@ IdleCraftingOverlay.ATTRS { default_pos = { x = -42, y = 41 }, default_enabled = true, viewscreens = { - 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Tasks', + 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Workers', }, frame = { w = 55, h = 1 }, visible = function () - return - #df.global.game.main_interface.building.button == 0 and - df.global.game.main_interface.stockpile_link.adding_new_link == false + return not df.global.game.main_interface.stockpile_link.adding_new_link end } @@ -374,8 +372,8 @@ function IdleCraftingOverlay:init() widgets.CycleHotkeyLabel { view_id = 'leisure_toggle', frame = { l = 0, t = 0 }, - label = 'Allow idle dwarfs to satisfy crafting needs:', - key = 'CUSTOM_L', + label = 'Allow idle dwarves to satisfy crafting needs:', + key = 'CUSTOM_I', options = { { label = 'yes', value = true, pen = COLOR_GREEN }, { label = 'no', value = false }, From 19f5e011fb787f6ede2f792d869c25305710bba9 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:22:11 +0200 Subject: [PATCH 09/13] adapt to dfhack#4858 --- idle-crafting.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index 4e8acc7c2..34767fc4a 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -362,9 +362,6 @@ IdleCraftingOverlay.ATTRS { 'dwarfmode/ViewSheets/BUILDING/Workshop/Craftsdwarfs/Workers', }, frame = { w = 55, h = 1 }, - visible = function () - return not df.global.game.main_interface.stockpile_link.adding_new_link - end } function IdleCraftingOverlay:init() From ead9be70257fad2d8ba6885804c831897fd4e499 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:22:15 +0200 Subject: [PATCH 10/13] exclude caged and chained units from idle crafting --- idle-crafting.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index 34767fc4a..c7b965c10 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -186,8 +186,8 @@ end ---@return boolean "proceed to next workshop" function processUnit(workshop, idx, unit_id) local unit = df.unit.find(unit_id) - -- check that unit is still there - if not unit then + -- check that unit is still there and not caged or chained + if not unit or unit.flags1.caged or unit.flags1.chained then watched[idx][unit_id] = nil return false elseif not canAccessWorkshop(unit, workshop) then From e3e54381b3d34ec7acd1421515352479fe1dcb59 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:30:36 +0200 Subject: [PATCH 11/13] update locality of function definitions --- idle-crafting.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index c7b965c10..f0cbc2572 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -116,7 +116,7 @@ local STONE_CRAFT = df.unit_labor['STONE_CRAFT'] ---negative crafting focus penalty ---@param unit df.unit ---@return number -local function getCraftingNeed(unit) +function getCraftingNeed(unit) local needs = unit.status.current_soul.personality.needs for _, need in ipairs(needs) do if need.id == CraftObject then @@ -163,7 +163,7 @@ end ---unit is ready to take jobs ---@param unit df.unit ---@return boolean -local function unitIsAvailable(unit) +function unitIsAvailable(unit) if unit.job.current_job then return false elseif #unit.social_activities > 0 then @@ -184,7 +184,7 @@ end ---@param idx integer "index of the unit's group" ---@param unit_id integer ---@return boolean "proceed to next workshop" -function processUnit(workshop, idx, unit_id) +local function processUnit(workshop, idx, unit_id) local unit = df.unit.find(unit_id) -- check that unit is still there and not caged or chained if not unit or unit.flags1.caged or unit.flags1.chained then @@ -220,7 +220,7 @@ end ---@param workshop df.building_workshopst ---@return boolean -function invalidProfile(workshop) +local function invalidProfile(workshop) local profile = workshop.profile return (#profile.permitted_workers > 0) or (profile.blocked_labors[BONE_CARVE] and profile.blocked_labors[STONE_CRAFT]) From 16621bf3a8dc6ed3632484bb17590ae6d9533a0e Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 15 Aug 2024 12:58:45 -0700 Subject: [PATCH 12/13] Apply suggestions from code review --- docs/idle-crafting.rst | 3 ++- idle-crafting.lua | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 2b3bbc451..39086443a 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -27,7 +27,8 @@ Usage to ``500,1000,10000``. ``disable idle-crafting`` - Disallow idle crafting at all workshops and disable the tool. + Disallow idle crafting at all workshops. You can re-enable idle crafting + at individual Craftsdwarf's workshops. Examples -------- diff --git a/idle-crafting.lua b/idle-crafting.lua index f0cbc2572..6ce511ac5 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -144,7 +144,7 @@ end ---@return df.building_workshopst|nil local function locateWorkshop(id) local workshop = df.building.find(id) - if df.building_workshopst:is_instance(workshop) and workshop.type == 3 then + if df.building_workshopst:is_instance(workshop) and workshop.type == df.workshop_type.Craftsdwarfs then return workshop else return nil @@ -355,7 +355,7 @@ end IdleCraftingOverlay = defclass(IdleCraftingOverlay, overlay.OverlayWidget) IdleCraftingOverlay.ATTRS { - desc = 'Adds a UI to the Workers tab too enable idle crafting.', + desc = 'Adds a toggle for recreational crafting to Craftdwarf's workshops.', default_pos = { x = -42, y = 41 }, default_enabled = true, viewscreens = { From e19d30ac8b636747db38e7d2afe3d66908ee353f Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 15 Aug 2024 13:01:58 -0700 Subject: [PATCH 13/13] Update idle-crafting.lua --- idle-crafting.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idle-crafting.lua b/idle-crafting.lua index 6ce511ac5..784222b7c 100644 --- a/idle-crafting.lua +++ b/idle-crafting.lua @@ -355,7 +355,7 @@ end IdleCraftingOverlay = defclass(IdleCraftingOverlay, overlay.OverlayWidget) IdleCraftingOverlay.ATTRS { - desc = 'Adds a toggle for recreational crafting to Craftdwarf's workshops.', + desc = "Adds a toggle for recreational crafting to Craftdwarf's workshops.", default_pos = { x = -42, y = 41 }, default_enabled = true, viewscreens = {