Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0e225de
Hide constructron_pathing_proxy
DerFailer36 Jun 27, 2026
850c10d
Update for 2.1, hide selection_tool
DerFailer36 Jun 27, 2026
84a54b7
Hide station_combinator
DerFailer36 Jun 27, 2026
9d549c8
fix: Skip logistic network checks for zero-count requests.
Jun 27, 2026
598e7ca
fix: Cargo request item count now respects quality, and queue entries…
Jun 27, 2026
5c76674
fix: Prevented in_transit_count from going negative on delivery or ca…
Jun 27, 2026
3eb435c
annotation fixes
Jun 27, 2026
4db8e83
fix: Minion jobs now clear their autopilot_destination upon job compl…
Jun 27, 2026
0e61e03
fix: Fixed a crash that occurred when attempting to cancel a Cargo jo…
Jun 28, 2026
b127cfa
fix: Fixed a crash when hovering on the ui locate button whilst the d…
Jun 28, 2026
c25d53c
fix: Fixed a crash that could occur after clicking the job locate but…
Jun 28, 2026
241fb50
fix: Clear invalid worker/station references on destroy and guard min…
Jun 28, 2026
fb1e8cc
Fix: Guarded a potential crash in the UI when the worker was invalid.
Jun 28, 2026
99a978f
fix: Fixed a crash when station cargo settings were pasted without an…
Jun 28, 2026
6883963
fix: Fixed a potential divide by zero crash in Cargo jobs.
Jun 28, 2026
266e9c0
random improvements
Jun 28, 2026
91d2ced
fix: Destroy jobs fall back to the default gun range for unknown qual…
Jun 28, 2026
eb6f9c7
fix: Guard against a missing upgrade target in upgrade jobs
Jun 28, 2026
2d52fa5
fix: Ignore superseded pathfinder results so a stale path can't overw…
Jun 28, 2026
44d77fa
fix: Guard worker validity in the 2.0.9 migration to avoid a load-tim…
Jun 28, 2026
49bdf09
fix: Rebuild and upgrade jobs no longer act on entities of non-player…
Jun 28, 2026
0f17e54
fix: Prevent station counts from going negative on teleport or removal
Jun 28, 2026
b2871dc
fix: Avoid a nil comparison when held ammo quality differs from the r…
Jun 28, 2026
3a004f8
fix: Clamp the chunk-split divisor to avoid corrupt item counts when …
Jun 28, 2026
cbecf88
fix: Query the player force when checking roboport construction coverage
Jun 28, 2026
767d638
fix: Correct duplicate cargo-request detection so the same item and q…
Jun 28, 2026
13a4211
fix: Count logistic requests correctly so the request grid is padded …
Jun 28, 2026
f53b2e9
fix: Harden job and main GUIs against invalid workers, stations and m…
Jun 28, 2026
2d19e99
other: Annotation/comment cleanups and scope a leaked job_label local
Jun 28, 2026
08d010b
update info.json
Jun 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions data/collision-mask-util-extended.lua
Original file line number Diff line number Diff line change
@@ -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")

Expand Down
2 changes: 2 additions & 0 deletions data/compatibility/nullius.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion data/constructron_pathing_proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions data/selection_tool.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -48,4 +49,4 @@ data:extend(
},
}
}
)
)
5 changes: 3 additions & 2 deletions data/station_combinator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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})
station_combinator.collision_mask = { layers = {} }
station_combinator.hidden = true
data:extend({station_combinator})
2 changes: 1 addition & 1 deletion info.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion locale/en/locale.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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.
invalid_ammo=Invalid ammo selection.
no_requests_copied=No station requests have been copied yet.
2 changes: 1 addition & 1 deletion migrations/Constructron-Continued_2.0.9.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions script/entity_processor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
30 changes: 15 additions & 15 deletions script/job.lua
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1013,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
Expand Down Expand Up @@ -1188,7 +1187,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"
Expand Down
8 changes: 4 additions & 4 deletions script/job/cargo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion script/job/destroy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion script/job/minion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions script/job/upgrade.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions script/pathfinder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading