Skip to content

tiletypes: remove plants when their tile is painted away - #5918

Merged
ab9rf merged 3 commits into
DFHack:developfrom
Alistair-Afton:tiletypes-remove-plants
Sep 18, 2026
Merged

ab9rf merged 3 commits into
DFHack:developfrom
Alistair-Afton:tiletypes-remove-plants

Conversation

@Alistair-Afton

Copy link
Copy Markdown
Contributor

Summary

Fixes #5838

Painting a tile (e.g. clearing ground to open space) left the plant record behind, so trees kept re-sprouting from empty air and then collapsing over whatever was built below.

After painting, a plant rooted at the tile is now removed when the new tiletype can no longer host it:

  • grown trees (tree_info) need a TREE/ROOT/MUSHROOM material tile
  • shrubs and saplings need SHRUB/SAPLING shaped tiles
  • unsprouted trees (DRY_TREE/WET_TREE records on ground tiles) need a tile that still exists

Tree records take their tree_info with them so nothing regrows; leftover canopy tiles remain for the user to paint over, same as after plant remove. Removal mirrors the plant plugin's uncat-and-delete flow.

Both paint paths are covered: single tiles (paintTile, also used by gui/tiletypes via tiletypes_setTile) and area brushes (paintArea, which skips filtered-out tiles).

Testing

Built and installed locally (win64, DF 53.16), then exercised in a live fort:

  • Painted a grown tree's TreeTrunkPillar base to open space via tiletypes_setTile — plant record removed from world->plants.all, tile became OpenSpace.
  • Same for a shrub and an unsprouted tree — records removed, no longer in the per-type vectors either.
  • Painted a 3x3 area to air with the rect brush (paintArea path) — the in-area plant was removed while a plant just outside the brush survived untouched.
  • Game stayed healthy afterwards (units and remaining plants fine).

Painting a tile (e.g. clearing ground to open space) left the plant
record behind, so trees kept re-sprouting from empty air and then
collapsing over whatever was built below.

After painting, a plant rooted at the tile is now removed when the new
tiletype can no longer host it: grown trees need a tree-material tile,
shrubs and saplings need their respective tile shapes, and unsprouted
trees need a tile that still exists. Tree records take their tree_info
with them so nothing regrows; leftover canopy tiles remain for the
user to paint over, same as after 'plant remove'.

Fixes DFHack#5838
Comment thread plugins/tiletypes.cpp Outdated
// remaining tiles are left as-is for the user to paint over or collapse
static void removePlant(df::plant *plant)
{
vector<df::plant *> *vec = NULL;

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.

Suggested change
vector<df::plant *> *vec = NULL;
vector<df::plant *> *vec = nullptr;

NULL is non-standard in C++

Comment thread plugins/tiletypes.cpp Outdated
}
if (vec)
{
for (size_t i = vec->size(); i-- > 0;)

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.

is there a good reason not to use std::erase here?

Comment thread plugins/tiletypes.cpp Outdated
auto col = Maps::getBlockColumn((plant->pos.x / 48) * 3, (plant->pos.y / 48) * 3);
if (col)
{
for (size_t i = col->plants.size(); i-- > 0;)

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.

also consider std::erase here

while std::erase will not quit once it's found an instance of the item to remove, and will instead scan the entire vector to remove duplicates, this is actually safer: if (for some reason) there is a duplicate, removing all of them will prevent a dangled reference. remember postel's law.

Comment thread plugins/tiletypes.cpp Outdated
}

auto &all = world->plants.all;
for (size_t i = all.size(); i-- > 0;)

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.

same issue here

Comment thread plugins/tiletypes.cpp Outdated
for (int16_t y = lo.y; y <= hi.y; y++)
{
df::coord pos(x, y, z);
if (filter(pos, NULL))

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.

Suggested change
if (filter(pos, NULL))
if (filter(pos, nullptr))

@Alistair-Afton

Copy link
Copy Markdown
Contributor Author

All addressed in c20b277:

  • nullptr in place of NULL in both spots
  • all three removal loops now use std::erase — agreed that's the safer form, since it removes duplicate entries rather than stopping at the first

Addresses review feedback: std::erase also guards against duplicate
entries leaving dangling references.
@Alistair-Afton

Copy link
Copy Markdown
Contributor Author

Added in-game regression coverage in 2a7c742: painting a soil wall over a shrub/sapling removes the plant record, an identity repaint preserves it, and painting over a grown tree's trunk removes the tree. Also covers the console/help/error paths. All 6 tests pass in-game.

@ab9rf
ab9rf merged commit 8176c75 into DFHack:develop Sep 18, 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.

Trees spawning mid air

2 participants