Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 24 additions & 4 deletions control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ end))

local ensure_storages = function()
storage.constructron_names = storage.constructron_names or { ["constructron"] = true, ["constructron-rocket-powered"] = true}
storage.flying_constructrons = storage.flying_constructrons or { ["constructron-rocket-powered"] = true }

-- Dynamically add custom constructron if configured
local custom_bases_string = settings.startup["custom-constructron-base"].value
if custom_bases_string and custom_bases_string ~= "" then
for custom_base_name in string.gmatch(custom_bases_string, '([^,]+)') do
custom_base_name = custom_base_name:match("^%s*(.-)%s*$") -- Trim whitespace
if custom_base_name and custom_base_name ~= "" then
local custom_ctron_name = custom_base_name .. "-constructron"
storage.constructron_names[custom_ctron_name] = true

-- Check if this custom constructron flies (no water collision)
local proto = prototypes.entity[custom_ctron_name]
if proto then
local layers = proto.collision_mask and (proto.collision_mask.layers or proto.collision_mask) or {}
if not layers["water_tile"] then
storage.flying_constructrons[custom_ctron_name] = true
end
end
end
end
end
Comment on lines 43 to +65
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

When ensure_storages is called via on_configuration_changed (e.g., when the setting is changed to remove a custom constructron), the or pattern on line 43 means that storage.constructron_names keeps its old value including any previously-registered custom entries. Because storage.constructron_names is never cleared of old custom constructron names, removing a name from the setting will not remove it from storage.constructron_names. Consequently, the mod will continue treating that entity as a valid constructron type even after removal. The custom constructron names should be rebuilt on every ensure_storages call to reflect the current settings, rather than relying on the persistent storage value.

Copilot uses AI. Check for mistakes.

storage.station_names = storage.station_names or { ["service_station"] = true }
--
storage.registered_entities = storage.registered_entities or {}
Expand Down Expand Up @@ -321,9 +344,6 @@ script.on_configuration_changed(init)

local ev = defines.events

-- script.on_event(ev.on_player_used_spidertron_remote, function(event)
-- end)

local research_handlers = {
["stronger-explosives-3"] = function()
storage.global_threat_modifier = math.max(0.4, storage.global_threat_modifier - 0.1)
Expand Down Expand Up @@ -534,4 +554,4 @@ remote.add_interface("ctron", {
["get-ctron-names"] = remote_get_ctron_names,
["add-station-names"] = remote_add_station_name,
["get-station-names"] = remote_get_station_names,
})
})
79 changes: 78 additions & 1 deletion data-final-fixes.lua
Original file line number Diff line number Diff line change
@@ -1,2 +1,79 @@
require("data/roboports")
require("data/constructron_pathing_proxy")
require("data/constructron_pathing_proxy")

-- Custom Constructron Logic (Supports comma-separated list)
local custom_bases_string = settings.startup["custom-constructron-base"].value

if custom_bases_string and custom_bases_string ~= "" then
-- Split by comma and loop
for custom_base_name in string.gmatch(custom_bases_string, '([^,]+)') do
custom_base_name = custom_base_name:match("^%s*(.-)%s*$") -- Trim extra whitespace

if custom_base_name and custom_base_name ~= "" then
local base_entity = data.raw["spider-vehicle"][custom_base_name]

if base_entity then
local custom_ctron_name = custom_base_name .. "-constructron"

-- Dynamic Localised Name: "{Original Name} (Constructron)"
local ctron_localised_name = {"", {"entity-name." .. custom_base_name}, " (Constructron)"}

-- Copy Entity
local new_entity = table.deepcopy(base_entity)
new_entity.name = custom_ctron_name
new_entity.minable.result = custom_ctron_name
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

On line 24, new_entity.minable.result = custom_ctron_name will throw a Lua error if the base entity's minable field is absent (i.e., the entity has no minable properties). While standard Factorio spider-vehicles are all minable, some modded spider-vehicles might not be, or may have a different minable structure. A nil-check on new_entity.minable before assigning result would prevent a data-stage crash for such edge cases.

Copilot uses AI. Check for mistakes.
new_entity.localised_name = ctron_localised_name

data:extend({new_entity})

-- Copy Item
local base_item = data.raw["item-with-entity-data"][custom_base_name] or data.raw["item"][custom_base_name]
if base_item then
local new_item = table.deepcopy(base_item)
new_item.name = custom_ctron_name
new_item.place_result = custom_ctron_name
new_item.localised_name = ctron_localised_name
data:extend({new_item})

-- Recipe (Create a recipe to turn the base vehicle into the constructron variant)
local base_recipe = data.raw["recipe"][custom_base_name]
if base_recipe then
local new_recipe = {
type = "recipe",
name = custom_ctron_name,
localised_name = ctron_localised_name,
enabled = base_recipe.enabled,
ingredients = {
{type = "item", name = custom_base_name, amount = 1}
},
results = {
{type = "item", name = custom_ctron_name, amount = 1}
},
energy = 1
}
data:extend({new_recipe})

-- Add to technology if base recipe is unlocked via research
for _, tech in pairs(data.raw["technology"]) do
if tech.effects then
for _, effect in pairs(tech.effects) do
if effect.type == "unlock-recipe" and effect.recipe == custom_base_name then
table.insert(tech.effects, {type = "unlock-recipe", recipe = custom_ctron_name})
end
end
end
end
end
end

-- Add to Selection Tool Filters so Shift+Click works
local selection_tool = data.raw["selection-tool"]["ctron-selection-tool"]
if selection_tool and selection_tool.alt_select and selection_tool.alt_select.entity_filters then
table.insert(selection_tool.alt_select.entity_filters, custom_ctron_name)
end
else
log("Constructron-Continued: Custom base entity '" .. custom_base_name .. "' not found. Could not generate custom constructron.")
end
end
end
end
16 changes: 11 additions & 5 deletions locale/en/locale.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ following_leader=Following the leader
[ctron_gui_locale]
all_logistics_req=All logistics requests
main_title=Constructron Control Center
station_label_caption=Service Station:
station_label_caption=Service Station:
surface_selector_tooltip=Select a surface to view Constructron jobs. Only shows surfaces with at least one Constructron and one Service Station.
settings=Settings
debug_button_tooltip=Displays additional information around the map
Expand All @@ -79,7 +79,7 @@ job_card_destroy_name=Destroy Job
job_card_utility_name=Utility Job
job_card_minion_name=Minion Job
job_status=Waiting for an available worker
chunk_delay=Starting in __1__ seconds
chunk_delay=Starting in 1 seconds
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The __1__ placeholder in chunk_delay has been replaced with the literal 1. This string is used as a Factorio localised string with a runtime-substituted parameter via {"ctron_gui_locale.chunk_delay", (math.floor(time / 60))} (see script/ui.lua:346 and script/ui_main_screen.lua:546). With the placeholder removed, the countdown timer will always display "Starting in 1 seconds" regardless of the actual remaining time. The original __1__ placeholder must be restored: chunk_delay=Starting in __1__ seconds.

Copilot uses AI. Check for mistakes.
job_locate_button=Locate
job_locate_button_tooltip=Click to view job location(s)
job_remote_button=Remote
Expand All @@ -92,7 +92,7 @@ main_no_jobs_label=No jobs
all_logistics_button=All Logistics
cargo_jobs_button=Cargo Jobs

logistic_requests_label=Constructrons with requests: __1__
logistic_requests_label=Constructrons with requests: 1
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The __1__ placeholder in logistic_requests_label has been replaced with the literal 1. This string is used with a runtime parameter via {"ctron_gui_locale.logistic_requests_label", ctron_count} (see script/ui_main_screen.lua:737). With the placeholder removed, the label will always show "Constructrons with requests: 1" instead of the actual count. The original __1__ placeholder must be restored: logistic_requests_label=Constructrons with requests: __1__.

Copilot uses AI. Check for mistakes.

settings_title=Constructron Settings
settings_global_settings_label=Global Settings
Expand Down Expand Up @@ -141,7 +141,7 @@ job_worker_minimap_label=Worker location
job_worker_minimap_tooltip=Click to follow worker
job_job_minimap_label=Job location
job_job_minimap_tooltip=Click to view job location(s)
job_job_stat_tasks_label=Number of tasks: __1__
job_job_stat_tasks_label=Number of tasks: 1
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The __1__ placeholder in job_job_stat_tasks_label has been replaced with the literal 1. This string is used with a runtime parameter via {"ctron_gui_locale.job_job_stat_tasks_label", (#job.task_positions or "0")} (see script/ui.lua:381 and script/ui_job_screen.lua:202). With the placeholder removed, the task count will always show "Number of tasks: 1" instead of the actual count. The original __1__ placeholder must be restored: job_job_stat_tasks_label=Number of tasks: __1__.

Copilot uses AI. Check for mistakes.
job_worker_ammo_label=Ammunition
job_worker_inventory_label=Inventory
job_worker_logistic_trash_label=Logistic trash
Expand Down Expand Up @@ -188,4 +188,10 @@ 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.

[mod-setting-name]
custom-constructron-base=Custom Constructron Base Vehicles

[mod-setting-description]
custom-constructron-base=A comma-separated list of spidertron internal entity names (e.g., spidertron, kr-advanced-spidertron). The mod will automatically generate a Constructron variant for each of these!
42 changes: 38 additions & 4 deletions script/command_functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,44 @@ me.reacquire_stations = function()
end
end

local ctron_entities = { "constructron", "constructron-rocket-powered" }
if not prototypes.entity["constructron-rocket-powered"] then
ctron_entities = {"constructron"}
end
local ctron_entities = setmetatable({}, {
__pairs = function()
local list = {}
if storage and storage.constructron_names then
for name in pairs(storage.constructron_names) do
if prototypes.entity[name] then table.insert(list, name) end
end
end
return pairs(list)
end,
__ipairs = function()
local list = {}
if storage and storage.constructron_names then
for name in pairs(storage.constructron_names) do
if prototypes.entity[name] then table.insert(list, name) end
end
end
return ipairs(list)
end,
__index = function(_, key)
local list = {}
if storage and storage.constructron_names then
for name in pairs(storage.constructron_names) do
if prototypes.entity[name] then table.insert(list, name) end
end
end
return list[key]
end,
__len = function()
local list = {}
if storage and storage.constructron_names then
for name in pairs(storage.constructron_names) do
if prototypes.entity[name] then table.insert(list, name) end
end
end
return #list
end
})
Comment on lines +165 to +202
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The list-building logic (iterating storage.constructron_names and filtering by prototypes.entity[name]) is duplicated identically in all four metamethods (__pairs, __ipairs, __index, and __len). This should be extracted into a local helper function to avoid the repeated code and make future changes easier to maintain.

Copilot uses AI. Check for mistakes.

me.reacquire_ctrons = function()
for _, surface in pairs(game.surfaces) do
Expand Down
4 changes: 2 additions & 2 deletions script/job.lua
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,12 @@ function job:claim_chunks_in_proximity()
end

function job:move_to_position(position)
local worker = self.worker
local worker = self.worker ---@cast worker -nil
if not worker or not worker.valid then return end
local distance = util_func.distance_between(worker.position, position)
worker.grid.inhibit_movement_bonus = (distance < 32)

if worker.name == "constructron-rocket-powered" then
if storage.flying_constructrons[worker.name] then
pathfinder.set_autopilot(worker, { { position = position, needs_destroy_to_reach = false } })
return
end
Expand Down
11 changes: 10 additions & 1 deletion settings.lua
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
-- no game settings defined
data:extend({
{
type = "string-setting",
name = "custom-constructron-base",
setting_type = "startup",
default_value = "",
allow_blank = true,
order = "z-a"
}
})