mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 08:04:53 +09:00
Review points from Cooldude: - Feature.register(name[, config]) is required again and returns a feature object. Each feature has its config, its fields, and its overrides, so one registry holds all the data. Only the overrides are stored. On load each feature points at its own stored table again. - The methods are feature:guard(lib), feature:update_config(enabled, values), and feature:is_enabled(). Feature.get(name) looks up a feature, and commands flag the feature object. - update_config raises on_config_updated once per changed value, with the feature name, path, old value, and new value. The gui refresh only reacts to the "enabled" path. - apply only sets values. on_apply is gone. Lookup sets built from lists use Feature.to_set, which is cached by the list itself. A replaced list gets a new set, so the sets are also correct after a load. Protection exposes getters for its protected names and types. Web: settings use small inputs, the same size as the config pages. Fields are shaped like clusterio config field definitions. A field with an inputComponent renders the component registered with the web interface, so the role setting now uses core's role input, and other plugin inputs can be used later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
285 lines
9.4 KiB
Lua
285 lines
9.4 KiB
Lua
--[[-- Control - Mine Depletion
|
|
Marks mining drills for deconstruction when resources deplete
|
|
]]
|
|
|
|
local Async = require("modules/exp_util/async")
|
|
local Feature = require("modules/exp_scenario/features")
|
|
|
|
local feature = Feature.register("mine_depletion", {
|
|
fluid = true, --- @setting fluid When true, checks for for fluid pipes when removing miners
|
|
chest = true, --- @setting chest When true, checks for for chest when removing miners
|
|
beacon = true, --- @setting beacon When true, checks for for beacon when removing miners
|
|
})
|
|
local config = feature.config
|
|
|
|
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
|
|
local entities = target.surface.find_entities_filtered{
|
|
type = { "mining-drill", "inserter" },
|
|
to_be_deconstructed = false,
|
|
area = {
|
|
left_top = { x = target_position.x - 1, y = target_position.y - 1 },
|
|
right_bottom = { x = target_position.x + 1, y = 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
|
|
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
|
|
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
|
|
local drills = resource.surface.find_entities_filtered{
|
|
type = "mining-drill",
|
|
area = {
|
|
left_top = { x = position.x - max_mining_radius, y = position.y - max_mining_radius },
|
|
right_bottom = { x = position.x + max_mining_radius, y = 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
|
|
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 feature:guard{
|
|
events = {
|
|
[e.on_resource_depleted] = on_resource_depleted,
|
|
},
|
|
}
|