local common = require("layouts.common") local base = require("layouts.base") local grid_mt = require("grid_mt") local pole_grid_mt = require("pole_grid_mt") local mpp_util = require("mpp_util") local builder = require("builder") local render_util = require("render_util") local coord_convert, coord_revert = mpp_util.coord_convert, mpp_util.coord_revert local internal_revert, internal_convert = mpp_util.internal_revert, mpp_util.internal_convert local miner_direction, opposite = mpp_util.miner_direction, mpp_util.opposite local floor, ceil = math.floor, math.ceil local min, max = math.min, math.max local EAST, NORTH, SOUTH, WEST = mpp_util.directions() ---@class SimpleLayout : Layout local layout = table.deepcopy(base) ---@class SimpleState : State ---@field debug_dump boolean ---@field saved_attempts PlacementAttempt[] ---@field first_pass any ---@field attempts PlacementCoords[] ---@field attempt_index number ---@field best_attempt PlacementAttempt ---@field best_attempt_score number Heuristic value ---@field best_attempt_index number ---@field resourcs LuaEntity[] ---@field resource_tiles GridTile ---@field longest_belt number For pole alignment information ---@field pole_step number ---@field miner_lane_count number Miner lane count ---@field miner_max_column number Miner column span ---@field grid Grid ---@field belts BeltSpecification[] ---@field belt_count number For info printout ---@field miner_lanes table ---@field place_pipes boolean ---@field pipe_layout_specification PlacementSpecification[] ---@field builder_miners GhostSpecification[] ---@field builder_pipes GhostSpecification[] ---@field builder_belts GhostSpecification[] ---@field builder_power_poles PowerPoleGhostSpecification[] ---@field builder_lamps GhostSpecification[] layout.name = "simple" layout.translation = {"mpp.settings_layout_choice_simple"} layout.restrictions.miner_near_radius = {1, 10e3} layout.restrictions.miner_far_radius = {1, 10e3} 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 = true layout.restrictions.module_available = true layout.restrictions.pipe_available = true layout.restrictions.placement_info_available = true layout.restrictions.lane_filling_info_available = true --[[----------------------------------- Basic rundown: * Create a virtual grid from resources * Or restore from a previous state * Run calculations on grid tiles to * _brute_ force several miner layouts on the grid and find the best one * Mark the area around for deconstruction * Place the entity ghosts and mark built-on tiles * Place landfill --]]----------------------------------- --- Called from script.on_load --- ONLY FOR SETMETATABLE USE ---@param self SimpleLayout ---@param state SimpleState function layout:on_load(state) if state.grid then setmetatable(state.grid, grid_mt) end end -- Validate the selection ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:validate(state) local c = state.coords -- if (state.direction_choice == "west" or state.direction_choice == "east") then -- if c.h < 7 then -- return nil, {"mpp.msg_miner_err_1_w", 7} -- end -- else -- if c.w < 7 then -- return nil, {"mpp.msg_miner_err_1_h", 7} -- end -- end return base.validate(self, state) end ---@param self SimpleLayout ---@param state SimpleState function layout:start(state) return "deconstruct_previous_ghosts" end ---@param self SimpleLayout ---@param state SimpleState 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 ---@param self SimpleLayout ---@param state SimpleState function layout:initialize_grid(state) local miner = state.miner local c = state.coords local th, tw = c.h, c.w if state.direction_choice == "south" or state.direction_choice == "north" then th, tw = tw, th end c.th, c.tw = th, tw local y1, y2 = -miner.full_size, th + miner.full_size+1 local x1, x2 = -miner.full_size, tw + miner.full_size+1 c.extent_x1, c.extent_y1, c.extent_x2, c.extent_y2 = x1, y1, x2, y2 --[[ debug rendering - bounds state._render_objects[#state._render_objects+1] = 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=4, color={0, 0, 1, 1}, players={state.player}, } state._render_objects[#state._render_objects+1] = rendering.draw_rectangle{ surface=state.surface, left_top={state.coords.ix1-miner.full_size-1, state.coords.iy1-miner.full_size-1}, right_bottom={state.coords.ix1+state.coords.tw+miner.full_size+1, state.coords.iy1+state.coords.th+miner.full_size+1}, filled=false, width=4, color={0, 0.5, 1, 1}, players={state.player}, } --]] local grid = {} grid.miner = miner for y = y1, y2 do local row = {} grid[y] = row for x = x1, x2 do --local tx1, ty1 = conv(c.x1, c.y1, c.tw, c.th) row[x] = { neighbor_count = 0, far_neighbor_count = 0, x = x, y = y, gx = c.x1 + x, gy = c.y1 + y, consumed = false, built_on = false, } end end state.grid = setmetatable(grid, grid_mt) return "process_grid" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:process_grid(state) local grid = state.grid local c = state.coords local conv = coord_convert[state.direction_choice] local gx, gy = state.coords.gx, state.coords.gy local resources = state.resources state.resource_tiles = state.resource_tiles or {} local resource_tiles = state.resource_tiles local price = state.miner.full_size ^ 2 local budget, cost = 12000, 0 local i = state.resource_iter or 1 while i <= #resources and cost < budget do local ent = resources[i] local x, y = ent.position.x - gx, ent.position.y - gy local tx, ty = conv(x, y, c.w, c.h) local tile = grid:get_tile(tx, ty) tile.amount = ent.amount grid:convolve(tx, ty) resource_tiles[#resource_tiles+1] = tile cost = cost + price i = i + 1 end state.resource_iter = i --[[ debug visualisation - resource and coord for _, ent in ipairs(resource_tiles) do state._render_objects[#state._render_objects+1] = rendering.draw_circle{ surface = state.surface, filled = false, color = {0.3, 0.3, 1}, width = 1, target = {c.gx + ent.x, c.gy + ent.y}, radius = 0.5, } state._render_objects[#state._render_objects+1] = rendering.draw_text{ text=string.format("%i,%i", ent.x, ent.y), surface = state.surface, color={1,1,1}, target={c.gx + ent.x, c.gy + ent.y}, alignment = "center", } end --]] if state.resource_iter >= #state.resources then return "prepare_layout_attempts" end return true end ---@class MinerPlacement ---@field tile GridTile ---@field center GridTile ---@field line number lane index ---@field stagger number Super compact layout stagger index ---@field column number column index ---@field ent BlueprintEntity|nil ---@field unconsumed number Unconsumed resource count for postponed miners ---@field direction string ---@class PlacementCoords ---@field sx number x shift ---@field sy number y shift ---@class PlacementAttempt ---@field sx number x shift ---@field sy number y shift ---@field miners MinerPlacement[] ---@field miner_count number ---@field postponed MinerPlacement[] ---@field heuristic_score number ---@field neighbor_sum number ---@field lane_layout LaneInfo ---@field far_neighbor_sum number ---@field unconsumed_count number ---@field simple_density number ---@field real_density number ---@field leech_sum number ---@field postponed_count number ---@field empty_space number ---@class LaneInfo ---@field y number ---@field row_index number function layout:_get_miner_placement_heuristic(state) if state.coverage_choice then return common.overfill_miner_placement(state.miner) else return common.simple_miner_placement(state.miner) end end function layout:_get_layout_heuristic(state) if state.coverage_choice then return common.overfill_layout_heuristic else return common.simple_layout_heuristic end end ---@param state SimpleState ---@return PlacementAttempt function layout:_placement_attempt(state, shift_x, shift_y) local c = state.coords local grid = state.grid local size, near, far, fullsize = state.miner.size, state.miner.near, state.miner.far, state.miner.full_size local miners, postponed = {}, {} local neighbor_sum = 0 local far_neighbor_sum = 0 local simple_density = 0 local real_density = 0 local leech_sum = 0 local empty_space = 0 local lane_layout = {} local heuristic = self:_get_miner_placement_heuristic(state) local row_index = 1 for y = 1 + shift_y, state.coords.th + near, size + 1 do local column_index = 1 lane_layout[#lane_layout+1] = {y = y+near, row_index = row_index} for x = 1 + shift_x, state.coords.tw + near, size do local tile = grid:get_tile(x, y) local center = grid:get_tile(x+near, y+near) --[[@as GridTile]] local miner = { tile = tile, line = row_index, center = center, column=column_index, } if center.far_neighbor_count > 0 and heuristic(center) then miners[#miners+1] = miner neighbor_sum = neighbor_sum + center.neighbor_count far_neighbor_sum = far_neighbor_sum + center.far_neighbor_count empty_space = empty_space + (size^2) - center.neighbor_count simple_density = simple_density + center.neighbor_count / (size ^ 2) real_density = real_density + center.far_neighbor_count / (fullsize ^ 2) leech_sum = leech_sum + max(0, center.far_neighbor_count - center.neighbor_count) elseif center.far_neighbor_count > 0 then postponed[#postponed+1] = miner end column_index = column_index + 1 end row_index = row_index + 1 end local result = { sx=shift_x, sy=shift_y, miners=miners, miner_count=#miners, lane_layout=lane_layout, postponed=postponed, neighbor_sum=neighbor_sum, far_neighbor_sum=far_neighbor_sum, leech_sum=leech_sum, simple_density=simple_density, real_density=real_density, empty_space=empty_space, unconsumed_count=0, postponed_count=0, } common.process_postponed(state, result, miners, postponed) return result end ---@param self SimpleLayout ---@param state SimpleState function layout:prepare_layout_attempts(state) local m = state.miner --local init_pos_x, init_pos_y = -m.near, -m.near local init_pos_x, init_pos_y = -m.near, -m.near local attempts = {{init_pos_x, init_pos_y}} state.attempts = attempts state.best_attempt_index = 1 state.attempt_index = 1 -- first attempt is used up --local ext_behind, ext_forward = -m.far, m.far - m.near local ext_behind, ext_forward = -m.far, m.far - m.near --for sy = ext_behind, ext_forward do -- for sx = ext_behind, ext_forward do for sy = ext_forward, ext_behind, -1 do for sx = ext_forward, ext_behind, -1 do if not (sx == init_pos_x and sy == init_pos_y) then attempts[#attempts+1] = {sx, sy} end end end return "init_layout_attempt" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState 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 = self:_get_layout_heuristic(state)(state.best_attempt) 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 state.attempt_index = state.attempt_index + 1 return "layout_attempt" end ---Bruteforce the best solution ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState 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 = self:_get_layout_heuristic(state)(current_attempt) if state.debug_dump then current_attempt.heuristic_score = current_attempt_score state.saved_attempts[#state.saved_attempts+1] = current_attempt end if current_attempt.unconsumed_count == 0 and 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 CallbackState function layout:prepare_miner_layout(state) local c = state.coords local g = state.grid ---@type GhostSpecification[] local builder_miners = {} state.builder_miners = builder_miners ---@type table 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 miner_max_column = 0 state.miner_lanes = miner_lanes for _, miner in ipairs(state.best_attempt.miners) do local center = miner.center g:build_miner(center.x, center.y) -- local can_place = surface.can_place_entity{ -- name=state.miner.name, -- force = state.player.force, -- position={center.gx, center.gy}, -- direction = defines.direction.north, -- build_check_type = -- } --[[ debug visualisation - miner placement local off = state.miner.size / 2 rendering.draw_rectangle{ surface = state.surface, filled = false, color = miner.postponed and {1, 0, 0} or {0, 1, 0}, width = 3, --target = {c.x1 + x, c.y1 + y}, left_top = {c.gx+x-off, c.gy + y - off}, right_bottom = {c.gx+x+off, c.gy + y + off}, } --]] local 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 miner_max_column = max(miner_max_column, miner.column) -- used for deconstruction, not ghost placement builder_miners[#builder_miners+1] = { thing="miner", grid_x = miner.center.x, grid_y = miner.center.y, padding_pre = state.miner.near, padding_post = state.miner.near, } end state.miner_lane_count = miner_lane_count state.miner_max_column = miner_max_column for _, lane in pairs(miner_lanes) do table.sort(lane, function(a, b) return a.center.x < b.center.x end) end return "prepare_pipe_layout" end ---@class PlacementSpecification ---@field x number ---@field w number ---@field y number ---@field h number ---@field structure string ---@field entity string ---@field radius number ---@field skip_up boolean ---@field skip_down boolean --- Process gaps between miners in "miner space" and translate them to grid specification ---@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 for _, pre_lane in ipairs(state.miner_lanes) do if not pre_lane[1] then goto continue_lanes end local y = pre_lane[1].center.y local sx = state.best_attempt.sx local lane = table.mapkey(pre_lane, function(t) return t.column end) -- make array with intentional gaps between miners -- Calculate a list of run-length gaps -- start and length are in miner count local gaps = {} 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 gaps[#gaps+1] = {start=current_start, length=current_length} current_start, current_length = nil, 0 elseif not miner and not current_start then current_start, current_length = i, 1 else current_length = current_length + 1 end end for i, gap in ipairs(gaps) do local start, length = gap.start, gap.length local gap_length = m.size * length local gap_start = sx + (start-1) * m.size + 1 pipe_layout[#pipe_layout+1] = { structure="horizontal", x = gap_start, w = gap_length-1, y = y, } end ::continue_lanes:: end for i = 1, state.miner_lane_count do local lane = attempt.lane_layout[i] pipe_layout[#pipe_layout+1] = { structure="cap_vertical", x=attempt.sx, y=lane.y, skip_up=i == 1, skip_down=i == state.miner_lane_count, } end return pipe_layout end function layout:prepare_pipe_layout(state) local builder_pipes = {} state.builder_pipes = builder_pipes local next_step = "prepare_belt_layout" if state.pipe_choice == "none" or (not state.requires_fluid and not state.force_pipe_placement_choice) then state.place_pipes = false return next_step end state.place_pipes = true state.pipe_layout_specification = self:_get_pipe_layout_specification(state) local function que_entity(t) builder_pipes[#builder_pipes+1] = t end local pipe = state.pipe_choice local ground_pipe, ground_proto = mpp_util.find_underground_pipe(pipe) ---@cast ground_pipe string local step, span if ground_proto then step = ground_proto.max_underground_distance span = step + 1 end local function horizontal_underground(x, y, w) que_entity{name=ground_pipe, thing="pipe", grid_x=x, grid_y=y, direction=WEST} que_entity{name=ground_pipe, thing="pipe", grid_x=x+w, grid_y=y, direction=EAST} end local function horizontal_filled(x1, y, w) for x = x1, x1+w do que_entity{name=pipe, thing="pipe", grid_x=x, grid_y=y} end end local function vertical_filled(x, y1, h) for y = y1, y1 + h do que_entity{name=pipe, thing="pipe", grid_x=x, grid_y=y} end end local function cap_vertical(x, y, skip_up, skip_down) que_entity{name=pipe, thing="pipe", grid_x=x, grid_y=y} if not ground_pipe then return end if not skip_up then que_entity{name=ground_pipe, thing="pipe", grid_x=x, grid_y=y-1, direction=SOUTH} end if not skip_down then que_entity{name=ground_pipe, thing="pipe", grid_x=x, grid_y=y+1, direction=NORTH} end end for i, p in ipairs(state.pipe_layout_specification) do local structure = p.structure local x1, w, y1, h = p.x, p.w, p.y, p.h if structure == "horizontal" then if not ground_pipe then horizontal_filled(x1, y1, w) goto continue_pipe end local quotient, remainder = math.divmod(w, span) for j = 1, quotient do local x = x1 + (j-1)*span horizontal_underground(x, y1, step) end local x = x1 + quotient * span if remainder >= 2 then horizontal_underground(x, y1, remainder) else horizontal_filled(x, y1, remainder) end elseif structure == "vertical" then vertical_filled(x, y1, h) elseif structure == "cap_vertical" then cap_vertical(x1, y1, p.skip_up, p.skip_down) end ::continue_pipe:: end return next_step end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:prepare_belt_layout(state) local m = state.miner local attempt = state.best_attempt ---@type table local miner_lanes = state.miner_lanes local miner_lane_count = state.miner_lane_count -- highest index of a lane, because using # won't do the job if a lane is missing local miner_max_column = state.miner_max_column ---@param lane MinerPlacement[] local function get_lane_length(lane) if lane and lane[#lane] then return lane[#lane].center.x end return 0 end local pipe_adjust = state.place_pipes and -1 or 0 local belts = {} state.belts = belts state.belt_count = 0 local longest_belt = 0 for i = 1, miner_lane_count, 2 do local lane1 = miner_lanes[i] local lane2 = miner_lanes[i+1] local y = attempt.sy + (m.size + 1) * i local belt = {x1=attempt.sx + 1 + pipe_adjust, x2=attempt.sx + 1, y=y, built=false, lane1=lane1, lane2=lane2} belts[#belts+1] = belt if lane1 or lane2 then state.belt_count = state.belt_count + 1 local x1 = belt.x1 local x2 = max(get_lane_length(lane1), get_lane_length(lane2)) + m.near longest_belt = max(longest_belt, x2 - x1 + 1) belt.x2, belt.built = x2, true end end state.longest_belt = longest_belt local builder_belts = {} state.builder_belts = builder_belts for _, belt in ipairs(belts) do if not belt.built then goto continue end local x1, x2, y = belt.x1, belt.x2, belt.y for x = x1, x2 do builder_belts[#builder_belts+1] = { name=state.belt_choice, thing="belt", grid_x=x, grid_y=y, direction=defines.direction.west, } end ::continue:: end return "prepare_pole_layout" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:prepare_pole_layout(state) local c = state.coords local m = state.miner local g = state.grid local attempt = state.best_attempt local pole_proto = game.entity_prototypes[state.pole_choice] or {supply_area_distance=3, max_wire_distance=9} local supply_area, wire_reach = 3, 9 if pole_proto then supply_area, wire_reach = floor(pole_proto.supply_area_distance), pole_proto.max_wire_distance end --TODO: figure out double lane coverage with insane supply areas local function get_covered_miners(ix, iy) --for sy = -supply_radius, supply_radius do for sy = -supply_area, supply_area, 1 do for sx = -supply_area, supply_area, 1 do local tile = g:get_tile(ix+sx, iy+sy) if tile and tile.built_on == "miner" then return true end end end return false end -----@type PowerPoleGrid --local power_poles_grid = setmetatable({}, pole_grid_mt) local builder_power_poles = {} state.builder_power_poles = builder_power_poles local coverage = mpp_util.calculate_pole_coverage(state, state.miner_max_column, state.miner_lane_count) -- rendering.draw_circle{ -- surface = state.surface, -- player = state.player, -- filled = true, -- color = {1, 1, 1}, -- radius = 0.5, -- target = mpp_revert(c.gx, c.gy, DIR, attempt.sx, attempt.sy, c.tw, c.th), -- } local pole_lanes = {} local iy = 1 for y = attempt.sy + coverage.lane_start, c.th + m.size, coverage.lane_step do local ix, pole_lane = 1, {} pole_lanes[#pole_lanes+1] = pole_lane for x = 1 + attempt.sx + coverage.pole_start, attempt.sx + coverage.full_miner_width + 1, coverage.pole_step do local built = get_covered_miners(x, y) ---@type GridPole local pole = {x=x, y=y, ix=ix, iy=iy, built=built} --power_poles_grid:set_pole(ix, iy, pole) --builder_power_poles[#builder_power_poles+1] = pole pole_lane[ix] = pole ix = ix + 1 end local backtrack_built = false for pole_i = #pole_lane, 1, -1 do ---@type GridPole local backtrack_pole = pole_lane[pole_i] if backtrack_built or backtrack_pole.built then backtrack_built = true backtrack_pole.built = true builder_power_poles[#builder_power_poles+1] = { name=state.pole_choice, thing="pole", grid_x = backtrack_pole.x, grid_y = backtrack_pole.y, } end end iy = iy + 1 end return "prepare_lamp_layout" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState 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 sx, sy = -1, 0 if state.pole_choice == "none" then sx = 0 end for _, pole in ipairs(state.builder_power_poles) do ---@cast pole PowerPoleGhostSpecification local x, y = pole.grid_x, pole.grid_y if not pole.no_light then lamps[#lamps+1] = { name="small-lamp", thing="lamp", grid_x=x+sx, grid_y=y+sy, } end end return next_step end ---@param self SimpleLayout ---@param state SimpleState function layout:_get_deconstruction_objects(state) return { state.builder_miners, state.builder_pipes, state.builder_belts, state.builder_power_poles, state.builder_lamps, } end ---@param self SimpleLayout ---@param state SimpleState function layout:expensive_deconstruct(state) local c, DIR = state.coords, state.direction_choice local player, surface = state.player, state.surface local deconstructor = global.script_inventory[state.deconstruction_choice and 2 or 1] for _, t in pairs(self:_get_deconstruction_objects(state)) do for _, object in ipairs(t) do ---@cast object GhostSpecification local cx, cy = object.grid_x, object.grid_y local pad_left, pad_right = object.padding_pre, object.padding_post local tpos1, tpos2 = { cx, cy }, { cx, cy } if pad_left ~= nil and pad_right ~= nil then pad_left, pad_right = pad_left or 0, pad_right or 0 tpos1[1], tpos1[2] = cx - pad_left, cy - pad_left tpos2[1], tpos2[2] = cx + pad_right, cy + pad_right end pos1 = mpp_util.revert(c.gx, c.gy, DIR, tpos1[1], tpos1[2], c.tw, c.th) pos2 = mpp_util.revert(c.gx, c.gy, DIR, tpos2[1], tpos2[2], c.tw, c.th) -- ugh pos1[1], pos2[1] = min(pos1[1], pos2[1]), max(pos1[1], pos2[1]) pos1[2], pos2[2] = min(pos1[2], pos2[2]), max(pos1[2], pos2[2]) surface.deconstruct_area{ force=player.force, player=player.index, area={ left_top={pos1[1]-.5,pos1[2]-.5}, right_bottom={pos2[1]+.5,pos2[2]+.5}, }, item=deconstructor, } --[[ debug rendering - deconstruction areas rendering.draw_rectangle{ surface=state.surface, players={state.player}, filled=false, width=3, color={1, 0, 0}, left_top={pos1[1]-.4,pos1[2]-.4}, right_bottom={pos2[1]+.4,pos2[2]+.4}, } --]] end end return "placement_miners" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_miners(state) local create_entity = builder.create_entity_builder(state) local module_inv_size = state.miner.module_inventory_size --[[@as uint]] local grid = state.grid for i, miner in ipairs(state.best_attempt.miners) do local direction = "south" local flip_lane = miner.line % 2 ~= 1 if flip_lane then direction = opposite[direction] end local x, y = miner.center.x, miner.center.y grid:build_miner(x, y) local ghost = create_entity{ name = state.miner_choice, thing="miner", grid_x = x, grid_y = y, direction = defines.direction[direction], } if state.module_choice ~= "none" then ghost.item_requests = {[state.module_choice] = module_inv_size} end end return "placement_pipes" end --- Pipe placement deals in tile grid space with spectifications from previous step ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_pipes(state) local create_entity = builder.create_entity_builder(state) if state.builder_pipes then for _, belt in ipairs(state.builder_pipes) do create_entity(belt) end end return "placement_belts" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_belts(state) local create_entity = builder.create_entity_builder(state) for _, belt in ipairs(state.builder_belts) do create_entity(belt) end return "placement_poles" end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_poles(state) local next_step = "placement_lamps" if state.pole_choice == "none" then return next_step end local create_entity = builder.create_entity_builder(state) for _, pole in ipairs(state.builder_power_poles) do local ghost = create_entity(pole) --ghost.disconnect_neighbour() --pole.ghost = ghost end return next_step end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_lamps(state) local next_step = "placement_landfill" if not layout.restrictions.lamp_available or not state.lamp_choice then return next_step end if not state.builder_lamps then return next_step end local create_entity = builder.create_entity_builder(state) for _, lamp in ipairs(state.builder_lamps) do create_entity(lamp) end return next_step end ---@param self SimpleLayout ---@param state SimpleState ---@return CallbackState function layout:placement_landfill(state) local c = state.coords local m = state.miner local grid = state.grid local surface = state.surface if state.landfill_choice then return "finish" end local conv = coord_convert[state.direction_choice] local gx, gy = state.coords.ix1 - 1, state.coords.iy1 - 1 local fill_tiles, landfill local area = {left_top={c.x1-m.full_size-1, c.y1-m.full_size-1}, right_bottom={c.x2+m.full_size+1, c.y2+m.full_size+1}} if state.is_space then fill_tiles = surface.find_tiles_filtered{area=area, name="se-space"} landfill = state.space_landfill_choice else fill_tiles = surface.find_tiles_filtered{area=area, collision_mask="water-tile"} landfill = "landfill" end local collected_ghosts = state._collected_ghosts for _, fill in ipairs(fill_tiles) do local tx, ty = fill.position.x, fill.position.y local x, y = conv(tx-gx, ty-gy, c.w, c.h) local tile = grid:get_tile(x, y) if tile and tile.built_on then local tile_ghost = surface.create_entity{ raise_built=true, name="tile-ghost", player=state.player, force=state.player.force, position=fill.position --[[@as MapPosition]], inner_name=landfill, } if tile_ghost then collected_ghosts[#collected_ghosts+1] = tile_ghost end --[[ debug rendering - landfill placement rendering.draw_circle{ surface=state.surface, players={state.player}, filled = true, color={0, .3, 0}, radius=0.3, target={tx+.5, ty+.5}, } --]] end end return "finish" end ---@param self SimpleLayout ---@param state SimpleState function layout:_display_lane_filling(state) if not state.display_lane_filling_choice or not state.belts then return end local drill_speed = game.entity_prototypes[state.miner_choice].mining_speed local belt_speed = game.entity_prototypes[state.belt_choice].belt_speed * 60 * 4 local dominant_resource = state.resource_counts[1].name local resource_hardness = game.entity_prototypes[dominant_resource].mineable_properties.mining_time or 1 local drill_productivity, module_speed = 1 + state.player.force.mining_drill_productivity_bonus, 1 if state.module_choice ~= "none" then local mod = game.item_prototypes[state.module_choice] module_speed = module_speed + (mod.module_effects.speed and mod.module_effects.speed.bonus or 0) * state.miner.module_inventory_size drill_productivity = drill_productivity + (mod.module_effects.productivity and mod.module_effects.productivity.bonus or 0) * state.miner.module_inventory_size end local multiplier = drill_speed / resource_hardness * module_speed * drill_productivity local throughput1, throughput2 = 0, 0 --local ore_hardness = game.entity_prototypes[state.found_resources for i, belt in pairs(state.belts) do local function lane_capacity(lane) if lane then return #lane * multiplier end return 0 end local speed1, speed2 = lane_capacity(belt.lane1), lane_capacity(belt.lane2) throughput1 = throughput1 + math.min(1, speed1 / belt_speed) throughput2 = throughput2 + math.min(1, speed2 / belt_speed) render_util.draw_belt_lane(state, belt) render_util.draw_belt_stats(state, belt, belt_speed, speed1, speed2) end if #state.belts > 1 then local x = min(state.belts[1].x1, state.belts[2].x1) local y = (state.belts[1].y + state.belts[#state.belts].y) / 2 render_util.draw_belt_total(state, x, y, throughput1, throughput2) end --local lanes = math.ceil(math.max(throughput1, throughput2)) --state.player.print("Enough to fill "..lanes.." belts after balancing") 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", state.best_attempt.miner_count, state.belt_count, #state.resources}) end self:_display_lane_filling(state) if mpp_util.get_dump_state(state.player.index) then common.save_state_to_file(state, "json") end return false end return layout