Repository navigation
Conversation
An order for an item type that has subtypes (shoes, armor, weapons, tools, ammo, ...) created without "item_subtype" was left at item_subtype -1. DF accepts and validates such an order, but segfaults in its job-name code whenever a resulting job is named (by dfhack.job.getName or by the game drawing the job), and the bad order persists in the save. Refuse these orders with an error that names a valid subtype. Meals (PrepareMeal / FOOD) keep item_subtype -1 as in vanilla and are exempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SilasD
left a comment
There was a problem hiding this comment.
I'd like you to make one change and re-test.
basically I would like you to use our API calls instead of probing the table of object classes.
I'm not asking you to change all of the existing code to use the API calls, just in your added code.
(but if you want to, I won't say no. just remember, bigger changes need more testing.)
also, this is not too important, but the DFHack Launcher (and the console window) support an alternate way to enter commands which is very helpful when dealing with double-quotes.
If the first non-whitespace character is
:, the command is parsed in an alternative mode. The non-whitespace characters following the:are the command name, and the remaining part of the line is used verbatim as the first argument.
workorder "{\"job\":\"MakeShoes\",\"material_category\":[\"leather\"],\"amount_total\":4}"
can also be written as
:workorder {"job":"MakeShoes","material_category":["leather"],"amount_total":4}
| if produced and produced ~= df.item_type.NONE and produced ~= df.item_type.FOOD then | ||
| local def = df['itemdef_' .. string.lower(df.item_type[produced]) .. 'st'] | ||
| if def and #def.get_vector() > 0 then | ||
| qerror(("Manager order %s needs an item_subtype (e.g. %s)"):format( | ||
| df.job_type[order.job_type], def.get_vector()[0].id)) | ||
| end | ||
| end |
There was a problem hiding this comment.
I fully understand that Claude copied working code, but the code it copied isn't really correct.
It should use our API calls
dfhack.items.getSubtypeCount(item_type)
Returns the number of raw-defined subtypes of the given item type, or -1 if not applicable.
dfhack.items.getSubtypeDef(item_type, subtype)
Returns the raw definition for the given item type and subtype, or nil if invalid.
for example (minimal testing):
| if produced and produced ~= df.item_type.NONE and produced ~= df.item_type.FOOD then | |
| local def = df['itemdef_' .. string.lower(df.item_type[produced]) .. 'st'] | |
| if def and #def.get_vector() > 0 then | |
| qerror(("Manager order %s needs an item_subtype (e.g. %s)"):format( | |
| df.job_type[order.job_type], def.get_vector()[0].id)) | |
| end | |
| end | |
| if produced and produced ~= df.item_type.NONE and produced ~= df.item_type.FOOD | |
| and dfhack.items.getSubtypeCount(produced) > -1 | |
| then | |
| local example_def = dfhack.items.getSubtypeDef(produced, 0) | |
| if example_def then | |
| qerror(("Manager order %s needs an item_subtype (e.g. %s)"):format( | |
| df.job_type[order.job_type], example_def.id)) | |
| end | |
| end |
Summary
workordercreates a manager order withitem_subtype = -1when the JSON omits"item_subtype"for a job whose product has subtypes (shoes, armor, weapons, tools, ammo, trap components, toys, siege ammo). DF validates the order and spawns jobs from it, but segfaults in its job-name code whenever one of those jobs is named. The vanilla UI cannot create such an order, and once created it persists in the save, so the fort crashes again after every reload.This PR makes
create_orders()refuse such orders with an error that names a valid subtype, e.g.:Repro
DF 53.16 (Steam, Linux), DFHack 53.16-r2:
The order is created and validated. As soon as a job from it is named, DF crashes. In our fort this happened 8 times before we traced it to the order.
Crash via DFHack naming the job:
Crash via DF drawing the job:
Fix
The guard sits right after
item_subtypeis resolved and before themeal_ingredients/ material handling. If the order's subtype is still -1, it works out the produced item type (the explicititem_type, otherwisedf.job_type.attrs[job].item). Ifdf.itemdef_<type>stexists and has entries, it raises aqerrornaming the first defined subtype as an example. Item types without itemdefs (tables, bars, scepters, ...) are unaffected.FOODis exempt.PrepareMealorders keepitem_subtype = -1in vanilla and store the ingredient count inmat_type. A vanilla meal order in a live fort readsitem_type -1, item_subtype -1, mat_type 2, even thoughitemdefs.foodhas 3 entries. Without the exemption,workorder PrepareMeal 10would be wrongly refused.The shorthand form (
workorder MakeShoes 4) also goes throughcreate_orders(), so the guard covers it too.Testing
Live, on DF 53.16 / DFHack 53.16-r2, with the guard applied to the installed
workorder.lua:MakeShoeswithoutitem_subtype: refused withManager order MakeShoes needs an item_subtype (e.g. ITEM_SHOES_SHOES)MakeScepter(no subtypes exist): queued as beforeMakeShoeswith"item_subtype":"ITEM_SHOES_SHOES": queued as beforeI added the
FOODexemption after those runs. To check it without queuing more orders in the fort, I evaluated the final guard expression against the live raws withitem_subtype -1:PrepareMealMakeScepterConstructTableMakeCharcoalEncrustWithGemsShearCreatureSmeltOreCustomReactionMakeShoesITEM_SHOES_SHOES)MakeToolITEM_TOOL_CAULDRON)MakeWeaponITEM_WEAPON_WHIP)AssembleSiegeAmmoITEM_SIEGEAMMO_BALLISTA)All the subtyped-item orders in the bundled
data/orders/*.jsonlibraries already specifyitem_subtype. Those files are imported byorders, not by this script, so they are unaffected either way.Changelog entry added under Future / Fixes.
🤖 Generated with Claude Code