Connections = {} -- Connection types -- local type_map = {} -- Not using metatables, for..... reasons local c_unlocked = {} local c_color = {} local c_connect = {} local c_recheck = {} local c_direction = {} local c_rotate = {} local c_adjust = {} local c_tick = {} local c_destroy = {} local indicator_names = {} Connections.indicator_names = indicator_names local function register_connection_type(ctype, class) for _, etype in pairs(class.entity_types) do type_map[etype] = ctype end c_unlocked[ctype] = class.unlocked c_color[ctype] = class.color c_connect[ctype] = class.connect c_recheck[ctype] = class.recheck c_direction[ctype] = class.direction c_rotate[ctype] = class.rotate c_adjust[ctype] = class.adjust c_tick[ctype] = class.tick c_destroy[ctype] = class.destroy for _, name in pairs(class.indicator_settings) do indicator_names['factory-connection-indicator-' .. ctype .. '-' .. name] = ctype end end local function is_connectable(entity) return type_map[entity.type] or type_map[entity.name] end Connections.is_connectable = is_connectable -- Connection data structure -- local CYCLIC_BUFFER_SIZE = 600 local function init_data_structure() global.connections = global.connections or {} global.delayed_connection_checks = global.delayed_connection_checks or {} for i = 0, CYCLIC_BUFFER_SIZE - 1 do global.connections[i] = global.connections[i] or {} end end Connections.init_data_structure = init_data_structure local function add_connection_to_queue(conn) local current_pos = (math.floor(game.tick / CONNECTION_UPDATE_RATE) + 1) * CONNECTION_UPDATE_RATE % CYCLIC_BUFFER_SIZE table.insert(global.connections[current_pos], conn) end -- Connection settings -- local function get_connection_settings(factory, cid, ctype) factory.connection_settings[cid] = factory.connection_settings[cid] or {} factory.connection_settings[cid][ctype] = factory.connection_settings[cid][ctype] or {} return factory.connection_settings[cid][ctype] end Connections.get_connection_settings = get_connection_settings -- Connection indicators -- local function set_connection_indicator(factory, cid, ctype, setting, dir) local old_indicator = factory.connection_indicators[cid] if old_indicator and old_indicator.valid then old_indicator.destroy() end local cpos = factory.layout.connections[cid] local new_indicator = factory.inside_surface.create_entity{ name = 'factory-connection-indicator-' .. ctype .. '-' .. setting, direction = dir, force = factory.force, position = {x = factory.inside_x + cpos.inside_x + cpos.indicator_dx, y = factory.inside_y + cpos.inside_y + cpos.indicator_dy}, create_build_effect_smoke = false } new_indicator.destructible = false factory.connection_indicators[cid] = new_indicator end local function delete_connection_indicator(factory, cid, ctype) local old_indicator = factory.connection_indicators[cid] if old_indicator and old_indicator.valid then old_indicator.destroy() end end local function refresh_connection_indicator(conn) -- Used in update 5 if conn and conn._valid then local setting, dir = c_direction[conn._type](conn) set_connection_indicator(conn._factory, conn._id, conn._type, setting, dir) end end Connections.refresh_connection_indicator = refresh_connection_indicator -- Connection changes -- local function register_connection(factory, cid, ctype, conn, settings) conn._id = cid conn._type = ctype conn._factory = factory conn._settings = settings conn._valid = true factory.connections[cid] = conn if conn.do_tick_update then add_connection_to_queue(conn) end local setting, dir = c_direction[ctype](conn) set_connection_indicator(factory, cid, ctype, setting, dir) end local function init_connection(factory, cid, cpos) -- Only call this when factory.connections[cid] == nil! local outside_entities = factory.outside_surface.find_entities_filtered{ position = {cpos.outside_x + factory.outside_x, cpos.outside_y + factory.outside_y}, force = factory.force } if (outside_entities == nil or next(outside_entities) == nil) then return end local inside_entities = factory.inside_surface.find_entities_filtered{ position = {cpos.inside_x + factory.inside_x, cpos.inside_y + factory.inside_y}, force = factory.force } if (inside_entities == nil or next(inside_entities) == nil) then return end for _, outside_entity in pairs(outside_entities) do local oct = type_map[outside_entity.type] or type_map[outside_entity.name] if oct ~= nil then for _, inside_entity in pairs(inside_entities) do local ict = type_map[inside_entity.type] or type_map[inside_entity.name] if oct == ict then if c_unlocked[oct](factory.force) then local sound_1 = {path = 'entity-close/assembling-machine-3', position = inside_entity.position} local sound_2 = {path = 'entity-close/assembling-machine-3', position = outside_entity.position} local settings = get_connection_settings(factory, cid, oct) local conn = c_connect[oct](factory, cid, cpos, outside_entity, inside_entity, settings) if conn then factory.inside_surface.play_sound(sound_1) factory.outside_surface.play_sound(sound_2) register_connection(factory, cid, oct, conn, settings) return end else factory.inside_surface.create_entity{name = 'flying-text', position = inside_entity.position, text = {'research-required'}} factory.outside_surface.create_entity{name = 'flying-text', position = outside_entity.position, text = {'research-required'}} end end end end end end Connections.init_connection = init_connection local function destroy_connection(conn) if conn._valid then c_destroy[conn._type](conn) conn._valid = false -- _valid should be true iff conn._factory.connections[conn._id] == conn conn._factory.connections[conn._id] = nil -- Lua can handle this delete_connection_indicator(conn._factory, conn._id, conn._type) end end Connections.destroy_connection = destroy_connection local function in_area(x, y, area) return (x >= area.left_top.x and x <= area.right_bottom.x and y >= area.left_top.y and y <= area.right_bottom.y) end local function recheck_factory(factory, outside_area, inside_area) -- Areas are optional if (not factory.built) then return end for cid, cpos in pairs(factory.layout.connections) do if (outside_area == nil or in_area(cpos.outside_x+factory.outside_x, cpos.outside_y+factory.outside_y, outside_area)) and (inside_area == nil or in_area(cpos.inside_x+factory.inside_x, cpos.inside_y+factory.inside_y, inside_area)) then local conn = factory.connections[cid] if conn then if c_recheck[conn._type](conn) then -- Everything is fine else destroy_connection(conn) init_connection(factory, cid, cpos) end else init_connection(factory, cid, cpos) end end end end Connections.recheck_factory = recheck_factory -- During deconstruction events of an entity that is part of a connection, the entity is still valid and built, so recheck_factory would not destroy the connection involved. -- Delaying the recheck causes these connections to be properly deconstructed immediately, instead of having to wait until the connection ticks again. local function recheck_factory_delayed(factory, outside_area, inside_area) -- Note that connections should still be designed such that absolutely nothing would break even if this function was empty! global.delayed_connection_checks[1+#(global.delayed_connection_checks)] = { factory = factory, outside_area = outside_area, inside_area = inside_area } end Connections.recheck_factory_delayed = recheck_factory_delayed local function disconnect_factory(factory) for cid, conn in pairs(factory.connections) do destroy_connection(conn) end end Connections.disconnect_factory = disconnect_factory -- Connection effects -- local function update() -- First let's run all them delayed connection checks for _, check in ipairs(global.delayed_connection_checks) do recheck_factory(check.factory, check.outside_area, check.inside_area) end global.delayed_connection_checks = {} local current_pos = game.tick % CYCLIC_BUFFER_SIZE local connections = global.connections local current_slot = connections[current_pos] connections[current_pos] = {} for _, conn in pairs(current_slot) do local delay = (conn._valid and c_tick[conn._type](conn)) if delay then -- Reinsert connection after delay -- Not checking for inappropriate delays, so keep your delays civil local queue_pos = (current_pos + delay) % CYCLIC_BUFFER_SIZE local new_slot = connections[queue_pos] new_slot[1 + #new_slot] = conn elseif conn._valid then destroy_connection(conn) init_connection(conn._factory, conn._id, conn._factory.layout.connections[conn._id]) end end end Connections.update = update local function rotate(factory, indicator) for cid, ind2 in pairs(factory.connection_indicators) do if ind2 and ind2.valid then if (ind2.unit_number == indicator.unit_number) then local conn = factory.connections[cid] local text = c_rotate[conn._type](conn) factory.inside_surface.create_entity{name='flying-text', position=indicator.position, color=c_color[conn._type], text=text} local setting, dir = c_direction[conn._type](conn) set_connection_indicator(factory, cid, conn._type, setting, dir) return end end end end Connections.rotate = rotate local function adjust(factory, indicator, positive) for cid, ind2 in pairs(factory.connection_indicators) do if ind2 and ind2.valid then if (ind2.unit_number == indicator.unit_number) then local conn = factory.connections[cid] local text = c_adjust[conn._type](conn, positive) factory.inside_surface.create_entity{name='flying-text', position=indicator.position, color=c_color[conn._type], text=text} local setting, dir = c_direction[conn._type](conn) set_connection_indicator(factory, cid, conn._type, setting, dir) return end end end end Connections.adjust = adjust local beeps = {'Beep', 'Boop', 'Beep', 'Boop', 'Beeple'} Connections.beep = function() local t = game.tick return beeps[t % 5 + 1] end register_connection_type('belt', require('belt')) register_connection_type('chest', require('chest')) register_connection_type('fluid', require('fluid')) register_connection_type('circuit', require('circuit')) register_connection_type('heat', require('heat'))