2019-10-06 17:50:50 +00:00
-- #################
-- # Constants #
-- #################
2019-10-04 17:20:34 +00:00
2019-10-06 17:50:50 +00:00
local INTERVAL = 30
2019-10-06 12:36:40 +00:00
2019-10-04 17:20:34 +00:00
2019-10-06 17:50:50 +00:00
-- #################
-- # Utilities #
-- #################
2019-10-05 20:08:15 +00:00
2019-10-06 17:50:50 +00:00
function starts_with ( str , start )
return str : sub ( 1 , # start ) == start
2019-10-05 20:08:15 +00:00
end
2019-10-06 17:50:50 +00:00
function positionToChunk ( position )
return { x = math.floor ( position.x / 32 ) , y = math.floor ( position.y / 32 ) }
2019-10-04 17:20:34 +00:00
end
2019-10-06 12:36:40 +00:00
function movePollution ( surface , chunkFrom , chunkTo , amount )
amount = math.min ( amount , surface.get_pollution ( chunkToPosition ( chunkFrom ) ) )
surface.pollute ( chunkToPosition ( chunkFrom ) , - amount )
surface.pollute ( chunkToPosition ( chunkTo ) , amount )
return amount
end
function getBasePurificationRate ( entity )
-- Depends mostly on recipe. Should be multiplied by crafting speed to achieve actual max purification rate
if entity.name == " air-filter-machine-1 " then
2019-10-06 17:50:50 +00:00
return 4 -- 4 max pollution cleaning per second among mk1 recipes
2019-10-06 12:36:40 +00:00
elseif entity.name == " air-filter-machine-2 " or entity.name == " air-filter-machine-3 " then
2019-10-06 17:50:50 +00:00
return 6 -- 6 max pollution cleaning for mk2 and mk3 recipes (liquid)
2019-10-06 12:36:40 +00:00
else
return 0
end
end
2019-10-05 20:08:15 +00:00
2019-10-06 12:36:40 +00:00
function getSuctionRate ( entity )
if not entity.is_crafting ( ) and getSpaceForPollution ( entity ) == 0 then
return 0
else
return getBasePurificationRate ( entity ) * entity.crafting_speed * energyCraftingModifier ( entity )
end
2019-10-04 17:20:34 +00:00
end
2019-10-06 17:50:50 +00:00
function getAbsorptionRate ( entity )
return math.min ( getSpaceForPollution ( entity ) , getSuctionRate ( entity ) )
end
2019-10-06 12:36:40 +00:00
function energyCraftingModifier ( entity )
-- Approximation to speed modifier for machine running out of power
if entity.electric_buffer_size then
return entity.energy / entity.electric_buffer_size
else
return 1
end
2019-10-05 20:08:15 +00:00
end
2019-10-06 12:36:40 +00:00
function getSpaceForPollution ( entity )
if # entity.fluidbox < 1 then
return 0
end
local capacity = entity.fluidbox . get_capacity ( 1 )
local pollutionFluid = entity.fluidbox [ 1 ]
local pollution = 0
if pollutionFluid then
pollution = pollutionFluid.amount
end
return capacity - pollution
2019-10-04 17:20:34 +00:00
end
2019-10-06 17:50:50 +00:00
function inRadius ( filter , radius )
if filter.name == " air-filter-machine-1 " then
return radius <= 0
elseif filter.name == " air-filter-machine-2 " then
return radius <= 2
elseif filter.name == " air-filter-machine-3 " then
return radius <= 3
else
return false
end
end
-- #####################
-- # Update script #
-- #####################
function insertPollution ( event )
-- game.print("Filtered chunks:")
-- for _, c in pairs(global.air_filtered_chunks) do
-- game.print(c.x .. ", " .. c.y)
-- end
--
-- game.print("Chunkmap:")
-- for surface, chunkListX in pairs(global.air_filtered_chunks_map) do
-- game.print("Surface " .. surface)
-- for x, chunkListY in pairs(chunkListX) do
-- game.print("x: " .. x)
-- for y, chunk in pairs(chunkListY) do
-- game.print("y: " .. y)
-- end
-- end
-- end
game.print ( " insertPollution " )
for _ , c in pairs ( global.air_filtered_chunks ) do
absorbChunk ( c )
end
end
function absorbChunk ( chunk )
if chunk : get_pollution ( ) == 0 then
return
end
local totalAbsorption = 0.0
for _ , filter in pairs ( chunk.filters ) do
local absorptionRate = getAbsorptionRate ( filter ) * INTERVAL / 60
totalAbsorption = totalAbsorption + absorptionRate
end
if totalAbsorption == 0 then
return
end
local toAbsorb = math.min ( chunk : get_pollution ( ) , totalAbsorption )
-- game.print("To absorb: " .. toAbsorb)
local totalInsertedAmount = 0.0
for _ , filter in pairs ( chunk.filters ) do
local toInsert = ( ( getAbsorptionRate ( filter ) * INTERVAL / 60 ) / totalAbsorption ) * toAbsorb
if toInsert > 0 then
local insertedAmount = filter.insert_fluid ( { name = " pollution " , amount = toInsert } )
totalInsertedAmount = totalInsertedAmount + insertedAmount
end
end
-- game.print("Total inserted: " .. totalInsertedAmount)
assert ( math.abs ( toAbsorb - totalInsertedAmount ) < 0.01 , " Error with inserting pollution in air filter machine. Different amounts absorbed/inserted: " .. toAbsorb .. " absorbed and " .. totalInsertedAmount .. " inserted. " )
chunk : pollute ( - totalInsertedAmount )
end
function generateRadiusUpdateFunctions ( radius )
return { }
end
2019-10-06 12:36:40 +00:00
function generateFunctions ( )
local functions = { }
2019-10-06 17:50:50 +00:00
-- for i = 1, 110 do
-- table.insert(functions, function(event)
-- end)
-- end
--
-- table.insert(functions, init)
table.insert ( functions , insertPollution )
for radius = 1 , 4 do
for _ , f in pairs ( generateRadiusUpdateFunctions ( radius ) ) do
table.insert ( functions , f )
2019-10-06 12:36:40 +00:00
end
end
return functions
end
2019-10-06 17:50:50 +00:00
2019-10-06 12:36:40 +00:00
function spreadOverTicks ( functions , interval )
local tickMap = { }
local funcs = { }
for index , f in pairs ( functions ) do
-- amount of functions to be inserted in this tick update to fit them all in the remaining interval
local functionsPerTick = math.ceil ( ( # functions - index ) / ( interval - # tickMap - 1 ) )
table.insert ( funcs , f )
if # funcs >= functionsPerTick then
table.insert ( tickMap , funcs )
funcs = { }
end
end
if # funcs > 0 then
table.insert ( tickMap , funcs )
funcs = { }
end
local function onTick ( event )
local step = ( event.tick % interval ) + 1
2019-10-06 17:50:50 +00:00
-- game.print("Step: " .. step)
2019-10-06 12:36:40 +00:00
local funcs = tickMap [ step ]
if funcs ~= nil then
for _ , f in pairs ( funcs ) do
f ( event )
end
end
end
return onTick
end
2019-10-06 17:50:50 +00:00
function FilteredChunk ( surface , x , y )
local self = {
surface = surface ,
x = x ,
y = y ,
filters = { } ,
}
local function equal ( self , other )
return self.surface . name == other.surface . name and self.x == other.x and self.y == other.y
end
local function addToMap ( )
game.print ( " Adding chunk to map " )
local chunkListX = global.air_filtered_chunks_map [ surface.name ] or { }
local chunkListY = chunkListX [ x ] or { }
assert ( chunkListY [ y ] == nil , " Chunklist entry should not exist yet. " )
chunkListY [ y ] = self
chunkListX [ x ] = chunkListY
global.air_filtered_chunks_map [ surface.name ] = chunkListX
table.insert ( global.air_filtered_chunks , self )
end
local function removeFromMap ( )
game.print ( " Removing chunk from map " )
table.remove ( global.air_filtered_chunks_map [ surface.name ] [ x ] , y )
for i , c in pairs ( global.air_filtered_chunks ) do
if equal ( self , c ) then
table.remove ( global.air_filtered_chunks , i )
break
end
end
end
local function get_pollution ( self )
return self.surface . get_pollution ( self : toPosition ( ) )
end
local function pollute ( self , amount )
self.surface . pollute ( self : toPosition ( ) , amount )
end
local function toPosition ( self )
return { x = self.x * 32 , y = self.y * 32 }
end
local function addFilter ( self , filter )
table.insert ( self.filters , filter )
if # self.filters == 1 then
addToMap ( )
end
end
local function removeFilter ( self , filter )
for i , f in pairs ( self.filters ) do
if f.unit_number == filter.unit_number then
table.remove ( self.filters , i )
break
end
end
if # self.filters == 0 then
removeFromMap ( )
end
end
self.get_pollution = get_pollution
self.pollute = pollute
self.toPosition = toPosition
self.addFilter = addFilter
self.removeFilter = removeFilter
return self
end
function getFilteredChunk ( surface , x , y )
local chunkListX = global.air_filtered_chunks_map [ surface.name ]
if chunkListX ~= nil then
local chunkListY = chunkListX [ x ]
if chunkListY ~= nil then
local chunk = chunkListY [ y ]
if chunk ~= nil then
return chunk
end
end
end
return FilteredChunk ( surface , x , y )
end
--function groupByChunk(entities)
-- local chunks = {}
-- for _, e in pairs(entities) do
-- local chunk = positionToChunk(e.position)
-- local chunkListX = chunks[chunk.x] or {}
-- local chunkList = chunkListX[chunk.y] or {}
-- table.insert(chunkList, e)
-- chunkListX[chunk.y] = chunkList
-- chunks[chunk.x] = chunkListX
-- end
-- local pretty_chunks = {}
-- for chunkX, t in pairs(chunks) do
-- for chunkY, l in pairs(t) do
-- print(t)
-- table.insert(pretty_chunks, { chunk = { x = chunkX, y = chunkY }, entities = l })
-- end
-- end
-- return pretty_chunks
--end
-- #################
-- # callbacks #
-- #################
function isAirFilterMachine ( entity )
return starts_with ( entity.name , " air-filter-machine- " )
end
function OnEntityCreated ( event )
game.print ( " entity created " )
game.print ( event.created_entity . name )
if isAirFilterMachine ( event.created_entity ) then
game.print ( " air filter created " )
local chunkPos = positionToChunk ( event.created_entity . position )
local chunk = getFilteredChunk ( event.created_entity . surface , chunkPos.x , chunkPos.y )
chunk : addFilter ( event.created_entity )
game.print ( " Air filter added to chunk " )
end
end
function OnEntityRemoved ( event )
if isAirFilterMachine ( event.entity ) then
local chunkPos = positionToChunk ( event.entity . position )
local chunk = getFilteredChunk ( event.entity . surface , chunkPos.x , chunkPos.y )
chunk : removeFilter ( event.entity )
end
end
script.on_event ( { defines.events . on_built_entity , defines.events . on_robot_built_entity } , OnEntityCreated )
script.on_event ( { defines.events . on_pre_player_mined_item , defines.events . on_robot_pre_mined , defines.events . on_entity_died } , OnEntityRemoved )
function init ( )
game.print ( " Init " )
-- gather all filters on every surface
global.air_filtered_chunks_map = { }
global.air_filtered_chunks = { }
for _ , surface in pairs ( game.surfaces ) do
local filters = surface.find_entities_filtered {
name = { " air-filter-machine-1 " , " air-filter-machine-2 " , " air-filter-machine-3 " }
}
for _ , filter in pairs ( filters ) do
game.print ( # filters )
local chunkPos = positionToChunk ( filter.position )
local chunk = getFilteredChunk ( surface , chunkPos.x , chunkPos.y )
game.print ( " Chunk: " .. chunk.x .. " , " .. chunk.y )
chunk : addFilter ( filter )
end
end
end
script.on_init ( init )
script.on_configuration_changed ( init )
2019-10-06 12:36:40 +00:00
script.on_load ( function ( )
local functions = generateFunctions ( )
2019-10-06 17:50:50 +00:00
local onTick = spreadOverTicks ( functions , INTERVAL )
2019-10-06 12:36:40 +00:00
script.on_event ( defines.events . on_tick , onTick )
end )
2019-10-04 17:20:34 +00:00