237 lines
6.1 KiB
Lua

local table_insert = table.insert
local min, max = math.min, math.max
-- broke: implement power pole connection calculation yourself
-- woke: place ghosts to make factorio calculate the connections
---@class PowerPoleGrid
---@field [number] table<number, GridPole>
local pole_grid_mt = {}
pole_grid_mt.__index = pole_grid_mt
---@class GridPole
---@field ix number Position in the pole grid
---@field iy number Position in the pole grid
---@field grid_x number Position in the full grid
---@field grid_y number Position in the full grid
---@field built boolean? Does the pole need to be built
---@field entity LuaEntity? Pole ghost LuaEntity
---@field has_consumers boolean Does pole cover any powered items
---@field backtracked boolean
---@field connections table<GridPole, true>
---@field set_id number?
---@field no_light boolean?
function pole_grid_mt.new()
local new = {
_max_x = 1,
_max_y = 1,
}
return setmetatable(new, pole_grid_mt)
end
---@param x number
---@param y number
---@param p GridPole
function pole_grid_mt:set_pole(x, y, p)
if p.connections == nil then p.connections = {} end
if not self[y] then self[y] = {} end
self._max_x = max(self._max_x, x)
self._max_y = max(self._max_y, y)
self[y][x] = p
end
---@param p GridPole
function pole_grid_mt:add_pole(p)
self:set_pole(p.ix, p.iy, p)
end
---@param x number
---@param y number
---@return GridPole | nil
function pole_grid_mt:get_pole(x, y)
if self[y] then return self[y][x] end
end
---@param p1 GridPole
---@param p2 GridPole
---@param struct PoleStruct
---@return boolean
function pole_grid_mt:pole_reaches(p1, p2, struct)
local x, y = p1.grid_x - p2.grid_x, p1.grid_y - p2.grid_y
return (x * x + y * y) ^ 0.5 <= (struct.wire)
end
---@param P PoleStruct
---@return table<number, table<GridPole, true>>
function pole_grid_mt:find_connectivity(P)
-- this went off the rails
local all_poles = {}
---@type table<GridPole, true>
local not_visited = {}
-- Make connections
for y1 = 1, #self do
local row = self[y1]
for x1 = 1, #row do
local pole = row[x1]
if pole == nil then goto continue end
table.insert(all_poles, pole)
local right = self:get_pole(x1+1, y1)
local bottom = self:get_pole(x1, y1+1)
if right and self:pole_reaches(pole, right, P) then
pole.connections[right], right.connections[pole] = true, true
end
if bottom and self:pole_reaches(pole, bottom, P) then
pole.connections[bottom], bottom.connections[pole] = true, true
end
::continue::
end
end
-- Process network connection sets
local unconnected = {}
---@type number, table<GridPole, true>
local set_id, current_set = 1, {}
---@type PoleConnectivity
local sets = {[0]=unconnected, [1]=current_set}
for _, v in pairs(all_poles) do not_visited[v] = true end
---@param pole GridPole
---@return number?
local function is_continuation(pole)
for other, _ in pairs(pole.connections) do
if other.set_id then
return other.set_id
end
end
end
---@param start_pole GridPole
local function iterate_connections(start_pole)
local continuation = is_continuation(start_pole)
if not continuation and table_size(current_set) > 0 then
if sets[set_id] == nil then
sets[set_id] = current_set
end
current_set, set_id = {}, set_id + 1
end
---@param pole GridPole
---@param depth_remaining number
local function recurse_pole(pole, depth_remaining)
not_visited[start_pole] = nil
if not pole.has_consumers then
unconnected[pole] = true
return
end
if pole.set_id and pole.set_id < set_id then
--Encountered a different set, merge to it
local target_set_id = pole.set_id
local target_set = sets[target_set_id]
for current_pole, v in pairs(current_set) do
current_set[current_pole], target_set[current_pole] = nil, true
current_pole.set_id = target_set_id
end
set_id = pole.set_id
current_set = target_set
else
pole.set_id = set_id
end
current_set[pole] = true
for other, _ in pairs(pole.connections) do
local other_not_visited = not_visited[other]
if other_not_visited and depth_remaining > 0 then
recurse_pole(other, depth_remaining-1)
end
end
end
recurse_pole(start_pole, 5)
end
local remaining = next(not_visited)
while remaining do
iterate_connections(remaining)
remaining = next(not_visited)
end
sets[set_id] = current_set
return sets
end
---List of sets
---set at index 0 are unconnected power poles
---@alias PoleConnectivity table<number, table<GridPole, true>>
---@param connectivity PoleConnectivity
---@return GridPole[]
function pole_grid_mt:ensure_connectivity(connectivity)
---@type GridPole[]
local connected = {}
local unconnecteds_set, main_set = connectivity[0], connectivity[1]
-- connect trivial power poles
for connector_pole in pairs(unconnecteds_set) do
--Set id is key and value is merge marker
local different_sets = {} --[[@as table<number, boolean>]]
for connection in pairs(connector_pole.connections) do
if connection.set_id ~= nil then
different_sets[connection.set_id] = true
end
end
if table_size(different_sets) < 2 then goto skip_pole end
local target_set_id = different_sets[1] and 1 or next(different_sets)
local target_set = connectivity[target_set_id]
for source_set_id in pairs(different_sets) do
if source_set_id == target_set_id then goto skip_merge_set end
local source_set = connectivity[source_set_id]
if different_sets[source_set_id] == true then
for source_pole in pairs(source_set) do
target_set[source_pole], source_set[source_pole] = true, nil
source_pole.set_id = target_set_id
different_sets[source_set_id] = false
end
end
::skip_merge_set::
end
connector_pole.built = true
connector_pole.set_id = target_set_id
target_set[connector_pole] = true
unconnecteds_set[connector_pole] = nil
::skip_pole::
end
---- append the main set to output list
--for pole in pairs(main_set) do table_insert(connected, pole) end
for set_id, pole_set in pairs(connectivity) do
if set_id == 0 then goto continue_set end
--if set_id == 1 then goto continue_set end
for pole in pairs(pole_set) do table_insert(connected, pole) end
::continue_set::
end
return connected
end
return pole_grid_mt