local Task = require 'utils.task' local Token = require 'utils.token' local Event = require 'utils.event' local insert = table.insert local raise_event = script.raise_event local tiles_per_tick local regen_decoratives local surfaces = {} local total_calls local Public = {} -- Set to false by modules that want to control the on_chunk_generated event themselves. Public.enable_register_events = true local on_chunk_generated = Event.generate_event_name('generate_on_chunk_generated') Public.events = { on_chunk_generated = on_chunk_generated } local function do_tile_inner(tiles, tile, pos) if not tile then insert(tiles, {name = 'out-of-map', position = pos}) elseif type(tile) == 'string' then insert(tiles, {name = tile, position = pos}) end end local function do_tile(y, x, data, shape) local pos = {x, y} -- local coords need to be 'centered' to allow for correct rotation and scaling. local tile = shape(x + 0.5, y + 0.5, data) if type(tile) == 'table' then do_tile_inner(data.tiles, tile.tile, pos) local hidden_tile = tile.hidden_tile if hidden_tile then insert(data.hidden_tiles, {tile = hidden_tile, position = pos}) end local entities = tile.entities if entities then for _, entity in ipairs(entities) do if not entity.position then entity.position = pos end insert(data.entities, entity) end end local decoratives = tile.decoratives if decoratives then for _, decorative in ipairs(decoratives) do insert(data.decoratives, decorative) end end else do_tile_inner(data.tiles, tile, pos) end end local function do_row(row, data, shape) local y = data.top_y + row local top_x = data.top_x local tiles = data.tiles data.y = y for x = top_x, top_x + 31 do data.x = x local pos = {data.x, data.y} -- local coords need to be 'centered' to allow for correct rotation and scaling. local tile = shape(x + 0.5, y + 0.5, data) if type(tile) == 'table' then do_tile_inner(tiles, tile.tile, pos) local hidden_tile = tile.hidden_tile if hidden_tile then insert(data.hidden_tiles, {tile = hidden_tile, position = pos}) end local entities = tile.entities if entities then for _, entity in ipairs(entities) do if not entity.position then entity.position = pos end insert(data.entities, entity) end end local decoratives = tile.decoratives if decoratives then for _, decorative in ipairs(decoratives) do if not decorative.position then decorative.position = pos end insert(data.decoratives, decorative) end end else do_tile_inner(tiles, tile, pos) end end end local function do_place_tiles(data) data.surface.set_tiles(data.tiles, true) end local function do_place_hidden_tiles(data) local surface = data.surface for _, t in ipairs(data.hidden_tiles) do surface.set_hidden_tile(t.position, t.tile) end end local function do_place_decoratives(data) if regen_decoratives then data.surface.regenerate_decorative(nil, {{data.top_x / 32, data.top_y / 32}}) end local dec = data.decoratives if #dec > 0 then data.surface.create_decoratives({check_collision = true, decoratives = dec}) end end local function do_place_entities(data) local surface = data.surface for _, e in ipairs(data.entities) do if e.always_place or surface.can_place_entity(e) then local entity = surface.create_entity(e) if entity and e.callback then local callback = Token.get(e.callback) callback(entity, e.data) end end end end local function run_chart_update(data) local x = data.top_x / 32 local y = data.top_y / 32 if game.forces.player.is_chunk_charted(data.surface, {x, y}) then -- Don't use full area, otherwise adjacent chunks get charted game.forces.player.chart( data.surface, { {data.top_x, data.top_y}, {data.top_x + 1, data.top_y + 1} } ) end end local function map_gen_action(data) local state = data.y if state < 32 then local shape = surfaces[data.surface.name] if shape == nil then return false end local count = tiles_per_tick local y = state + data.top_y local x = data.x local max_x = data.top_x + 32 data.y = y repeat count = count - 1 do_tile(y, x, data, shape) x = x + 1 if x == max_x then y = y + 1 if y == data.top_y + 32 then break end x = data.top_x data.y = y end data.x = x until count == 0 data.y = y - data.top_y return true elseif state == 32 then do_place_tiles(data) data.y = 33 return true elseif state == 33 then do_place_hidden_tiles(data) data.y = 34 return true elseif state == 34 then do_place_entities(data) data.y = 35 return true elseif state == 35 then do_place_decoratives(data) data.y = 36 return true elseif state == 36 then run_chart_update(data) raise_event(on_chunk_generated, data) return false end end local map_gen_action_token = Token.register(map_gen_action) --- Adds generation of a Chunk of the map to the queue -- @param event the event table from on_chunk_generated function Public.schedule_chunk(event) local surface = event.surface local shape = surfaces[surface.name] if not shape then return end local area = event.area local data = { y = 0, x = area.left_top.x, area = area, top_x = area.left_top.x, top_y = area.left_top.y, position = event.position, surface = surface, tiles = {}, hidden_tiles = {}, entities = {}, decoratives = {} } Task.queue_task(map_gen_action_token, data, total_calls) end --- Generates a Chunk of map when called -- @param event
the event table from on_chunk_generated function Public.do_chunk(event) local surface = event.surface local shape = surfaces[surface.name] if not shape then return end local area = event.area local data = { area = area, top_x = area.left_top.x, top_y = area.left_top.y, position = event.position, surface = surface, tiles = {}, hidden_tiles = {}, entities = {}, decoratives = {} } for row = 0, 31 do do_row(row, data, shape) end do_place_tiles(data) do_place_hidden_tiles(data) do_place_entities(data) do_place_decoratives(data) raise_event(on_chunk_generated, data) end --- Sets the variables for the generate functions, should only be called from map_loader -- @param args
function Public.init(args) tiles_per_tick = args.tiles_per_tick or 32 regen_decoratives = args.regen_decoratives or false for surface_name, shape in pairs(args.surfaces or {}) do surfaces[surface_name] = shape end total_calls = math.ceil(1024 / tiles_per_tick) + 5 end local do_chunk = Public.do_chunk local schedule_chunk = Public.schedule_chunk local function on_chunk(event) if event.tick == 0 then do_chunk(event) else schedule_chunk(event) end end --- Registers the event to generate our map when Chunks are generated, should only be called from map_loader function Public.register() if not Public.enable_register_events then return end if _DEBUG then Event.add(defines.events.on_chunk_generated, do_chunk) else Event.add(defines.events.on_chunk_generated, on_chunk) end end --- Returns the surfaces that the generate functions will act on -- Warning! Changing this table after on_init or on_load has run will cause desyncs! -- @return dictionary of surface_name -> shape function function Public.get_surfaces() if _LIFECYCLE == 8 then error('Calling Generate.get_surfaces after on_init() or on_load() has run is a desync risk.', 2) end return surfaces end return Public