mirror of
https://github.com/Refactorio/RedMew.git
synced 2024-12-14 10:13:13 +02:00
1456 lines
38 KiB
Lua
1456 lines
38 KiB
Lua
local math = require "utils.math"
|
|
|
|
-- helpers
|
|
local inv_pi = 1 / math.pi
|
|
|
|
local Builders = {}
|
|
|
|
local function add_entity(tile, entity)
|
|
if type(tile) == 'table' then
|
|
if tile.entities then
|
|
table.insert(tile.entities, entity)
|
|
else
|
|
tile.entities = {entity}
|
|
end
|
|
elseif tile then
|
|
tile = {
|
|
tile = tile,
|
|
entities = {entity}
|
|
}
|
|
end
|
|
|
|
return tile
|
|
end
|
|
|
|
function Builders.add_entity(tile, entity)
|
|
return add_entity(tile, entity)
|
|
end
|
|
|
|
-- shape builders
|
|
function Builders.empty_shape()
|
|
return false
|
|
end
|
|
|
|
function Builders.full_shape()
|
|
return true
|
|
end
|
|
|
|
function Builders.no_entity()
|
|
return nil
|
|
end
|
|
|
|
function Builders.tile(tile)
|
|
return function()
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.path(thickness, optional_thickness_height)
|
|
local width = thickness / 2
|
|
local thickness2 = optional_thickness_height or thickness
|
|
local height = thickness2 / 2
|
|
return function(x, y)
|
|
return (x > -width and x <= width) or (y > -height and y <= height)
|
|
end
|
|
end
|
|
|
|
function Builders.rectangle(width, height)
|
|
width = width / 2
|
|
if height then
|
|
height = height / 2
|
|
else
|
|
height = width
|
|
end
|
|
return function(x, y)
|
|
return x > -width and x <= width and y > -height and y <= height
|
|
end
|
|
end
|
|
|
|
function Builders.line_x(thickness)
|
|
thickness = thickness / 2
|
|
return function(_, y)
|
|
return y > -thickness and y <= thickness
|
|
end
|
|
end
|
|
|
|
function Builders.line_y(thickness)
|
|
thickness = thickness / 2
|
|
return function(x, _)
|
|
return x > -thickness and x <= thickness
|
|
end
|
|
end
|
|
|
|
function Builders.square_diamond(size)
|
|
size = size / 2
|
|
return function(x, y)
|
|
return math.abs(x) + math.abs(y) <= size
|
|
end
|
|
end
|
|
|
|
local rot = math.sqrt(2) / 2 -- 45 degree rotation.
|
|
function Builders.rectangle_diamond(width, height)
|
|
width = width / 2
|
|
height = height / 2
|
|
return function(x, y)
|
|
local rot_x = rot * (x - y)
|
|
local rot_y = rot * (x + y)
|
|
return math.abs(rot_x) < width and math.abs(rot_y) < height
|
|
end
|
|
end
|
|
|
|
function Builders.circle(radius)
|
|
local rr = radius * radius
|
|
return function(x, y)
|
|
return x * x + y * y < rr
|
|
end
|
|
end
|
|
|
|
function Builders.oval(x_radius, y_radius)
|
|
local x_rr = x_radius * x_radius
|
|
local y_rr = y_radius * y_radius
|
|
return function(x, y)
|
|
return ((x * x) / x_rr + (y * y) / y_rr) < 1
|
|
end
|
|
end
|
|
|
|
local tau = math.tau
|
|
function Builders.sine_fill(width, height)
|
|
local width_inv = tau / width
|
|
local height_inv = -2 / height
|
|
return function(x, y)
|
|
local x2 = x * width_inv
|
|
local y2 = y * height_inv
|
|
if y <= 0 then
|
|
return y2 < math.sin(x2)
|
|
else
|
|
return y2 > math.sin(x2)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.sine_wave(width, height, thickness)
|
|
local width_inv = tau / width
|
|
local height_inv = 2 / height
|
|
thickness = thickness * 0.5
|
|
return function(x, y)
|
|
local x2 = x * width_inv
|
|
local y2 = math.sin(x2)
|
|
y = y * height_inv
|
|
local d = math.abs(y2 - y)
|
|
|
|
return d < thickness
|
|
end
|
|
end
|
|
|
|
function Builders.rectangular_spiral(x_size, optional_y_size)
|
|
optional_y_size = optional_y_size or x_size
|
|
|
|
x_size = 1 / x_size
|
|
optional_y_size = 1 / optional_y_size
|
|
return function(x, y)
|
|
x, y = x * x_size, y * optional_y_size
|
|
x, y = math.floor(x + 0.5), math.floor(y + 0.5)
|
|
local a = -math.max(math.abs(x), math.abs(y))
|
|
|
|
if a % 2 == 0 then
|
|
return y ~= a or x == a
|
|
else
|
|
return y == a and x ~= a
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral(in_thickness, total_thickness)
|
|
local half_total_thickness = total_thickness * 0.5
|
|
return function(x, y)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
local offset = d + (angle * half_total_thickness)
|
|
|
|
return offset % total_thickness < in_thickness
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral_grow(in_thickness, total_thickness, grow_factor)
|
|
local half_total_thickness = total_thickness * 0.5
|
|
local inv_grow_factor = 1 / grow_factor
|
|
return function(x, y)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local factor = (d * inv_grow_factor) + 1
|
|
local total_thickness2 = total_thickness * factor
|
|
local in_thickness2 = in_thickness * factor
|
|
local half_total_thickness2 = half_total_thickness * factor
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
local offset = d + (angle * half_total_thickness2)
|
|
|
|
return offset % total_thickness2 < in_thickness2
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral_n_threads(in_thickness, total_thickness, n_threads)
|
|
local half_total_thickness = total_thickness * 0.5 * n_threads
|
|
return function(x, y)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
local offset = d + (angle * half_total_thickness)
|
|
|
|
return offset % total_thickness < in_thickness
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral_grow_n_threads(in_thickness, total_thickness, grow_factor, n_threads)
|
|
local half_total_thickness = total_thickness * 0.5 * n_threads
|
|
local inv_grow_factor = 1 / grow_factor
|
|
return function(x, y)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local factor = (d * inv_grow_factor) + 1
|
|
local total_thickness2 = total_thickness * factor
|
|
local in_thickness2 = in_thickness * factor
|
|
local half_total_thickness2 = half_total_thickness * factor
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
local offset = d + (angle * half_total_thickness2)
|
|
|
|
return offset % total_thickness2 < in_thickness2
|
|
end
|
|
end
|
|
|
|
local tile_map = {
|
|
[1] = false,
|
|
[2] = true,
|
|
[3] = 'concrete',
|
|
[4] = 'deepwater-green',
|
|
[5] = 'deepwater',
|
|
[6] = 'dirt-1',
|
|
[7] = 'dirt-2',
|
|
[8] = 'dirt-3',
|
|
[9] = 'dirt-4',
|
|
[10] = 'dirt-5',
|
|
[11] = 'dirt-6',
|
|
[12] = 'dirt-7',
|
|
[13] = 'dry-dirt',
|
|
[14] = 'grass-1',
|
|
[15] = 'grass-2',
|
|
[16] = 'grass-3',
|
|
[17] = 'grass-4',
|
|
[18] = 'hazard-concrete-left',
|
|
[19] = 'hazard-concrete-right',
|
|
[20] = 'lab-dark-1',
|
|
[21] = 'lab-dark-2',
|
|
[22] = 'lab-white',
|
|
[23] = 'out-of-map',
|
|
[24] = 'red-desert-0',
|
|
[25] = 'red-desert-1',
|
|
[26] = 'red-desert-2',
|
|
[27] = 'red-desert-3',
|
|
[28] = 'sand-1',
|
|
[29] = 'sand-2',
|
|
[30] = 'sand-3',
|
|
[31] = 'stone-path',
|
|
[32] = 'water-green',
|
|
[33] = 'water'
|
|
}
|
|
|
|
function Builders.decompress(pic)
|
|
local data = pic.data
|
|
local width = pic.width
|
|
local height = pic.height
|
|
|
|
local uncompressed = {}
|
|
|
|
for y = 1, height do
|
|
local row = data[y]
|
|
local u_row = {}
|
|
uncompressed[y] = u_row
|
|
local x = 1
|
|
for index = 1, #row, 2 do
|
|
local pixel = tile_map[row[index]]
|
|
local count = row[index + 1]
|
|
|
|
for _ = 1, count do
|
|
u_row[x] = pixel
|
|
x = x + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return {width = width, height = height, data = uncompressed}
|
|
end
|
|
|
|
function Builders.picture(pic)
|
|
local data = pic.data
|
|
local width = pic.width
|
|
local height = pic.height
|
|
|
|
-- the plus one is because lua tables are one based.
|
|
local half_width = math.floor(width / 2) + 1
|
|
local half_height = math.floor(height / 2) + 1
|
|
return function(x, y)
|
|
x = math.floor(x)
|
|
y = math.floor(y)
|
|
local x2 = x + half_width
|
|
local y2 = y + half_height
|
|
|
|
if y2 > 0 and y2 <= height and x2 > 0 and x2 <= width then
|
|
return data[y2][x2]
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
-- transforms and shape helpers
|
|
function Builders.translate(shape, x_offset, y_offset)
|
|
return function(x, y, world)
|
|
return shape(x - x_offset, y - y_offset, world)
|
|
end
|
|
end
|
|
|
|
function Builders.scale(shape, x_scale, y_scale)
|
|
y_scale = y_scale or x_scale
|
|
|
|
x_scale = 1 / x_scale
|
|
y_scale = 1 / y_scale
|
|
|
|
return function(x, y, world)
|
|
return shape(x * x_scale, y * y_scale, world)
|
|
end
|
|
end
|
|
|
|
function Builders.rotate(shape, angle)
|
|
local qx = math.cos(angle)
|
|
local qy = math.sin(angle)
|
|
return function(x, y, world)
|
|
local rot_x = qx * x - qy * y
|
|
local rot_y = qy * x + qx * y
|
|
return shape(rot_x, rot_y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.flip_x(shape)
|
|
return function(x, y, world)
|
|
return shape(-x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.flip_y(shape)
|
|
return function(x, y, world)
|
|
return shape(x, -y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.flip_xy(shape)
|
|
return function(x, y, world)
|
|
return shape(-x, -y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.any(shapes)
|
|
return function(x, y, world)
|
|
for _, s in ipairs(shapes) do
|
|
local tile = s(x, y, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Builders.all(shapes)
|
|
return function(x, y, world)
|
|
local tile
|
|
for _, s in ipairs(shapes) do
|
|
tile = s(x, y, world)
|
|
if not tile then
|
|
return false
|
|
end
|
|
end
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.combine(shapes)
|
|
return function(x, y, world)
|
|
local function combine_table(tile, index)
|
|
local i, s = next(shapes, index)
|
|
while i do
|
|
local t = s(x, y, world)
|
|
if type(t) == 'table' then
|
|
if not tile.tile then
|
|
tile.tile = t.tile
|
|
end
|
|
|
|
local es = t.entities
|
|
if es then
|
|
for _, e in ipairs(es) do
|
|
add_entity(tile, e)
|
|
end
|
|
end
|
|
else
|
|
if not tile.tile then
|
|
tile.tile = t
|
|
end
|
|
end
|
|
|
|
i, s = next(shapes, i)
|
|
end
|
|
|
|
return tile
|
|
end
|
|
|
|
local tile = false
|
|
|
|
local i, s = next(shapes, nil)
|
|
while i do
|
|
local t = s(x, y, world)
|
|
if not tile then
|
|
tile = t
|
|
elseif type(t) == 'table' then
|
|
t.tile = tile
|
|
return combine_table(t, i)
|
|
end
|
|
|
|
if type(tile) == 'table' then
|
|
return combine_table(tile, i)
|
|
end
|
|
|
|
i, s = next(shapes, i)
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.subtract(shape, minus_shape)
|
|
return function(x, y, world)
|
|
if minus_shape(x, y, world) then
|
|
return false
|
|
else
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.invert(shape)
|
|
return function(x, y, world)
|
|
return not shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.throttle_x(shape, x_in, x_size)
|
|
return function(x, y, world)
|
|
if x % x_size < x_in then
|
|
return shape(x, y, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.throttle_y(shape, y_in, y_size)
|
|
return function(x, y, world)
|
|
if y % y_size < y_in then
|
|
return shape(x, y, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.throttle_xy(shape, x_in, x_size, y_in, y_size)
|
|
return function(x, y, world)
|
|
if x % x_size < x_in and y % y_size < y_in then
|
|
return shape(x, y, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.throttle_world_xy(shape, x_in, x_size, y_in, y_size)
|
|
return function(x, y, world)
|
|
if world.x % x_size < x_in and world.y % y_size < y_in then
|
|
return shape(x, y, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.choose(condition, true_shape, false_shape)
|
|
return function(x, y, world)
|
|
if condition(x, y, world) then
|
|
return true_shape(x, y, world)
|
|
else
|
|
return false_shape(x, y, world)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.if_else(shape, else_shape)
|
|
return function(x, y, world)
|
|
return shape(x, y, world) or else_shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.linear_grow(shape, size)
|
|
return function(x, y, world)
|
|
local t = math.ceil((y / size) + 0.5)
|
|
local n = math.ceil((math.sqrt(8 * t + 1) - 1) / 2)
|
|
local t_upper = n * (n + 1) * 0.5
|
|
local t_lower = t_upper - n
|
|
|
|
y = (y - size * (t_lower + n / 2 - 0.5)) / n
|
|
x = x / n
|
|
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grow(in_shape, out_shape, size, offset)
|
|
local half_size = size / 2
|
|
return function(x, y, world)
|
|
local tx = math.ceil(math.abs(x) / half_size)
|
|
local ty = math.ceil(math.abs(y) / half_size)
|
|
local t = math.max(tx, ty)
|
|
|
|
for i = t, 2.5 * t, 1 do
|
|
local out_t = 1 / (i - offset)
|
|
local in_t = 1 / i
|
|
|
|
if out_shape(out_t * x, out_t * y, world) then
|
|
return nil
|
|
end
|
|
|
|
local tile = in_shape(in_t * x, in_t * y, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function Builders.project(shape, size, r)
|
|
local ln_r = math.log(r)
|
|
local r2 = 1 / (r - 1)
|
|
local a = 1 / size
|
|
|
|
return function(x, y, world)
|
|
local offset = 0.5 * size
|
|
local sn = math.ceil(y + offset)
|
|
|
|
local n = math.ceil(math.log((r - 1) * sn * a + 1) / ln_r - 1)
|
|
local rn = r ^ n
|
|
local rn2 = 1 / rn
|
|
local c = size * rn
|
|
|
|
local sn_upper = size * (r ^ (n + 1) - 1) * r2
|
|
x = x * rn2
|
|
y = (y - (sn_upper - 0.5 * c) + offset) * rn2
|
|
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.project_pattern(pattern, size, r, columns, rows)
|
|
local ln_r = math.log(r)
|
|
local r2 = 1 / (r - 1)
|
|
local a = 1 / size
|
|
local half_size = size / 2
|
|
|
|
return function(x, y, world)
|
|
local offset = 0.5 * size
|
|
local sn = math.ceil(y + offset)
|
|
|
|
local n = math.ceil(math.log((r - 1) * sn * a + 1) / ln_r - 1)
|
|
local rn = r ^ n
|
|
local rn2 = 1 / rn
|
|
local c = size * rn
|
|
|
|
local sn_upper = size * (r ^ (n + 1) - 1) * r2
|
|
x = x * rn2
|
|
y = (y - (sn_upper - 0.5 * c) + offset) * rn2
|
|
|
|
local row_i = n % rows + 1
|
|
local row = pattern[row_i]
|
|
|
|
local x2 = ((x + half_size) % size) - half_size
|
|
local col_pos = math.floor(x / size + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
local shape = row[col_i]
|
|
|
|
return shape(x2, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.project_overlap(shape, size, r)
|
|
local ln_r = math.log(r)
|
|
local r2 = 1 / (r - 1)
|
|
local a = 1 / size
|
|
local offset = 0.5 * size
|
|
|
|
return function(x, y, world)
|
|
local sn = math.ceil(y + offset)
|
|
|
|
local n = math.ceil(math.log((r - 1) * sn * a + 1) / ln_r - 1)
|
|
local rn = r ^ n
|
|
local rn2 = 1 / rn
|
|
local c = size * rn
|
|
|
|
local sn_upper = size * (r ^ (n + 1) - 1) * r2
|
|
x = x * rn2
|
|
y = (y - (sn_upper - 0.5 * c) + offset) * rn2
|
|
|
|
local tile
|
|
|
|
tile = shape(x, y, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
local rn_above = rn / r
|
|
local rn2_above = 1 / rn_above
|
|
local c_above = size * rn_above
|
|
|
|
local sn_upper_above = sn_upper - c
|
|
local x_above = x * rn2_above
|
|
local y_above = (y - (sn_upper_above - 0.5 * c_above) + offset) * rn2_above
|
|
|
|
tile = shape(x_above, y_above, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
local rn_below = rn * r
|
|
local rn2_below = 1 / rn_below
|
|
local c_below = size * rn_below
|
|
|
|
local sn_upper_below = sn_upper + c_below
|
|
local x_below = x * rn2_below
|
|
local y_below = (y - (sn_upper_below - 0.5 * c_below) + offset) * rn2_below
|
|
|
|
return shape(x_below, y_below, world)
|
|
end
|
|
end
|
|
|
|
-- Entity generation
|
|
function Builders.entity(shape, name)
|
|
return function(x, y, world)
|
|
if shape(x, y, world) then
|
|
return {name = name}
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.entity_func(shape, func)
|
|
return function(x, y, world)
|
|
if shape(x, y, world) then
|
|
return func(x, y, world)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.resource(shape, resource_type, amount_function, always_place)
|
|
amount_function = amount_function or function()
|
|
return 404
|
|
end
|
|
return function(x, y, world)
|
|
if shape(x, y, world) then
|
|
return {
|
|
name = resource_type,
|
|
amount = amount_function(world.x, world.y),
|
|
always_place = always_place
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.apply_entity(shape, entity_shape)
|
|
return function(x, y, world)
|
|
local tile = shape(x, y, world)
|
|
|
|
if not tile then
|
|
return false
|
|
end
|
|
|
|
local e = entity_shape(x, y, world)
|
|
if e then
|
|
tile = add_entity(tile, e)
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.apply_entities(shape, entity_shapes)
|
|
return function(x, y, world)
|
|
local tile = shape(x, y, world)
|
|
|
|
if not tile then
|
|
return false
|
|
end
|
|
|
|
for _, es in ipairs(entity_shapes) do
|
|
local e = es(x, y, world)
|
|
if e then
|
|
tile = add_entity(tile, e)
|
|
end
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
-- pattern builders.
|
|
function Builders.single_pattern(shape, width, height)
|
|
shape = shape or Builders.empty_shape
|
|
local half_width = width / 2
|
|
local half_height
|
|
if height then
|
|
half_height = height / 2
|
|
else
|
|
half_height = half_width
|
|
end
|
|
|
|
return function(x, y, world)
|
|
y = ((y + half_height) % height) - half_height
|
|
x = ((x + half_width) % width) - half_width
|
|
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.single_pattern_overlap(shape, width, height)
|
|
shape = shape or Builders.empty_shape
|
|
local half_width = width / 2
|
|
local half_height
|
|
if height then
|
|
half_height = height / 2
|
|
else
|
|
half_height = half_width
|
|
end
|
|
|
|
return function(x, y, world)
|
|
y = ((y + half_height) % height) - half_height
|
|
x = ((x + half_width) % width) - half_width
|
|
|
|
return shape(x, y, world) or shape(x + width, y, world) or shape(x - width, y, world) or
|
|
shape(x, y + height, world) or
|
|
shape(x, y - height, world)
|
|
end
|
|
end
|
|
|
|
function Builders.single_x_pattern(shape, width)
|
|
shape = shape or Builders.empty_shape
|
|
local half_width = width / 2
|
|
|
|
return function(x, y, world)
|
|
x = ((x + half_width) % width) - half_width
|
|
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.single_y_pattern(shape, height)
|
|
shape = shape or Builders.empty_shape
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
y = ((y + half_height) % height) - half_height
|
|
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_x_pattern(pattern, columns, width)
|
|
local half_width = width / 2
|
|
|
|
return function(x, y, world)
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local columns_pos = math.floor(x / width + 0.5)
|
|
local column_i = columns_pos % columns + 1
|
|
local shape = pattern[column_i] or Builders.empty_shape
|
|
|
|
return shape(x2, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_y_pattern(pattern, rows, height)
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local shape = pattern[row_i] or Builders.empty_shape
|
|
|
|
return shape(x, y2, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_pattern(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
local shape = row[col_i] or Builders.empty_shape
|
|
return shape(x2, y2, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_pattern_overlap(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
local shape = row[col_i] or Builders.empty_shape
|
|
|
|
local tile = shape(x2, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
-- edges
|
|
local col_i_left = (col_pos - 1) % columns + 1
|
|
shape = row[col_i_left] or Builders.empty_shape
|
|
tile = shape(x2 + width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
local col_i_right = (col_pos + 1) % columns + 1
|
|
shape = row[col_i_right] or Builders.empty_shape
|
|
tile = shape(x2 - width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
local row_i_up = (row_pos - 1) % rows + 1
|
|
local row_up = pattern[row_i_up] or {}
|
|
shape = row_up[col_i] or Builders.empty_shape
|
|
tile = shape(x2, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
local row_i_down = (row_pos + 1) % rows + 1
|
|
local row_down = pattern[row_i_down] or {}
|
|
shape = row_down[col_i] or Builders.empty_shape
|
|
return shape(x2, y2 - height, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_pattern_full_overlap(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
local row_i_up = (row_pos - 1) % rows + 1
|
|
local row_up = pattern[row_i_up] or {}
|
|
local row_i_down = (row_pos + 1) % rows + 1
|
|
local row_down = pattern[row_i_down] or {}
|
|
|
|
local col_i_left = (col_pos - 1) % columns + 1
|
|
local col_i_right = (col_pos + 1) % columns + 1
|
|
|
|
-- start from top left, move left to right then down
|
|
local shape = row_up[col_i_left] or Builders.empty_shape
|
|
local tile = shape(x2 + width, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_up[col_i] or Builders.empty_shape
|
|
tile = shape(x2, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_up[col_i_right] or Builders.empty_shape
|
|
tile = shape(x2 - width, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i_left] or Builders.empty_shape
|
|
tile = shape(x2 + width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i] or Builders.empty_shape
|
|
tile = shape(x2, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i_right] or Builders.empty_shape
|
|
tile = shape(x2 - width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i_left] or Builders.empty_shape
|
|
tile = shape(x2 + width, y2 - height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i] or Builders.empty_shape
|
|
tile = shape(x2, y2 - height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i_right] or Builders.empty_shape
|
|
return shape(x2 - width, y2 - height, world)
|
|
end
|
|
end
|
|
|
|
local function is_spiral(x, y)
|
|
local a = -math.max(math.abs(x), math.abs(y))
|
|
|
|
if a % 2 == 0 then
|
|
return y ~= a or x == a
|
|
else
|
|
return y == a and x ~= a
|
|
end
|
|
end
|
|
|
|
function Builders.single_spiral_pattern(shape, width, height)
|
|
local inv_width = 1 / width
|
|
local inv_height = 1 / height
|
|
return function(x, y, world)
|
|
local x1 = math.floor(x * inv_width + 0.5)
|
|
local y1 = math.floor(y * inv_height + 0.5)
|
|
|
|
if is_spiral(x1, y1) then
|
|
x1 = x - x1 * width
|
|
y1 = y - y1 * height
|
|
return shape(x1, y1, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
local function rotate_0(x, y)
|
|
return x, y
|
|
end
|
|
|
|
local function rotate_90(x, y)
|
|
return y, -x
|
|
end
|
|
|
|
local function rotate_180(x, y)
|
|
return -x, -y
|
|
end
|
|
|
|
local function rotate_270(x, y)
|
|
return -y, x
|
|
end
|
|
|
|
local function spiral_rotation(x, y)
|
|
local a = -math.max(math.abs(x), math.abs(y))
|
|
|
|
if a % 2 == 0 then
|
|
if y ~= a or x == a then
|
|
if x == a then
|
|
return rotate_0
|
|
elseif y >= x then
|
|
return rotate_270
|
|
else
|
|
return rotate_180
|
|
end
|
|
end
|
|
else
|
|
if y == a and x ~= a then
|
|
return rotate_90
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.single_spiral_rotate_pattern(shape, width, optional_height)
|
|
optional_height = optional_height or width
|
|
|
|
local inv_width = 1 / width
|
|
local inv_height = 1 / optional_height
|
|
return function(x, y, world)
|
|
local x1 = math.floor(x * inv_width + 0.5)
|
|
local y1 = math.floor(y * inv_height + 0.5)
|
|
|
|
local t = spiral_rotation(x1, y1)
|
|
if t then
|
|
x1 = x - x1 * width
|
|
y1 = y - y1 * optional_height
|
|
x1, y1 = t(x1, y1)
|
|
return shape(x1, y1, world)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral_pattern(in_thickness, total_thickness, pattern)
|
|
local n_threads = #pattern
|
|
total_thickness = total_thickness * n_threads
|
|
local half_total_thickness = total_thickness * 0.5
|
|
local delta = total_thickness / n_threads
|
|
return function(x, y, world)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
|
|
local offset = d + (angle * half_total_thickness)
|
|
if offset % total_thickness < in_thickness then
|
|
return pattern[1](x, y, world)
|
|
end
|
|
|
|
for i = 2, n_threads do
|
|
offset = offset + delta
|
|
if offset % total_thickness < in_thickness then
|
|
return pattern[i](x, y, world)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Builders.circular_spiral_grow_pattern(in_thickness, total_thickness, grow_factor, pattern)
|
|
local n_threads = #pattern
|
|
total_thickness = total_thickness * n_threads
|
|
local half_total_thickness = total_thickness * 0.5
|
|
local inv_grow_factor = 1 / grow_factor
|
|
local delta = total_thickness / n_threads
|
|
return function(x, y, world)
|
|
local d = math.sqrt(x * x + y * y)
|
|
|
|
local factor = (d * inv_grow_factor) + 1
|
|
local total_thickness2 = total_thickness * factor
|
|
local in_thickness2 = in_thickness * factor
|
|
local half_total_thickness2 = half_total_thickness * factor
|
|
local delta2 = delta * factor
|
|
|
|
local angle = 1 + inv_pi * math.atan2(x, y)
|
|
|
|
local offset = d + (angle * half_total_thickness2)
|
|
if offset % total_thickness2 < in_thickness2 then
|
|
return pattern[1](x, y, world)
|
|
end
|
|
|
|
for i = 2, n_threads do
|
|
offset = offset + delta2
|
|
if offset % total_thickness2 < in_thickness2 then
|
|
return pattern[i](x, y, world)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Builders.segment_pattern(pattern)
|
|
local count = #pattern
|
|
|
|
return function(x, y, world)
|
|
local angle = math.atan2(-y, x)
|
|
local index = math.floor(angle / tau * count) % count + 1
|
|
local shape = pattern[index] or Builders.empty_shape
|
|
return shape(x, y, world)
|
|
end
|
|
end
|
|
|
|
function Builders.pyramid_pattern(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
if row_pos % 2 ~= 0 then
|
|
x = x - half_width
|
|
end
|
|
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
if col_pos > row_pos / 2 or -col_pos > (row_pos + 1) / 2 then
|
|
return false
|
|
end
|
|
|
|
local shape = row[col_i] or Builders.empty_shape
|
|
return shape(x2, y2, world)
|
|
end
|
|
end
|
|
|
|
function Builders.pyramid_pattern_inner_overlap(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
local x_odd
|
|
local x_even
|
|
if row_pos % 2 == 0 then
|
|
x_even = x
|
|
x_odd = x - half_width
|
|
else
|
|
x_even = x - half_width
|
|
x_odd = x
|
|
x = x - half_width
|
|
end
|
|
|
|
x_even = ((x_even + half_width) % width) - half_width
|
|
x_odd = ((x_odd + half_width) % width) - half_width
|
|
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
|
|
local offset = 1
|
|
local offset_odd = 0
|
|
if (col_pos % 2) == (row_pos % 2) then
|
|
offset = 0
|
|
offset_odd = 1
|
|
end
|
|
|
|
local col_i = (col_pos - offset) % columns + 1
|
|
local col_i_odd = (col_pos - offset_odd) % columns + 1
|
|
|
|
if col_pos > row_pos / 2 or -col_pos > (row_pos + 1) / 2 then
|
|
return false
|
|
end
|
|
|
|
local row_i_up = (row_pos - 1) % rows + 1
|
|
local row_up = pattern[row_i_up] or {}
|
|
local row_i_down = (row_pos + 1) % rows + 1
|
|
local row_down = pattern[row_i_down] or {}
|
|
|
|
local col_i_left = (col_pos - 1) % columns + 1
|
|
local col_i_right = (col_pos + 1) % columns + 1
|
|
|
|
-- start from top left, move left to right then down
|
|
local shape = row_up[col_i_left] or Builders.empty_shape
|
|
local tile = shape(x_even + width, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_up[col_i_odd] or Builders.empty_shape
|
|
tile = shape(x_odd, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_up[col_i_right] or Builders.empty_shape
|
|
tile = shape(x_even - width, y2 + height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i_left] or Builders.empty_shape
|
|
tile = shape(x_even + width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i] or Builders.empty_shape
|
|
tile = shape(x_even, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row[col_i_right] or Builders.empty_shape
|
|
tile = shape(x_even - width, y2, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i_left] or Builders.empty_shape
|
|
tile = shape(x_even + width, y2 - height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i_odd] or Builders.empty_shape
|
|
tile = shape(x_odd, y2 - height, world)
|
|
if tile then
|
|
return tile
|
|
end
|
|
|
|
shape = row_down[col_i_right] or Builders.empty_shape
|
|
return shape(x_even - width, y2 - height, world)
|
|
end
|
|
end
|
|
|
|
function Builders.grid_pattern_offset(pattern, columns, rows, width, height)
|
|
local half_width = width / 2
|
|
local half_height = height / 2
|
|
|
|
return function(x, y, world)
|
|
local y2 = ((y + half_height) % height) - half_height
|
|
local row_pos = math.floor(y / height + 0.5)
|
|
local row_i = row_pos % rows + 1
|
|
local row = pattern[row_i] or {}
|
|
|
|
local x2 = ((x + half_width) % width) - half_width
|
|
local col_pos = math.floor(x / width + 0.5)
|
|
local col_i = col_pos % columns + 1
|
|
|
|
y2 = y2 + height * math.floor((row_pos + 1) / rows)
|
|
x2 = x2 + width * math.floor((col_pos + 1) / columns)
|
|
|
|
local shape = row[col_i] or Builders.empty_shape
|
|
return shape(x2, y2, world)
|
|
end
|
|
end
|
|
|
|
-- tile converters
|
|
function Builders.change_tile(shape, old_tile, new_tile)
|
|
return function(x, y, world)
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
if tile.tile == old_tile then
|
|
tile.tile = new_tile
|
|
end
|
|
else
|
|
if tile == old_tile then
|
|
tile = new_tile
|
|
end
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.change_collision_tile(shape, collides, new_tile)
|
|
return function(x, y, world)
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
if tile.tile.collides_with(collides) then
|
|
tile.tile = new_tile
|
|
return tile
|
|
end
|
|
else
|
|
if tile.collides_with(collides) then
|
|
return new_tile
|
|
end
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
-- only changes tiles made by the factorio map generator.
|
|
function Builders.change_map_gen_tile(shape, old_tile, new_tile)
|
|
return function(x, y, world)
|
|
local function handle_tile(tile)
|
|
if type(tile) == 'boolean' and tile then
|
|
local gen_tile = world.surface.get_tile(world.x, world.y).name
|
|
if gen_tile == old_tile then
|
|
return new_tile
|
|
end
|
|
end
|
|
return tile
|
|
end
|
|
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
tile.tile = handle_tile(tile.tile)
|
|
else
|
|
tile = handle_tile(tile)
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
-- only changes tiles made by the factorio map generator.
|
|
function Builders.change_map_gen_collision_tile(shape, collides, new_tile)
|
|
return function(x, y, world)
|
|
local function handle_tile(tile)
|
|
if type(tile) == 'boolean' and tile then
|
|
local gen_tile = world.surface.get_tile(world.x, world.y)
|
|
if gen_tile.collides_with(collides) then
|
|
return new_tile
|
|
end
|
|
end
|
|
return tile
|
|
end
|
|
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
tile.tile = handle_tile(tile.tile)
|
|
else
|
|
tile = handle_tile(tile)
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
local bad_tiles = {
|
|
['out-of-map'] = true,
|
|
['water'] = true,
|
|
['deepwater'] = true,
|
|
['water-green'] = true,
|
|
['deepwater-green'] = true
|
|
}
|
|
|
|
function Builders.overlay_tile_land(shape, tile_shape)
|
|
return function(x, y, world)
|
|
local function handle_tile(tile)
|
|
if type(tile) == 'boolean' then
|
|
return tile and not world.surface.get_tile(world.x, world.y).collides_with('water-tile')
|
|
else
|
|
return not bad_tiles[tile]
|
|
end
|
|
end
|
|
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
if handle_tile(tile.tile) then
|
|
tile.tile = tile_shape(x, y, world) or tile.tile
|
|
end
|
|
else
|
|
if handle_tile(tile) then
|
|
tile = tile_shape(x, y, world) or tile
|
|
end
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
local water_tiles = {
|
|
['water'] = true,
|
|
['deepwater'] = true,
|
|
['water-green'] = true,
|
|
['deepwater-green'] = true
|
|
}
|
|
|
|
function Builders.fish(shape, spawn_rate)
|
|
return function(x, y, world)
|
|
local function handle_tile(tile)
|
|
if type(tile) == 'string' then
|
|
if water_tiles[tile] and spawn_rate >= math.random() then
|
|
return {name = 'fish'}
|
|
end
|
|
elseif tile then
|
|
if world.surface.get_tile(world.x, world.y).collides_with('water-tile') and spawn_rate >= math.random() then
|
|
return {name = 'fish'}
|
|
end
|
|
end
|
|
end
|
|
|
|
local tile = shape(x, y, world)
|
|
|
|
if type(tile) == 'table' then
|
|
local entity = handle_tile(tile.tile)
|
|
if entity then
|
|
add_entity(tile, entity)
|
|
end
|
|
else
|
|
local entity = handle_tile(tile)
|
|
if entity then
|
|
tile = {
|
|
tile = tile,
|
|
entities = {entity}
|
|
}
|
|
end
|
|
end
|
|
|
|
return tile
|
|
end
|
|
end
|
|
|
|
function Builders.apply_effect(shape, func)
|
|
return function(x, y, world)
|
|
local tile = shape(x, y, world)
|
|
return func(x, y, world, tile)
|
|
end
|
|
end
|
|
|
|
function Builders.manhattan_value(base, mult)
|
|
return function(x, y)
|
|
return mult * (math.abs(x) + math.abs(y)) + base
|
|
end
|
|
end
|
|
|
|
function Builders.euclidean_value(base, mult)
|
|
return function(x, y)
|
|
return mult * math.sqrt(x * x + y * y) + base
|
|
end
|
|
end
|
|
|
|
function Builders.exponential_value(base, mult, pow)
|
|
return function(x, y)
|
|
local d_sq = x * x + y * y
|
|
return base + mult * d_sq ^ (pow / 2)
|
|
end
|
|
end
|
|
|
|
function Builders.prepare_weighted_array(array)
|
|
local total = 0
|
|
local weights = {}
|
|
|
|
for _, v in ipairs(array) do
|
|
total = total + v.weight
|
|
table.insert(weights, total)
|
|
end
|
|
|
|
weights.total = total
|
|
|
|
return weights
|
|
end
|
|
|
|
return Builders
|