mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 23:54:52 +09:00
- Feature.register returns the config as a second value, so files write `local feature, config = Feature.register(...)`. - The lib is ExpScenario.Features and the instance class with its fields sits on Feature._prototype, which is the __index, the same shape as exp_commands. - update_config only looks at values whose override was added, removed, or replaced. _apply_field sets one value and returns it, so the comparison happens while applying and the second loop only raises. Overrides dropped from the previous set count as candidates because the value reverts to its default. - The `@setting name` tags are gone from the config comments. - Protection is guarded as a whole instead of checking config.enabled in one handler. The handlers also forget mined entities, so get_entities and is_entity_protected now drop invalid references, which also covers entities destroyed without an event. The list getters are gone, the selection overlay builds its lists from Protection.config. - Chat and damage popups are one feature in control/popups.lua. - ignore_admins and command_admin_only are removed, roles cover them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
284 lines
9.4 KiB
Lua
284 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, config = Feature.register("mine_depletion", {
|
|
fluid = true, -- When true, checks for for fluid pipes when removing miners
|
|
chest = true, -- When true, checks for for chest when removing miners
|
|
beacon = true, -- When true, checks for for beacon when removing miners
|
|
})
|
|
|
|
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,
|
|
},
|
|
}
|