Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

workorder-detail-fix: Enforce order item details for buggy job types #929

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c1960e0
add workorder-detail-fix
flashy-man Jan 6, 2024
0ac190c
better way to get the job type enums
flashy-man Jan 6, 2024
152fe5b
print valid commands when no arg is passed
flashy-man Jan 6, 2024
4e42bd8
print valid cmds when no arg is passed
flashy-man Jan 6, 2024
323121d
fix bad commit
flashy-man Jan 6, 2024
08a56b0
delete old job name -> id function
flashy-man Jan 6, 2024
b4b2518
workorder-detail-fix: use local variables
flashy-man Jan 6, 2024
9635fab
fix trailing whitespace; doc underline
flashy-man Jan 6, 2024
5c643b7
move workorder-detail-fix to fix directory, add enable API
flashy-man Jan 6, 2024
69a5e1f
no arg parsing when being used as a module (for enable api)
flashy-man Jan 6, 2024
e52adbe
actually set enable state
flashy-man Jan 6, 2024
b30668f
trailing whitespace again
flashy-man Jan 6, 2024
fae0027
hopefully fix line endings (sublime text calls them 'unix' endings)
flashy-man Jan 6, 2024
45f87de
hopefully fix EOF error (just needed newline at end of file)
flashy-man Jan 6, 2024
47761b6
only print when called from command line. also fix to work better wit…
flashy-man Jan 6, 2024
e1f170e
move doc to docs/fix
flashy-man Jan 7, 2024
ea76f59
workorder-detail-fix -> fix/workorder-detail-fix
flashy-man Jan 7, 2024
727200b
add detail_fix_is_needed() to say when the fix is required
flashy-man Jan 9, 2024
39a4db4
rework: only enable handler when detail_fix_is_needed
flashy-man Jan 9, 2024
a4ce456
better status() function to account for more statuses
flashy-man Jan 9, 2024
8ad4f77
woowee doc underline
flashy-man Jan 10, 2024
a5c2cc9
rename workorder-detail-fix in changelog
flashy-man Jan 14, 2024
4e950dd
no more eventful; only repeatutil
flashy-man Jan 14, 2024
a39ec2e
merged changelog
flashy-man Jan 14, 2024
d6b0775
fix redundancy, confusing modulo math
flashy-man Jan 15, 2024
f6275f0
line endings
flashy-man Jan 15, 2024
384625d
copy flags whole instead of one-by-one
flashy-man Jan 15, 2024
3cff781
alphebetize entry in control panel registry
flashy-man Jan 18, 2024
b0146e7
update docs: new name/usage
flashy-man Jan 18, 2024
f504edc
fix problems with status()/jobs_corrected
flashy-man Jan 18, 2024
027f410
fix redundant repeatutil usage
flashy-man Jan 18, 2024
b7c0ce8
print jobs_corrected only when enabled
flashy-man Jan 18, 2024
4ca5a42
remove active job fix (unstable)
flashy-man Jan 18, 2024
f961c8f
use ipairs, utils.invert
flashy-man Jan 18, 2024
fa308ab
better way to get manager; move local declarations
flashy-man Jan 18, 2024
e584bba
automatic re-sync if lost
flashy-man Jan 18, 2024
fcc8546
documented active job limitation
flashy-man Jan 18, 2024
fea88ca
delimit block so goto can jump over local scope
flashy-man Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Template for new versions:
- `warn-stranded`: Update onZoom to use df's centering functionality
- `ban-cooking`: fix banning creature alcohols resulting in error
- `confirm`: properly detect clicks on the remove zone button even when the unit selection screen is also open (e.g. the vanilla assign animal to pasture panel)
- `fix/workorder-detail-fix`: fix item types not being passed properly on some modified work order jobs
- `quickfort`: if a blueprint specifies an up/down stair, but the tile the blueprint is applied to cannot make an up stair (e.g. it has already been dug out), still designate a down stair if possible

## Misc Improvements
Expand Down
24 changes: 24 additions & 0 deletions docs/fix/workorder-detail-fix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
fix/workorder-detail-fix
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
========================

.. dfhack-tool::
:summary: Fixes a bug with modified work orders creating incorrect jobs.
:tags: fort bugfix workorders

Some work order jobs have a bug when their input item details have been modified.

Example 1: a Stud With Iron order, modified to stud a cabinet, instead creates a job to stud any furniture.

Example 2: a Prepare Meal order, modified to use all plant type ingredients, instead creates a job to use any ingredients.

This fix forces these jobs to properly inherit the item details from their work order.

Usage
-----

``fix/workorder-detail-fix enable``
enables the fix
``fix/workorder-detail-fix disable``
disables the fix
``fix/workorder-detail-fix status``
print fix status
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
199 changes: 199 additions & 0 deletions fix/workorder-detail-fix.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
--@enable = true
--@module = true
local repeatutil = require 'repeat-util'
local utils = require 'utils'
local script_name = "workorder-detail-fix"
local schedule_key = script_name..":dispatch"

enabled = enabled or false -- enabled API
function isEnabled() return enabled end

local managers = df.global.plotinfo.main.fortress_entity.assignments_by_type.MANAGE_PRODUCTION
if not managers then error("NO MANAGERS VECTOR!") end
local last_job_id = -1
local jobs_corrected = 0

-- all jobs with the NONE (-1) type in its default job_items may be a problem
local offending_jobs = {
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
[df.job_type.EncrustWithGems] = true,
[df.job_type.EncrustWithGlass] = true,
[df.job_type.StudWith] = true,
[df.job_type.PrepareMeal] = true,
[df.job_type.DecorateWith] = true,
[df.job_type.SewImage] = true,
-- list may be incomplete
}

-- copy order.item fields/flags over to job's job_item
-- only the essentials: stuff that is editable via gui/job-details
local function correct_item_details(job_item, order_item)
local fields = {'item_type', 'item_subtype', 'mat_type', 'mat_index'}
for _, field in pairs(fields) do
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
job_item[field] = order_item[field]
end

local flags_names = {'flags1', 'flags2', 'flags3', 'flags4', 'flags5'}
for _, flags in pairs(flags_names) do
local order_flags = order_item[flags]
if type(order_flags) == 'number' then
job_item[flags] = order_flags
else
job_item[flags].whole = order_flags.whole
end
end
end

-- correct job's job_items to match the order's
local function enforce_order_details(job, order)
if not job.job_items then return end -- never happens (error here?)
local modified = false
for idx, job_item in ipairs(job.job_items) do
local order_item = order.items[idx]
if not order_item then break end -- never happens (error here?)
if job_item.item_type ~= order_item.item_type then
-- dfhack's isSuitableItem function will allow the orders we want,
-- but disallow insane combinations like meals made of shoes
local suitable = dfhack.job.isSuitableItem(
job_item, order_item.item_type, order_item.item_subtype )
if suitable then
correct_item_details(job_item, order_item)
modified = true
else --[[ error on unsuitable item?]] end
end
end
if modified then jobs_corrected = jobs_corrected + 1 end
end

-- check orders list to see if fix is needed
-- yields id-order table for the orders with issues, or nil when no issues
local PrepareMeal = df.job_type.PrepareMeal
local SewImage = df.job_type.SewImage
local NONE = df.job_type.NONE
local function get_bugged_orders()
local orders = {}
local num_bugged, improve_item_index, item = 0, 1, nil
for _, order in ipairs(df.global.world.manager_orders) do
if not order.items then goto nextorder end
local order_job_type = order.job_type
if not offending_jobs[order_job_type] then goto nextorder end
if #order.items == 0 then goto nextorder end -- doesn't happen

-- for PrepareMeal jobs, any one of the items could be an issue.
if order_job_type == PrepareMeal then
for _, _item in ipairs(order.items) do
if _item.item_type ~= NONE then goto fix end
end
goto nextorder
end

-- All other types are improve jobs; only the improved item is checked
-- Only SewImage has the item-to-improve at items[0]
improve_item_index = (order_job_type ~= SewImage) and 1 or 0
item = order.items[improve_item_index]
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
if not item then goto nextorder end -- error here?
if item.item_type == NONE then goto nextorder end
:: fix ::
num_bugged = num_bugged + 1
orders[order.id] = order
:: nextorder ::
end
if num_bugged == 0 then return nil end
return orders
end

-- correct newly dispatched work order jobs
local disable
local function on_dispatch_tick()
if df.global.cur_year_tick % 150 ~= 30 then
print(script_name.." desynced from dispatch tick")
repeatutil.cancel(schedule_key)
disable(true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a fatal error. we could regularly get desynchronized because of tools like timestream. I think we can just transparently attempt to resync.

jobs that have already started to gather items can be skipped (and we can document that this script is not infallible)

Copy link
Author

@flashy-man flashy-man Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it re-syncing now. Surprisingly the fix works with timestream on (though it does de-sync a lot). I wouldn't think timestream handles work orders well in the first place

Also, the already-gathering-items "fix" is gone for now because I got a couple crashes (!), either relating to the general refs or the while loop checking the vector. It's not crucial anyway

end
if #managers == 0 then return end
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
if df.global.plotinfo.manager_timer ~= 10 then return end
local orders = get_bugged_orders()
if not orders then return end -- no bugs to fix
local highest = last_job_id
for _, job in utils.listpairs(df.global.world.jobs.list) do
if job.id <= last_job_id then goto nextjob end
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
highest = math.max(job.id, highest)
if job.order_id == -1 then goto nextjob end
local order = orders[job.order_id]
if not order then goto nextjob end -- order wasn't bugged
-- job in progress: only happens on the first run.
-- experimental fix: remove the items and un-task them
while #job.items ~= 0 do
job.items[0].item.flags.in_job = false
job.items:erase(0)
end
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
enforce_order_details(job, order)
:: nextjob ::
end
last_job_id = highest
end

timeout_id = timeout_id or nil
local function schedule_handler()
if repeatutil.cancel(schedule_key) then
print(script_name..": canceled old dispatch handler")
end
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
repeatutil.scheduleEvery(schedule_key, 150, 'ticks', on_dispatch_tick)
timeout_id = nil
end

local function enable(yell)
local manager_timer = df.global.plotinfo.manager_timer
local d = df.global.cur_year_tick
-- it's a potential dispatch tick when tick % 150 == 30
local time_until = (30 - d) % 150 -- + manager_timer * 150
if time_until == 0 then
schedule_handler()
else
timeout_id = dfhack.timeout(time_until, 'ticks', schedule_handler)
end
enabled = true
if yell then print(script_name.." ENABLED") end
end

function disable(yell)
if timeout_id then
dfhack.timeout_active(timeout_id, nil)
timeout_id = nil
end
repeatutil.cancel(schedule_key)
enabled = false
if yell then print(script_name.." DISABLED") end
end

-- (not working with enabled API, probably something to do with module mode)
local function status()
local status = "DISABLED" or enabled and "ENABLED"
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
print(script_name.." status: "..status.." # jobs corrected: "..tostring(jobs_corrected))
end

-- check if script was called by enable API
if dfhack_flags.enable then
if dfhack_flags.enable_state then
enable(false)
else
disable(false)
end
return
end

if dfhack_flags.module then return end

-- check the arguments
local args={...}

if not args[1] then
flashy-man marked this conversation as resolved.
Show resolved Hide resolved
print(script_name.." valid cmds: enable, disable, status")
return
end

local cmd_table = { ['enable']=enable, ['disable']=disable, ['status']=status }

local cmd = cmd_table[args[1]:lower()]
if cmd then cmd(true) else
print(script_name.." valid cmds: enable, disable, status")
end
1 change: 1 addition & 0 deletions internal/control-panel/registry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ COMMANDS_BY_IDX = {
desc='Fix activity references on stuck instruments to make them usable again.',
params={'--time', '1', '--timeUnits', 'days', '--command', '[', 'fix/stuck-instruments', ']'}},
{command='preserve-tombs', group='bugfix', mode='enable', default=true},
{command='fix/workorder-detail-fix', group='bugfix', mode='enable', default=true},
flashy-man marked this conversation as resolved.
Show resolved Hide resolved

-- gameplay tools
{command='combine', group='gameplay', mode='repeat',
Expand Down