1159 lines
32 KiB
Lua
1159 lines
32 KiB
Lua
local floor, ceil = math.floor, math.ceil
|
|
local min, max = math.min, math.max
|
|
|
|
local base = require("layouts.base")
|
|
local simple = require("layouts.simple")
|
|
local grid_mt = require("mpp.grid_mt")
|
|
local mpp_util = require("mpp.mpp_util")
|
|
local builder = require("mpp.builder")
|
|
local common = require("layouts.common")
|
|
local drawing = require("mpp.drawing")
|
|
local compatibility = require("mpp.compatibility")
|
|
local is_buildable_in_space = compatibility.is_buildable_in_space
|
|
local coord_convert, coord_revert = mpp_util.coord_convert, mpp_util.coord_revert
|
|
local direction_coord = mpp_util.direction_coord
|
|
|
|
local EAST, NORTH, SOUTH, WEST, ROTATION = mpp_util.directions()
|
|
|
|
---@param dir defines.direction
|
|
---@return defines.direction
|
|
local function opposing(dir) return (dir + SOUTH) % ROTATION end
|
|
|
|
---@class BlueprintLayout : Layout
|
|
local layout = table.deepcopy(base)
|
|
|
|
---@class BlueprintState : SimpleState
|
|
---@field bp_w number
|
|
---@field bp_h number
|
|
---@field bp_mining_drills BlueprintEntityEx[]
|
|
---@field bp_category_map table<string, GridBuilding>
|
|
---@field attempts BpPlacementAttempt[]
|
|
---@field attempt_index number
|
|
---@field best_attempt BpPlacementAttempt
|
|
---@field best_attempt_score number Heuristic value
|
|
---@field best_attempt_index number
|
|
---@field beacons BpPlacementEnt[]
|
|
---@field builder_power_poles BpPlacementEnt[]
|
|
---@field lamps BpPlacementEnt[]
|
|
---
|
|
---@field entity_output_locations table<number, table<number, true>> Mining drill output locations
|
|
---@field entity_input_locations table<number, table<number, true>> Inserter pickup spots
|
|
---@field collected_beacons BpPlacementEnt[]
|
|
---@field collected_containers BpPlacementEnt[]
|
|
---@field collected_inserters BpPlacementEnt[]
|
|
---@field collected_power BpPlacementEnt[]
|
|
---@field collected_belts BpPlacementEnt[]
|
|
---@field collected_other BpPlacementEnt[]
|
|
---
|
|
---@field builder_all GhostSpecification[]
|
|
---@field builder_index number Progress index of creating entities
|
|
|
|
--- 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 sx number x start
|
|
---@field sy 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 name string
|
|
---@field ent BlueprintEntityEx
|
|
---@field type string
|
|
---@field thing GridBuilding
|
|
---@field tile GridTile Top-left tile
|
|
---@field x number Top-left tile coordinate
|
|
---@field y number Top-left tile coordinate
|
|
---@field w number Direction rotated width
|
|
---@field h number Direction rotated height
|
|
---@field origin_x number Grid-independent position for correct in-world placement
|
|
---@field origin_y number Grid-independent position for correct in-world placement
|
|
---@field direction defines.direction
|
|
|
|
---@class BpPlacementEnt.inserter : BpPlacementEnt
|
|
---@field out_x number Output coordinate in grid
|
|
---@field out_y number Output coordinate in grid
|
|
---@field in_x number Input coordinate in grid
|
|
---@field in_y number Input coordinate in grid
|
|
|
|
layout.name = "blueprints"
|
|
layout.translation = {"", "[item=blueprint] ", {"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:initialize(state)
|
|
state.miner = mpp_util.miner_struct(state.cache.miner_name)
|
|
state.miner_choice = state.cache.miner_name
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:start(state)
|
|
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
|
|
|
|
state.bp_mining_drills = bp:get_mining_drills()
|
|
state.bp_category_map = bp:get_entity_categories()
|
|
|
|
state.entity_output_locations = {}
|
|
state.entity_input_locations = {}
|
|
state.collected_beacons = {}
|
|
state.collected_power = {}
|
|
state.collected_belts = {}
|
|
state.collected_inserters = {}
|
|
state.collected_containers = {}
|
|
state.collected_other = {}
|
|
|
|
state.builder_all = {}
|
|
|
|
return "deconstruct_previous_ghosts"
|
|
end
|
|
|
|
layout.initialize_grid = simple.initialize_grid
|
|
|
|
-- ---@param self BlueprintLayout
|
|
-- ---@param state BlueprintState
|
|
-- function layout:process_grid(state)
|
|
-- simple.process_grid(self --[[@as SimpleLayout]], state)
|
|
-- return "prepare_layout_attempts"
|
|
-- end
|
|
|
|
layout.process_grid = simple.process_grid
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:prepare_layout_attempts(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 = -ceil(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
|
|
|
|
-- TODO: make attempts use
|
|
attempts[1] = {
|
|
sx = start_x, sy = start_y,
|
|
cx = count_x, cy = count_y,
|
|
slack_x = slack_x, slack_y = slack_y,
|
|
miners = {}, postponed = {},
|
|
other_ents = {},
|
|
s_ix = 0, s_iy = 0, s_ie = 1,
|
|
}
|
|
|
|
--[[ 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 "init_layout_attempt"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return BpPlacementAttempt
|
|
function layout:_placement_attempt(state, attempt)
|
|
local grid = state.grid
|
|
local bp = state.cache
|
|
local M = state.miner
|
|
local entities, num_ents = state.bp_mining_drills, #state.bp_mining_drills
|
|
local sx, sy, countx, county = attempt.sx, attempt.sy, attempt.cx-1, attempt.cy-1
|
|
local bpw, bph = bp.w, bp.h
|
|
local heuristic = simple._get_miner_placement_heuristic(self --[[@as SimpleLayout]], state)
|
|
local heuristic_values = common.init_heuristic_values()
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
-- debug_draw:draw_circle{
|
|
-- x = 0,
|
|
-- y = 0,
|
|
-- color = {0, 0, 0},
|
|
-- radius = 0.5,
|
|
-- }
|
|
|
|
local miners, postponed = attempt.miners, {}
|
|
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, 100
|
|
--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, num_ents 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) or (ent.capstone_x and not capstone_x) then
|
|
goto skip_ent
|
|
end
|
|
|
|
local ent_struct = mpp_util.entity_struct(ent.name)
|
|
local bpx = ceil(ent.position.x - ent_struct.x)
|
|
local bpy = ceil(ent.position.y - ent_struct.y)
|
|
local x, y = sx + ix * bpw + bpx, sy + iy * bph + bpy
|
|
local tile = grid:get_tile(x, y)
|
|
|
|
if not tile or M.name ~= ent.name then goto skip_ent end
|
|
|
|
local struct = {
|
|
ent = ent,
|
|
tile = tile,
|
|
x = x, y = y,
|
|
origin_x = x + M.x,
|
|
origin_y = y + M.y,
|
|
line = s_iy,
|
|
column = s_ix,
|
|
direction = ent.direction,
|
|
name = ent.name,
|
|
}
|
|
if heuristic(tile) then
|
|
miners[#miners+1] = struct
|
|
common.add_heuristic_values(heuristic_values, M, tile)
|
|
else
|
|
postponed[#postponed+1] = struct
|
|
end
|
|
|
|
::skip_ent::
|
|
end
|
|
s_ie = 1
|
|
end
|
|
s_ix = 0
|
|
end
|
|
|
|
local result = {
|
|
sx = sx, sy = sy,
|
|
cx = attempt.cx, cy = attempt.cy,
|
|
miners = miners,
|
|
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 BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:init_layout_attempt(state)
|
|
local attempt = state.attempts[state.attempt_index]
|
|
|
|
state.best_attempt = self:_placement_attempt(state, attempt)
|
|
state.best_attempt_score = simple._get_layout_heuristic(self --[[@as SimpleLayout]], state)(state.best_attempt.heuristics)
|
|
state.best_attempt.heuristic_score = state.best_attempt_score
|
|
|
|
if state.debug_dump then
|
|
state.saved_attempts = {}
|
|
state.saved_attempts[#state.saved_attempts+1] = state.best_attempt
|
|
end
|
|
|
|
state.attempt_index = state.attempt_index + 1
|
|
return "layout_attempt"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:layout_attempt(state)
|
|
return "collect_entities"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:_get_deconstruction_objects(state)
|
|
return {
|
|
state.builder_miners,
|
|
state.builder_all,
|
|
--state.builder_pipes,
|
|
--state.builder_belts,
|
|
--state.builder_power_poles,
|
|
--state.builder_lamps,
|
|
}
|
|
end
|
|
|
|
---@param self SimpleLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:collect_entities(state)
|
|
local grid = state.grid
|
|
local C = state.coords
|
|
local bp = state.cache
|
|
local category_map = state.bp_category_map
|
|
local attempt = state.best_attempt
|
|
local entities, num_ents = bp.entities, #bp.entities
|
|
local sx, sy, countx, county = attempt.sx, attempt.sy, attempt.cx-1, attempt.cy-1
|
|
local bpw, bph = bp.w, bp.h
|
|
local is_space = state.is_space
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
local collected_beacons = state.collected_beacons
|
|
local collected_inserters = state.collected_inserters
|
|
local collected_power = state.collected_power
|
|
local collected_belts = state.collected_belts
|
|
local collected_other = state.collected_other
|
|
local collected_containers = state.collected_containers
|
|
|
|
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, 100
|
|
--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, num_ents do
|
|
local ent = entities[ie]
|
|
local ent_name = ent.name
|
|
if is_space and not is_buildable_in_space(ent_name) then goto skip_ent end
|
|
local ent_category = category_map[ent_name]
|
|
ie = ie + 1
|
|
local capstone_x = ix == countx
|
|
if
|
|
ent_category == "miner"
|
|
or (ent.capstone_y and not capstone_y)
|
|
or (ent.capstone_x and not capstone_x)
|
|
then
|
|
goto skip_ent
|
|
end
|
|
local entity_struct = mpp_util.entity_struct(ent_name)
|
|
local rx, ry, rw, rh = mpp_util.rotate_struct(entity_struct, ent.direction)
|
|
local bpx = ceil(ent.position.x - rx)
|
|
local bpy = ceil(ent.position.y - ry)
|
|
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
|
|
|
|
---@type BpPlacementEnt
|
|
local base_collected = {
|
|
tile = tile,
|
|
ent = ent,
|
|
name = ent_name,
|
|
type = entity_struct.type,
|
|
x = x,
|
|
y = y,
|
|
origin_x = x + rx,
|
|
origin_y = y + ry,
|
|
w = rw, h = rh,
|
|
direction = ent.direction,
|
|
}
|
|
|
|
if ent_category == "beacon" then
|
|
collected_beacons[#collected_beacons+1] = base_collected
|
|
elseif ent_category == "inserter" then
|
|
collected_inserters[#collected_inserters+1] = base_collected
|
|
elseif ent_category == "pole" then
|
|
collected_power[#collected_power+1] = base_collected
|
|
elseif ent_category == "belt" then
|
|
collected_belts[#collected_belts+1] = base_collected
|
|
elseif ent_category == "container" then
|
|
collected_containers[#collected_containers+1] = base_collected
|
|
else
|
|
collected_other[#collected_other+1] = base_collected
|
|
end
|
|
|
|
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 "prepare_miner_layout"
|
|
end
|
|
|
|
local function append_transfer_location(locations, x, y)
|
|
local output_row = locations[y]
|
|
if output_row then
|
|
output_row[x] = true
|
|
else
|
|
locations[y] = {[x] = true}
|
|
end
|
|
end
|
|
|
|
---@param self SimpleLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_miner_layout(state)
|
|
local C, M, G = state.coords, state.miner, state.grid
|
|
|
|
local builder_miners = {}
|
|
state.builder_miners = builder_miners
|
|
|
|
local output_locations = state.entity_output_locations
|
|
|
|
for _, miner in ipairs(state.best_attempt.miners) do
|
|
|
|
G:build_miner(miner.x, miner.y, M.size-1)
|
|
|
|
builder_miners[#builder_miners+1] = {
|
|
thing = "miner",
|
|
name = miner.ent.name,
|
|
direction = miner.direction,
|
|
grid_x = miner.origin_x,
|
|
grid_y = miner.origin_y,
|
|
extent_w = M.extent_w,
|
|
extent_h = M.extent_h,
|
|
}
|
|
|
|
local output = M.output_rotated[miner.direction]
|
|
append_transfer_location(output_locations, miner.x + output.x, miner.y + output.y)
|
|
|
|
--[[ debug visualisation - mining drill placement
|
|
local x, y = miner.origin_x, miner.origin_y
|
|
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},
|
|
}
|
|
--]]
|
|
|
|
end
|
|
|
|
return "prepare_beacon_layout"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_beacon_layout(state)
|
|
local c = state.coords
|
|
local surface = state.surface
|
|
local grid = state.grid
|
|
local builder_all = state.builder_all
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
for _, beacon in ipairs(state.collected_beacons) do
|
|
local struct = mpp_util.beacon_struct(beacon.name)
|
|
local x, y = beacon.x, beacon.y
|
|
local ext = struct.extent_negative
|
|
local found = grid:find_thing(x+ext, y+ext, "miner", struct.area-1)
|
|
|
|
if not found then goto continue end
|
|
|
|
grid:build_thing(x, y, "beacon", struct.w-1, struct.h-1)
|
|
|
|
builder_all[#builder_all+1] = {
|
|
thing = "beacon",
|
|
name = struct.name,
|
|
grid_x = beacon.origin_x,
|
|
grid_y = beacon.origin_y,
|
|
extent_w = struct.extent_w,
|
|
extent_h = struct.extent_h,
|
|
items = beacon.ent.items,
|
|
}
|
|
|
|
::continue::
|
|
end
|
|
|
|
return "prepare_inserter_layout"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_inserter_layout(state)
|
|
local G = state.grid
|
|
local builder_all = state.builder_all
|
|
local output_locations = state.entity_output_locations
|
|
local input_locations = state.entity_input_locations
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
for _, inserter in ipairs(state.collected_inserters) do
|
|
local struct, out_x, out_y, in_x, in_y
|
|
if inserter.type == "inserter" then
|
|
struct = mpp_util.inserter_struct(inserter.name)
|
|
out_x = inserter.x + struct.drop_rotated[inserter.direction].x
|
|
out_y = inserter.y + struct.drop_rotated[inserter.direction].y
|
|
in_x = inserter.x + struct.pickup_rotated[inserter.direction].x
|
|
in_y = inserter.y + struct.pickup_rotated[inserter.direction].y
|
|
elseif inserter.type == "loader-1x1" then
|
|
struct = mpp_util.entity_struct(inserter.name)
|
|
local next_coord = direction_coord[inserter.direction]
|
|
out_x = inserter.x + next_coord.x
|
|
out_y = inserter.y + next_coord.y
|
|
in_x = inserter.x - next_coord.x
|
|
in_y = inserter.y - next_coord.y
|
|
|
|
if inserter.ent.type == "input" then
|
|
in_x, in_y, out_x, out_y = out_x, out_y, in_x, in_y
|
|
end
|
|
--debug_draw:draw_rectangle{x=inserter.x-.5,y=inserter.y-.5,w=inserter.w,h=inserter.h}
|
|
elseif inserter.type == "loader" then
|
|
local direction = inserter.direction
|
|
local simple_direction = (inserter.direction) % 4
|
|
local next_coord = direction_coord[inserter.direction % 4]
|
|
|
|
struct = mpp_util.entity_struct(inserter.name)
|
|
|
|
local tx1, ty1, tx2, ty2
|
|
|
|
if simple_direction == EAST then
|
|
tx1 = inserter.x - next_coord.x
|
|
ty1 = inserter.y - next_coord.y
|
|
tx2 = inserter.x + inserter.w - 1 + next_coord.x
|
|
ty2 = inserter.y + inserter.h - 1 + next_coord.y
|
|
else
|
|
tx1 = inserter.x + inserter.w - 1 - next_coord.x
|
|
ty1 = inserter.y + inserter.h - 1 - next_coord.y
|
|
tx2 = inserter.x + next_coord.x
|
|
ty2 = inserter.y + next_coord.y
|
|
end
|
|
|
|
if direction > EAST then
|
|
tx1, ty1, tx2, ty2 = tx2, ty2, tx1, ty1
|
|
end
|
|
out_x, out_y, in_x, in_y = tx1, ty1, tx2, ty2
|
|
else
|
|
goto continue
|
|
end
|
|
|
|
--debug_draw:draw_circle{x = inserter.x, y = inserter.y, filled = true, radius = 0.2}
|
|
-- output point is green
|
|
--debug_draw:draw_circle{x = out_x, y = out_y, radius = 0.3, color = {0.2, 0.8, 0.2}}
|
|
-- input point is blue
|
|
--debug_draw:draw_circle{x = in_x, y = in_y, radius = 0.3, color = {0.2, 0.2, 0.8}}
|
|
|
|
local input_tile = G:get_tile(in_x, in_y)
|
|
local output_tile = G:get_tile(out_x, out_y)
|
|
|
|
-- TODO: figure out a better way to determine if inserter should be placed
|
|
if
|
|
(not input_tile or not output_tile)
|
|
or
|
|
(
|
|
input_tile
|
|
and input_tile.built_on ~= "miner"
|
|
and output_tile
|
|
and output_tile.built_on ~= "miner"
|
|
)
|
|
then
|
|
goto continue
|
|
end
|
|
|
|
append_transfer_location(output_locations, out_x, out_y)
|
|
--debug_draw:draw_circle{radius=0.2, x=out_x, y=out_y, color={1, 1, 0}}
|
|
append_transfer_location(input_locations, in_x, in_y)
|
|
--debug_draw:draw_circle{radius=0.2, x=in_x, y=in_y, color={1, 0, 1}}
|
|
|
|
builder_all[#builder_all+1] = {
|
|
thing = "inserter",
|
|
name = struct.name,
|
|
grid_x = inserter.origin_x,
|
|
grid_y = inserter.origin_y,
|
|
direction = inserter.direction,
|
|
extent_w = struct.extent_w,
|
|
extent_h = struct.extent_h,
|
|
conditions = inserter.ent.conditions, -- inserter
|
|
filters = inserter.ent.filters, -- inserter
|
|
filter_mode = inserter.ent.filter_mode,
|
|
override_stack_size = inserter.ent.override_stack_size,
|
|
}
|
|
|
|
-- output_locations[#output_locations+1] = {
|
|
-- inserter.x,-- + output.x,
|
|
-- inserter.y,-- + output.y,
|
|
-- }
|
|
::continue::
|
|
end
|
|
|
|
return "prepare_electricity"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_electricity(state)
|
|
local c = state.coords
|
|
local G = state.grid
|
|
local surface = state.surface
|
|
local grid = state.grid
|
|
local builder_all = state.builder_all
|
|
|
|
for _, power_pole in ipairs(state.collected_power) do
|
|
local struct = mpp_util.pole_struct(power_pole.name)
|
|
local x, y = power_pole.x, power_pole.y
|
|
local needs_power = G:needs_power(x, y, struct)
|
|
|
|
if not needs_power then goto continue end
|
|
|
|
grid:build_thing(x, y, "pole", struct.w-1, struct.h-1)
|
|
|
|
builder_all[#builder_all+1] = {
|
|
thing = "pole",
|
|
name = struct.name,
|
|
grid_x = power_pole.origin_x,
|
|
grid_y = power_pole.origin_y,
|
|
direction = power_pole.direction,
|
|
extent_w = struct.extent_w,
|
|
extent_h = struct.extent_h,
|
|
}
|
|
|
|
::continue::
|
|
end
|
|
|
|
return "prepare_belt_layout_init"
|
|
end
|
|
|
|
---@class BpBeltPiece
|
|
---@field ent BlueprintEntityEx
|
|
---@field type string
|
|
---@field x number
|
|
---@field y number
|
|
---@field direction defines.direction
|
|
---@field is_underground "input"|"output"|false
|
|
---@field w number
|
|
---@field h number
|
|
---@field group_output number?
|
|
---@field group_input number?
|
|
---@field previous table<BpBeltPiece, true>
|
|
---@field next BpBeltPiece?
|
|
|
|
---@type table<string, number>
|
|
local underground_belt_length_cache = {}
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_belt_layout_init(state)
|
|
local G = state.grid
|
|
local builder_all = state.builder_all
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
if not state.collected_belts or #state.collected_belts == 0 then
|
|
return "prepare_container_layout"
|
|
end
|
|
|
|
---@type table<BpBeltPiece, true>
|
|
local all_belts = {}
|
|
state.all_belts = all_belts
|
|
local belt_grid = setmetatable({}, grid_mt)
|
|
state.belt_grid = belt_grid
|
|
---@type table<BpBeltPiece, true>
|
|
local belts_unvisited = {}
|
|
|
|
local function set_piece(px, py, piece)
|
|
local belt_row = belt_grid[py]
|
|
if belt_row then
|
|
belt_row[px] = piece --[[@as GridTile]]
|
|
else
|
|
belt_grid[py] = {[px]=piece --[[@as GridTile]] }
|
|
end
|
|
end
|
|
|
|
for _, belt in ipairs(state.collected_belts) do
|
|
local struct = mpp_util.entity_struct(belt.name)
|
|
|
|
---@type BpBeltPiece
|
|
local belt_piece = {
|
|
ent = belt.ent,
|
|
type = belt.type,
|
|
x = belt.x, y = belt.y,
|
|
origin_x = belt.origin_x,
|
|
origin_y = belt.origin_y,
|
|
w = belt.w, h = belt.h,
|
|
direction = belt.direction,
|
|
is_underground = belt.ent.type or false,
|
|
group_input = nil,
|
|
group_output = nil,
|
|
next = nil,
|
|
previous = {},
|
|
extent_w = belt.w/2,
|
|
extent_h = belt.h/2,
|
|
}
|
|
--all_belts[#all_belts+1] = belt_piece
|
|
--belts_unvisited[belt_piece] = true
|
|
|
|
if struct.type == "splitter" then
|
|
set_piece(belt.x+belt.w-1, belt.y+belt.h-1, belt_piece)
|
|
end
|
|
set_piece(belt.x, belt.y, belt_piece)
|
|
|
|
end
|
|
|
|
return "prepare_belt_layout_forward"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_belt_layout_forward(state)
|
|
|
|
local active_group = 1
|
|
local belt_grid = state.belt_grid
|
|
local all_belts = state.all_belts
|
|
|
|
---@type (table<string, BpBeltPiece>)[]
|
|
local extra_output_locations = {}
|
|
|
|
---@param piece BpBeltPiece
|
|
---@param previous BpBeltPiece?
|
|
local function traverse(piece, previous)
|
|
if
|
|
piece == nil
|
|
or piece.group_output
|
|
or (previous and (piece.direction == (previous.direction+SOUTH) % ROTATION) )
|
|
then
|
|
return
|
|
end
|
|
|
|
--belts_unvisited[piece] = nil
|
|
piece.group_output = active_group
|
|
if previous then
|
|
piece.previous[previous] = true
|
|
end
|
|
all_belts[piece] = true
|
|
|
|
local x, y = piece.x, piece.y
|
|
local next_pos = direction_coord[piece.direction] --[[@as BpBeltPiece]]
|
|
local next_x, next_y = next_pos.x, next_pos.y
|
|
|
|
if piece.type == "splitter" then
|
|
local other_x = x + next_pos.x + piece.w - 1
|
|
local other_y = y + next_pos.y + piece.h - 1
|
|
-- extra_output_locations[#extra_output_locations+1] = {
|
|
-- piece = belt_grid:get_tile(other_x, other_y) --[[@as BpBeltPiece]],
|
|
-- previous = previous,
|
|
-- }
|
|
traverse(
|
|
belt_grid:get_tile(other_x, other_y) --[[@as BpBeltPiece]],
|
|
piece
|
|
)
|
|
return traverse(
|
|
belt_grid:get_tile(x+next_x, y+next_y) --[[@as BpBeltPiece]],
|
|
piece
|
|
)
|
|
elseif piece.type == "underground-belt" then
|
|
local underground_length = underground_belt_length_cache[piece.ent.name]
|
|
if not underground_length then
|
|
local proto = game.entity_prototypes[piece.ent.name]
|
|
underground_length = proto.max_underground_distance or 0
|
|
underground_belt_length_cache[piece.ent.name] = underground_length
|
|
end
|
|
|
|
for i = 1, underground_length do
|
|
local next_piece = belt_grid:get_tile(x+next_x*i, y+next_y*i) --[[@as BpBeltPiece]]
|
|
if next_piece then
|
|
return traverse(next_piece, piece)
|
|
end
|
|
end
|
|
end
|
|
|
|
return traverse(
|
|
belt_grid:get_tile(x+next_x, y+next_y) --[[@as BpBeltPiece]],
|
|
piece
|
|
)
|
|
end
|
|
|
|
for y, output_row in pairs(state.entity_output_locations) do
|
|
local belt_row = belt_grid[y]
|
|
if not belt_row then goto continue end
|
|
for x in pairs(output_row) do
|
|
traverse(belt_row[x] --[[@as BpBeltPiece]])
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
-- for _, pieces in pairs(extra_output_locations) do
|
|
-- traverse(pieces.piece, pieces.previous) -- --[[@as BpBeltPiece]])
|
|
-- end
|
|
|
|
return "prepare_belt_layout_backward"
|
|
end
|
|
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_belt_layout_backward(state)
|
|
|
|
--local debug_draw = drawing(state, true, false)
|
|
|
|
local active_group = 1
|
|
local belt_grid = state.belt_grid
|
|
local all_belts = state.all_belts
|
|
|
|
---@class BeltTraverseBacktrack
|
|
---@field piece BpBeltPiece
|
|
---@field next BpBeltPiece
|
|
---@field direction defines.direction Direction to which piece should be pointed
|
|
|
|
local backtrack_positions = {}
|
|
|
|
---@param piece BpBeltPiece
|
|
---@param next BpBeltPiece? Feeding into this piece
|
|
---@param to_direction defines.direction? Direction to which piece should point to
|
|
local function traverse(piece, next, to_direction)
|
|
if
|
|
piece == nil
|
|
or piece.group_input
|
|
or (to_direction and (piece.direction ~= to_direction) )
|
|
then
|
|
return
|
|
end
|
|
local piece_dir = piece.direction
|
|
local x, y = piece.x, piece.y
|
|
|
|
piece.group_input = active_group
|
|
if next then
|
|
next.previous[piece] = true
|
|
end
|
|
|
|
all_belts[piece] = true
|
|
|
|
--local backtrack_directions = direction_previous[piece.direction]
|
|
if piece.type == "transport-belt" then
|
|
local dir_side1 = (piece_dir - EAST) % ROTATION
|
|
local side1 = direction_coord[dir_side1]
|
|
local piece_side1 = belt_grid:get_tile(x+side1.x, y+side1.y)
|
|
backtrack_positions[#backtrack_positions+1] = {
|
|
piece=piece_side1,
|
|
next=piece,
|
|
direction=opposing(dir_side1)
|
|
}
|
|
|
|
local dir_side2 = (piece_dir + EAST) % ROTATION
|
|
local side2 = direction_coord[dir_side2]
|
|
local piece_side2 = belt_grid:get_tile(x+side2.x, y+side2.y)
|
|
backtrack_positions[#backtrack_positions+1] = {
|
|
piece=piece_side2,
|
|
next=piece,
|
|
direction=opposing(dir_side2)
|
|
}
|
|
|
|
local dir_straight = (piece_dir + SOUTH) % ROTATION
|
|
local straight = direction_coord[dir_straight]
|
|
local piece_straight = belt_grid:get_tile(x+straight.x, y+straight.y)
|
|
return traverse(
|
|
piece_straight --[[@as BpBeltPiece]],
|
|
piece,
|
|
opposing(dir_straight)
|
|
)
|
|
elseif piece.type == "splitter" then
|
|
local next_pos = direction_coord[opposing(piece.direction)]
|
|
|
|
backtrack_positions[#backtrack_positions+1] = {
|
|
piece = belt_grid:get_tile(x + next_pos.x, y + next_pos.y),
|
|
next = piece,
|
|
piece.direction
|
|
}
|
|
|
|
return traverse(
|
|
belt_grid:get_tile(x + next_pos.x + piece.w - 1, y + next_pos.y + piece.h - 1),
|
|
piece,
|
|
piece.direction
|
|
)
|
|
elseif piece.type == "underground-belt" then
|
|
local next_pos = direction_coord[opposing(piece.direction)]
|
|
|
|
local underground_length = underground_belt_length_cache[piece.ent.name]
|
|
if not underground_length then
|
|
local proto = game.entity_prototypes[piece.ent.name]
|
|
underground_length = proto.max_underground_distance or 0
|
|
underground_belt_length_cache[piece.ent.name] = underground_length
|
|
end
|
|
|
|
for i = 1, underground_length do
|
|
local next_piece = belt_grid:get_tile(x+next_pos.x*i, y+next_pos.y*i) --[[@as BpBeltPiece]]
|
|
if next_piece then
|
|
return traverse(next_piece, piece)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- for y, output_row in pairs(state.entity_input_locations) do
|
|
-- local belt_row = belt_grid[y]
|
|
-- if not belt_row then goto continue end
|
|
-- for x in pairs(output_row) do
|
|
-- traverse(belt_row[x] --[[@as BpBeltPiece]])
|
|
-- end
|
|
-- ::continue::
|
|
-- end
|
|
|
|
for y, input_row in pairs(state.entity_input_locations) do
|
|
for x in pairs(input_row) do
|
|
backtrack_positions[#backtrack_positions+1] = {
|
|
piece = belt_grid:get_tile(x, y),
|
|
}
|
|
end
|
|
end
|
|
|
|
local iter, position = next(backtrack_positions)
|
|
while iter do
|
|
traverse(position.piece, position.next, position.direction)
|
|
iter, position = next(backtrack_positions, iter)
|
|
end
|
|
|
|
return "prepare_belt_layout_finalize"
|
|
end
|
|
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_belt_layout_finalize(state)
|
|
local G = state.grid
|
|
local builder_all = state.builder_all
|
|
|
|
for piece in pairs(state.all_belts) do
|
|
local bp_ent = piece.ent
|
|
G:build_thing(piece.x, piece.y, "belt", piece.w-1, piece.h-1)
|
|
builder_all[#builder_all+1] = {
|
|
thing="belt",
|
|
name = bp_ent.name,
|
|
grid_x = piece.origin_x,
|
|
grid_y = piece.origin_y,
|
|
direction = piece.direction,
|
|
extent_w = piece.extent_w,
|
|
extent_h = piece.extent_h,
|
|
type = bp_ent.type, -- underground belt
|
|
conditions = bp_ent.conditions, -- inserter
|
|
filters = bp_ent.filters, -- inserter
|
|
request_filters = bp_ent.request_filters,
|
|
input_priority=bp_ent.input_priority,
|
|
output_priority=bp_ent.output_priority,
|
|
filter=bp_ent.filter,
|
|
}
|
|
end
|
|
|
|
return "prepare_container_layout"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:prepare_container_layout(state)
|
|
local G = state.grid
|
|
local builder_all = state.builder_all
|
|
local output_locations = state.entity_output_locations
|
|
local input_locations = state.entity_input_locations
|
|
|
|
for _, container in ipairs(state.collected_containers) do
|
|
local name = container.name
|
|
local struct = mpp_util.entity_struct(name)
|
|
local x, y = container.x, container.y
|
|
|
|
local output_row = output_locations[y]
|
|
if not output_row or not output_row[x] then goto continue end
|
|
|
|
G:build_thing(container.x, container.y, "container", struct.w-1, struct.h-1)
|
|
|
|
builder_all[#builder_all+1] = {
|
|
thing = "container",
|
|
name = name,
|
|
grid_x = container.origin_x,
|
|
grid_y = container.origin_y,
|
|
extent_w = struct.extent_w,
|
|
extent_h = struct.extent_h,
|
|
}
|
|
|
|
::continue::
|
|
end
|
|
|
|
return "prepare_other"
|
|
end
|
|
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:prepare_other(state)
|
|
local G = state.grid
|
|
local builder_all = state.builder_all
|
|
|
|
for _, other in ipairs(state.collected_other) do
|
|
local name = other.name
|
|
local struct = mpp_util.entity_struct(name)
|
|
G:build_thing(other.x, other.y, "other", struct.w-1, struct.h-1)
|
|
|
|
builder_all[#builder_all+1] = {
|
|
thing = "other",
|
|
name = name,
|
|
grid_x = other.origin_x,
|
|
grid_y = other.origin_y,
|
|
extent_w = struct.extent_w,
|
|
extent_h = struct.extent_h,
|
|
}
|
|
|
|
::continue::
|
|
end
|
|
|
|
return "expensive_deconstruct"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:expensive_deconstruct(state)
|
|
simple.expensive_deconstruct(self --[[@as SimpleLayout]], state)
|
|
return "placement_miners"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:placement_miners(state)
|
|
local create_entity = builder.create_entity_builder(state)
|
|
|
|
for i, 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 = miner.direction,
|
|
}
|
|
|
|
if ghost and miner.ent.items then
|
|
ghost.item_requests = miner.ent.items
|
|
end
|
|
end
|
|
|
|
return "placement_all"
|
|
end
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
---@return CallbackState
|
|
function layout:placement_all(state)
|
|
|
|
local create_entity = builder.create_entity_builder(state)
|
|
|
|
local builder_all = state.builder_all
|
|
local builder_index = state.builder_index or 1
|
|
|
|
local is_space = state.is_space
|
|
|
|
local progress = builder_index + 32
|
|
for i = builder_index, #builder_all do
|
|
--for _, thing in pairs(builder_all) do
|
|
if i > progress then
|
|
state.builder_index = i
|
|
return true
|
|
end
|
|
|
|
local thing = state.builder_all[i]
|
|
|
|
local ghost = create_entity(thing)
|
|
|
|
if ghost and thing.items then
|
|
ghost.item_requests = thing.items
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
|
|
return "placement_landfill"
|
|
end
|
|
|
|
layout.placement_landfill = simple.placement_landfill
|
|
|
|
---@param self BlueprintLayout
|
|
---@param state BlueprintState
|
|
function layout:finish(state)
|
|
return false
|
|
end
|
|
|
|
return layout
|
|
|