Добавлены все обновления от сообщества, вплоть до #148
This commit is contained in:
107
mining-patch-planner/layouts/base.lua
Normal file
107
mining-patch-planner/layouts/base.lua
Normal 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
|
||||
1158
mining-patch-planner/layouts/blueprints.lua
Normal file
1158
mining-patch-planner/layouts/blueprints.lua
Normal file
File diff suppressed because it is too large
Load Diff
400
mining-patch-planner/layouts/common.lua
Normal file
400
mining-patch-planner/layouts/common.lua
Normal 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
|
||||
592
mining-patch-planner/layouts/compact.lua
Normal file
592
mining-patch-planner/layouts/compact.lua
Normal 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
|
||||
123
mining-patch-planner/layouts/compact_logistics.lua
Normal file
123
mining-patch-planner/layouts/compact_logistics.lua
Normal 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
|
||||
93
mining-patch-planner/layouts/logistics.lua
Normal file
93
mining-patch-planner/layouts/logistics.lua
Normal 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
|
||||
1275
mining-patch-planner/layouts/simple.lua
Normal file
1275
mining-patch-planner/layouts/simple.lua
Normal file
File diff suppressed because it is too large
Load Diff
402
mining-patch-planner/layouts/sparse.lua
Normal file
402
mining-patch-planner/layouts/sparse.lua
Normal 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
|
||||
43
mining-patch-planner/layouts/sparse_logistics.lua
Normal file
43
mining-patch-planner/layouts/sparse_logistics.lua
Normal 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
|
||||
420
mining-patch-planner/layouts/super_compact.lua
Normal file
420
mining-patch-planner/layouts/super_compact.lua
Normal 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
|
||||
Reference in New Issue
Block a user