416 lines
13 KiB
Lua

local util = {}
-- Position adjustments
function util.moveposition(position, offset)
return {x=position.x + offset.x, y=position.y + offset.y}
end
function util.offset(direction, longitudinal, orthogonal)
if direction == defines.direction.north then
return {x=orthogonal, y=-longitudinal}
end
if direction == defines.direction.south then
return {x=-orthogonal, y=longitudinal}
end
if direction == defines.direction.east then
return {x=longitudinal, y=orthogonal}
end
if direction == defines.direction.west then
return {x=-longitudinal, y=-orthogonal}
end
end
-- BoundingBox utilities
--[[
+----------------------+
| |
| |
| |
| O |
| |
+----------------------+
]]
function util.rotate_box(box, direction)
local left = box.left_top.x
local top = box.left_top.y
local right = box.right_bottom.x
local bottom = box.right_bottom.y
if direction == defines.direction.north then
return box
elseif direction == defines.direction.east then
-- 90 degree rotation
return {
left_top = {x=-bottom, y=left},
right_bottom = {x=-top, y=right},
}
elseif direction == defines.direction.south then
-- 180 degree rotation
return {
left_top = {x=-right, y=-bottom},
right_bottom = {x=-left, y=-top},
}
elseif direction == defines.direction.west then
-- 270 degree rotation
return {
left_top = {x=top, y=-right},
right_bottom = {x=bottom, y=-left},
}
else
error('invalid direction passed to rotate_box')
end
end
function util.move_box(box, offset)
return {
left_top = util.moveposition(box.left_top, offset),
right_bottom = util.moveposition(box.right_bottom, offset),
}
end
function util.expand_box(box, size)
return {
left_top = { x = box.left_top.x - size, y = box.left_top.y - size },
right_bottom = { x = box.right_bottom.x + size, y = box.right_bottom.y + size },
}
end
function util.entity_key(entity)
return entity.surface.name.."@"..entity.position.x..","..entity.position.y
end
-- Direction utilities
function util.is_ns(direction)
return direction == 0 or direction == 4
end
function util.is_ew(direction)
return direction == 2 or direction == 6
end
function util.opposite_direction(direction)
if direction >= 4 then
return direction - 4
end
return direction + 4
end
-- orientation utilities
-- hood_side returns the "back" or hood side of a loader or underground belt
function util.hood_side(entity)
if entity.type == "loader-1x1" and entity.loader_type == "output" then
return util.opposite_direction(entity.direction)
end
if entity.type == "underground-belt" and entity.belt_to_ground_type == "output" then
return util.opposite_direction(entity.direction)
end
return entity.direction
end
-- belt_side returns the "front" side of a loader or underground belt
function util.belt_side(entity)
if entity.type == "loader-1x1" and entity.loader_type == "input" then
return util.opposite_direction(entity.direction)
end
if entity.type == "underground-belt" and entity.belt_to_ground_type == "input" then
return util.opposite_direction(entity.direction)
end
return entity.direction
end
-- miniloader utilities
function util.find_miniloaders(params)
params.type = "loader-1x1"
local entities = params.surface.find_entities_filtered(params)
local out = {}
for i=1,#entities do
local ent = entities[i]
if util.is_miniloader(ent) then
out[#out+1] = ent
end
end
return out
end
function util.is_miniloader(entity)
return string.find(entity.name, "miniloader%-loader$") ~= nil
end
function util.is_miniloader_inserter(entity)
return util.is_miniloader_inserter_name(entity.name)
end
function util.is_miniloader_inserter_name(name)
return name:find("miniloader%-inserter$") ~= nil
end
function util.is_output_miniloader_inserter(inserter)
local orientation = util.orientation_from_inserter(inserter)
return orientation and orientation.type == "output"
end
-- 60 items/second / 60 ticks/second / 8 items/tile = X tiles/tick
local BELT_SPEED_FOR_60_PER_SECOND = 60/60/8
function util.num_inserters(entity)
return math.ceil(entity.prototype.belt_speed / BELT_SPEED_FOR_60_PER_SECOND) * 2
end
function util.pickup_position(entity)
if entity.loader_type == "output" then
return util.moveposition(entity.position, util.offset(entity.direction, -0.8, 0))
end
return util.moveposition(entity.position, util.offset(entity.direction, -0.2, 0))
end
local moveposition = util.moveposition
local offset = util.offset
-- drop positions for input (belt->chest) = { 0.7, +-0.25}, { 0.9, +-0.25}, {1.1, +-0.25}, {1.3, +-0.25}
-- drop positions for output (chest->belt) = {-0.2, +-0.25}, {-0.0, +-0.25}, {0.1, +-0.25}, {0.3, +-0.25}
function util.drop_positions(entity)
local base_offset = 1.2
if entity.loader_type == "output" then
base_offset = base_offset - 1
end
local out = {}
local dir = entity.direction
local p1 = moveposition(entity.position, offset(dir, base_offset, -0.25))
local p2 = moveposition(p1, offset(dir, 0, 0.5))
out[1] = p1
out[2] = p2
for i=1,3 do
local j = i * 2 + 1
out[j ] = moveposition(p1, offset(dir, -0.20*i, 0))
out[j+1] = moveposition(p2, offset(dir, -0.20*i, 0))
end
for i=0,3 do
local j = i * 2 + 9
out[j ] = moveposition(p1, offset(dir, -0.20*i, 0))
out[j+1] = moveposition(p2, offset(dir, -0.20*i, 0))
end
return out
end
function util.get_loader_inserters(entity)
local out = {}
for _, e in pairs(entity.surface.find_entities_filtered{
position = entity.position,
type = "inserter",
}) do
if util.is_miniloader_inserter(e) then
out[#out+1] = e
end
end
return out
end
local function update_miniloader_ghost(ghost, direction, type)
local position = ghost.position
-- We should normally destroy the ghost and recreate it facing the right direction,
-- but destroying an entity during its on_built_entity handler makes for compatibility
-- headaches.
-- add offset within tile to inform orientation_from_inserters that this ghost is preconfigured
if type == "input" then
ghost.pickup_position = moveposition(position, offset(direction, 0.25, 0.25))
ghost.drop_position = moveposition(position, offset(direction, 1, 0.25))
else
ghost.pickup_position = moveposition(position, offset(direction, -1, 0.25))
ghost.drop_position = moveposition(position, offset(direction, 0.25, 0.25))
end
end
function util.update_miniloader(entity, direction, type)
if entity.type == "entity-ghost" and entity.ghost_type == "inserter" then
return update_miniloader_ghost(entity, direction, type)
end
if entity.loader_type ~= type then
entity.rotate()
end
entity.direction = direction
util.update_inserters(entity)
end
local function dump_miniloader(entity)
local inserters = util.get_loader_inserters(entity)
local info = {}
for _, inserter in ipairs(inserters) do
info[#info+1] = inserter.unit_number..":"..serpent.line(inserter.get_or_create_control_behavior().circuit_condition)
end
return table.concat(info, "; ")
end
function util.update_inserters(entity)
local inserters = util.get_loader_inserters(entity)
local pickup = util.pickup_position(entity)
local drop = util.drop_positions(entity)
local direction = entity.direction
local loader_type = entity.loader_type
if loader_type == "input" then
direction = util.opposite_direction(direction)
end
for i=#inserters,1,-1 do
inserters[i].direction = direction
inserters[i].pickup_position = pickup
inserters[i].drop_position = drop[i]
if loader_type == "input" then
inserters[i].pickup_target = entity
else
inserters[i].drop_target = entity
end
end
end
function util.select_main_inserter(surface, position)
local inserters = surface.find_entities_filtered{type = "inserter", position = position}
if not next(inserters) then
inserters = surface.find_entities_filtered{ghost_type = "inserter", position = position}
end
if not next(inserters) then return nil end
for _, inserter in ipairs(inserters) do
if inserter.pickup_target == nil and inserter.drop_target == nil then
return inserter
end
end
return inserters[1]
end
function util.orientation_from_bp_inserter(bp_inserter)
local position_x = bp_inserter.position.x
local position_y = bp_inserter.position.y
local drop_position_x = bp_inserter.drop_position.x + position_x
local drop_position_y = bp_inserter.drop_position.y + position_y
local pickup_position_x = bp_inserter.pickup_position.x + position_x
local pickup_position_y = bp_inserter.pickup_position.y + position_y
if drop_position_x == position_x or drop_position_y == position_y then
return nil -- freshly placed with no inherited positions
elseif drop_position_x > position_x + 0.5 then
return {direction=defines.direction.east, type="input"}
elseif drop_position_x < position_x - 0.5 then
return {direction=defines.direction.west, type="input"}
elseif drop_position_y > position_y + 0.5 then
return {direction=defines.direction.south, type="input"}
elseif drop_position_y < position_y - 0.5 then
return {direction=defines.direction.north, type="input"}
elseif pickup_position_x > position_x + 0.5 then
return {direction=defines.direction.west, type="output"}
elseif pickup_position_x < position_x - 0.5 then
return {direction=defines.direction.east, type="output"}
elseif pickup_position_y > position_y + 0.5 then
return {direction=defines.direction.north, type="output"}
elseif pickup_position_y < position_y - 0.5 then
return {direction=defines.direction.south, type="output"}
end
end
function util.orientation_from_inserter(inserter)
if inserter.drop_position.x == inserter.position.x and inserter.drop_position.y == inserter.position.y then
return nil -- freshly placed with no inherited positions
elseif inserter.drop_position.x > inserter.position.x + 0.5 then
return {direction=defines.direction.east, type="input"}
elseif inserter.drop_position.x < inserter.position.x - 0.5 then
return {direction=defines.direction.west, type="input"}
elseif inserter.drop_position.y > inserter.position.y + 0.5 then
return {direction=defines.direction.south, type="input"}
elseif inserter.drop_position.y < inserter.position.y - 0.5 then
return {direction=defines.direction.north, type="input"}
elseif inserter.pickup_position.x > inserter.position.x + 0.5 then
return {direction=defines.direction.west, type="output", is_secondary=inserter.drop_position.y < inserter.position.y}
elseif inserter.pickup_position.x < inserter.position.x - 0.5 then
return {direction=defines.direction.east, type="output", is_secondary=inserter.drop_position.y > inserter.position.y}
elseif inserter.pickup_position.y > inserter.position.y + 0.5 then
return {direction=defines.direction.north, type="output", is_secondary=inserter.drop_position.x > inserter.position.x}
elseif inserter.pickup_position.y < inserter.position.y - 0.5 then
return {direction=defines.direction.south, type="output", is_secondary=inserter.drop_position.x < inserter.position.x}
end
end
function util.orientation_from_inserters(entity)
local inserter = util.select_main_inserter(entity.surface, entity.position)
return util.orientation_from_inserter(inserter)
end
function util.rebuild_belt(entity)
local surface = entity.surface
local name = entity.name
local protos = game.get_filtered_entity_prototypes{{filter="type", type=entity.type}}
local temporary_replacement
for proto_name in pairs(protos) do
if proto_name ~= name and proto_name ~= "__self" then
temporary_replacement = proto_name
break
end
end
if not temporary_replacement then return false end
local last_user = entity.last_user
local params = {
name = temporary_replacement,
position = entity.position,
direction = entity.direction,
force = entity.force,
fast_replace = true,
spill = false,
create_build_effect_smoke = false,
type = entity.type:find("loader") and entity.loader_type
or entity.type == "underground-belt" and entity.belt_to_ground_type,
}
surface.create_entity(params)
params.name = name
local belt = surface.create_entity(params)
if belt then
belt.last_user = last_user
return true
else
return false
end
end
local control_behavior_keys = {
"circuit_condition", "logistic_condition", "connect_to_logistic_network",
"circuit_read_hand_contents", "circuit_mode_of_operation", "circuit_hand_read_mode", "circuit_set_stack_size", "circuit_stack_control_signal",
}
function util.capture_settings(ghost)
local control_behavior = ghost.get_or_create_control_behavior()
local control_behavior_state = {}
for _, key in pairs(control_behavior_keys) do
control_behavior_state[key] = control_behavior[key]
end
local filters = {}
for i=1,ghost.filter_slot_count do
filters[i] = ghost.get_filter(i)
end
return {
control_behavior = control_behavior_state,
filters = filters,
}
end
function util.apply_settings(settings, inserter)
local limit = math.min(inserter.filter_slot_count, #settings.filters)
for i = 1, limit do
inserter.set_filter(i, settings.filters[i])
end
local control_behavior = inserter.get_or_create_control_behavior()
for k, v in pairs(settings.control_behavior) do
control_behavior[k] = v
end
end
return util