Добавлены все обновления от сообщества, вплоть до #148

This commit is contained in:
2024-09-12 14:28:43 +03:00
parent 98159766c4
commit 487a0e6e16
8841 changed files with 23077 additions and 20175 deletions

View File

@@ -0,0 +1,107 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local mpp_util = require("mpp.mpp_util")
---@class Layout
local layout = {}
layout.name = "Base"
layout.translation = {"mpp.settings_layout_choice_base"}
---@diagnostic disable-next-line: missing-fields
layout.defaults = {}
layout.defaults.miner = "electric-mining-drill"
layout.defaults.belt = "transport-belt"
layout.defaults.pole = "medium-electric-pole"
layout.defaults.logistics = "logistic-chest-passive-provider"
layout.defaults.pipe = "pipe"
---@diagnostic disable-next-line: missing-fields
layout.restrictions = {}
layout.restrictions.miner_available = true
layout.restrictions.miner_size = {0, 10}
layout.restrictions.miner_radius = {0, 20}
layout.restrictions.belt_available = true
layout.restrictions.uses_underground_belts = false
layout.restrictions.pole_available = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.lamp_available = true
layout.restrictions.coverage_tuning = false
layout.restrictions.logistics_available = false
layout.restrictions.landfill_omit_available = true
layout.restrictions.start_alignment_tuning = false
layout.restrictions.deconstruction_omit_available = true
layout.restrictions.module_available = false
layout.restrictions.pipe_available = false
layout.restrictions.placement_info_available = false
layout.restrictions.lane_filling_info_available = false
--- Called from script.on_load
--- ONLY FOR SETMETATABLE USE
---@param self Layout
---@param state State
function layout:on_load(state) end
---@param proto MinerStruct
function layout:restriction_miner(proto)
return true
end
--- Validate the selection
---@param self Layout
---@param state State
function layout:validate(state)
local r = self.restrictions
return true
end
---Layout-specific state initialisation
---@param self Layout
---@param state State
function layout:initialize(state)
state.miner = mpp_util.miner_struct(state.miner_choice)
state.pole = mpp_util.pole_struct(state.pole_choice)
if layout.restrictions.belt_available then
state.belt = mpp_util.belt_struct(state.belt_choice)
end
end
---Starting step
---@param self Layout
---@param state State
---@return CallbackState
function layout:start(state)
return false
end
---Probably too much indirection at this point
---@param self Layout
---@param state State
function layout:tick(state)
state.tick = state.tick + 1
return self[state._callback](self, state)
end
---@param self Layout
---@param state State
function layout:deconstruct_previous_ghosts(state)
local next_step = "initialize_grid"
if state._previous_state == nil or state._previous_state._collected_ghosts == nil then
return next_step
end
local force, player = state.player.force, state.player
for _, ghost in pairs(state._previous_state._collected_ghosts) do
if ghost.valid then
ghost.order_deconstruction(force, player)
end
end
return next_step
end
return layout

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,400 @@
local mpp_util = require("mpp.mpp_util")
local common = {}
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local sqrt, log = math.sqrt, math.log
---@alias HeuristicMinerPlacement fun(tile: GridTile): boolean
---@param miner MinerStruct
---@return HeuristicMinerPlacement
function common.simple_miner_placement(miner)
local size, area = miner.size, miner.area
local neighbor_cap = (size / 2) ^ 2
return function(tile)
return tile.neighbors_inner > neighbor_cap
-- return tile.neighbor_count > neighbor_cap or tile.far_neighbor_count > leech
end
end
---@param miner MinerStruct
---@return HeuristicMinerPlacement
function common.overfill_miner_placement(miner)
local size, area = miner.size, miner.area
local neighbor_cap = (size/ 2) ^ 2 - 1
local leech = (area * 0.5) ^ 2 - 1
return function(tile)
return tile.neighbors_inner > 0 or tile.neighbors_outer > leech
end
end
---@param heuristic HeuristicsBlock
function common.simple_layout_heuristic(heuristic)
local lane_mult = 1 + ceil(heuristic.lane_count / 2) * 0.05
local unconsumed = 1 + log(max(1, heuristic.unconsumed), 10)
local value =
(heuristic.inner_density + heuristic.empty_space / heuristic.drill_count)
--* heuristic.centricity
* lane_mult
* unconsumed
return value
end
---@param heuristic HeuristicsBlock
function common.overfill_layout_heuristic(heuristic)
local lane_mult = 1 + ceil(heuristic.lane_count / 2) * 0.05
local unconsumed = 1 + log(max(1, heuristic.unconsumed), 10)
local value =
heuristic.outer_density
--* heuristic.centricity
* lane_mult
* unconsumed
return value
end
---@class HeuristicsBlock
--- Values counted per miner placement
---@field inner_neighbor_sum number Sum of resource tiles drills physically cover
---@field outer_neighbor_sum number Sum of all resource tiles drills physically cover
---@field empty_space number Sum of empty tiles drills physically cover
---@field leech_sum number Sum of resources only in the outer reach
---@field postponed_count number Number of postponed drills
---
--- Values calculated after completed layout placement
---@field drill_count number Total number of mining drills
---@field lane_count number Number of lanes
---@field inner_density number Density of tiles physically covered by drills
---@field outer_density number Pseudo (because of overlap) density of all tiles reached by drills
---@field centricity number How centered is the layout in respect to patch bounds, 1 to inf
---@field unconsumed number Unreachable resources count (usually insufficient drill reach)
---@return HeuristicsBlock
function common.init_heuristic_values()
return {
inner_neighbor_sum = 0,
outer_neighbor_sum = 0,
empty_space = 0,
leech_sum = 0,
postponed_count = 0,
drill_count = 1,
lane_count = 1,
inner_density = 1,
outer_density = 1,
centricity = -(0/0),
unconsumed = 0,
}
end
---@param H HeuristicsBlock
---@param M MinerStruct
---@param tile GridTile
---@param postponed boolean?
function common.add_heuristic_values(H, M, tile, postponed)
H.inner_neighbor_sum = H.inner_neighbor_sum + tile.neighbors_inner
H.outer_neighbor_sum = H.outer_neighbor_sum + tile.neighbors_outer
H.empty_space = H.empty_space + (M.size_sq - tile.neighbors_inner)
H.leech_sum = H.leech_sum + max(0, tile.neighbors_outer - tile.neighbors_inner)
if postponed then H.postponed_count = H.postponed_count + 1 end
end
---@param attempt PlacementAttempt
---@param block HeuristicsBlock
---@param coords Coords
function common.finalize_heuristic_values(attempt, block, coords)
block.drill_count = #attempt.miners
block.lane_count = #attempt.lane_layout
block.inner_density = block.inner_neighbor_sum / block.drill_count
block.outer_density = block.outer_neighbor_sum / block.drill_count
local function centricity(m1, m2, size)
local center = size / 2
local drill = m1 + (m2-m1)/2
return center - drill
end
local x = centricity(attempt.sx-1, attempt.bx, coords.w)
local y = centricity(attempt.sy-1, attempt.by, coords.h)
block.centricity = 1 + (x * x + y * y) ^ 0.5
block.unconsumed = attempt.unconsumed
end
---Utility to fill in postponed miners on unconsumed resources
---@param state SimpleState
---@param attempt PlacementAttempt
---@param miners MinerPlacement[]
---@param postponed MinerPlacement[]
function common.process_postponed(state, attempt, miners, postponed)
local grid = state.grid
local M = state.miner
local bx, by = attempt.sx + M.size - 1, attempt.sy + M.size - 1
for _, miner in ipairs(miners) do
grid:consume(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
bx, by = max(bx, miner.x + M.size -1), max(by, miner.y + M.size -1)
end
for _, miner in ipairs(postponed) do
miner.unconsumed = grid:get_unconsumed(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
bx, by = max(bx, miner.x + M.size -1), max(by, miner.y + M.size -1)
end
table.sort(postponed, function(a, b)
if a.unconsumed == b.unconsumed then
local atile, btile = a.tile, b.tile
if atile.neighbors_outer == btile.neighbors_outer then
return atile.neighbors_inner > btile.neighbors_inner
end
return atile.neighbors_outer > btile.neighbors_outer
end
return a.unconsumed > b.unconsumed
end)
for _, miner in ipairs(postponed) do
local tile = miner.tile
local unconsumed_count = grid:get_unconsumed(miner.x+M.extent_negative, miner.y+M.extent_negative, M.area)
if unconsumed_count > 0 then
common.add_heuristic_values(attempt.heuristics, M, tile, true)
grid:consume(tile.x+M.extent_negative, tile.y+M.extent_negative, M.area)
miners[#miners+1] = miner
miner.postponed = true
bx, by = max(bx, miner.x + M.size - 1), max(by, miner.y + M.size - 1)
end
end
local unconsumed_sum = 0
for _, tile in ipairs(state.resource_tiles) do
if not tile.consumed then unconsumed_sum = unconsumed_sum + 1 end
end
attempt.unconsumed = unconsumed_sum
attempt.bx, attempt.by = bx, by
grid:clear_consumed(state.resource_tiles)
end
local seed
local function get_map_seed()
if seed then return seed end
local game_exchange_string = game.get_map_exchange_string()
local map_data = game.parse_map_exchange_string(game_exchange_string)
local seed_number = map_data.map_gen_settings.seed
seed = string.format("%x", seed_number)
return seed
end
---Dump state to json for inspection
---@param state SimpleState
function common.save_state_to_file(state, type_)
local c = state.coords
local gx, gy = floor(c.gx), floor(c.gy)
local dir = state.direction_choice
local coverage = state.coverage_choice and "t" or "f"
local filename = string.format("layout_%s_%i;%i_%s_%i_%s_%s_%x.%s", get_map_seed(), gx, gy, state.miner_choice, #state.resources, dir, coverage, game.tick, type_)
if type_ == "json" then
game.print(string.format("Dumped data to %s ", filename))
game.write_file("mpp-inspect/"..filename, game.table_to_json(state), false, state.player.index)
elseif type_ == "lua" then
game.print(string.format("Dumped data to %s ", filename))
game.write_file("mpp-inspect/"..filename, serpent.dump(state, {}), false, state.player.index)
end
end
function common.calculate_patch_slack(state)
end
---@param miner MinerStruct
---@param restrictions Restrictions
---@return boolean
function common.is_miner_restricted(miner, restrictions)
return false
or miner.size < restrictions.miner_size[1]
or restrictions.miner_size[2] < miner.size
or miner.radius < restrictions.miner_radius[1]
or restrictions.miner_radius[2] < miner.radius
end
---@param belt BeltStruct
---@param restrictions Restrictions
function common.is_belt_restricted(belt, restrictions)
return false
or (restrictions.uses_underground_belts and not belt.related_underground_belt)
end
---@param pole PoleStruct
---@param restrictions Restrictions
function common.is_pole_restricted(pole, restrictions)
return false
or pole.size < restrictions.pole_width[1]
or restrictions.pole_width[2] < pole.size
or pole.supply_area_distance < restrictions.pole_supply_area[1]
or restrictions.pole_supply_area[2] < pole.supply_area_distance
or pole.wire < restrictions.pole_length[1]
or restrictions.pole_length[2] < pole.wire
end
local triangles = {
west={
{{target={-.6, 0}}, {target={.6, -0.6}}, {target={.6, 0.6}}},
{{target={-.4, 0}}, {target={.5, -0.45}}, {target={.5, 0.45}}},
},
east={
{{target={.6, 0}}, {target={-.6, -0.6}}, {target={-.6, 0.6}}},
{{target={.4, 0}}, {target={-.5, -0.45}}, {target={-.5, 0.45}}},
},
north={
{{target={0, -.6}}, {target={-.6, .6}}, {target={.6, .6}}},
{{target={0, -.4}}, {target={-.45, .5}}, {target={.45, .5}}},
},
south={
{{target={0, .6}}, {target={-.6, -.6}}, {target={.6, -.6}}},
{{target={0, .4}}, {target={-.45, -.5}}, {target={.45, -.5}}},
},
}
local alignment = {
west={"center", "center"},
east={"center", "center"},
north={"left", "right"},
south={"right", "left"},
}
local bound_alignment = {
west="right",
east="left",
north="center",
south="center",
}
---Draws a belt lane overlay
---@param state State
---@param belt BeltSpecification
function common.draw_belt_lane(state, belt)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local x1, y1, x2, y2 = belt.x1, belt.y, math.max(belt.x1+2, belt.x2), belt.y
local function l2w(x, y) -- local to world
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1, c2, c3 = {.9, .9, .9}, {0, 0, 0}, {.4, .4, .4}
local w1, w2 = 4, 10
if not belt.lane1 and not belt.lane2 then c1 = c3 end
r[#r+1] = rendering.draw_line{ -- background main line
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
from=l2w(x1, y1), to=l2w(x2+.5, y1),
}
r[#r+1] = rendering.draw_line{ -- background vertical cap
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
from=l2w(x2+.5, y1-.6), to=l2w(x2+.5, y2+.6),
}
r[#r+1] = rendering.draw_polygon{ -- background arrow
surface=state.surface, players=player, only_in_alt_mode=true,
width=w2, color=c2, time_to_live=ttl or 1,
target=l2w(x1, y1),
vertices=triangles[state.direction_choice][1],
}
r[#r+1] = rendering.draw_line{ -- main line
surface=state.surface, players=player, only_in_alt_mode=true,
width=w1, color=c1, time_to_live=ttl or 1,
from=l2w(x1-.2, y1), to=l2w(x2+.5, y1),
}
r[#r+1] = rendering.draw_line{ -- vertical cap
surface=state.surface, players=player, only_in_alt_mode=true,
width=w1, color=c1, time_to_live=ttl or 1,
from=l2w(x2+.5, y1-.5), to=l2w(x2+.5, y2+.5),
}
r[#r+1] = rendering.draw_polygon{ -- arrow
surface=state.surface, players=player, only_in_alt_mode=true,
width=0, color=c1, time_to_live=ttl or 1,
target=l2w(x1, y1),
vertices=triangles[state.direction_choice][2],
}
end
---Draws a belt lane overlay
---@param state State
---@param belt BeltSpecification
function common.draw_belt_stats(state, belt, belt_speed, speed1, speed2)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local x1, y1, x2, y2 = belt.x1, belt.y, belt.x2, belt.y
local function l2w(x, y) -- local to world
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1, c2, c3, c4 = {.9, .9, .9}, {0, 0, 0}, {.9, 0, 0}, {.4, .4, .4}
local ratio1 = speed1 / belt_speed
local ratio2 = speed2 / belt_speed
local function get_color(ratio)
return ratio > 1.01 and c3 or ratio == 0 and c4 or c1
end
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=get_color(ratio1), time_to_live=ttl or 1,
alignment=alignment[state.direction_choice][1], vertical_alignment="middle",
target=l2w(x1-2, y1-.6), scale=1.6,
text=string.format("%.2fx", ratio1),
}
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=get_color(ratio2), time_to_live=ttl or 1,
alignment=alignment[state.direction_choice][2], vertical_alignment="middle",
target=l2w(x1-2, y1+.6), scale=1.6,
text=string.format("%.2fx", ratio2),
}
end
---Draws a belt lane overlay
---@param state State
---@param pos_x number
---@param pos_y number
---@param speed1 number
---@param speed2 number
function common.draw_belt_total(state, pos_x, pos_y, speed1, speed2)
local r = state._render_objects
local c, ttl, player = state.coords, 0, {state.player}
local function l2w(x, y, b) -- local to world
if ({south=true, north=true})[state.direction_choice] then
x = x + (b and -.5 or .5)
y = y + (b and -.5 or .5)
end
return mpp_util.revert(c.gx, c.gy, state.direction_choice, x, y, c.tw, c.th)
end
local c1 = {0.7, 0.7, 1.0}
local lower_bound = math.min(speed1, speed2)
local upper_bound = math.max(speed1, speed2)
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=c1, time_to_live=ttl or 1,
alignment=bound_alignment[state.direction_choice], vertical_alignment="middle",
target=l2w(pos_x-4, pos_y-.6, false), scale=2,
text={"mpp.msg_print_info_lane_saturation_belts", string.format("%.2fx", upper_bound), string.format("%.2fx", (lower_bound+upper_bound)/2)},
}
r[#r+1] = rendering.draw_text{
surface=state.surface, players=player, only_in_alt_mode=true,
color=c1, time_to_live=ttl or 1,
alignment=bound_alignment[state.direction_choice], vertical_alignment="middle",
target=l2w(pos_x-4, pos_y+.6, true), scale=2,
text={"mpp.msg_print_info_lane_saturation_bounds", string.format("%.2fx", lower_bound), string.format("%.2fx", upper_bound)},
}
end
return common

View File

@@ -0,0 +1,592 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local common = require("layouts.common")
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local pole_grid_mt = require("mpp.pole_grid_mt")
local drawing = require("mpp.drawing")
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local table_insert = table.insert
---@class CompactLayout : SimpleLayout
local layout = table.deepcopy(simple)
---@class CompactState : SimpleState
layout.name = "compact"
layout.translation = {"", "[entity=underground-belt] ", {"mpp.settings_layout_choice_compact"}}
layout.restrictions.miner_size = {3, 10e3}
layout.restrictions.miner_radius = {1, 10e3}
layout.restrictions.uses_underground_belts = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {7.5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.coverage_tuning = true
layout.restrictions.lamp_available = true
layout.restrictions.module_available = true
layout.restrictions.pipe_available = true
---@param state CompactState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size = M.size
local miners, postponed = {}, {}
local heuristic_values = common.init_heuristic_values()
local lane_layout = {}
local bx, by = shift_x + M.size - 1, shift_y + M.size - 1
local heuristic = self:_get_miner_placement_heuristic(state)
local row_index = 1
for y_float = shift_y, state.coords.th + size, size + 0.5 do
local y = ceil(y_float)
local column_index = 1
lane_layout[#lane_layout+1] = {y = y, row_index = row_index}
for x = shift_x, state.coords.tw, size do
local tile = grid:get_tile(x, y) --[[@as GridTile]]
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
line = row_index,
column = column_index,
}
if tile.neighbors_outer > 0 and heuristic(tile) then
miners[#miners+1] = miner
common.add_heuristic_values(heuristic_values, M, tile)
elseif tile.neighbors_outer > 0 then
postponed[#postponed+1] = miner
end
column_index = column_index + 1
end
row_index = row_index + 1
end
local result = {
sx = shift_x,
sy = shift_y,
bx = bx,
by = by,
miners = miners,
lane_layout = lane_layout,
heuristics = heuristic_values,
heuristic_score = -(0/0),
unconsumed = 0,
}
common.process_postponed(state, result, miners, postponed)
common.finalize_heuristic_values(result, heuristic_values, state.coords)
return result
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_belt_layout(state)
local belts = {}
state.belts = belts
state.builder_belts = {}
local coverage = mpp_util.calculate_pole_spacing(state, state.miner_max_column, state.miner_lane_count, true)
state.builder_power_poles = {}
if coverage.capable_span then
self:_placement_capable(state)
else
self:_placement_incapable(state)
end
return "prepare_belts"
end
---Placement of belts+poles when they tile nicely (enough reach/supply area)
---@param self CompactLayout
---@param state CompactState
function layout:_placement_capable(state)
local M, C, P, G = state.miner, state.coords, state.pole, state.grid
local attempt = state.best_attempt
local miner_lane_count = state.miner_lane_count
local coverage = mpp_util.calculate_pole_spacing(state, state.miner_max_column, state.miner_lane_count, true)
local steps = {}
for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do
table.insert(steps, i)
end
local power_grid = pole_grid_mt.new()
state.power_grid = power_grid
--local transport_layout = {}
--state.transport_layout = transport_layout
local iy = 1
for i = 1, miner_lane_count, 2 do
y = attempt.sy + floor((M.size + 0.5) * i)
--local transport_layout_lane = {}
--transport_layout[ceil(i / 2)] = transport_layout_lane
local backtrack_build = false
for step_i = #steps, 1, -1 do
local x = attempt.sx + steps[step_i]
local has_consumers = G:needs_power(x, y, P)
if backtrack_build or step_i == 1 or has_consumers then
backtrack_build = true
power_grid:add_pole{
grid_x = x, grid_y = y,
ix = step_i, iy = iy,
backtracked = backtrack_build,
has_consumers = has_consumers,
built = has_consumers,
connections = {},
}
--transport_layout_lane[step_i] = {x=x, y=y, built=true}
-- table_insert(builder_power_poles, {
-- name=P.name,
-- thing="pole",
-- grid_x=x,
-- grid_y=y,
-- ix = step_i,
-- iy = iy,
-- })
else
power_grid:add_pole{
grid_x = x, grid_y = y,
ix = step_i, iy = iy,
backtracked = false,
has_consumers = has_consumers,
built = false,
connections = {},
}
end
end
iy = iy + 1
end
local connectivity = power_grid:find_connectivity(state.pole)
state.power_connectivity = connectivity
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_belts(state)
local M, C, P, G = state.miner, state.coords, state.pole, state.grid
local attempt = state.best_attempt
local miner_lanes = state.miner_lanes
local miner_lane_count = state.miner_lane_count
local builder_power_poles = state.builder_power_poles
local connectivity = state.power_connectivity
local power_grid = state.power_grid
--local debug_draw = drawing(state, false, false)
if power_grid then
local connected = power_grid:ensure_connectivity(connectivity)
for _, pole in pairs(connected) do
table_insert(builder_power_poles, {
name = P.name,
thing = "pole",
grid_x = pole.grid_x,
grid_y = pole.grid_y,
ix = pole.ix,
iy = pole.iy,
})
end
end
local builder_belts = state.builder_belts
local belt_name = state.belt.name
local underground_name = state.belt.related_underground_belt --[[@as string]]
local belts = {}
state.belts = belts
state.belt_count = 0
local pipe_adjust = state.place_pipes and -1 or 0
for i_lane = 1, miner_lane_count, 2 do
local iy = ceil(i_lane / 2)
local lane1 = miner_lanes[i_lane]
local lane2 = miner_lanes[i_lane+1]
--local transport_layout_lane = transport_layout[ceil(i_lane / 2)]
local function get_lane_length(lane, out_x) if lane and lane[#lane] then return lane[#lane].x+out_x end return 0 end
local y = attempt.sy + floor((M.size + 0.5) * i_lane)
local x1 = attempt.sx + pipe_adjust
local belt = {x1=x1, x2=attempt.sx, y=y, built=false, lane1=lane1, lane2=lane2}
belts[#belts+1] = belt
if not lane1 and not lane2 then goto continue_lane end
local x2 = max(get_lane_length(lane1, M.output_rotated[SOUTH].x), get_lane_length(lane2, M.out_x))
state.belt_count = state.belt_count + 1
belt.x2, belt.built = x2, true
-- debug_draw:draw_circle{x = x1, y = y, width = 7}
-- debug_draw:draw_circle{x = x2, y = y, width = 7}
if not power_grid then goto continue_lane end
local previous_start, first_interval = x1, true
local power_poles, pole_count, belt_intervals = power_grid[iy], #power_grid[iy], {}
for i, pole in pairs(power_poles) do
if not pole.built then goto continue_poles end
local next_x = pole.grid_x
table_insert(belt_intervals, {
x1 = previous_start, x2 = min(x2, next_x - 1),
is_first = first_interval,
})
previous_start = next_x + 2
first_interval = false
if x2 < next_x then
--debug_draw:draw_circle{x = x2, y = y, color = {1, 0, 0}}
break
end
::continue_poles::
end
if previous_start <= x2 then
--debug_draw:draw_circle{x = x2, y = y, color = {0, 1, 0}}
table_insert(belt_intervals, {
x1 = previous_start, x2 = x2, is_last = true,
})
end
for i, interval in pairs(belt_intervals) do
local bx1, bx2 = interval.x1, interval.x2
builder_belts[#builder_belts+1] = {
name = interval.is_first and belt_name or underground_name,
thing = "belt", direction=WEST,
grid_x = bx1, grid_y = y,
type = not interval.is_first and "input" or nil
}
for x = bx1 + 1, bx2 - 1 do
builder_belts[#builder_belts+1] = {
name = belt_name,
thing = "belt", direction=WEST,
grid_x = x, grid_y = y,
}
end
builder_belts[#builder_belts+1] = {
name = i == #belt_intervals and belt_name or underground_name,
thing = "belt", direction=WEST,
grid_x = bx2, grid_y = y,
type = not interval.is_last and "output" or nil
}
end
::continue_lane::
end
return "prepare_pole_layout"
end
---Placement of belts+poles when they don't tile nicely (not enough reach/supply area)
---@param self CompactLayout
---@param state CompactState
function layout:_placement_incapable(state)
end
---@param self CompactLayout
---@param state CompactState
function layout:_placement_belts_small(state)
local m = state.miner
local attempt = state.best_attempt
local belt_choice = state.belt_choice
local underground_belt = state.belt.related_underground_belt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {}
local miner_lane_count = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local belts = state.belts
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_count = max(miner_lane_count, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
end
for _, lane in ipairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane then return lane[#lane].x + m.out_x end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column end return 0 end
state.belt_count = 0
local que_entity = create_entity_que(state.builder_belts)
local function belts_filled(x1, y, w)
for x = x1, x1 + w do
que_entity{name=belt_choice, direction=WEST, grid_x=x, grid_y=y, thing="belt"}
end
end
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.size * i + ceil(i/2)
local x0 = attempt.sx + 1
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
if column_count == 0 then goto continue_lane end
state.belt_count = state.belt_count + 1
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
if state.place_pipes then
que_entity{
name=state.belt_choice,
thing="belt",
grid_x = x0 + pipe_adjust,
grid_y = y,
direction=WEST,
}
end
belts[#belts+1] = {
x1 = x0 + pipe_adjust, x2 = x0 + column_count * m.size,
y = y, lane1 = lane1, lane2 = lane2,
}
for j = 1, column_count do
local x1 = x0 + (j-1) * m.size
if j % 2 == 1 then -- part one
if indices[j] or indices[j+1] then
que_entity{
name=state.belt_choice, thing="belt", grid_x=x1, grid_y=y, direction=WEST,
}
local stopper = (j+1 > column_count) and state.belt_choice or underground_belt
que_entity{
name=stopper, thing="belt", grid_x=x1+1, grid_y=y, direction=WEST, type="output",
}
-- power_poles[#power_poles+1] = {
-- x=x1+3, y=y,
-- ix=1+floor(i/2), iy=1+floor(j/2),
-- built = true,
-- }
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x1+3,
grid_y = y,
ix=1+floor(i/2), iy=1+floor(j/2),
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 2 == 0 then -- part two
if indices[j-1] or indices[j] then
que_entity{
name=belt_choice, thing="belt", grid_x=x1+2, grid_y=y, direction=WEST,
}
que_entity{
name=underground_belt, thing="belt", grid_x=x1+1, grid_y=y, direction=WEST,
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
end
end
::continue_lane::
end
end
---@param self CompactLayout
function layout:_placement_belts_large(state)
local m = state.miner
local attempt = state.best_attempt
local belt_choice = state.belt_choice
local underground_belt = game.entity_prototypes[belt_choice].related_underground_belt.name
local power_poles = {}
state.builder_power_poles = power_poles
local belts = state.belts
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_count = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_count = max(miner_lane_count, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
end
state.miner_lane_count = miner_lane_count
local que_entity = create_entity_que(state.builder_belts)
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.center.x < b.center.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane and #lane > 0 then return lane[#lane].center.x or 0 end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column or 0 end return 0 end
local function belts_filled(x1, y, w)
for x = x1, x1 + w do
que_entity{name=belt_choice, direction=WEST, grid_x=x, grid_y=y, thing="belt"}
end
end
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.size * i + ceil(i/2)
local x0 = attempt.sx + 1
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
if column_count == 0 then goto continue_lane end
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
if state.place_pipes then
que_entity{
name=state.belt_choice,
thing="belt",
grid_x = x0 + pipe_adjust,
grid_y = y,
direction=WEST,
}
end
belts[#belts+1] = {
x1 = x0 + pipe_adjust, x2 = x0 + column_count * m.size,
y = y, lane1 = lane1, lane2 = lane2,
}
for j = 1, column_count do
local x1 = x0 + (j-1) * m.size
if j % 3 == 1 then -- part one
if indices[j] or indices[j+1] or indices[j+2] then
que_entity{
name=belt_choice, grid_x=x1, grid_y=y, thing="belt", direction=WEST,
}
local stopper = (j+1 > column_count) and state.belt_choice or underground_belt
que_entity{
name=stopper, grid_x=x1+1, grid_y=y, thing="belt", direction=WEST,
type="output",
}
-- power_poles[#power_poles+1] = {
-- x=x1+3, y=y,
-- ix=1+floor(i/2), iy=1+floor(j/2),
-- built = true,
-- }
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x1+3,
grid_y = y,
ix=1+floor(i/2),
iy=1+floor(j/2),
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 3 == 2 then -- part two
if indices[j-1] or indices[j] or indices[j+1] then
que_entity{
name=underground_belt, grid_x=x1+1, grid_y=y, thing="belt", direction=WEST,
type="input",
}
que_entity{
name=belt_choice, grid_x=x1+2, grid_y=y, thing="belt", direction=WEST,
}
else -- just a passthrough belt
belts_filled(x1, y, m.size - 1)
end
elseif j % 3 == 0 then
belts_filled(x1, y, m.size - 1)
end
end
::continue_lane::
end
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_pole_layout(state)
return "prepare_lamp_layout"
end
---@param self CompactLayout
---@param state CompactState
function layout:prepare_lamp_layout(state)
local next_step = "expensive_deconstruct"
if state.lamp_choice ~= true then return next_step end
local lamps = {}
state.builder_lamps = lamps
local grid = state.grid
local sx, sy = state.pole.size, 0
local lamp_spacing = true
if state.pole.wire > 7 then lamp_spacing = false end
for _, pole in ipairs(state.builder_power_poles) do
local x, y = pole.grid_x + sx, pole.grid_y + sy
local ix, iy = pole.ix, pole.iy
local tile = grid:get_tile(x, y)
local skippable_lamp = iy % 2 == 1 and ix % 2 == 1
if tile and (not lamp_spacing or skippable_lamp) then
lamps[#lamps+1] = {
name="small-lamp",
thing="lamp",
grid_x = x,
grid_y = y,
}
end
end
return next_step
end
return layout

View File

@@ -0,0 +1,123 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local super_compact = require("layouts.super_compact")
local logistics =require("layouts.logistics")
local builder = require("mpp.builder")
---@class CompactLogisticsLayout: SuperCompactLayout
local layout = table.deepcopy(super_compact)
layout.name = "compact_logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_compact_logistics"}}
layout.restrictions.lamp_available = false
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local C = state.coords
local attempt = state.best_attempt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local builder_belts = {}
state.builder_belts = builder_belts
local function que_entity(t) builder_belts[#builder_belts+1] = t end
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line._index = index
local out_x = m.output_rotated[defines.direction[miner.direction]][1]
if line.last_x == nil or (miner.x+out_x) > line.last_x then
line.last_x = miner.x + out_x
line.last_miner = miner
end
line[#line+1] = miner
end
local shift_x, shift_y = state.best_attempt.sx, state.best_attempt.sy
local function place_logistics(lane, start_x, end_x, y)
local belt_start = 1 + shift_x + start_x
if start_x ~= 0 then
local miner = g:get_tile(shift_x+m.size, y)
if miner and miner.built_on == "miner" then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=shift_x+m.size+1,
grid_y=y,
}
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = shift_x,
grid_y = y,
}
end
end
for x = belt_start, end_x, m.size * 2 do
local miner1 = g:get_tile(x, y-1) --[[@as GridTile]]
local miner2 = g:get_tile(x, y+1) --[[@as GridTile]]
local miner3 = g:get_tile(x+3, y) --[[@as GridTile]]
local built = (miner1 and miner1.built_on == "miner") or (miner2 and miner2.built_on == "miner")
local capped = miner3 and miner3.built_on == "miner"
local pole_built = built or capped
if capped then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=x+m.size*2,
grid_y=y,
}
end
if built then
que_entity{
name=state.logistics_choice,
thing="belt",
grid_x=x+1,
grid_y=y,
}
end
if pole_built then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x + 2,
grid_y = y,
}
end
end
end
for i = 1, miner_lane_number do
local lane = miner_lanes[i]
if lane and lane.last_x then
local y = m.size + shift_y - 1 + (m.size + 2) * (i-1)
local x_start = i % 2 == 0 and 3 or 0
place_logistics(lane, x_start, lane.last_x, y)
end
end
return "expensive_deconstruct"
end
layout.finish = logistics.finish
return layout

View File

@@ -0,0 +1,93 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local mpp_revert = mpp_util.revert
local pole_grid_mt = require("mpp.pole_grid_mt")
---@class LogisticsLayout:SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_logistics"}}
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self LogisticsLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local attempt = state.best_attempt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local miner_lanes = {{}}
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local miner_max_column = 0
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
line[#line+1] = miner
miner_max_column = max(miner_max_column, miner.column)
end
state.miner_lane_count = miner_lane_number
state.miner_max_column = miner_max_column
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane then return lane[#lane].x end return 0 end
---@param lane MinerPlacement[]
local function get_lane_column(lane) if lane and #lane > 0 then return lane[#lane].column or 0 end return 0 end
local belts = {}
state.builder_belts = belts
for i = 1, miner_lane_number, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy - 1 + (m.size + 1) * i
local x0 = attempt.sx
local column_count = max(get_lane_column(lane1), get_lane_column(lane2))
local indices = {}
if lane1 then for _, v in ipairs(lane1) do indices[v.column] = v end end
if lane2 then for _, v in ipairs(lane2) do indices[v.column] = v end end
for j = 1, column_count do
local x = x0 + m.out_x + m.size * (j-1)
if indices[j] then
belts[#belts+1] = {
name=state.logistics_choice,
thing="belt",
grid_x=x,
grid_y=y,
}
end
end
end
return "prepare_pole_layout"
end
---@param self SimpleLayout
---@param state SimpleState
---@return CallbackState
function layout:finish(state)
if state.print_placement_info_choice and state.player.valid then
state.player.print({"mpp.msg_print_info_miner_placement_no_lanes", #state.best_attempt.miners, #state.resources})
end
return false
end
return layout

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
local mpp_util = require("mpp.mpp_util")
local common = require("layouts.common")
local renderer = require("mpp.render_util")
local drawing = require("mpp.drawing")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local simple = require("layouts.simple")
---@class SparseLayout : SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "sparse"
layout.translation = {"", "[entity=transport-belt] ", {"mpp.settings_layout_choice_sparse"}}
layout.restrictions.miner_size = {1, 10e3}
layout.restrictions.miner_radius = {1, 10e3}
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {7.5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.lamp_available = true
layout.restrictions.module_available = true
layout.restrictions.pipe_available = true
layout.restrictions.coverage_tuning = false
---@param state SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size, area = state.miner.size, state.miner.area
local miners = {}
local heuristic_values = common.init_heuristic_values()
local row_index = 1
local lane_layout = {}
for uy = shift_y, state.coords.th + size, area do
local column_index = 1
local y = uy - M.extent_negative
lane_layout[#lane_layout+1] = {y=y, row_index = row_index}
for ux = shift_x, state.coords.tw, area do
local x = ux - M.extent_negative
local tile = grid:get_tile(x, y) --[[@as GridTile]]
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
line = row_index,
column = column_index,
direction = row_index % 2 == 1 and SOUTH or NORTH
}
if tile.neighbors_outer > 0 then
miners[#miners+1] = miner
end
column_index = column_index + 1
end
row_index = row_index + 1
end
local result = {
sx=shift_x,
sy=shift_y,
bx = 1,
by = 1,
miners=miners,
lane_layout=lane_layout,
heuristics = heuristic_values,
heuristic_score = #miners,
unconsumed = 0,
}
common.process_postponed(state, result, miners, {})
common.finalize_heuristic_values(result, heuristic_values, state.coords)
result.heuristic_score = #miners
return result
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_layout_attempts(state)
local c = state.coords
local m = state.miner
local attempts = {}
state.attempts = attempts
state.best_attempt_index = 1
state.attempt_index = 1 -- first attempt is used up
local function calc_slack(tw, near, far)
local fullsize = far * 2 + 1
local count = ceil(tw / fullsize)
local overrun = count * fullsize - tw
local slack = overrun % 2
--local start = far-near-floor(overrun / 2) - slack
local start = (far-near)-floor(overrun / 2) - slack
return count, start, slack
end
local countx, slackw2, modx = calc_slack(c.tw, floor(m.size/2), floor(m.area/2))
local county, slackh2, mody = calc_slack(c.th, floor(m.size/2), floor(m.area/2))
for sy = slackh2, slackh2 + mody do
for sx = slackw2, slackw2 + modx do
attempts[#attempts+1] = {
sx,
sy,
cx = countx,
cy = county,
slackw = slackw2,
slackh = slackh2,
modx = modx,
mody = mody,
}
end
end
return "init_layout_attempt"
end
---@param self SimpleLayout
---@param state SimpleState
function layout:init_layout_attempt(state)
local attempt = state.attempts[state.attempt_index]
state.best_attempt = self:_placement_attempt(state, attempt[1], attempt[2])
state.best_attempt_score = #state.best_attempt.miners
if state.debug_dump then
state.best_attempt.heuristic_score = state.best_attempt_score
state.saved_attempts = {}
state.saved_attempts[#state.saved_attempts+1] = state.best_attempt
end
if #state.attempts > 1 then
return "layout_attempt"
end
return "prepare_miner_layout"
end
function layout:_get_layout_heuristic(state)
return function(attempt) return #attempt.miners end
end
---Bruteforce the best solution
---@param self SimpleLayout
---@param state SimpleState
function layout:layout_attempt(state)
local attempt_state = state.attempts[state.attempt_index]
---@type PlacementAttempt
local current_attempt = self:_placement_attempt(state, attempt_state[1], attempt_state[2])
local current_attempt_score = #current_attempt.miners
if current_attempt_score < state.best_attempt_score then
state.best_attempt_index = state.attempt_index
state.best_attempt = current_attempt
state.best_attempt_score = current_attempt_score
end
if state.attempt_index >= #state.attempts then
return "prepare_miner_layout"
end
state.attempt_index = state.attempt_index + 1
return true
end
---@param self SimpleLayout
---@param state SimpleState
---@return PlacementSpecification[]
function layout:_get_pipe_layout_specification(state)
local pipe_layout = {}
local M = state.miner
local attempt = state.best_attempt
local gutter = M.outer_span
for _, pre_lane in pairs(state.miner_lanes) do
if not pre_lane[1] then goto continue_lanes end
local y = pre_lane[1].y + M.pipe_left
local sx = attempt.sx + M.outer_span - 1
---@type MinerPlacement[]
local lane = table.mapkey(pre_lane, function(t) return t.column end) -- make array with intentional gaps between miners
local current_start, current_length = nil, 0
for i = 1, state.miner_max_column do
local miner = lane[i]
if miner and current_start then
local start_shift = 0
if current_start == 1 then start_shift = gutter * 2 end
pipe_layout[#pipe_layout+1] = {
structure="horizontal",
x=sx + start_shift + (current_start-1) * M.area - gutter * 2 + 1,
y=y,
w=current_length * M.area+gutter * 2 - 1 - start_shift,
}
current_start, current_length = nil, 0
elseif not miner and not current_start then
current_start, current_length = i, 1
elseif current_start then
current_length = current_length + 1
elseif i > 1 then
pipe_layout[#pipe_layout+1] = {
structure="horizontal",
x=sx+(i-1)*M.area-gutter*2+1,
y=y,
w=gutter*2-1
}
end
end
::continue_lanes::
end
for i = 1, state.miner_lane_count do
local lane = attempt.lane_layout[i]
pipe_layout[#pipe_layout+1] = {
structure="cap_vertical",
x=attempt.sx + M.outer_span - 1,
y=lane.y+M.pipe_left,
skip_up=i == 1,
skip_down=i == state.miner_lane_count,
}
end
return pipe_layout
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local attempt = state.best_attempt
local miner_lanes = state.miner_lanes
local miner_lane_count = state.miner_lane_count
local miner_max_column = state.miner_max_column
for _, lane in pairs(miner_lanes) do
table.sort(lane, function(a, b) return a.x < b.x end)
end
local builder_belts = {}
state.builder_belts = builder_belts
local function get_lane_length(lane) if lane then return lane[#lane].x end return 0 end
local function que_entity(t) builder_belts[#builder_belts+1] = t end
local belt_lanes = {}
state.belts = belt_lanes
local longest_belt = 0
local pipe_adjust = state.place_pipes and -1 or 0
for i = 1, miner_lane_count, 2 do
local lane1 = miner_lanes[i]
local lane2 = miner_lanes[i+1]
local y = attempt.sy + m.area - m.outer_span + m.area * (i-1)
local belt = {x1=attempt.sx + 1, x2=attempt.sx + 1, y=y, built=false, lane1=lane1, lane2=lane2}
belt_lanes[#belt_lanes+1] = belt
if lane1 or lane2 then
local x1 = attempt.sx + 1 + pipe_adjust
local x2 = max(get_lane_length(lane1)+m.out_x+1, get_lane_length(lane2)+ m.out_x + 1)
longest_belt = max(longest_belt, x2 - x1 + 1)
belt.x1, belt.x2, belt.built = x1, x2, true
for x = x1, x2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x,
grid_y=y,
direction=defines.direction.west,
}
end
end
if lane2 then
for _, miner in ipairs(lane2) do
local out_x = m.out_x
for ny = y + 1, y + m.outer_span * 2 - 1 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=miner.x + out_x,
grid_y=ny,
direction=defines.direction.north,
}
end
end
end
end
state.belt_count = #belt_lanes
return "prepare_pole_layout"
end
---@param self SimpleLayout
---@param state SimpleState
function layout:prepare_pole_layout(state)
local c = state.coords
local m = state.miner
local G = state.grid
local P = state.pole
local attempt = state.best_attempt
local pole_proto = game.entity_prototypes[state.pole_choice] or {supply_area_distance=3, max_wire_distance=9}
local supply_area_distance, supply_radius, supply_area = 3.5, 3, 6
if pole_proto then
supply_area = pole_proto.supply_area_distance
supply_area_distance = pole_proto.supply_area_distance or supply_area_distance
supply_radius = floor(supply_area_distance)
supply_area = floor(supply_area_distance * 2)
end
local builder_power_poles = {}
state.builder_power_poles = builder_power_poles
local pole_step = min(floor(pole_proto.max_wire_distance), supply_area + 2)
state.pole_step = pole_step
local miner_lane_width = (state.miner_max_column-1) * m.area + state.miner_max_column
local pole_start = m.outer_span
local function place_pole_lane(y, iy, skip_light)
local pole_lane = {}
local ix = 1
for x = attempt.sx + pole_start, c.tw+m.size, pole_step do
local built = false
if G:needs_power(x, y, P) then
built = true
end
local pole = {x=x, y=y, ix=ix, iy=iy, built=built}
pole_lane[ix] = pole
ix = ix + 1
end
local backtrack_built = false
for pole_i = #pole_lane, 1, -1 do
local no_light = (P.wire < 9) and (pole_i % 2 == 0) or nil
---@type GridPole
local backtrack_pole = pole_lane[pole_i]
if backtrack_built or backtrack_pole.built then
backtrack_built = true
backtrack_pole.built = true
builder_power_poles[#builder_power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = backtrack_pole.x,
grid_y = backtrack_pole.y,
no_light = skip_light or no_light,
}
end
end
return pole_lane
end
local initial_y = attempt.sy + m.outer_span - 1
local iy, between_lane = 1, 0
local y_max, y_step = initial_y + c.th + m.area + 1, m.area * 2
for y = initial_y, y_max, y_step do
if y + y_step > y_max then -- last pole lane
local backstep = m.outer_span * 2 - 1
if state.miner_lanes[between_lane+1] then
backstep = ceil(m.size/2)
end
place_pole_lane(y - backstep)
elseif (m.outer_span * 2 + 2) > supply_area then -- single pole can't supply two lanes
place_pole_lane(y, iy)
if y ~= initial_y then
iy = iy + 1
place_pole_lane(y - m.outer_span * 2 + 1, iy, true)
end
else
local backstep = y == initial_y and 0 or ceil(m.size/2)
place_pole_lane(y - backstep)
end
iy = iy + 1
between_lane = between_lane + 2
end
return "prepare_lamp_layout"
end
return layout

View File

@@ -0,0 +1,43 @@
local mpp_util = require("mpp.mpp_util")
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
local sparse = require("layouts.sparse")
local logistics = require("layouts.logistics")
---@class SparseLogisticsLayout : SparseLayout
local layout = table.deepcopy(sparse)
layout.name = "sparse_logistics"
layout.translation = {"", "[entity=logistic-chest-passive-provider] ", {"mpp.settings_layout_choice_sparse_logistics"}}
layout.restrictions.belt_available = false
layout.restrictions.logistics_available = true
layout.restrictions.lane_filling_info_available = false
---@param self SparseLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local M = state.miner
local belts = {}
state.builder_belts = belts
for _, miner in ipairs(state.best_attempt.miners) do
local out_pos = state.miner.output_rotated[miner.direction]
belts[#belts+1] = {
name=state.logistics_choice,
thing="belt",
grid_x=miner.x + out_pos.x,
grid_y=miner.y + out_pos.y,
}
end
return "prepare_pole_layout"
end
layout.finish = logistics.finish
return layout

View File

@@ -0,0 +1,420 @@
local common = require("layouts.common")
local simple = require("layouts.simple")
local mpp_util = require("mpp.mpp_util")
local builder = require("mpp.builder")
local table_insert = table.insert
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
---@class SuperCompactLayout : SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "super_compact"
layout.translation = {"", "[entity=underground-belt] ", {"mpp.settings_layout_choice_super_compact"}}
layout.restrictions.miner_size = {3, 3}
layout.restrictions.miner_radius = {1, 20}
layout.restrictions.uses_underground_belts = true
layout.restrictions.pole_omittable = true
layout.restrictions.pole_width = {1, 1}
layout.restrictions.pole_length = {5, 10e3}
layout.restrictions.pole_supply_area = {2.5, 10e3}
layout.restrictions.coverage_tuning = true
layout.restrictions.lamp_available = false
layout.restrictions.module_available = true
layout.restrictions.pipe_available = false
---@class SuperCompactState : SimpleState
---@field miner_bounds any
-- Validate the selection
---@param self SuperCompactLayout
---@param state SimpleState
function layout:validate(state)
local c = state.coords
if (state.direction_choice == "west" or state.direction_choice == "east") then
if c.h < 3 then
return nil, {"mpp.msg_miner_err_1_w", 3}
end
else
if c.w < 3 then
return nil, {"mpp.msg_miner_err_1_h", 3}
end
end
return true
end
---@param proto MinerStruct
function layout:restriction_miner(proto)
return proto.symmetric
end
---@param self SuperCompactLayout
---@param state SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local M = state.miner
local size, area = M.size, M.area
local miners, postponed = {}, {}
local heuristic_values = common.init_heuristic_values()
local lane_layout = {}
local bx, by = shift_x + size - 1, shift_y + size - 1
--@param tile GridTile
--local function heuristic(tile) return tile.neighbor_count > 2 end
local heuristic = self:_get_miner_placement_heuristic(state)
local function miner_stagger(start_x, start_y, direction, row_start, mark_lane)
local row_index = row_start
for y = 1 + shift_y + start_y, state.coords.th + 2, size * 3 + 1 do
if mark_lane then lane_layout[#lane_layout+1] = {y=y+1, row_index=row_index} end
local ix = 1
for x = 1 + shift_x + start_x, state.coords.tw + 2, size * 2 do
local tile = grid:get_tile(x, y) --[[@as GridTile]]
---@type MinerPlacementInit
local miner = {
x = x,
y = y,
origin_x = x + M.x,
origin_y = y + M.y,
tile = tile,
direction = direction,
stagger = row_start,
line = row_index,
column = ix,
}
if tile.neighbors_outer > 0 and heuristic(tile) then
table_insert(miners, miner)
common.add_heuristic_values(heuristic_values, M, tile)
elseif tile.neighbors_outer > 0 then
postponed[#postponed+1] = miner
end
ix = ix + 1
end
row_index = row_index + 2
end
end
miner_stagger(0, -2, "south", 1)
miner_stagger(3, 0, "east", 1, true)
miner_stagger(0, 2, "north", 1)
-- the redundant calculation makes it easier to find the stagger offset
miner_stagger(0+size, -2+size+2, "south", 2)
miner_stagger(3-size, 0+size+2, "east", 2, true)
miner_stagger(0+size, 2+size+2, "north", 2)
local result = {
sx = shift_x,
sy = shift_y,
bx = bx,
by = by,
miners = miners,
lane_layout = lane_layout,
heuristics = heuristic_values,
heuristic_score = -(0/0),
unconsumed = 0,
}
common.process_postponed(state, result, miners, postponed)
common.finalize_heuristic_values(result, heuristic_values, state.coords)
for _, miner in pairs(miners) do
---@cast miner MinerPlacement
local current_lane = lane_layout[miner.line]
if not current_lane then
current_lane = {}
lane_layout[miner.line] = current_lane
end
table_insert(current_lane, miner)
end
return result
end
---@param self SimpleLayout
---@param state SimpleState
---@return CallbackState
function layout:prepare_miner_layout(state)
local grid = state.grid
local M = state.miner
local builder_miners = {}
state.builder_miners = builder_miners
for _, miner in ipairs(state.best_attempt.miners) do
grid:build_miner(miner.x, miner.y, state.miner.size-1)
-- used for deconstruction, not ghost placement
builder_miners[#builder_miners+1] = {
thing="miner",
extent_=state.miner.size,
grid_x = miner.origin_x,
grid_y = miner.origin_y,
radius = M.size / 2,
}
--[[ debug visualisation - miner placement
local color = {1, 0, 0}
if miner.direction == "west" then
color = {0, 1, 0}
elseif miner.direction == "north" then
color = {0, 0, 1}
end
local rect_color = miner.stagger == 1 and {1, 1, 1} or {0, 0, 0}
local off = state.miner.size / 2 - 0.1
local tx, ty = coord_revert[DIR](center.x, center.y, c.tw, c.th)
rendering.draw_rectangle{
surface = state.surface,
filled = false,
--color = miner.postponed and {1, 0, 0} or {0, 1, 0},
color = rect_color,
width = 3,
--target = {c.x1 + x, c.y1 + y},
left_top = {c.gx+tx-off, c.gy + ty - off},
right_bottom = {c.gx+tx+off, c.gy + ty + off},
}
rendering.draw_text{
surface=state.surface,
color=color,
text=miner_dir,
target=mpp_revert(c.gx, c.gy, DIR, center.x, center.y, c.tw, c.th),
vertical_alignment = "top",
alignment = "center",
}
rendering.draw_text{
surface=state.surface,
color={1, 1, 1},
text=miner.line * 2 + miner.stagger - 2,
target={c.gx + tx, c.gy + ty},
vertical_alignment = "bottom",
alignment = "center",
}
--]]
end
return "prepare_belt_layout"
end
---@param self SuperCompactLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local m = state.miner
local g = state.grid
local attempt = state.best_attempt
local underground_belt = state.belt.related_underground_belt
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local belt_lanes = attempt.lane_layout
local miner_lane_number = 0 -- highest index of a lane, because using # won't do the job if a lane is missing
local builder_belts = {}
state.builder_belts = builder_belts
local function que_entity(t) builder_belts[#builder_belts+1] = t end
state.belt_count = 0
for _, miner in ipairs(attempt.miners) do
local index = miner.line
miner_lane_number = max(miner_lane_number, index)
if not belt_lanes[index] then belt_lanes[index] = {} end
local line = belt_lanes[index]
line._index = index
local out_x = m.output_rotated[defines.direction[miner.direction]][1]
if line.last_x == nil or (miner.x + out_x) > line.last_x then
line.last_x = miner.x + out_x
line.last_miner = miner
end
line[#line+1] = miner
end
local temp_belts = {}
for k, v in pairs(belt_lanes) do temp_belts[#temp_belts+1] = v end
table.sort(temp_belts, function(a, b) return a.row_index < b.row_index end)
state.belts = temp_belts
local shift_x, shift_y = state.best_attempt.sx, state.best_attempt.sy
local function place_belts(start_x, end_x, y)
local belt_start, belt_end = 1 + shift_x + start_x, end_x
local pre_miner = g:get_tile(shift_x+m.size, y)
local built_miner = pre_miner and pre_miner.built_on == "miner"
if start_x == 0 then
-- straight runoff
for sx = 0, 2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=belt_start-sx,
grid_y=y,
direction=WEST,
}
end
elseif not built_miner then
for sx = 0, m.size+2 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=belt_start-sx,
grid_y=y,
direction=WEST,
}
end
else
-- underground exit
que_entity{
name=underground_belt,
type="output",
thing="belt",
grid_x=shift_x-1,
grid_y=y,
direction=WEST,
}
que_entity{
name=underground_belt,
type="input",
thing="belt",
grid_x=shift_x+m.size+1,
grid_y=y,
direction=WEST,
}
if built_miner then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = shift_x,
grid_y = y,
}
end
end
for x = belt_start, end_x, m.size * 2 do
local miner1 = g:get_tile(x, y-1) --[[@as GridTile]]
local miner2 = g:get_tile(x, y+1) --[[@as GridTile]]
local miner3 = g:get_tile(x+3, y) --[[@as GridTile]]
local built = (miner1 and miner1.built_on == "miner") or (miner2 and miner2.built_on == "miner")
local capped = miner3 and miner3.built_on == "miner"
local pole_built = built or capped
local last = x + m.size * 2 > end_x
if last and not capped then
-- last passtrough and no trailing miner
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x+1,
grid_y=y,
direction=WEST,
}
elseif capped or built then
que_entity{
name=underground_belt,
type="output",
thing="belt",
grid_x=x+1,
grid_y=y,
direction=WEST,
}
que_entity{
name=underground_belt,
type="input",
thing="belt",
grid_x=x+m.size*2,
grid_y=y,
direction=WEST,
}
else
for sx = 1, 6 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=x+sx,
grid_y=y,
direction=WEST,
}
end
end
if last and capped then belt_end = x+6 end
if pole_built then
power_poles[#power_poles+1] = {
name=state.pole_choice,
thing="pole",
grid_x = x + 2,
grid_y = y,
}
end
end
return belt_start, belt_end
end
for i = 1, miner_lane_number do
local belt = belt_lanes[i]
if belt and belt.last_x then
local y = m.size + shift_y - 1 + (m.size + 2) * (i-1)
local x_start = i % 2 == 0 and 3 or 0
local bx1, bx2 = place_belts(x_start, belt.last_x, y)
belt.x1, belt.x2, belt.y = bx1-3, bx2, y
state.belt_count = state.belt_count + 1
local lane1, lane2 = {}, {}
for _, miner in ipairs(belt) do
if miner.direction == "north" then
lane2[#lane2+1] = miner
else
lane1[#lane1+1] = miner
end
end
if #lane1 > 0 then belt.lane1 = lane1 end
if #lane2 > 0 then belt.lane2 = lane2 end
end
end
return "expensive_deconstruct"
end
---@param self SuperCompactLayout
---@param state SimpleState
---@return CallbackState
function layout:placement_miners(state)
local create_entity = builder.create_entity_builder(state)
local M = state.miner
local grid = state.grid
local module_inv_size = state.miner.module_inventory_size --[[@as uint]]
for _, miner in ipairs(state.best_attempt.miners) do
local ghost = create_entity{
name = state.miner_choice,
thing = "miner",
grid_x = miner.origin_x,
grid_y = miner.origin_y,
direction = defines.direction[miner.direction],
}
if state.module_choice ~= "none" then
ghost.item_requests = {[state.module_choice] = module_inv_size}
end
end
return "placement_belts"
end
return layout