Files
factorio-scenario-ExpCluster/exp_scenario/module/control/mine_depletion.lua
T
bbassieandClaude Opus 5 3f46055b70 Enable need-check-nil and start the burn down
`Roles.new_role` returned the result of `error`, which made it nullable,
and `set_permission_group` returned nil for an unknown group, which
broke the config chain with an index error rather than saying so.

Positions and bounding boxes read back from the game always use the
named members, so they are narrowed once where they enter a function.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:31:43 +00:00

278 lines
9.1 KiB
Lua

--[[-- Control - Mine Depletion
Marks mining drills for deconstruction when resources deplete
]]
local Async = require("modules/exp_util/async")
local config = require("modules.exp_legacy.config.miner")
local floor = math.floor
--- Orders the deconstruction of an entity by its own force
local order_deconstruction_async =
Async.register(function(entity)
--- @cast entity LuaEntity
entity.order_deconstruction(entity.force)
end)
--- Reliability get the drop target of an entity
--- @param entity LuaEntity
--- @return LuaEntity?
local function get_drop_chest(entity)
-- First check the direct drop target
local target = entity.drop_target
if target and (target.type == "container" or target.type == "logistic-container" or target.type == "infinity-container") then
return target
end
-- Then check all entities at the drop position
local entities = entity.surface.find_entities_filtered{
position = entity.drop_position,
type = { "container", "logistic-container", "infinity-container" },
}
return #entities > 0 and entities[1] or nil
end
--- Check if an entity should has checked performed
--- @param entity LuaEntity
--- @return boolean
local function prevent_deconstruction(entity)
-- Already waiting to be deconstructed
if not entity.valid or entity.to_be_deconstructed() then
return true
end
-- Not minable, selectable, or deconstructive
if not entity.minable_flag or not entity.prototype.selectable_in_game or entity.has_flag("not-deconstructable") then
return true
end
-- Is connected to the circuit network
local red_write_connection = entity.get_wire_connector(defines.wire_connector_id.circuit_red, false)
local green_write_connection = entity.get_wire_connector(defines.wire_connector_id.circuit_green, false)
if red_write_connection and red_write_connection.connection_count > 0
or green_write_connection and green_write_connection.connection_count > 0 then
return true
end
return false
end
--- Check if an output chest should be deconstructed
--- @param entity LuaEntity
local function try_deconstruct_output_chest(entity)
-- Get a valid chest as the target
local target = get_drop_chest(entity)
if not target or prevent_deconstruction(target) then
return
end
-- Get all adjacent mining drills and inserters
local target_position = target.position --[[@as MapPosition.struct]]
local entities = target.surface.find_entities_filtered{
type = { "mining-drill", "inserter" },
to_be_deconstructed = false,
area = {
{ target_position.x - 1, target_position.y - 1 },
{ target_position.x + 1, target_position.y + 1 }
},
}
-- Check if any other entity is using this chest
for _, other in ipairs(entities) do
if other ~= entity and get_drop_chest(other) == target then
return
end
end
-- Deconstruct the chest
order_deconstruction_async:start_after(10, target)
end
--- Check if beacon should be deconstructed
--- @param entity LuaEntity
local function try_deconstruct_nearby_beacon(entity)
-- Get a valid chest as the target
local beacons = entity.get_beacons()
local beacon_in_use = false
-- if beacon is not supported
if not beacons then
return
end
for _, b in pairs(beacons) do
if not b.to_be_deconstructed() then
for _, r in pairs(b.get_beacon_effect_receivers()) do
if r ~= entity then
if not r.to_be_deconstructed() then
beacon_in_use = true
break
end
end
end
-- Deconstruct the beacon
if not beacon_in_use then
order_deconstruction_async:start_after(10, { name = b.name, position = b.position })
end
end
end
end
--- Check if a miner should be deconstructed
--- @param entity LuaEntity
local function try_deconstruct_miner(entity)
-- Check if the miner should be deconstructed
if prevent_deconstruction(entity) then
return
end
-- Check if there are any resources remaining for the miner
local surface = entity.surface
local resources = surface.find_entities_filtered{
type = "resource",
area = entity.mining_area,
}
for _, resource in ipairs(resources) do
if resource.amount > 0 then
return
end
end
-- Deconstruct the miner
order_deconstruction_async:start_after(10, entity)
-- Try deconstruct the output chest
if config.chest then
try_deconstruct_output_chest(entity)
end
-- Try deconstruct the beacon
if config.beacon then
try_deconstruct_nearby_beacon(entity)
end
-- Skip pipe build if not required
if not config.fluid or entity.fluids_count > 1 then
return
end
-- Build pipes if the miner used fluid
local position = entity.position --[[@as MapPosition.struct]]
local create_entity_position = { x = position.x, y = position.y }
local create_entity_param = { name = "entity-ghost", inner_name = "pipe", force = entity.force, position = create_entity_position }
local create_entity = surface.create_entity
create_entity(create_entity_param)
-- Find all the entities to connect to
local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]]
local search_area = {
{ bounding_box.left_top.x - 1, bounding_box.left_top.y - 1 },
{ bounding_box.right_bottom.x + 1, bounding_box.right_bottom.y + 1 },
}
local entities = surface.find_entities_filtered{ area = search_area, type = { "mining-drill", "pipe", "pipe-to-ground", "infinity-pipe" } }
local ghosts = surface.find_entities_filtered{ area = search_area, ghost_type = { "pipe", "pipe-to-ground", "infinity-pipe" } }
table.insert_array(entities, ghosts)
-- Check which directions to add pipes in
local pos_x, pos_y, neg_x, neg_y = false, false, false, false
for _, other in ipairs(entities) do
if (other.position.x > position.x) and (other.position.y == position.y) then
pos_x = true
elseif (other.position.x < position.x) and (other.position.y == position.y) then
neg_x = true
elseif (other.position.x == position.x) and (other.position.y > position.y) then
pos_y = true
elseif (other.position.x == position.x) and (other.position.y < position.y) then
neg_y = true
end
end
-- Build the pipes
if pos_x then
create_entity_position.y = floor(position.y)
for x = position.x + 1, bounding_box.right_bottom.x do
create_entity_position.x = x
create_entity(create_entity_param)
end
end
if neg_x then
create_entity_position.y = floor(position.y)
for x = floor(bounding_box.left_top.x), floor(position.x - 1) do
create_entity_position.x = x
create_entity(create_entity_param)
end
end
if pos_y then
create_entity_position.x = floor(position.x)
for y = floor(position.y + 1), floor(bounding_box.right_bottom.y) do
create_entity_position.y = y
create_entity(create_entity_param)
end
end
if neg_y then
create_entity_position.x = floor(position.x)
for y = floor(bounding_box.left_top.y), floor(position.y - 1) do
create_entity_position.y = y
create_entity(create_entity_param)
end
end
end
local max_quality = ""
for _, q in pairs(prototypes.quality) do
if not q.next then
max_quality = q.name
end
end
--- Get the max mining radius
local max_mining_radius = 0
for _, proto in pairs(prototypes.get_entity_filtered{ { filter = "type", type = "mining-drill" } }) do
if proto.mining_drill_radius then
max_mining_radius = math.max(assert(proto.get_mining_drill_radius(max_quality)), max_mining_radius)
end
end
--- Try deconstruct a miner when its resources deplete
--- @param event EventData.on_resource_depleted
local function on_resource_depleted(event)
local resource = event.entity
if resource.prototype.infinite_resource then
return
end
-- Find all mining drills within the area
local position = resource.position --[[@as MapPosition.struct]]
local drills = resource.surface.find_entities_filtered{
type = "mining-drill",
area = {
{ position.x - max_mining_radius, position.y - max_mining_radius },
{ position.x + max_mining_radius, position.y + max_mining_radius },
},
}
-- Check which could have reached this resource
for _, drill in pairs(drills) do
local radius = drill.prototype.mining_drill_radius
local drill_position = drill.position --[[@as MapPosition.struct]]
local dx = math.abs(drill_position.x - position.x)
local dy = math.abs(drill_position.y - position.y)
if dx <= radius and dy <= radius then
try_deconstruct_miner(drill)
end
end
end
local e = defines.events
return {
events = {
[e.on_resource_depleted] = on_resource_depleted,
},
}