Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions map_gen/maps/danger_ores/changelog.lua
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,5 @@ return [[
2026-07-07:
- [DO:ScrapMaze] Added DO/Scrapworld-Maze preset
- [DO:OmniMaze] Added DO/Omnimatter-Maze preset
- [DO:CityBlocks] Added DO/City-Blocks preset
]]
383 changes: 383 additions & 0 deletions map_gen/maps/danger_ores/modules/city_blocks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,383 @@
-- City Blocks: isolated 4x4-chunk one-ore rooms separated by a paved, rail-only corridor
-- lattice carrying a ready-made double-track network -- ordinary, minable rails: continuous
-- lines ring every room and meet at a signalled RAIL ROUNDABOUT on every corner (geometry
-- decoded from the user's blueprint; legacy rail pieces, fully functional in 2.0). Players
-- may branch their own rails, signals and poles anywhere on the lattice, but nothing else
-- can be built there, and belts cannot span the 32-tile corridors: trains are the only
-- inter-room logistics. Room ores are assigned once in on_init and stored in Global.
local b = require 'map_gen.shared.builders'
local Event = require 'utils.event'
local Generate = require 'map_gen.shared.generate'
local Global = require 'utils.global'
local RS = require 'map_gen.shared.redmew_surface'
local RestrictEntities = require 'map_gen.shared.entity_placement_restriction'
local Token = require 'utils.token'
local table = require 'utils.table'

local floor = math.floor
local random = math.random
local shuffle = table.shuffle_table

local PITCH = 5 -- chunks per room+wall cell
local ROOMS_RADIUS = 12 -- rooms span -R..R on both axes
local RAIL_A = 13 -- track offsets within a corridor chunk: odd (chunk edges are multiples of
local RAIL_B = 19 -- 32, matching the rail grid) and at corridor-center -3/+3, exactly where
-- the corner roundabouts' approach lanes sit; the 4-tile gap still fits signals everywhere

-- The corner roundabout, decoded from the user's blueprint (31x31, centered on the corner
-- chunk's midpoint). Legacy rail pieces as in the blueprint -- functional in 2.0, connect
-- to modern rails, and RedMew's allowed-entities list already includes them.
local ROUNDABOUT = {
{ name = 'legacy-curved-rail', x = -4, y = -12, direction = 10 },
{ name = 'legacy-curved-rail', x = 4, y = -12, direction = 8 },
{ name = 'legacy-curved-rail', x = -4, y = -10, direction = 12 },
{ name = 'legacy-curved-rail', x = 4, y = -10, direction = 6 },
{ name = 'legacy-curved-rail', x = -12, y = -4, direction = 4 },
{ name = 'legacy-curved-rail', x = -10, y = -4, direction = 2 },
{ name = 'legacy-curved-rail', x = 10, y = -4, direction = 0 },
{ name = 'legacy-curved-rail', x = 12, y = -4, direction = 14 },
{ name = 'legacy-curved-rail', x = -12, y = 4, direction = 6 },
{ name = 'legacy-curved-rail', x = -10, y = 4, direction = 8 },
{ name = 'legacy-curved-rail', x = 10, y = 4, direction = 10 },
{ name = 'legacy-curved-rail', x = 12, y = 4, direction = 12 },
{ name = 'legacy-curved-rail', x = -4, y = 10, direction = 14 },
{ name = 'legacy-curved-rail', x = 4, y = 10, direction = 4 },
{ name = 'legacy-curved-rail', x = -4, y = 12, direction = 0 },
{ name = 'legacy-curved-rail', x = 4, y = 12, direction = 2 },
{ name = 'legacy-straight-rail', x = -7, y = -9, direction = 6 },
{ name = 'legacy-straight-rail', x = 7, y = -9, direction = 10 },
{ name = 'legacy-straight-rail', x = -9, y = -7, direction = 6 },
{ name = 'legacy-straight-rail', x = -7, y = -7, direction = 14 },
{ name = 'legacy-straight-rail', x = 7, y = -7, direction = 2 },
{ name = 'legacy-straight-rail', x = 9, y = -7, direction = 10 },
{ name = 'legacy-straight-rail', x = -9, y = 7, direction = 2 },
{ name = 'legacy-straight-rail', x = -7, y = 7, direction = 10 },
{ name = 'legacy-straight-rail', x = 7, y = 7, direction = 6 },
{ name = 'legacy-straight-rail', x = 9, y = 7, direction = 14 },
{ name = 'legacy-straight-rail', x = -7, y = 9, direction = 2 },
{ name = 'legacy-straight-rail', x = 7, y = 9, direction = 14 },
{ name = 'rail-signal', x = -4.5, y = -15.5, direction = 0 },
{ name = 'rail-signal', x = 4.5, y = -15.5, direction = 8 },
{ name = 'rail-signal', x = -15.5, y = -4.5, direction = 4 },
{ name = 'rail-signal', x = 15.5, y = -4.5, direction = 4 },
{ name = 'rail-signal', x = -15.5, y = 4.5, direction = 12 },
{ name = 'rail-signal', x = 15.5, y = 4.5, direction = 12 },
{ name = 'rail-signal', x = -4.5, y = 15.5, direction = 0 },
{ name = 'rail-signal', x = 4.5, y = 15.5, direction = 8 },
}
Comment thread
RedRafe marked this conversation as resolved.
Outdated

local Public = {}

local data = {
room_ore = {}, -- ['i/j'] = 1..num_ores
}
Global.register(data, function(tbl)
data = tbl
end)

local function key(i, j)
return i .. '/' .. j
end

local function in_bounds(i, j)
return i >= -ROOMS_RADIUS and i <= ROOMS_RADIUS and j >= -ROOMS_RADIUS and j <= ROOMS_RADIUS
end

-- chunk coord -> room index and local offset (wall iff local offset == PITCH - 1)
local function room_of_chunk(c)
return floor((c + 2) / PITCH), (c + 2) % PITCH
end

local function neighbours_of(i, j)
local list = {}
for _, d in pairs({ { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }) do
local ni, nj = i + d[1], j + d[2]
if in_bounds(ni, nj) then
list[#list + 1] = { ni, nj }
end
end
return list
end

-- Room ore assignment is derived from the ore config passed to register(): any number of
-- ores works, and optional weights (config.room_ore_weights) can bias the distribution.
local num_ores = 1
local ore_weights = nil
local ore_weight_total = 0

local function random_ore()
if not ore_weights then
return random(num_ores)
end
local r = random(ore_weight_total)
for ore, weight in ipairs(ore_weights) do
r = r - weight
if r <= 0 then
return ore
end
end
return 1
end

local function assign_ores()
-- BFS from spawn: the first rooms reached cover every ore once (shuffled)
local first = {}
for i = 1, num_ores do
first[i] = i
end
shuffle(first)
local order = 1
local spawn_key = key(0, 0)
local seen = { [spawn_key] = true }
local queue = { { 0, 0 } }
local head = 1
while queue[head] do
local room = queue[head]
head = head + 1
local k = key(room[1], room[2])
if order <= num_ores and k ~= spawn_key then
data.room_ore[k] = first[order]
order = order + 1
elseif not data.room_ore[k] then
data.room_ore[k] = random_ore()
end
for _, n in pairs(neighbours_of(room[1], room[2])) do
local nk = key(n[1], n[2])
if not seen[nk] then
seen[nk] = true
queue[#queue + 1] = n
end
end
end
end

-- === chunk rendering =======================================================

local function void_area(surface, area)
local tiles = {}
for x = area.left_top.x, area.right_bottom.x - 1 do
for y = area.left_top.y, area.right_bottom.y - 1 do
tiles[#tiles + 1] = { name = 'out-of-map', position = { x, y } }
end
end
surface.set_tiles(tiles, true)
end

-- Trunk rails are ordinary player rails: mine them, reroute them, do what you want --
-- the pre-laid network is a head start, not a constraint.
local function lay_rail(surface, x, y, direction)
surface.create_entity {
name = 'straight-rail',
position = { x, y },
direction = direction,
force = 'player',
}
end

-- Trunk tracks run at RAIL_A/RAIL_B relative to each corridor chunk's own edge, so every
-- chunk of a corridor lays the same lines and they join seamlessly across chunk borders --
-- each chunk only ever writes inside itself (generation order can never matter). Odd
-- offsets match Factorio's 2-tile rail grid; the 2-tile gap between tracks and the wide
-- outer margins leave room for signals on BOTH sides of both tracks.

-- east-west trunk through a horizontal corridor chunk
local function lay_h_rails(surface, area)
local top = area.left_top.y
for x = area.left_top.x + 1, area.right_bottom.x - 1, 2 do
lay_rail(surface, x, top + RAIL_A, defines.direction.east)
lay_rail(surface, x, top + RAIL_B, defines.direction.east)
end
end

-- north-south trunk through a vertical corridor chunk
local function lay_v_rails(surface, area)
local left = area.left_top.x
for y = area.left_top.y + 1, area.right_bottom.y - 1, 2 do
lay_rail(surface, left + RAIL_A, y, defines.direction.north)
lay_rail(surface, left + RAIL_B, y, defines.direction.north)
end
end

-- Rail corridor paving: gravel (stone path) rail beds with concrete curb lanes along both
-- edges and a concrete median between the two tracks; corner crossings are the inverse --
-- a concrete junction square with gravel cross-bands under the rails. Setting tiles does
-- not remove generated entities, so ores/trees/rocks are destroyed explicitly.
local function pave_corridor(surface, area, x_wall, y_wall)
local left, top = area.left_top.x, area.left_top.y
local tiles = {}
for x = left, area.right_bottom.x - 1 do
local xo = x - left
for y = top, area.right_bottom.y - 1 do
local yo = y - top
local name
if x_wall and y_wall then
-- roundabout square: concrete frame around a gravel field under the ring
if xo < 3 or xo > 28 or yo < 3 or yo > 28 then
name = 'concrete'
else
name = 'stone-path'
end
else
local o = y_wall and yo or xo
if o < 2 or o >= 30 or o == 15 or o == 16 then
name = 'concrete' -- curb lanes at the edges, median between the tracks
else
name = 'stone-path'
end
end
tiles[#tiles + 1] = { name = name, position = { x, y } }
end
end
surface.set_tiles(tiles, true)
for _, entity in pairs(surface.find_entities_filtered { area = area, type = { 'resource', 'tree', 'simple-entity' } }) do
entity.destroy()
end
end

local function on_chunk(event)
local surface = event.surface
if surface ~= RS.get_surface() then
return
end
local area = event.area
local cx = floor(area.left_top.x / 32)
local cy = floor(area.left_top.y / 32)
local ri, lx = room_of_chunk(cx)
local rj, ly = room_of_chunk(cy)
local x_wall = lx == PITCH - 1
local y_wall = ly == PITCH - 1

if not in_bounds(ri, rj) then
void_area(surface, area)
return
end
if not (x_wall or y_wall) then
return -- room interior: pure ore field, clear your own ground the danger-ores way
end

-- the whole wall lattice is a paved, rail-only corridor players can branch into
pave_corridor(surface, area, x_wall, y_wall)

if x_wall and y_wall then
-- corner: a signalled roundabout connects all four corridor approaches
local center_x = area.left_top.x + 16
local center_y = area.left_top.y + 16
for _, e in pairs(ROUNDABOUT) do
surface.create_entity {
name = e.name,
position = { center_x + e.x, center_y + e.y },
direction = e.direction,
force = 'player',
}
end
-- no connector straights needed: the ring's curve ends reach the chunk edge and
-- meet the corridor trunk lines directly
return
end

-- continuous trunk lines along every straight corridor chunk
if y_wall then
lay_h_rails(surface, area)
else
lay_v_rails(surface, area)
end
end

-- === strip placement rule ==================================================

local ALLOWED_ON_STRIP = {
Comment thread
RedRafe marked this conversation as resolved.
['straight-rail'] = true,
['curved-rail-a'] = true,
['curved-rail-b'] = true,
['half-diagonal-rail'] = true,
['rail-ramp'] = true,
['rail-support'] = true,
['rail-signal'] = true,
['rail-chain-signal'] = true,
['train-stop'] = true,
['electric-pole'] = true,
['locomotive'] = true,
['cargo-wagon'] = true,
['fluid-wagon'] = true,
['artillery-wagon'] = true,
}

local function on_wall_chunk(x, y)
local _, lx = room_of_chunk(floor(x / 32))
local _, ly = room_of_chunk(floor(y / 32))
return lx == PITCH - 1 or ly == PITCH - 1
end

-- Corridor rule via the shared entity_placement_restriction module (handles ghosts,
-- refunds and destruction): keep everything off the corridors, and on them only the
-- allowed types (whitelisted by LuaEntity type, not name, for mod compatibility).
local keep_alive_callback = Token.register(function(entity)
local e_type = entity.type
if e_type == 'entity-ghost' then
e_type = entity.ghost_type
end
if ALLOWED_ON_STRIP[e_type] then
return true
end
local pos = entity.position
return not on_wall_chunk(pos.x, pos.y)
end)

local function on_restricted_destroyed(event)
local player = event.player
if player and player.valid then
player.print('Only rail infrastructure (rails, signals, stations, power poles) and trains can be built on the rail corridors!')
end
end

-- === public ================================================================

function Public.register(config)
num_ores = #config.main_ores
ore_weights = config.room_ore_weights
if ore_weights then
ore_weight_total = 0
for _, weight in ipairs(ore_weights) do
ore_weight_total = ore_weight_total + weight
end
end

RestrictEntities.set_keep_alive_callback(keep_alive_callback)
RestrictEntities.enable_refund()

Event.on_init(assign_ores)
Event.add(Generate.events.on_chunk_generated, on_chunk)
Event.add(RestrictEntities.events.on_restricted_entity_destroyed, on_restricted_destroyed)
end

-- danger-ores main_ores_builder: per tile, pick the room's dominant-ore shape
function Public.main_ores_builder(config)
local main_ores = config.main_ores

return function(tile_builder, ore_builder, spawn_shape, water_shape, _)
local shapes = {}
for _, ore_data in ipairs(main_ores) do
local land = tile_builder(ore_data.tiles)
local ratios = ore_data.ratios
local weighted = b.prepare_weighted_array(ratios)
local ore = ore_builder(ore_data.name, ore_data.start, ratios, weighted)
shapes[#shapes + 1] = b.apply_entity(land, ore)
end

local function rooms(x, y, world)
local ri = floor((floor(x / 32) + 2) / PITCH)
local rj = floor((floor(y / 32) + 2) / PITCH)
if ri == 0 and rj == 0 then
-- spawn room splits the main ores across its quadrants
local quadrant = ((x >= 0) and 1 or 0) + ((y >= 0) and 2 or 0)
return shapes[quadrant % #shapes + 1](x, y, world)
end
local ore_index = data.room_ore[ri .. '/' .. rj] or 1
return shapes[ore_index](x, y, world)
end

return b.any { spawn_shape, water_shape, rooms }
end
end

return Public
1 change: 1 addition & 0 deletions map_gen/maps/danger_ores/modules/map_poll.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ local maps = {
--{ name = 'danger-ore-bz', display_name = 'Very BZ (default)', mod_pack = mod_packs.danger_ore_bz },
{ name = 'danger-ore-chessboard', display_name = 'Chessboard (random squares)', mod_pack = mod_packs.danger_ore_normal },
{ name = 'danger-ore-circles', display_name = 'Circles (ore rings)', mod_pack = mod_packs.danger_ore_normal },
{ name = 'danger-ore-city-blocks', display_name = 'City Blocks (train-only blocks)', mod_pack = mod_packs.danger_ore_normal },
{ name = 'danger-ore-coal-maze', display_name = 'Coal Maze (maze)', mod_pack = mod_packs.danger_ore_normal },
{ name = 'danger-ore-collapse', display_name = 'Collapse (increasing hardness)', mod_pack = mod_packs.danger_ore_collapse },
--{ name = 'danger-ore-exotic-industries', display_name = 'Exotic Industries (default)', mod_pack = mod_packs.danger_ore_ei },
Expand Down
Loading
Loading