From 80d6c928ade5ad2262b84f849433d15dd76ff649 Mon Sep 17 00:00:00 2001 From: Alistair-Afton Date: Wed, 16 Sep 2026 02:19:31 +0200 Subject: [PATCH] buildingplan: fix Unlink all skipping mechanisms when freeing Freeing a mechanism removes it from contained_items, which shifts the indices of all entries after it. "Unlink all" iterated the precomputed index list, so after the first freed mechanism the remaining indices pointed at the wrong items: links were skipped or the loop walked off the end of the vector and errored, leaving mechanisms still linked until the option was used again. Snapshot the linked items before mutating instead of iterating the index list directly. Fixes #5776 --- docs/changelog.txt | 1 + plugins/lua/buildingplan/unlink_mechanisms.lua | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8730c9c446..df0b37b533 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -65,6 +65,7 @@ Template for new versions: - `aquifer`: make ``--skip-top`` and top-relative ``--levels`` take effect for the ``drain``, ``convert``, and ``add`` actions instead of being silently ignored - `autodump`: ``destroy`` no longer leaves the contents of destroyed containers in limbo or crashes when destroying unit-held items - `timestream`: deal properly with units who have breathing difficulties +- `buildingplan`: fix "Unlink all" only unlinking some mechanisms (or crashing) when freeing mechanisms from the building ## Misc Improvements - Added ``Coord2d`` and ``Coord3d`` C++ templates, providing a standard set operations for 2-tuples and 3-tuples of any numeric type diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 81bd0fdabe..d6a676fdd1 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -402,8 +402,17 @@ function MechLinkOverlay:ask_unlink_all() local free_src = (saved_mode % 2 == 1) local free_dst = (saved_mode // 2 > 0) + -- freeing a mechanism removes it from contained_items and shifts the + -- indices of the entries after it, so resolve items before mutating + local items = {} for _, idx in ipairs(self.links) do - local item = self.building.contained_items[idx].item + local entry = self.building.contained_items[idx] + if entry then + table.insert(items, entry.item) + end + end + + for _, item in ipairs(items) do local target = get_mech_target(item) if target then