From 0e225de92c13b5e0eefbf43d0025e8d546491e59 Mon Sep 17 00:00:00 2001 From: DerFailer36 Date: Sat, 27 Jun 2026 20:55:53 +0200 Subject: [PATCH 01/30] Hide constructron_pathing_proxy (cherry picked from commit c64c31c5774f98cd639e2c0d53afcb8c38d6768d) --- data/constructron_pathing_proxy.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/constructron_pathing_proxy.lua b/data/constructron_pathing_proxy.lua index 85edf39..beb3cff 100644 --- a/data/constructron_pathing_proxy.lua +++ b/data/constructron_pathing_proxy.lua @@ -173,6 +173,7 @@ local template_entity = { icon_mipmaps = 0, flags = {"placeable-neutral", "not-on-map"}, order = "z", + hidden = true, max_health = 1, render_layer = "object", collision_mask = pathing_collision_mask, From 850c10ddf515ab2b9fed132d54f3009f033f9195 Mon Sep 17 00:00:00 2001 From: DerFailer36 Date: Sat, 27 Jun 2026 21:11:44 +0200 Subject: [PATCH 02/30] Update for 2.1, hide selection_tool (cherry picked from commit cc707ac0f768c21cafc2c93007b4e3b59eec6e9b) --- data/selection_tool.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/selection_tool.lua b/data/selection_tool.lua index 628e522..c65711d 100644 --- a/data/selection_tool.lua +++ b/data/selection_tool.lua @@ -4,6 +4,7 @@ data:extend( type = "selection-tool", name = "ctron-selection-tool", icons = {{ icon = "__Constructron-Continued__/graphics/icon_texture.png", icon_size = 128, mipmap_count = 2 }}, + hidden = true, always_include_tiles = true, skip_fog_of_war = false, stack_size = 1, @@ -36,7 +37,7 @@ data:extend( reverse_select = { border_color = { r = 1 }, -- red cursor_box_type = "entity", - mode = { "deconstruct", "items", "trees" } + mode = { "deconstruct", "items", "trees", "any-tile" } }, -- shift right click @@ -48,4 +49,4 @@ data:extend( }, } } -) \ No newline at end of file +) From 84a54b704523cd56786122b9517c226637b39df0 Mon Sep 17 00:00:00 2001 From: DerFailer36 Date: Sat, 27 Jun 2026 21:13:28 +0200 Subject: [PATCH 03/30] Hide station_combinator (cherry picked from commit 5c8856aa048968decc883077bc5a940ace013c17) --- data/station_combinator.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/station_combinator.lua b/data/station_combinator.lua index c1ffaae..9099eb9 100644 --- a/data/station_combinator.lua +++ b/data/station_combinator.lua @@ -5,5 +5,6 @@ station_combinator.selectable_in_game = false station_combinator.item_slot_count = 100 station_combinator.draw_circuit_wires = false station_combinator.flags = { "hide-alt-info", "not-blueprintable", "not-on-map", } -station_combinator.collision_mask = { layers = {}} -data:extend({station_combinator}) \ No newline at end of file +station_combinator.collision_mask = { layers = {} } +station_combinator.hidden = true +data:extend({station_combinator}) From 9d549c8b7281766b2a541e9fb8399670729179f9 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 08:23:57 +1000 Subject: [PATCH 04/30] fix: Skip logistic network checks for zero-count requests. (cherry picked from commit e0871008242764a19caa28db9f22563fb04682d8) --- script/job.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/script/job.lua b/script/job.lua index 247bf41..c1a1371 100644 --- a/script/job.lua +++ b/script/job.lua @@ -772,7 +772,7 @@ function job:check_items_are_available() local logistic_network = self.station.logistic_network for request_name, value in pairs(current_requests) do for quality, count in pairs(value) do - if logistic_network.can_satisfy_request({name = request_name, quality = quality}, count, true) then + if count > 0 and logistic_network.can_satisfy_request({name = request_name, quality = quality}, count, true) then return true end end @@ -801,11 +801,9 @@ function job:check_roaming_candidate(station, current_items, current_requests) if not station_logistic_network then return false end for request_name, value in pairs(current_requests) do for quality, count in pairs(value) do - local needed_count = count - if current_items[request_name] and current_items[request_name][quality] then - needed_count = count - current_items[request_name][quality] - end - if station_logistic_network.can_satisfy_request({name = request_name, quality = quality}, needed_count, true) then + local held = (current_items[request_name] and current_items[request_name][quality]) or 0 + local needed_count = count - held + if needed_count > 0 and station_logistic_network.can_satisfy_request({name = request_name, quality = quality}, needed_count, true) then debug_lib.VisualDebugText({"ctron_status.trying_diff_station"}, self.worker, 0, 5) self.sub_state = nil self.station = station From 598e7ca3987b9f22f3649bb36770180d176c0f1c Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 08:24:12 +1000 Subject: [PATCH 05/30] fix: Cargo request item count now respects quality, and queue entries are deep-copied. (cherry picked from commit 04f9da890b271c4a3ac8aaaf99084ad0a6be2f27) --- script/job/cargo.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/job/cargo.lua b/script/job/cargo.lua index ed4e549..26a83b4 100644 --- a/script/job/cargo.lua +++ b/script/job/cargo.lua @@ -38,7 +38,7 @@ function cargo_job.prospect_cargo_jobs() if not next(items_to_fullfill) then goto continue end -- top up all items at the station for id, request in pairs(requests) do - local current_count = station.logistic_network.get_item_count(request.name) + local current_count = station.logistic_network.get_item_count(request) if ((current_count + request.in_transit_count) < request.max) then if not (items_to_fullfill[request.name] and items_to_fullfill[request.name][request.quality]) then -- ignore if already in the list local total = request.max - current_count - request.in_transit_count @@ -152,7 +152,7 @@ function cargo_job:setup() storage.cargo_queue[self.surface_index][storage.cargo_index] = { index = storage.cargo_index, station = self.destination_station, - items = self.required_items + items = table.deepcopy(self.required_items) } end cargo_job.process_cargo_job_queue(self.surface_index) From 5c76674508c2cb428799a52837fbcbb3e806ff3d Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 08:24:18 +1000 Subject: [PATCH 06/30] fix: Prevented in_transit_count from going negative on delivery or cancel. (cherry picked from commit 6678e1c7d6ee4dc797a534a353bb99d4ddc5d718) --- script/job/cargo.lua | 2 +- script/ui.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/job/cargo.lua b/script/job/cargo.lua index 26a83b4..bca1ebb 100644 --- a/script/job/cargo.lua +++ b/script/job/cargo.lua @@ -227,7 +227,7 @@ function cargo_job:deliver_items() for item, value in pairs(self.required_items) do for quality, count in pairs(value) do if (request.name == item) and (request.quality == quality) then - request.in_transit_count = request.in_transit_count - count + request.in_transit_count = math.max(0, request.in_transit_count - count) section.set_slot(slot, { value = { name = item, diff --git a/script/ui.lua b/script/ui.lua index ddd9945..823e0f2 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -580,14 +580,14 @@ function gui_handlers.ctron_cancel_job(player, element, ctrl_clicked) if job.worker then job.worker.autopilot_destination = nil end - if job.job_type == "cargo" then + if job.job_type == "cargo" and job.sub_state ~= "unloading_items" then local station_unit_number = job.destination_station.unit_number for _, request in pairs(storage.station_requests[station_unit_number]) do local requested_item = request.name for item, value in pairs(job.required_items) do for quality, item_count in pairs(value) do if requested_item == item and request.quality == quality then - request.in_transit_count = request.in_transit_count - item_count + request.in_transit_count = math.max(0, request.in_transit_count - item_count) break end end From 3eb435c1bf6bc38a032f5ac8965e8df5d0bd3248 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 09:20:50 +1000 Subject: [PATCH 07/30] annotation fixes --- control.lua | 2 +- data/collision-mask-util-extended.lua | 1 + data/compatibility/nullius.lua | 2 ++ script/job.lua | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/control.lua b/control.lua index 93bb59b..0cf3e5e 100644 --- a/control.lua +++ b/control.lua @@ -388,7 +388,7 @@ script.on_event(ev.on_lua_shortcut, function (event) end end) ----@param event EventData.on_custom_input +---@param event EventData.CustomInputEvent script.on_event("ctron-get-selection-tool", function (event) local name = event.input_name if name ~= "ctron-get-selection-tool" then return end diff --git a/data/collision-mask-util-extended.lua b/data/collision-mask-util-extended.lua index 96f215f..7e41cd3 100644 --- a/data/collision-mask-util-extended.lua +++ b/data/collision-mask-util-extended.lua @@ -1,4 +1,5 @@ -- luacheck: ignore +---@diagnostic disable: missing-fields, unused-local, unused-function -- File Licence: You can copy and distribute this file regardless of the mod's general licence. This licence exception does not affect other files of this mod. local collision_mask_util_extended = require("__core__/lualib/collision-mask-util") diff --git a/data/compatibility/nullius.lua b/data/compatibility/nullius.lua index 0b3acb2..01b738c 100644 --- a/data/compatibility/nullius.lua +++ b/data/compatibility/nullius.lua @@ -9,7 +9,9 @@ station_item.order = "nullius-z" .. station_item.order local ctron_recipe = data.raw.recipe["constructron"] local station_recipe = data.raw.recipe["service_station"] +---@diagnostic disable-next-line: inject-field ctron_recipe.category = "huge-crafting" +---@diagnostic disable-next-line: inject-field station_recipe.category = "large-crafting" ctron_recipe.order = ctron_item.order station_recipe.order = station_item.order diff --git a/script/job.lua b/script/job.lua index c1a1371..31d1f59 100644 --- a/script/job.lua +++ b/script/job.lua @@ -1,7 +1,7 @@ local util_func = require("script/utility_functions") local debug_lib = require("script/debug_lib") local pathfinder = require("script/pathfinder") -local entity_proc = require("entity_processor") +local entity_proc = require("script/entity_processor") -- class for jobs local job = {} ---@class job From 4db8e83092f2e0f1f308d6de8bd2b8255be49057 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 09:22:21 +1000 Subject: [PATCH 08/30] fix: Minion jobs now clear their autopilot_destination upon job completion. (cherry picked from commit dd173cbe0eaadcb8cad052c1fefa1ac12bc47dc1) --- script/job.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/job.lua b/script/job.lua index 31d1f59..643e447 100644 --- a/script/job.lua +++ b/script/job.lua @@ -1186,7 +1186,8 @@ function job:finishing() util_func.set_constructron_status(worker, 'busy', false) -- change selected constructron colour util_func.paint_constructron(worker, 'idle') - + -- clear autopilot_destination (for minion jobs) + worker.autopilot_destination = nil -- release vassals for _, vassal_job in pairs((self.vassal_jobs or {})) do vassal_job.state = "finishing" From 0e61e039e22f43d84d61c9bc357a07555aa53e37 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 11:14:22 +1000 Subject: [PATCH 09/30] fix: Fixed a crash that occurred when attempting to cancel a Cargo job through the UI. (cherry picked from commit b2bc8f3eb6354b85b0a62b92e9006b623f253f1b) --- script/ui.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/script/ui.lua b/script/ui.lua index 823e0f2..b114bab 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -581,14 +581,19 @@ function gui_handlers.ctron_cancel_job(player, element, ctrl_clicked) job.worker.autopilot_destination = nil end if job.job_type == "cargo" and job.sub_state ~= "unloading_items" then - local station_unit_number = job.destination_station.unit_number - for _, request in pairs(storage.station_requests[station_unit_number]) do - local requested_item = request.name - for item, value in pairs(job.required_items) do - for quality, item_count in pairs(value) do - if requested_item == item and request.quality == quality then - request.in_transit_count = math.max(0, request.in_transit_count - item_count) - break + if job.destination_station and job.destination_station.valid then + local station_unit_number = job.destination_station.unit_number + local station_requests = storage.station_requests[station_unit_number] + if station_requests then + for _, request in pairs(station_requests) do + local requested_item = request.name + for item, value in pairs(job.required_items) do + for quality, item_count in pairs(value) do + if requested_item == item and request.quality == quality then + request.in_transit_count = math.max(0, request.in_transit_count - item_count) + break + end + end end end end From b127cfa41f415e4681ea1c2bfaf44b14f295ccae Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 11:20:14 +1000 Subject: [PATCH 10/30] fix: Fixed a crash when hovering on the ui locate button whilst the destination entity was invalid. (cherry picked from commit 4ea617a052660557764d1474bae480f15754f37e) --- script/ui.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/ui.lua b/script/ui.lua index b114bab..c28a786 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -1185,8 +1185,10 @@ function gui_handlers.on_gui_hover_job(player, element) if not job then return end local _, chunk = next(job.chunks) chunk = chunk or {} - local destination_station = job.destination_station or {} - local position = (chunk.midpoint or destination_station.position or job.task_positions[1] or job.worker.position) + local destination_station = job.destination_station + local station_position = destination_station and destination_station.valid and destination_station.position + local worker_position = job.worker and job.worker.valid and job.worker.position + local position = (chunk.midpoint or station_position or job.task_positions[1] or worker_position) if not position then return end if player.gui.screen.ctron_hover_frame then player.gui.screen.ctron_hover_frame.destroy() From c25d53ce4d0c196db66485fba439b50555f66bb1 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 11:51:11 +1000 Subject: [PATCH 11/30] fix: Fixed a crash that could occur after clicking the job locate button whilst entities were invalid. (cherry picked from commit e4160b1ab0d1aadba68a297212071cdad9e8fee7) --- script/ui.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/script/ui.lua b/script/ui.lua index c28a786..e91fb19 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -500,7 +500,13 @@ function gui_handlers.ctron_locate_job(player, element) end local _, chunk = next(job.chunks) chunk = chunk or {} - local position = (chunk.midpoint or job.station.position or job.worker.position) + local station_position = job.station and job.station.valid and job.station.position + local worker_position = job.worker and job.worker.valid and job.worker.position + local position = (chunk.midpoint or station_position or worker_position) + if not position then + player.print({"ctron_warnings.job_no_exist"}) + return + end player.set_controller{ type = defines.controllers.remote, position = position, surface = job.surface_index } -- move UI for visibility gui_handlers.resize_gui(player) From 241fb50eb77daf05f3029aabfbf5701c8c4331fb Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 20:01:06 +1000 Subject: [PATCH 12/30] fix: Clear invalid worker/station references on destroy and guard minion and cargo-window access --- script/entity_processor.lua | 5 +++++ script/job/minion.lua | 2 +- script/ui.lua | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/script/entity_processor.lua b/script/entity_processor.lua index c7de964..3f2d5e8 100644 --- a/script/entity_processor.lua +++ b/script/entity_processor.lua @@ -330,6 +330,11 @@ entity_proc.on_object_destroyed = function(event) util_func.setup_station_combinator(new_station) end end + for _, job in pairs(storage.jobs) do + if job.worker and job.worker.valid == false then job.worker = nil end + if job.station and job.station.valid == false then job.station = nil end + if job.destination_station and job.destination_station.valid == false then job.destination_station = nil end + end storage.registered_entities[event.registration_number] = nil end diff --git a/script/job/minion.lua b/script/job/minion.lua index ca8fad6..1be0df3 100644 --- a/script/job/minion.lua +++ b/script/job/minion.lua @@ -85,7 +85,7 @@ function minion_job:in_progress() self.job_status = "Following leader" local worker = self.worker ---@cast worker -nil -- check if the leader is alive and job is still valid - if not storage.jobs[self.primary_job.job_index] or not self.primary_job.worker then + if not storage.jobs[self.primary_job.job_index] or not (self.primary_job.worker and self.primary_job.worker.valid) then self.state = "finishing" -- IDEA: assign util to primary? worker.autopilot_destination = nil return diff --git a/script/ui.lua b/script/ui.lua index e91fb19..00b1d12 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -992,6 +992,7 @@ function gui_handlers.open_cargo_window(player, element) return end local station = storage.service_stations[element.tags.station_unit_number] + if not (station and station.valid) then return end gui_cargo.buildCargoGui(player, game.surfaces[station.surface.name]) gui_handlers.cargo_hide_stations(player, element) else From fb1e8cc72258e050beaab7e81f11ed14404acf84 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 11:53:30 +1000 Subject: [PATCH 13/30] Fix: Guarded a potential crash in the UI when the worker was invalid. (cherry picked from commit 38b2d5e8b7729b0323b9dced3e77b3ad2c661efa) --- script/ui.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/script/ui.lua b/script/ui.lua index 00b1d12..9f81807 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -532,16 +532,21 @@ end function gui_handlers.ctron_map_view(player, element) local job = storage.jobs[element.tags.job_index] if not job then return end + -- worker/station may be nil or invalid if the entity was removed while the job was active + local worker = (job.worker and job.worker.valid) and job.worker or nil if element.tags.follow_entity then - player.set_controller{ type = defines.controllers.remote, surface = job.worker.surface} - player.centered_on = job.worker + if not worker then return end + player.set_controller{ type = defines.controllers.remote, surface = worker.surface} + player.centered_on = worker else local current_task = 1 if #job.task_positions > 1 then current_task = math.random(1, #job.task_positions) end - local position = (job.task_positions[current_task] or job.station.position or job.worker.position) - player.set_controller{ type = defines.controllers.remote, position = position, surface = job.worker.surface } + local station_position = job.station and job.station.valid and job.station.position + local position = (job.task_positions[current_task] or station_position or (worker and worker.position)) + if not position then return end + player.set_controller{ type = defines.controllers.remote, position = position, surface = (worker and worker.surface) or job.surface_index } -- draw chunks for _, chunk_to_draw in pairs(job.chunks) do debug_lib.draw_rectangle(chunk_to_draw.minimum, chunk_to_draw.maximum, job.surface_index, job_colors[job.job_type], true, 120) From 99a978f7ce36aac6803e7fe0eea1558f3f09136f Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 12:18:46 +1000 Subject: [PATCH 14/30] fix: Fixed a crash when station cargo settings were pasted without any being copied first. (cherry picked from commit b00152df047dd2beac9ebc7ac93060807d393d16) --- script/ui.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/ui.lua b/script/ui.lua index 9f81807..9a0feda 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -1183,7 +1183,12 @@ function gui_handlers.copy_station_requests(player, element) end function gui_handlers.paste_station_requests(player, element) - storage.station_requests[element.tags.station_unit_number] = table.deepcopy(storage.user_interface[player.index]["settings_export"]) + local settings_export = storage.user_interface[player.index]["settings_export"] + if not settings_export then + player.print({"ctron_warnings.no_requests_copied"}) + return + end + storage.station_requests[element.tags.station_unit_number] = table.deepcopy(settings_export) -- reset in_transit count on all requests for the new station for _, request in pairs(storage.station_requests[element.tags.station_unit_number]) do request.in_transit_count = 0 From 68839638ea4e4d3c192e14d0e7f78af7747f3cac Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 12:19:20 +1000 Subject: [PATCH 15/30] fix: Fixed a potential divide by zero crash in Cargo jobs. (cherry picked from commit c9ef2fbf00d5e147af97069320d1b67d14690404) --- script/job/cargo.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/job/cargo.lua b/script/job/cargo.lua index bca1ebb..9eef12d 100644 --- a/script/job/cargo.lua +++ b/script/job/cargo.lua @@ -140,7 +140,7 @@ function cargo_job:setup() local total_required_slots = util_func.calculate_required_inventory_slot_count(self.required_items) if total_required_slots > self.empty_slot_count then -- job will not fit in one constructron, split the job to use multiple constructrons - local divisor = math.ceil(total_required_slots / (self.empty_slot_count - 1)) + local divisor = math.ceil(total_required_slots / math.max(1, self.empty_slot_count - 1)) for item, value in pairs(self.required_items) do for quality, count in pairs(value) do self.required_items[item][quality] = math.ceil(count / divisor) From 266e9c01de1287a411ef5b79ede5a861f1d1b6a3 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 12:19:45 +1000 Subject: [PATCH 16/30] random improvements (cherry picked from commit 88fecc177e8587aedbb94c90eb27282e53baf67e) --- locale/en/locale.cfg | 3 ++- script/ui.lua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/locale/en/locale.cfg b/locale/en/locale.cfg index a5f8749..b0cf9eb 100644 --- a/locale/en/locale.cfg +++ b/locale/en/locale.cfg @@ -187,4 +187,5 @@ station_no_exist=This station no longer exists. item_already_requested=This item is already requested on this station. job_no_exist=This job no longer exists. job_finished=Job is already finishing. -invalid_ammo=Invalid ammo selection. \ No newline at end of file +invalid_ammo=Invalid ammo selection. +no_requests_copied=No station requests have been copied yet. \ No newline at end of file diff --git a/script/ui.lua b/script/ui.lua index 9a0feda..ac7acc7 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -588,7 +588,7 @@ function gui_handlers.ctron_cancel_job(player, element, ctrl_clicked) if job.state ~= "finishing" then -- handle job cancellation job.state = "finishing" - if job.worker then + if job.worker and job.worker.valid then job.worker.autopilot_destination = nil end if job.job_type == "cargo" and job.sub_state ~= "unloading_items" then From 91d2cedbf366ffbff1df32838d10cac005af3c7e Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:20:37 +1000 Subject: [PATCH 17/30] fix: Destroy jobs fall back to the default gun range for unknown quality tiers (cherry picked from commit af551365eefb012c9b96685679bb612b528bfda1) --- script/job/destroy.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/job/destroy.lua b/script/job/destroy.lua index ffc065e..6684426 100644 --- a/script/job/destroy.lua +++ b/script/job/destroy.lua @@ -57,7 +57,7 @@ function destroy_job:setup() local repair_tool = storage.repair_tool_name[self.surface_index] self.required_items[repair_tool.name] = { [repair_tool.quality] = 16} -- set the gun range based on the workers quality - self.gun_range = gun_range[self.worker.quality.name] + self.gun_range = gun_range[self.worker.quality.name] or 36 -- disable roboports self:disable_roboports(0) -- state change From eb6f9c718f4f4947ef92c839dfcadaeefbe09eb8 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:20:37 +1000 Subject: [PATCH 18/30] fix: Guard against a missing upgrade target in upgrade jobs (cherry picked from commit b723de1f325f8d7425cea699667aa06c3417907e) --- script/job/upgrade.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/job/upgrade.lua b/script/job/upgrade.lua index a346e7e..076370d 100644 --- a/script/job/upgrade.lua +++ b/script/job/upgrade.lua @@ -21,9 +21,9 @@ function upgrade_job:specific_action() } -- only detects entities in range local can_upgrade_entity = false for _, entity in pairs(entities) do - local target, target_quality = entity.get_upgrade_target() ---@cast target -nil - local item_name = target.items_to_place_this[1].name - if self.worker_logistic_network.can_satisfy_request({name = item_name, quality = target_quality}, 1, false) then -- does inventory have the required item? + local target, target_quality = entity.get_upgrade_target() + local item = target and target.items_to_place_this[1] + if item and self.worker_logistic_network.can_satisfy_request({name = item.name, quality = target_quality}, 1, false) then -- does inventory have the required item? can_upgrade_entity = true end end From 2d52fa59cf9dcd4dd330bdb0cca4d7f45dc221eb Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:20:37 +1000 Subject: [PATCH 19/30] fix: Ignore superseded pathfinder results so a stale path can't overwrite a newer one (cherry picked from commit 5243e1bd9a17d572fa1d0735d2759abda2dd6bf2) --- script/pathfinder.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/pathfinder.lua b/script/pathfinder.lua index c446edb..cdfda59 100644 --- a/script/pathfinder.lua +++ b/script/pathfinder.lua @@ -54,6 +54,10 @@ end script.on_event(defines.events.on_script_path_request_finished, (function(event) local job = storage.pathfinder_requests[event.id] if not job or not job.worker or not job.worker.valid then return end + if job.path_request_id ~= event.id then -- a newer request superseded this one; ignore the stale result + storage.pathfinder_requests[event.id] = nil + return + end local path = event.path if event.try_again_later then -- COMMENT: job should handle itself From 44d77faeb28844cad9cdd41b033beefbb8fc579e Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:20:37 +1000 Subject: [PATCH 20/30] fix: Guard worker validity in the 2.0.9 migration to avoid a load-time crash (cherry picked from commit 5355bdb71462c7aa3c1737e746e52ff923e4ac6f) --- migrations/Constructron-Continued_2.0.9.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/Constructron-Continued_2.0.9.lua b/migrations/Constructron-Continued_2.0.9.lua index 2ba5003..62d57d3 100644 --- a/migrations/Constructron-Continued_2.0.9.lua +++ b/migrations/Constructron-Continued_2.0.9.lua @@ -11,7 +11,7 @@ for _, job in pairs(storage.jobs or {}) do end end end - local logistic_point = job.worker.get_logistic_point(0) + local logistic_point = job.worker and job.worker.valid and job.worker.get_logistic_point(0) if logistic_point then local section = logistic_point.get_section(1) if section then From 49bdf095950141a8ff60dd203735fcafc63cb414 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:21:23 +1000 Subject: [PATCH 21/30] fix: Rebuild and upgrade jobs no longer act on entities of non-player forces (cherry picked from commit ccff2844c5d453a96bec6552dcf63a837f277a0c) --- script/entity_processor.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/entity_processor.lua b/script/entity_processor.lua index 3f2d5e8..950f356 100644 --- a/script/entity_processor.lua +++ b/script/entity_processor.lua @@ -160,7 +160,7 @@ script.on_event(ev.on_robot_built_entity, entity_proc.on_built_entity, { script.on_event(ev.on_post_entity_died, function(event) local ghost_entity = event.ghost ---@cast ghost_entity -nil if not (ghost_entity and ghost_entity.valid) then return end - if not ghost_entity.force.name == "player" then return end + if ghost_entity.force.name ~= "player" then return end local surface_index = ghost_entity.surface.index if storage.rebuild_job_toggle[surface_index] and (ghost_entity.type == 'entity-ghost') then entity_proc.create_chunk(ghost_entity, storage.construction_queue[surface_index], surface_index) @@ -179,7 +179,7 @@ end, {{filter = "type", type = "fish", invert = true, mode = "or"}}) script.on_event(ev.on_marked_for_upgrade, function(event) local entity = event.entity local surface_index = entity.surface.index - if not storage.upgrade_job_toggle[surface_index] or not entity or not entity.force.name == "player" then return end + if not storage.upgrade_job_toggle[surface_index] or entity.force.name ~= "player" then return end entity_proc.create_chunk(entity, storage.upgrade_queue[surface_index], surface_index) end) From 0f17e544ff72ab1e916ede97867bbb1f575c579d Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:21:40 +1000 Subject: [PATCH 22/30] fix: Prevent station counts from going negative on teleport or removal (cherry picked from commit d5d611aeaa9202e0d55c69c89c75aa0e888c3e45) --- script/entity_processor.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/entity_processor.lua b/script/entity_processor.lua index 950f356..a516485 100644 --- a/script/entity_processor.lua +++ b/script/entity_processor.lua @@ -273,7 +273,7 @@ script.on_event(ev.script_raised_teleported, function(event) storage.managed_surfaces[surface_index] = entity.surface.name end elseif storage.station_names[entity.name] then - storage.stations_count[event.old_surface_index] = storage.stations_count[event.old_surface_index] - 1 + storage.stations_count[event.old_surface_index] = math.max((storage.stations_count[event.old_surface_index] or 0) - 1, 0) storage.stations_count[surface_index] = storage.stations_count[surface_index] + 1 -- configure surface management if (storage.constructrons_count[surface_index] > 0) then @@ -314,7 +314,7 @@ entity_proc.on_object_destroyed = function(event) util_func.update_ctron_combinator_signals(removed_entity.surface) elseif storage.station_names[removed_entity.name] then if game.surfaces[surface_index] then - storage.stations_count[surface_index] = storage.stations_count[surface_index] - 1 + storage.stations_count[surface_index] = math.max((storage.stations_count[surface_index] or 0) - 1, 0) end storage.service_stations[event.useful_id] = nil -- surface management From b2871dc64e32f067b2a3d9a7d8ad9cc52beb0dee Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:22:08 +1000 Subject: [PATCH 23/30] fix: Avoid a nil comparison when held ammo quality differs from the requested quality (cherry picked from commit 48588f642433cb2f554a90991ac9cd397df7e219) --- script/job.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/script/job.lua b/script/job.lua index 643e447..0766377 100644 --- a/script/job.lua +++ b/script/job.lua @@ -1011,12 +1011,13 @@ function job:check_ammo_count() local ammo = storage.ammo_name[self.surface_index] local ammo_name = ammo.name local ammo_quality = ammo.quality - -- Check if the current ammo count is more than 25% of the expected ammo count. - -- If the ammo count is less than or equal to 25%, update the job state to "starting" and return false. - if not (ammunition and ammunition[ammo_name] and (ammunition[ammo_name][ammo_quality] > (math.ceil(ammo_count * 25 / 100)))) then - -- Additional check in worker inventory for extra ammo + -- start a restock if less than 25% of the expected ammo is present, in either the slots or the trunk + local threshold = math.ceil(ammo_count * 25 / 100) + local ammo_in_slots = ammunition[ammo_name] and ammunition[ammo_name][ammo_quality] + if not (ammo_in_slots and ammo_in_slots > threshold) then local inventory_ammo = util_func.convert_to_item_list(self.worker_inventory.get_contents()) - if not (inventory_ammo and inventory_ammo[ammo_name] and (inventory_ammo[ammo_name][ammo_quality] > (math.ceil(ammo_count * 25 / 100)))) then + local ammo_in_inventory = inventory_ammo[ammo_name] and inventory_ammo[ammo_name][ammo_quality] + if not (ammo_in_inventory and ammo_in_inventory > threshold) then self.state = "starting" return false end From 3a004f88ec0ecb0fd2b9510bc2c41d0c7b266af0 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:22:23 +1000 Subject: [PATCH 24/30] fix: Clamp the chunk-split divisor to avoid corrupt item counts when few inventory slots are free (cherry picked from commit 516a50d9b68eba9981ccb98bd2e2f0167f66e205) --- script/job.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/job.lua b/script/job.lua index 0766377..1894ee6 100644 --- a/script/job.lua +++ b/script/job.lua @@ -228,7 +228,7 @@ function job:claim_chunk(chunk) -- check for chunk overload local total_required_slots = util_func.calculate_required_inventory_slot_count(util_func.combine_tables { self.required_items, chunk.required_items, chunk.trash_items }) if total_required_slots > self.empty_slot_count then - local divisor = math.ceil(total_required_slots / (self.empty_slot_count - 5)) -- minus 5 slots to account for rounding up of items and trash + local divisor = math.ceil(total_required_slots / math.max(1, self.empty_slot_count - 5)) -- minus 5 slots to account for rounding up of items and trash for item, value in pairs(chunk.required_items) do for quality, count in pairs(value) do chunk.required_items[item] = { From cbecf887d3f18570a0456760ebbc45e1570fc8f0 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:22:36 +1000 Subject: [PATCH 25/30] fix: Query the player force when checking roboport construction coverage (cherry picked from commit 7dcf35f9d60226d658af3dbe5d9c9cc0e3be6f21) --- script/job.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/job.lua b/script/job.lua index 1894ee6..6f9d9f3 100644 --- a/script/job.lua +++ b/script/job.lua @@ -180,7 +180,7 @@ local find_entities = { } function job:check_roboport_coverage(entity) - local networks = entity.surface.find_logistic_networks_by_construction_area(entity.position, game.players[1].force) + local networks = entity.surface.find_logistic_networks_by_construction_area(entity.position, game.forces["player"]) if next(networks) then return true end return false end From 767d6384aa8b6f7c69996858fa382e64d0b03466 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:23:01 +1000 Subject: [PATCH 26/30] fix: Correct duplicate cargo-request detection so the same item and quality can't be requested twice (cherry picked from commit eb97b8c04ec27d1acacdef02c3ce949f6865350e) --- script/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/ui.lua b/script/ui.lua index ac7acc7..f731193 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -1081,7 +1081,7 @@ function gui_handlers.on_gui_elem_changed(player, element) local request = storage.station_requests[station_unit_number][slot_number] -- check if item is already requested for k, v in pairs(element.parent.children) do - if v.elem_value and v.elem_value == item and v.name ~= element.name then + if v.elem_value and v.elem_value.name == item and v.elem_value.quality == quality and v.name ~= element.name then element.elem_value = nil element.style = "ctron_slot_button" player.print({"ctron_warnings.item_already_requested"}) From 13a421137358bc766e626099faeac66ea30ab132 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:25:09 +1000 Subject: [PATCH 27/30] fix: Count logistic requests correctly so the request grid is padded to the right size (cherry picked from commit 045a8618b57a43d58784c69f81ba84db5c858f09) --- script/ui_main_screen.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/ui_main_screen.lua b/script/ui_main_screen.lua index dd00616..19e30f2 100644 --- a/script/ui_main_screen.lua +++ b/script/ui_main_screen.lua @@ -749,8 +749,10 @@ function gui_main.BuildLogisticsContent(player, surface_index, logistics_window) column_count = 10, } + local request_count = 0 for item, value in pairs(logistics_requests) do for quality, count in pairs(value) do + request_count = request_count + 1 local button = logistic_table.add{ type = "choose-elem-button", elem_type = "item-with-quality", @@ -790,7 +792,6 @@ function gui_main.BuildLogisticsContent(player, surface_index, logistics_window) end end - local request_count = #logistics_requests or 1 -- Round up to the nearest 10 local logistic_slot_count = math.ceil(request_count / 10) * 10 if request_count == logistic_slot_count then From f53b2e9a050fd6a572caca8b592bc871475faf66 Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:25:50 +1000 Subject: [PATCH 28/30] fix: Harden job and main GUIs against invalid workers, stations and missing logistic sections (cherry picked from commit 7d77e9df9f5c4f3c26442f70e776cbc145ff311a) --- script/ui.lua | 4 ++-- script/ui_job_screen.lua | 15 ++++++++------- script/ui_main_screen.lua | 9 +++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/script/ui.lua b/script/ui.lua index f731193..b5d6fee 100644 --- a/script/ui.lua +++ b/script/ui.lua @@ -366,7 +366,7 @@ function gui_handlers.update_job_gui(player) local job = storage.jobs[job_window.tags.job_index] if not job then return end local job_worker = job.worker - if not job_worker then return end -- TODO: refactor this behavior + if not (job_worker and job_worker.valid) then return end local job_ui_elements = storage.user_interface[player.index].job_ui.elements -- update worker_minimap @@ -375,7 +375,7 @@ function gui_handlers.update_job_gui(player) worker_minimap.entity = job_worker -- update job_minimap - job_ui_elements["job_minimap"].position = (job.task_positions[1] or job.station.position or job_worker.position) + job_ui_elements["job_minimap"].position = (job.task_positions[1] or (job.station and job.station.valid and job.station.position) or job_worker.position) -- update job stats job_ui_elements["job_tasks_stat_label"].caption = {"ctron_gui_locale.job_job_stat_tasks_label", (#job.task_positions or "0")} diff --git a/script/ui_job_screen.lua b/script/ui_job_screen.lua index 716482f..f45df59 100644 --- a/script/ui_job_screen.lua +++ b/script/ui_job_screen.lua @@ -156,7 +156,7 @@ function gui_job.buildJobContent(player, frame, job) follow_entity = true } } - storage.user_interface[player.index].job_ui.elements["worker_minimap"].entity = job.worker + storage.user_interface[player.index].job_ui.elements["worker_minimap"].entity = (job.worker and job.worker.valid) and job.worker or nil left_pane.add{ type = "line", @@ -186,7 +186,7 @@ function gui_job.buildJobContent(player, frame, job) tooltip = {"ctron_gui_locale.job_job_minimap_tooltip"}, zoom = 2, surface_index = job.surface_index, - position = (job.task_positions[1] or job.station.position or job.worker.position), + position = (job.task_positions[1] or (job.station and job.station.valid and job.station.position) or (job.worker and job.worker.valid and job.worker.position)), tags = { mod = "constructron", on_gui_click = "ctron_map_view", @@ -379,16 +379,17 @@ function gui_job.build_logistic_display(worker, logistic_table) local inventory = worker.get_inventory(defines.inventory.spider_trunk) assert(inventory, "Worker inventory is nil") -- get worker logistic requests - local logistic_point = worker.get_logistic_point(0) ---@cast logistic_point -nil - local section = logistic_point.get_section(1) - local slot_count = #(section.filters or {}) + local logistic_point = worker.get_logistic_point(0) + local section = logistic_point and logistic_point.get_section(1) + local filters = section and section.filters or {} + local slot_count = #filters -- Ensure the value is at least 10 local min_slot_count = math.max(slot_count, 10) -- Round up to the nearest 20 local logistic_request_count = math.ceil(min_slot_count / 20) * 20 for i = 1, logistic_request_count do - if section.filters[i] and section.filters[i].value then - local slot = section.filters[i] + if filters[i] and filters[i].value then + local slot = filters[i] local slot_item = slot.value gui_job.build_button(logistic_table, "ctron_logistic_button_" .. i, slot_item.name, slot_item.quality, ((slot.max or slot.min) - inventory.get_item_count(slot_item)), nil, worker) else diff --git a/script/ui_main_screen.lua b/script/ui_main_screen.lua index 19e30f2..36a5ae6 100644 --- a/script/ui_main_screen.lua +++ b/script/ui_main_screen.lua @@ -705,13 +705,14 @@ function gui_main.BuildLogisticsContent(player, surface_index, logistics_window) for _, job in pairs(storage.jobs) do if (job.surface_index == surface_index) and job.state == "starting" and job.worker and job.worker.valid then local worker = job.worker - local logistic_point = worker.get_logistic_point(0) ---@cast logistic_point -nil - local section = logistic_point.get_section(1) + local logistic_point = worker.get_logistic_point(0) + local section = logistic_point and logistic_point.get_section(1) + local filters = section and section.filters or {} local flag = false local inventory = worker.get_inventory(defines.inventory.spider_trunk) assert(inventory, "Worker inventory is nil") - for i = 1, #section.filters do - local slot = section.filters[i] + for i = 1, #filters do + local slot = filters[i] if slot.value then local item_name = slot.value.name local quality = slot.value.quality From 2d19e99b859fcc3821e064e2ea39aa091ebf61cb Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 18:26:06 +1000 Subject: [PATCH 29/30] other: Annotation/comment cleanups and scope a leaked job_label local (cherry picked from commit fdb16b7e3d29a9aeed6b38b1b51125b0d52ae72b) --- data/constructron_pathing_proxy.lua | 2 +- script/ui_main_screen.lua | 2 +- script/utility_functions.lua | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/constructron_pathing_proxy.lua b/data/constructron_pathing_proxy.lua index beb3cff..cd52acc 100644 --- a/data/constructron_pathing_proxy.lua +++ b/data/constructron_pathing_proxy.lua @@ -133,7 +133,7 @@ for _, type in pairs(types) do local collision_box = prototype.collision_box local width = collision_box[2][1] - collision_box[1][1] local height = collision_box[2][2] - collision_box[1][2] - -- Check if entity is larger than 7x7 tiles + -- large entities: >7 tiles in one dimension and >4 in the other if (width>7 and height>4) or (height>7 and width>4) then -- log("Entity '" .. name .. "' is larger than 7x7 tiles. Size: " .. width .. "x" .. height) local add = false diff --git a/script/ui_main_screen.lua b/script/ui_main_screen.lua index 36a5ae6..0726f2d 100644 --- a/script/ui_main_screen.lua +++ b/script/ui_main_screen.lua @@ -432,7 +432,7 @@ function gui_main.create_job_card(job, section) label_bg.style.color = job_colors[job_type] label_bg.style.width = 180 label_bg.style.bar_width = 30 -- bar thickness - job_label = label_bg.add{ + local job_label = label_bg.add{ type = "label", name = "ctron_job_type_label", caption = {"ctron_gui_locale.job_card_" .. job_type .. "_name"}, diff --git a/script/utility_functions.lua b/script/utility_functions.lua index 7ad90dc..2ad3caa 100644 --- a/script/utility_functions.lua +++ b/script/utility_functions.lua @@ -388,13 +388,13 @@ me.string_split = function(to_split, sep) return values end --- this function is used to get the first value of a LuaCustomTable +-- returns the first key of a LuaCustomTable (e.g. the prototype name from a name-indexed prototype table) -- https://discord.com/channels/139677590393716737/306402592265732098/1279094347052220447 ---@generic K,V ---@param lct LuaCustomTable ----@return V +---@return K me.firstoflct = function(lct) - for key, value in pairs(lct) do + for key, _ in pairs(lct) do return key end end From 08d010bd51f2c662fbb7b6008428d8dd99e1d79a Mon Sep 17 00:00:00 2001 From: ILLISIS Date: Sun, 28 Jun 2026 21:20:13 +1000 Subject: [PATCH 30/30] update info.json --- info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/info.json b/info.json index 6e9500e..d855ffe 100644 --- a/info.json +++ b/info.json @@ -7,7 +7,7 @@ "description": "A spidertron that automatically moves, constructs, upgrades and/or deconstructs entities of any size, anywhere on the map with items from your logistic network for the ultimate fire and forget automation experience and unmatched scaling potential.", "factorio_version": "2.0", "dependencies": [ - "base >= 1.1.77", + "base >= 2.0.0", "! Constructron", "(?) space-exploration", "(?) Krastorio2",