Skip to content

autodump: add 'undestroy' option; fix container/unit-item destruction - #5904

Merged
ab9rf merged 3 commits into
DFHack:developfrom
Alistair-Afton:autodump-undestroy
Sep 15, 2026
Merged

ab9rf merged 3 commits into
DFHack:developfrom
Alistair-Afton:autodump-undestroy

Conversation

@Alistair-Afton

@Alistair-Afton Alistair-Afton commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an autodump undestroy command that reverts items pending destruction, the same undo that gui/autodump already had (fixes #5896).

While in there I fixed two related issues with autodump destroy (fixes #5769):

  • It now recursively marks the contents of destroyed containers instead of leaving them in limbo
  • It skips items held in unit inventories — destroying an equipped container (e.g. a quiver) could previously strand its contents and crash

Pending-destruction flags are also restored from saved state on undo rather than blindly cleared, so items that were already marked before the destroy don't get unmarked.

Testing

  • Live fort: container + contents both get marked on destroy; autodump undestroy restores both
  • Equipped pick correctly skipped
  • ci/lint.py clean; plugin builds cleanly

'autodump destroy' marked items for destruction with no way to revert,
despite the docs claiming re-running the command before unpausing would
undo it. 'undestroy' now restores the original flags of all items with
destruction pending, reusing the per-frame pending_destroy tracking that
autodump-destroy-item already had.

The bulk destroy path also marked containers without their contents,
leaving contained items pointing at deleted containers, and did not
skip unit-held items, which could corrupt equipment refs (observed as a
crash on save after destroying an equipped quiver). Items in unit
inventories are now skipped like autodump-destroy-item does, and the
contents of destroyed containers are marked along with them.

fixes DFHack#5896
fixes DFHack#5769
Comment thread plugins/autodump.cpp Outdated
for (auto &entry : pending_destroy)
{
if (auto itm = df::item::find(entry.first))
{ // Item could have already been garbage collected

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If another plugin forcibly deleted the item while the game was still paused, itm could be NULL and result in a crash

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Isn't that case already covered? itm is only dereferenced inside if (auto itm = df::item::find(entry.first)), so if the item was deleted while paused, find returns NULL and the entry is skipped. Unless you mean a case where find can still return a pointer to a dead item?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the comment here is confusing - it implies that this block handles the case where the item has been deleted, which is not actually what is going on

@quietust quietust Sep 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Disregard - I misread the code, not noticing that this was an assignment inside an if statement (which, to be honest, is probably something to be avoided specifically because it leads to confusion like this).

@Alistair-Afton Alistair-Afton Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ab9rf Reworded it, hopefully clearer now.

@Alistair-Afton Alistair-Afton Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@quietust Fair point, I split the declaration out of the if.

Comment thread plugins/autodump.cpp Outdated
}
reset_pending_destroy();

if (pending_destroy.count(item->id))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use contains here - you don't actually care what the count is and there is no reason to force the library to count them when all you care about is whether the key is present

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread plugins/autodump.cpp Outdated
// contained items are not left pointing at a deleted container
static void mark_for_destroy(df::item *itm)
{
if (!pending_destroy.count(itm->id))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use contains here as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Use map::contains instead of count for membership tests, spell out
the NULL check instead of declaring itm inside the if, and reword the
comment so it is clear that find() is what skips already-deleted
items.
@ab9rf
ab9rf merged commit ab941a4 into DFHack:develop Sep 15, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: autodump destroy on containers leaves items in limbo autodump destroy doesn't revert pending item destruction

3 participants