Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local mpp_util = require("mpp_util")
---@type Layout
local layout = {}
layout.name = "Base"
layout.translation = {"mpp.settings_layout_choice_base"}
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"
layout.restrictions = {}
layout.restrictions.miner_available = true
layout.restrictions.miner_near_radius = {1, 10e3}
layout.restrictions.miner_far_radius = {1, 10e3}
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_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
--- 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)
local miner_proto = game.entity_prototypes[state.miner_choice]
state.miner = mpp_util.miner_struct(miner_proto)
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
return layout

View File

@@ -0,0 +1,687 @@
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
local base = require("layouts.base")
local grid_mt = require("grid_mt")
local mpp_util = require("mpp_util")
local coord_convert, coord_revert = mpp_util.coord_convert, mpp_util.coord_revert
local bp_direction = mpp_util.bp_direction
---@class BlueprintLayout : Layout
local layout = table.deepcopy(base)
---@class BlueprintState : SimpleState
---@field bp_w number
---@field bp_h number
---@field attempts BpPlacementAttempt[]
---@field best_attempt BpPlacementAttempt
---@field beacons BpPlacementEnt[]
---@field builder_power_poles BpPlacementEnt[]
---@field lamps BpPlacementEnt[]
--- Coordinate space for the attempt
---@class BpPlacementAttempt : PlacementAttempt
---@field other_ents BpPlacementEnt[]
---@field s_ix number Current blueprint metatile x
---@field s_iy number Current blueprint metatile y
---@field s_ie number Current entity index
---@field x number x start
---@field y number y start
---@field cx number number of blueprint repetitions on x axis
---@field cy number number of blueprint repetitions on y axis
---@class BpPlacementEnt
---@field ent BlueprintEntityEx
---@field center GridTile
---@field x number
---@field y number
---@field direction defines.direction
layout.name = "blueprints"
layout.translation = {"mpp.settings_layout_choice_blueprints"}
layout.restrictions.miner_available = false
layout.restrictions.belt_available = false
layout.restrictions.pole_available = false
layout.restrictions.lamp_available = false
layout.restrictions.coverage_tuning = true
layout.restrictions.landfill_omit_available = true
layout.restrictions.start_alignment_tuning = true
---Called from script.on_load
---@param self BlueprintLayout
---@param state BlueprintState
function layout:on_load(state)
if state.grid then
setmetatable(state.grid, grid_mt)
end
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:validate(state)
return base.validate(self, state)
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:start(state)
local grid = {}
local c = state.coords
local bp = state.cache
bp.tw, bp.th = bp.w, bp.h
local th, tw = c.h, c.w
if state.direction_choice == "south" or state.direction_choice == "north" then
th, tw = tw, th
bp.tw, bp.th = bp.h, bp.w
end
c.th, c.tw = th, tw
for y = -bp.h, th+bp.h do
local row = {}
grid[y] = row
for x = -bp.w, tw+bp.w do
row[x] = {
resources = 0,
x = x, y = y,
gx = c.x1 + x, gy = c.y1 + y,
consumed = false,
built_on = false,
neighbor_counts = {},
}
end
end
state.grid = setmetatable(grid, grid_mt)
return "process_grid"
end
---Called from script.on_load
---@param self BlueprintLayout
---@param state BlueprintState
function layout:process_grid(state)
local c = state.coords
local grid = state.grid
local resources = state.resources
local conv = coord_convert[state.direction_choice]
local gx, gy = state.coords.gx, state.coords.gy
local gw, gh = state.coords.w, state.coords.h
local resources = state.resources
state.resource_tiles = state.resource_tiles or {}
local resource_tiles = state.resource_tiles
local convolve_size = 0
local convolve_steps = {}
for _, miner in pairs(state.cache.miners) do
---@cast miner MinerStruct
convolve_size = miner.full_size ^ 2 + miner.size ^ 2
convolve_steps[miner.far] = true
convolve_steps[miner.near] = true
end
local budget, cost = 12000, 0
local i = state.resource_iter or 1
while i <= #resources and cost < budget do
local r = resources[i]
local x, y = r.position.x, r.position.y
local tx, ty = conv(x-gx, y-gy, gw, gh)
local tile = grid:get_tile(tx, ty)
tile.amount = r.amount
for width, _ in pairs(convolve_steps) do
grid:convolve_custom(tx, ty, width)
end
resource_tiles[#resource_tiles+1] = tile
cost = cost + convolve_size
i = i + 1
--[[ debug visualisation - resource
rendering.draw_circle{
surface = state.surface,
filled = false,
color = {0, 1, 0.3},
width = 1,
target = {c.gx + tx, c.gy + ty},
radius = 0.5,
}
--]]
end
state.resource_iter = i
if state.resource_iter >= #state.resources then
return "init_first_pass"
end
return true
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:init_first_pass(state)
local c = state.coords
local bp = state.cache
---@type BpPlacementAttempt[]
local attempts = {}
state.attempts = attempts
state.best_attempt_index = 1
state.attempt_index = 1
local function calc_slack(tw, bw, offset)
local count = ceil((tw-offset) / bw)
local overrun = count * bw - tw + offset
local start = -floor(overrun / 2)
local slack = overrun % 2
return count, start, slack
end
local count_x, start_x, slack_x = calc_slack(c.tw, bp.w, bp.ox)
local count_y, start_y, slack_y = calc_slack(c.th, bp.h, bp.oy)
if state.start_choice then
start_x, slack_x = 0, 0
end
attempts[1] = {
x = start_x, y = start_y,
cx = count_x, cy = count_y,
slack_x = slack_x, slack_y = slack_y,
miners = {}, postponed = {},
other_ents = {},
}
state.bp_grid = {}
for iy = 0, start_y - 1 do
local row = state.bp_grid[iy]
for ix = 0, start_x - 1 do
row[ix] = {completed = false}
end
end
--[[ debug rendering
rendering.draw_rectangle{
surface=state.surface,
left_top={state.coords.ix1, state.coords.iy1},
right_bottom={state.coords.ix1 + c.tw, state.coords.iy1 + c.th},
filled=false, width=8, color={0, 0, 1, 1},
players={state.player},
}
for iy = 0, count_y-1 do
for ix = 0, count_x-1 do
rendering.draw_rectangle{
surface=state.surface,
left_top={
c.ix1 + start_x + bp.w * ix,
c.iy1 + start_y + bp.h * iy,
},
right_bottom={
c.ix1 + start_x + bp.w * (ix+1),
c.iy1 + start_y + bp.h * (iy+1),
},
filled=false, width=2, color={0, 0.5, 1, 1},
players={state.player},
}
end
end
--]]
return "first_pass"
end
---@param miner MinerStruct
local function miner_heuristic(miner, coverage)
local near, far = miner.near, miner.far
local full, size = miner.full_size, miner.size
local neighbor_cap = ceil((size ^ 2) * 0.5)
if coverage then
---@param tile GridTile
return function(tile)
local nearc, farc = tile.neighbor_counts[near], tile.neighbor_counts[far]
return nearc and (nearc > 0 or
(farc and farc > neighbor_cap and nearc > (size * near))
)
end
end
---@param tile GridTile
return function(tile)
local nearc, farc = tile.neighbor_counts[near], tile.neighbor_counts[far]
return nearc and (nearc > neighbor_cap or
(farc and farc > neighbor_cap and nearc > (size * near))
)
end
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:first_pass(state)
local grid = state.grid
local bp = state.cache
local entities = bp.entities
local attempt = state.attempts[state.attempt_index]
local sx, sy, countx, county = attempt.x, attempt.y, attempt.cx-1, attempt.cy-1
local bpconv = bp_direction[state.direction_choice]
local bpw, bph = bp.w, bp.h
local heuristics = {}
for k, v in pairs(bp.miners) do heuristics[k] = miner_heuristic(v, state.coverage_choice) end
local miners, postponed = attempt.miners, attempt.postponed
local other_ents = attempt.other_ents
local s_ix = attempt.s_ix or 0
local s_iy = attempt.s_iy or 0
local s_ie = attempt.s_ie or 1
local progress, progress_cap = 0, 64
--local ix, iy, ie = s_ix, s_iy, s_ie
for iy = s_iy, county do
--while iy <= county do
local capstone_y = iy == county
for ix = s_ix, countx do
--while ix <= countx do
--for _, ent in pairs(bp.entities) do
for ie = s_ie, #entities do
--while ie <= #entities do
local ent = entities[ie]
ie = ie + 1
local capstone_x = ix == countx
if ent.capstone_y and not capstone_y then goto skip_ent end
if ent.capstone_x and not capstone_x then goto skip_ent end
local bpx, bpy = ceil(ent.position.x), ceil(ent.position.y)
local x, y = sx + ix * bpw + bpx, sy + iy * bph + bpy
local tile = grid:get_tile(x, y)
if not tile then goto skip_ent end
local bptr = bpconv[ent.direction or defines.direction.north]
local miner = state.cache.miners[ent.name]
if state.cache.miners[ent.name] then
local struct = {
ent = ent,
line = s_iy,
center = tile,
column = s_ix,
direction = bptr,
name = ent.name,
near = miner.near,
far = miner.far,
x = x, y = y,
}
local count_near = tile.neighbor_counts[miner.near]
local count_far = tile.neighbor_counts[miner.far]
if heuristics[ent.name](tile) then
miners[#miners+1] = struct
local even = mpp_util.entity_even_width(ent.name)
grid:consume_custom(x, y, miner.far, even[1], even[2])
else
postponed[#postponed+1] = struct
end
else
other_ents[#other_ents+1] = {
ent = ent,
center = tile,
x = x, y = y,
direction = bptr,
}
end
--[[ debug rendering
rendering.draw_circle{
surface = state.surface,
player = state.player,
filled = false,
color = {1,1,1,1},
radius= 0.5,
target = {c.gx + x, c.gy + y},
}
--]]
progress = progress + 1
if progress > progress_cap then
attempt.s_ix, attempt.s_iy, attempt.s_ie = ix, iy, ie
return true
end
::skip_ent::
end
s_ie = 1
end
s_ix = 0
end
return "first_pass_finish"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:first_pass_finish(state)
local grid = state.grid
local attempt = state.attempts[state.attempt_index]
local miners, postponed = attempt.miners, attempt.postponed
-- second pass
for _, miner in ipairs(miners) do
local even = mpp_util.entity_even_width(miner.ent.name)
grid:consume_custom(miner.center.x, miner.center.y, miner.far, even[1], even[2])
end
for _, miner in ipairs(postponed) do
local center = miner.center
miner.unconsumed = grid:get_unconsumed_custom(center.x, center.y, miner.far)
end
table.sort(postponed, function(a, b)
if a.unconsumed == b.unconsumed then
local sizes = mpp_util.keys_to_set(a.center.neighbor_counts, b.center.neighbor_counts)
for i = #sizes, 1, -1 do
local size = sizes[i]
local left, right = a.center.neighbor_counts[size], b.center.neighbor_counts[size]
if left ~= nil and right ~= nil then
return left > right
elseif left ~= nil then
return true
end
end
return false
end
return a.unconsumed > b.unconsumed
end)
for _, miner in ipairs(postponed) do
local center = miner.center
local unconsumed_count = grid:get_unconsumed_custom(center.x, center.y, miner.far)
if unconsumed_count > 0 then
local even = mpp_util.entity_even_width(miner.ent.name)
grid:consume_custom(center.x, center.y, miner.far, even[1], even[2])
miners[#miners+1] = miner
end
end
state.best_attempt = attempt
return "simple_deconstruct"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:simple_deconstruct(state)
local c = state.coords
local m = state.miner
local player = state.player
local surface = state.surface
local bp = state.cache
local deconstructor = global.script_inventory[state.deconstruction_choice and 2 or 1]
surface.deconstruct_area{
force=player.force,
player=player.index,
area={
left_top={c.x1-(bp.tw/2), c.y1-(bp.th/2)},
right_bottom={c.x2+(bp.tw/2), c.y2+(bp.th/2)}
},
item=deconstructor,
}
--[[ debug rendering - deconstruction area
rendering.draw_rectangle{
surface=state.surface,
players={state.player},
filled=false,
width=3,
color={1, 0, 0},
left_top={c.x1-(bp.tw/2), c.y1-(bp.th/2)},
right_bottom={c.x2+(bp.tw/2), c.y2+(bp.th/2)}
}
]]
return "place_miners"
end
---@param tile GridTile
---@param ent BlueprintEntity|MinerPlacement
---@return number
---@return number
local function fix_offgrid(tile, ent)
local ex, ey = ent.position.x, ent.position.y
local ox, oy = 0, 0
if ex == ceil(ex) then ox = 0.5 end
if ey == ceil(ey) then oy = 0.5 end
return tile.x + ox, tile.y + oy
end
---@param self SimpleLayout
---@param state BlueprintState
function layout:place_miners(state)
local c = state.coords
local grid = state.grid
local surface = state.surface
for _, miner in ipairs(state.best_attempt.miners) do
local center = miner.center
--g:build_miner_custom(center.x, center.y, miner.near)
local even = mpp_util.entity_even_width(miner.ent.name)
grid:build_thing(center.x, center.y, "miner", miner.near, even[1])
local ex, ey = fix_offgrid(center, miner.ent)
local x, y = coord_revert[state.direction_choice](ex, ey, c.tw, c.th)
-- local can_place = surface.can_place_entity{
-- name=state.miner.name,
-- force = state.player.force,
-- position={center.gx, center.gy},
-- direction = defines.direction.north,
-- build_check_type =
-- }
--[[ debug visualisation - miner placement
local off = state.miner.size / 2
rendering.draw_rectangle{
surface = state.surface,
filled = false,
color = miner.postponed and {1, 0, 0} or {0, 1, 0},
width = 3,
--target = {c.x1 + x, c.y1 + y},
left_top = {c.gx+x-off, c.gy + y - off},
right_bottom = {c.gx+x+off, c.gy + y + off},
}
--]]
local target = surface.create_entity{
raise_built=true,
name="entity-ghost",
player=state.player,
force = state.player.force,
position = {c.gx + x, c.gy + y},
direction = miner.direction,
inner_name = miner.name,
}
if miner.ent.items then
target.item_requests = miner.ent.items
end
end
return "place_other"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:place_other(state)
local c = state.coords
local grid = state.grid
local surface = state.surface
local beacons, power, lamps = {}, {}, {}
state.beacons, state.builder_power_poles, state.lamps = beacons, power, lamps
for _, other_ent in ipairs(state.best_attempt.other_ents) do
---@type BlueprintEntity
local ent = other_ent.ent
local center = other_ent.center
local ent_type = game.entity_prototypes[ent.name].type
if ent_type == "beacon" then
beacons[#beacons+1] = other_ent
goto continue
elseif ent_type == "electric-pole" then
power[#power+1] = other_ent
goto continue
--elseif ent_type == "lamp" then
--lamps[#lamps+1] = other_ent
--goto continue
end
local ex, ey = fix_offgrid(center, ent)
local x, y = coord_revert[state.direction_choice](ex, ey, c.tw, c.th)
local target = surface.create_entity{
raise_built=true,
name="entity-ghost",
player=state.player,
force=state.player.force,
position= {c.gx + x, c.gy + y},
direction = other_ent.direction,
inner_name = ent.name,
type=ent.type,
output_priority=ent.output_priority,
input_priority=ent.input_priority,
filter=ent.filter,
filters=ent.filters,
filter_mode=ent.filter_mode,
override_stack_size=ent.override_stack_size,
}
if ent.items then
target.item_requests = ent.items
end
--[[ debug rendering
rendering.draw_circle{
surface = state.surface,
player = state.player,
filled = false,
color = {0.5,0.5,1,1},
width=3,
radius= 0.4,
target = {c.gx + center.x, c.gy + center.y},
}
--]]
::continue::
end
return "place_beacons"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:place_beacons(state)
local c = state.coords
local surface = state.surface
local grid = state.grid
for _, other_ent in ipairs(state.beacons) do
---@type BlueprintEntity
local ent = other_ent.ent
local center = other_ent.center
local even = mpp_util.entity_even_width(ent.name)
local range = floor(game.entity_prototypes[ent.name].supply_area_distance)
if grid:find_thing(center.x, center.y, "miner", range+even.near, even[1]) then
local ex, ey = fix_offgrid(center, ent)
local x, y = coord_revert[state.direction_choice](ex, ey, c.tw, c.th)
local target = surface.create_entity{
raise_built=true,
name="entity-ghost",
player=state.player,
force=state.player.force,
position= {c.gx + x, c.gy + y},
direction = other_ent.direction,
inner_name = ent.name,
type=ent.type,
}
if ent.items then
target.item_requests = ent.items
end
grid:build_thing(center.x, center.y, "beacon", even.near, even[1])
end
end
return "place_electricity"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:place_electricity(state)
local c = state.coords
local surface = state.surface
local grid = state.grid
for _, other_ent in ipairs(state.builder_power_poles) do
---@type BlueprintEntity
local ent = other_ent.ent
local center = other_ent.center
local even = mpp_util.entity_even_width(ent.name)
local range = floor(game.entity_prototypes[ent.name].supply_area_distance)
if grid:find_thing_in(center.x, center.y, {"miner", "beacon"}, range, even[1]) then
local ex, ey = fix_offgrid(center, ent)
local x, y = coord_revert[state.direction_choice](ex, ey, c.tw, c.th)
local target = surface.create_entity{
raise_built=true,
name="entity-ghost",
player=state.player,
force=state.player.force,
position= {c.gx + x, c.gy + y},
direction = other_ent.direction,
inner_name = ent.name,
type=ent.type,
}
if ent.items then
target.item_requests = ent.items
end
grid:build_thing(center.x, center.y, "electricity", even.near, even[1])
end
end
return "finish"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:debug_overlay(state)
local c = state.coords
local surface = state.surface
local grid = state.grid
-- Draws built thing overlay
for iy, row in pairs(grid) do
if type(iy) ~= "number" then
game.print("asdf")
return
end
for ix, tile in pairs(row) do
local building = tile.built_on
if building then
local color = {0.3, 0.3, 0.3, 0.5}
if building == "miner" then
color = {0.1, 0.1, 0.8, 0.7}
elseif building == "beacon" then
color = {0.1, 0.8, 0.8, 0.7}
end
rendering.draw_circle{
surface=state.surface,
player=state.player,
filled=false,
radius=0.5,
color=color,
target={tile.gx-1, tile.gy-1},
}
end
end
end
return "finish"
end
---@param self BlueprintLayout
---@param state BlueprintState
function layout:finish(state)
return false
end
return layout

View File

@@ -0,0 +1,134 @@
local common = {}
local floor, ceil = math.floor, math.ceil
local min, max = math.min, math.max
---@param miner MinerStruct
function common.simple_miner_placement(miner)
local near, far, size, fullsize = miner.near, miner.far, miner.size, miner.full_size
local neighbor_cap = (near + 1) ^ 2
local leech = (far + 1) ^ 2
---@param center GridTile
return function(center)
return center.neighbor_count > neighbor_cap
-- return center.neighbor_count > neighbor_cap or center.far_neighbor_count > leech
end
end
---@param miner MinerStruct
function common.overfill_miner_placement(miner)
local near, far, size, fullsize = miner.near, miner.far, miner.size, miner.full_size
local neighbor_cap = (near + 1) ^ 2 - 1
local leech = far ^ 2 - 1
---@param center GridTile
return function(center)
return center.neighbor_count > 0 or center.far_neighbor_count > leech
end
end
---@param attempt PlacementAttempt
function common.simple_layout_heuristic(attempt)
--local lane_weigth = 0.05 * ((#attempt.lane_layout) ^ (1/3) - 1) + 1
return attempt.simple_density / attempt.miner_count + attempt.empty_space / attempt.miner_count
--return attempt.simple_density / attempt.miner_count * lane_weigth
--return (attempt.real_density - attempt.simple_density) -- * math.log(#attempt.lane_layout)
end
---@param attempt PlacementAttempt
function common.overfill_layout_heuristic(attempt)
--return (attempt.simple_density-attempt.real_density) / attempt.miner_count
--return attempt.simple_density - attempt.real_density -- * math.log(#attempt.lane_layout)
return -attempt.far_neighbor_sum
end
---Utility to fill in postponed miners on unconsumed resources
---@param state SimpleState
---@param heuristics PlacementAttempt
---@param miners MinerPlacement[]
---@param postponed MinerPlacement[]
function common.process_postponed(state, heuristics, miners, postponed)
local grid = state.grid
local size, near, far, fullsize = state.miner.size, state.miner.near, state.miner.far, state.miner.full_size
for _, miner in ipairs(miners) do
grid:consume(miner.center.x, miner.center.y)
end
for _, miner in ipairs(postponed) do
local center = miner.center
miner.unconsumed = grid:get_unconsumed(center.x, center.y)
end
table.sort(postponed, function(a, b)
if a.unconsumed == b.unconsumed then
local a_center, b_center = a.center, b.center
if a_center.neighbor_count == b_center.neighbor_count then
return a_center.far_neighbor_count > b_center.far_neighbor_count
end
return a_center.neighbor_count > b_center.neighbor_count
end
return a.unconsumed > b.unconsumed
end)
for _, miner in ipairs(postponed) do
local center = miner.center
local unconsumed_count = grid:get_unconsumed(center.x, center.y)
if unconsumed_count > 0 then
heuristics.neighbor_sum = heuristics.neighbor_sum + center.neighbor_count
heuristics.far_neighbor_sum = heuristics.far_neighbor_sum + center.far_neighbor_count
heuristics.empty_space = heuristics.empty_space + (size^2) - center.neighbor_count
heuristics.simple_density = heuristics.simple_density + center.neighbor_count / (size ^ 2)
heuristics.real_density = heuristics.real_density + center.far_neighbor_count / (fullsize ^ 2)
heuristics.leech_sum = heuristics.leech_sum + max(0, center.far_neighbor_count - center.neighbor_count)
heuristics.postponed_count = heuristics.postponed_count + 1
grid:consume(center.x, center.y)
miners[#miners+1] = miner
heuristics.miner_count = heuristics.miner_count + 1
miner.postponed = true
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
heuristics.unconsumed_count = unconsumed_sum
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
return common

View File

@@ -0,0 +1,402 @@
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_util")
local EAST, NORTH, SOUTH, WEST = mpp_util.directions()
---@class CompactLayout : SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "compact"
layout.translation = {"mpp.settings_layout_choice_compact"}
layout.restrictions.miner_near_radius = {1, 1}
layout.restrictions.miner_far_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 SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local size, near, far = state.miner.size, state.miner.near, state.miner.far
local fullsize = state.miner.full_size
local neighbor_sum = 0
local far_neighbor_sum = 0
local simple_density = 0
local real_density = 0
local miners, postponed = {}, {}
local leech_sum = 0
local empty_space = 0
local lane_layout = {}
local heuristic = self:_get_miner_placement_heuristic(state)
local row_index = 1
for ry = 1 + shift_y, state.coords.th + near, size + 0.5 do
local y = ceil(ry)
local column_index = 1
lane_layout[#lane_layout+1] = {y = y+near, row_index = row_index}
for x = 1 + shift_x, state.coords.tw, size do
local tile = grid:get_tile(x, y)
local center = grid:get_tile(x+near, y+near) --[[@as GridTile]]
local miner = {
tile = tile,
line = row_index,
column = column_index,
center = center,
}
if heuristic(center) then
miners[#miners+1] = miner
neighbor_sum = neighbor_sum + center.neighbor_count
far_neighbor_sum = far_neighbor_sum + center.far_neighbor_count
empty_space = empty_space + (size^2) - center.neighbor_count
real_density = real_density + center.far_neighbor_count / (fullsize ^ 2)
simple_density = simple_density + center.neighbor_count / (size ^ 2)
leech_sum = leech_sum + max(0, center.far_neighbor_count - center.neighbor_count)
elseif center.far_neighbor_count > 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,
miners=miners,
miner_count=#miners,
lane_layout=lane_layout,
postponed=postponed,
neighbor_sum=neighbor_sum,
far_neighbor_sum=far_neighbor_sum,
leech_sum=leech_sum,
simple_density=simple_density,
real_density=real_density,
empty_space=empty_space,
unconsumed_count=0,
postponed_count=0,
}
common.process_postponed(state, result, miners, postponed)
return result
end
---@param self CompactLayout
---@param state SimpleState
function layout:prepare_belt_layout(state)
local pole_proto = game.entity_prototypes[state.pole_choice] or {supply_area_distance=3, max_wire_distance=9}
local supply_area, wire_reach = 3.5, 9
if pole_proto then
supply_area, wire_reach = pole_proto.supply_area_distance, pole_proto.max_wire_distance
end
local belts = {}
state.belts = belts
state.builder_belts = {}
if supply_area < 3 or wire_reach < 9 then
state.pole_step = 6
self:_placement_belts_small(state)
else
state.pole_step = 9
self:_placement_belts_large(state)
end
return "prepare_pole_layout"
end
local function create_entity_que(belts)
return function(t) belts[#belts+1] = t end
end
---@param self CompactLayout
---@param state SimpleState
function layout:_placement_belts_small(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
---@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.center.x < b.center.x end)
end
---@param lane MinerPlacement[]
local function get_lane_length(lane) if lane then return lane[#lane].center.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 SimpleState
function layout:prepare_lamp_layout(state)
local lamps = {}
state.builder_lamps = lamps
local grid = state.grid
local sx, sy = -1, 0
local lamp_spacing = true
if state.pole_step > 7 then lamp_spacing = false end
for _, pole in ipairs(state.builder_power_poles) do
local x, y = pole.grid_x, pole.grid_y
local ix, iy = pole.ix, pole.iy
local tile = grid:get_tile(x+sx, y+sy)
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+sx,
grid_y = y+sy,
}
end
end
return "expensive_deconstruct"
end
---@param self CompactLayout
---@param state SimpleState
function layout:prepare_pole_layout(state)
return "prepare_lamp_layout"
end
return layout

View File

@@ -0,0 +1,122 @@
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("builder")
---@class CompactLogisticsLayout: SuperCompactLayout
local layout = table.deepcopy(super_compact)
layout.name = "compact_logistics"
layout.translation = {"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 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 * 2 + miner.stagger - 2
miner_lane_number = max(miner_lane_number, index)
if not miner_lanes[index] then miner_lanes[index] = {} end
local line = miner_lanes[index]
if miner.center.x > (line.last_x or 0) then
line.last_x = miner.center.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(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.built_on == "miner" or miner2.built_on == "miner"
local capped = 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
local stagger_shift = 1
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 = stagger_shift % 2 == 0 and 3 or 0
place_logistics(x_start, lane.last_x, y)
end
stagger_shift = stagger_shift + 1
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_util")
local mpp_revert = mpp_util.revert
local pole_grid_mt = require("pole_grid_mt")
---@class LogisticsLayout:SimpleLayout
local layout = table.deepcopy(simple)
layout.name = "logistics"
layout.translation = {"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 ipairs(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 then return lane[#lane].center.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 + (m.size + 1) * i
local x0 = attempt.sx + 1
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.near + 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,397 @@
local mpp_util = require("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 simple = require("layouts.simple")
local layout = table.deepcopy(simple) --[[@as Layout]]
layout.name = "sparse"
layout.translation = {"mpp.settings_layout_choice_sparse"}
layout.restrictions.miner_near_radius = {1, 10e3}
layout.restrictions.miner_far_radius = {2, 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 size, near, far = state.miner.size, state.miner.near, state.miner.far
local full_size = far * 2 + 1
local miners = {}
local row_index = 1
local lane_layout = {}
for y = 1 + shift_y, state.coords.th + near, full_size do
local column_index = 1
lane_layout[#lane_layout+1] = {y=y+near, row_index = row_index}
for x = 1 + shift_x, state.coords.tw, full_size do
local tile = grid:get_tile(x, y)
local center = grid:get_tile(x+near, y+near) --[[@as GridTile]]
local miner = {
tile = tile,
line = row_index,
column = column_index,
center = center,
direction = row_index % 2 == 1 and SOUTH or NORTH
}
if center.far_neighbor_count > 0 then
miners[#miners+1] = miner
end
column_index = column_index + 1
end
row_index = row_index + 1
end
return {
sx=shift_x, sy=shift_y,
miners=miners,
miner_count=#miners,
lane_layout=lane_layout,
}
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, m.near, m.far)
local county, slackh2, mody = calc_slack(c.th, m.near, m.far)
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.far-m.near
for _, pre_lane in ipairs(state.miner_lanes) do
if not pre_lane[1] then goto continue_lanes end
local y = pre_lane[1].center.y
local sx = attempt.sx
---@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.full_size-gutter*2+1,
y=y,
w=current_length*m.full_size+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.full_size-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,
y=lane.y,
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 ipairs(miner_lanes) do
table.sort(lane, function(a, b) return a.center.x < b.center.x end)
end
local builder_belts = {}
state.builder_belts = builder_belts
local function get_lane_length(lane) if lane then return lane[#lane].center.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.size + 1 + (m.far * 2 + 1) * (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), get_lane_length(lane2)) + m.near
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 center = miner.center
local mx, my = center.x, center.y
for ny = y + 1, y + (m.far - m.near) * 2 - 1 do
que_entity{
name=state.belt_choice,
thing="belt",
grid_x=mx,
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 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.far*2 + state.miner_max_column
local slack = ceil(miner_lane_width / pole_step) * pole_step - c.tw
local pole_start = supply_radius - floor(slack / 2) - 1
local function get_covered_miners(ix, iy)
for sy = -supply_radius, supply_radius do
for sx = -supply_radius, supply_radius do
local tile = g:get_tile(ix+sx, iy+sy)
if tile and tile.built_on == "miner" then
return true
end
end
end
end
local function place_pole_lane(y, iy, no_light)
local pole_lane = {}
local ix = 1
for x = attempt.sx + pole_start, c.tw, pole_step do
local built = false
if get_covered_miners(x, y) then
built = true
end
local pole = {x=x, y=y, ix=ix, iy=iy, built=built, no_light=no_light}
pole_lane[ix] = pole
ix = ix + 1
end
-- if built and ix > 1 and pole_lane[ix-1] then
-- for bx = ix - 1, 1, -1 do
-- local backtrack_pole = pole_lane[bx]
-- if not backtrack_pole.built then
-- backtrack_pole.built = true
-- else
-- break
-- end
-- end
-- end
-- builder_power_poles[#builder_power_poles+1] = pole
local backtrack_built = false
for pole_i = #pole_lane, 1, -1 do
---@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,
}
end
end
return pole_lane
end
local initial_y = attempt.sy
local iy = 1
local y_max, y_step = c.th + m.full_size, m.full_size * 2
for y = initial_y, y_max, y_step do
if ((m.far - m.near) * 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.far - m.near) * 2 + 1, iy, true)
end
elseif y + y_step > y_max and state.miner_lane_count % 2 == 0 then -- last pole lane
local backstep = (m.far - m.near) * 2 - 1
place_pole_lane(y - backstep)
else
local backstep = y == initial_y and 0 or m.near - m.far
place_pole_lane(y + backstep)
end
iy = iy + 1
end
return "expensive_deconstruct"
end
return layout

View File

@@ -0,0 +1,43 @@
local mpp_util = require("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")
local layout = table.deepcopy(sparse) --[[@as Layout]]
layout.name = "sparse_logistics"
layout.translation = {"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 SimpleLayout
---@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 center = miner.center
local y_shift = miner.direction == SOUTH and (1+m.near) or (-1-m.near)
belts[#belts+1] = {
name=state.logistics_choice,
thing="belt",
grid_x=center.x,
grid_y=center.y + y_shift,
}
end
return "prepare_pole_layout"
end
layout.finish = logistics.finish
return layout

View File

@@ -0,0 +1,398 @@
local common = require("layouts.common")
local simple = require("layouts.simple")
local mpp_util = require("mpp_util")
local builder = require("builder")
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 = {"mpp.settings_layout_choice_super_compact"}
layout.restrictions.miner_near_radius = {1, 1}
layout.restrictions.miner_far_radius = {1, 10e3}
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 self SuperCompactLayout
---@param state SimpleState
---@return PlacementAttempt
function layout:_placement_attempt(state, shift_x, shift_y)
local grid = state.grid
local size, near, fullsize = state.miner.size, state.miner.near, state.miner.full_size
local neighbor_sum = 0
local far_neighbor_sum = 0
local miners, postponed = {}, {}
local simple_density = 0
local real_density = 0
local leech_sum = 0
local empty_space = 0
local lane_layout = {}
--@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, stagger_step, mark_lane)
local miner_index = 1
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} end
for x = 1 + shift_x + start_x, state.coords.tw + 2, size * 2 do
local tile = grid:get_tile(x, y)
local center = grid:get_tile(x+near, y+near) --[[@as GridTile]]
local miner = {
tile = tile,
center = center,
direction = direction,
stagger = stagger_step,
line = miner_index,
}
if center.far_neighbor_count > 0 then
if heuristic(center) then
miners[#miners+1] = miner
neighbor_sum = neighbor_sum + center.neighbor_count
far_neighbor_sum = far_neighbor_sum + center.far_neighbor_count
empty_space = empty_space + (size^2) - center.neighbor_count
simple_density = simple_density + center.neighbor_count / (size ^ 2)
real_density = real_density + center.far_neighbor_count / (fullsize ^ 2)
leech_sum = leech_sum + max(0, center.far_neighbor_count - center.neighbor_count)
else
postponed[#postponed+1] = miner
end
end
end
miner_index = miner_index + 1
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,
miners = miners,
miner_count=#miners,
lane_layout=lane_layout,
postponed = postponed,
neighbor_sum = neighbor_sum,
far_neighbor_sum = far_neighbor_sum,
leech_sum=leech_sum,
simple_density = simple_density,
real_density = real_density,
empty_space=empty_space,
unconsumed_count = 0,
postponed_count = 0,
}
common.process_postponed(state, result, miners, postponed)
return result
end
---@param self SimpleLayout
---@param state SimpleState
---@return CallbackState
function layout:prepare_miner_layout(state)
local grid = state.grid
local builder_miners = {}
state.builder_miners = builder_miners
for _, miner in ipairs(state.best_attempt.miners) do
local center = miner.center
grid:build_miner(center.x, center.y)
-- used for deconstruction, not ghost placement
builder_miners[#builder_miners+1] = {
thing="miner",
extent_=state.miner.size,
grid_x = miner.center.x,
grid_y = miner.center.y,
padding_pre = state.miner.near,
padding_post = state.miner.near,
}
--[[ 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 = game.entity_prototypes[state.belt_choice].related_underground_belt.name
local power_poles = {}
state.builder_power_poles = power_poles
---@type table<number, MinerPlacement[]>
local belt_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
state.belt_count = 0
for _, miner in ipairs(attempt.miners) do
local index = miner.line * 2 + miner.stagger - 2
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
if miner.center.x > (line.last_x or 0) then
line.last_x = miner.center.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._index < b._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
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
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,
}
local miner = g:get_tile(shift_x+m.size, y)
if miner and miner.built_on == "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.built_on == "miner" or miner2.built_on == "miner"
local capped = 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
local stagger_shift = 1
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 =stagger_shift % 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
stagger_shift = stagger_shift + 1
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 grid = state.grid
local module_inv_size = state.miner.module_inventory_size --[[@as uint]]
for _, miner in ipairs(state.best_attempt.miners) do
local center = miner.center
grid:build_miner(center.x, center.y)
local ghost = create_entity{
name = state.miner_choice,
grid_x = center.x,
grid_y = center.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