-- Input Signal Key: -- - red[some-item] - The number of that item in the logistic network, -- negative if there is unsatisfied demand -- - green[ore-type] - The amount of ore appearing on the resource scanner -- Output Signal Key: -- - out['red/signal-X'] - The X coordinate to survey -- - out['red/signal-Y'] - The Y coordinate to survey -- - out['red/signal-W'] - The width of the survey -- - out['red/signal-H'] - The height of the survey -- - out['construction-robot'] - The index of the blueprint to deploy -- - out['signal-X'] - The X coordinate to deploy the next blueprint -- - out['signal-Y'] - The Y coordinate to deploy the next blueprint -- - out['signal-check'] - 1 if now is a good time for construction and research, -- 0 otherwise -- (Note: The red wire goes to the resource scanner, the green wire goes to the -- blueprint deployer and also feeds back to the input) local MAX_MEGATILES = 484 local MAX_RESEARCH_TILES = 25 local PIN_OFFSET_X = -47 local PIN_OFFSET_Y = 10 local newSignal = 0 local tagSignal = nil -- Convert the input signals to local variables for readability local currently_constructed_megatiles = red['signal-info'] local currently_constructed_research_tiles = red['signal-dot'] local available_logistic_bots = red['signal-A'] local total_logistic_bots = red['signal-B'] local available_construction_bots = red['signal-C'] local total_construction_bots = red['signal-D'] local accumulator_charge = red['signal-E'] local lastSignal = green['signal-P'] local busy_construction_bots = total_construction_bots - available_construction_bots -- We wait less the larger the factory is, -- since overproduction in the late game is less catastrophic. local waiting_on_construction_bots = busy_construction_bots > (currently_constructed_megatiles / 25) + 1 local currently_in_logistic_shock = available_logistic_bots < (total_logistic_bots / 10) -- This will only happen in biter runs local currently_in_construction_shock = total_construction_bots < 300 -- This combined logic ensures that we keep building power megatiles even if -- the power situation has temporarily recovered var.currently_in_power_shock = accumulator_charge < 40 or (var.currently_in_power_shock and accumulator_charge < 50) var.need_power = (var.currently_in_power_shock or var.need_power) local can_build_tile = not waiting_on_construction_bots and not currently_in_logistic_shock and not currently_in_construction_shock out = {} -- Each item is in the first tier that does not contain any of its ingredients local ITEM_TIERS = { { {name = 'iron-plate', outSignal = 3}, {name = 'copper-plate', outSignal = 4}, {name = 'stone-brick', outSignal = 5} }, { {name = 'iron-gear-wheel', outSignal = 7}, {name = 'copper-cable', outSignal = 10}, {name = 'steel-plate', outSignal = 6}, {name = 'iron-stick', outSignal = 11}, {name = 'pipe', outSignal = 22}, {name = 'grenade', outSignal = 17}, {name = 'stone-wall', outSignal = 19}, {name = 'firearm-magazine', outSignal = 16}, {name = 'stone-furnace', outSignal = 111} }, { {name = 'empty-barrel', outSignal = 85}, {name = 'storage-tank', outSignal = 90}, {name = 'heat-pipe', outSignal = 100}, {name = 'rail', outSignal = 27}, {name = 'electronic-circuit', outSignal = 12}, {name = 'transport-belt', outSignal = 13}, {name = 'pipe-to-ground', outSignal = 73}, {name = 'engine-unit', outSignal = 24}, {name = 'heat-exchanger', outSignal = 101}, {name = 'steam-turbine', outSignal = 102}, {name = 'steel-chest', outSignal = 67}, {name = 'automation-science-pack', outSignal = 8}, {name = 'piercing-rounds-magazine', outSignal = 18}, {name = 'boiler', outSignal = 112} }, { {name = 'water-barrel', outSignal = 86}, {name = 'electric-mining-drill', outSignal = 91}, {name = 'repair-pack', outSignal = 93}, {name = 'oil-refinery', outSignal = 84}, {name = 'assembling-machine-1', outSignal = 58}, {name = 'small-lamp', outSignal = 74}, {name = 'constant-combinator', outSignal = 75}, {name = 'decider-combinator', outSignal = 76}, {name = 'arithmetic-combinator', outSignal = 107}, {name = 'radar', outSignal = 45}, {name = 'inserter', outSignal = 14}, {name = 'lab', outSignal = 77}, {name = 'chemical-plant', outSignal = 78}, {name = 'pump', outSignal = 89}, {name = 'solar-panel', outSignal = 46}, {name = 'logistic-science-pack', outSignal = 15}, {name = 'military-science-pack', outSignal = 20} }, { {name = 'concrete', outSignal = 79}, {name = 'fast-inserter', outSignal = 55}, {name = 'assembling-machine-2', outSignal = 59}, {name = 'plastic-bar', outSignal = 21}, {name = 'lubricant-barrel', outSignal = 34}, {name = 'solid-fuel', outSignal = 42}, {name = 'sulfur', outSignal = 25} }, { {name = 'hazard-concrete', outSignal = 80}, {name = 'refined-concrete', outSignal = 81}, {name = 'advanced-circuit', outSignal = 23}, {name = 'explosives', outSignal = 96}, {name = 'sulfuric-acid-barrel', outSignal = 32}, {name = 'chemical-science-pack', outSignal = 26}, {name = 'low-density-structure', outSignal = 31}, {name = 'electric-engine-unit', outSignal = 35}, {name = 'rocket-fuel', outSignal = 43} }, { {name = 'logistic-chest-active-provider', outSignal = 68}, {name = 'logistic-chest-passive-provider', outSignal = 69}, {name = 'logistic-chest-storage', outSignal = 70}, {name = 'logistic-chest-buffer', outSignal = 71}, {name = 'logistic-chest-requester', outSignal = 72}, {name = 'refined-hazard-concrete', outSignal = 82}, {name = 'nuclear-reactor', outSignal = 99}, {name = 'centrifuge', outSignal = 103}, {name = 'substation', outSignal = 57}, {name = 'roboport', outSignal = 66}, {name = 'stack-inserter', outSignal = 56}, {name = 'electric-furnace', outSignal = 29}, {name = 'explosive-cannon-shell', outSignal = 97}, {name = 'battery', outSignal = 33}, {name = 'processing-unit', outSignal = 36}, {name = 'productivity-module', outSignal = 28}, {name = 'speed-module', outSignal = 39}, {name = 'effectivity-module', outSignal = 61}, {name = 'artillery-turret', outSignal = 113} }, { {name = 'laser-turret', outSignal = 92}, {name = 'artillery-shell', outSignal = 98}, {name = 'assembling-machine-3', outSignal = 60}, {name = 'productivity-module-2', outSignal = 64}, {name = 'speed-module-2', outSignal = 62}, {name = 'rocket-silo', outSignal = 83}, {name = 'accumulator', outSignal = 40}, {name = 'rocket-control-unit', outSignal = 41}, {name = 'production-science-pack', outSignal = 30}, {name = 'flying-robot-frame', outSignal = 37} }, { {name = 'productivity-module-3', outSignal = 65}, {name = 'speed-module-3', outSignal = 63}, {name = 'logistic-robot', outSignal = 94}, {name = 'construction-robot', outSignal = 95}, {name = 'satellite', outSignal = 44}, {name = 'utility-science-pack', outSignal = 38} }, {{name = 'space-science-pack', outSignal = 47}} } function choose_item_from_tiers() for i = 1, #ITEM_TIERS do local current_tier = ITEM_TIERS[i] local most_needed_item = nil local check_again = false for j = 1, #current_tier do local currrent_item = current_tier[j] -- Check if this items demand is higher than the other ones -- in the tier that we've seen if red[currrent_item.name] < 0 and (most_needed_item == nil or red[currrent_item.name] < red[most_needed_item.name]) then -- If we just built this tile, don't choose it, but set -- check_again to true in case nothing else in the tier -- is in demand if currrent_item.outSignal == lastSignal then check_again = true else most_needed_item = currrent_item end end end -- If the only item in this tier that is in demand is the thing we -- built last tile, then fine, we'll build another. -- We do not want to start checking higher tiers, since they might -- depend on this item. if most_needed_item == nil and check_again then for j = 1, #current_tier do local currrent_item = current_tier[j] if red[currrent_item.name] < 0 and (most_needed_item == nil or red[currrent_item.name] < red[most_needed_item.name]) then most_needed_item = currrent_item end end end if most_needed_item ~= nil then out['signal-L'] = i return most_needed_item end end return nil end if not var.doneInit and red['blueprint-deployer'] > 0 then var.doneInit = true var.last_nuclear_megatile = 0 var.researchDeadline = math.huge var.need_artillery = false -- We have to track tilesBuilt as a variable because we can't trust the -- input from the outside world. If part of a tile has been placed down, -- but not the combinator that reports its existence, we could end up -- deploying two different blueprints on the same tile space. -- Instead we wait for the numbers from the circuit network to match -- the expected number from the variable before proceeeding. var.tilesBuilt = red['blueprint-deployer'] game.print('Initialized with ' .. var.tilesBuilt .. ' tiles built') for i = 1, #ITEM_TIERS do local current_tier = ITEM_TIERS[i] local tier_text = 'Tier ' .. i .. ': ' for j = 1, #current_tier do local currrent_item = current_tier[j] tier_text = tier_text .. ' [img=item.' .. currrent_item.name .. ']' end game.print(tier_text) end end if can_build_tile and currently_constructed_megatiles < MAX_MEGATILES then -- Megatile stuff if var.tilesBuilt % 8 == 0 and var.tilesBuilt / 8 >= currently_constructed_megatiles then if not var.is_surveying then var.is_surveying = true local n = currently_constructed_megatiles local x = -1 local y = 0 local steps = 0 local max_steps = 1 local turns_taken = 0 for i = 2, n, 1 do steps = steps + 1 if steps == max_steps then steps = 0 turns_taken = turns_taken + 1 end if steps == 0 and turns_taken % 2 == 0 then max_steps = max_steps + 1 end if turns_taken % 4 == 0 then x = x - 1 elseif turns_taken % 4 == 1 then y = y - 1 elseif turns_taken % 4 == 2 then x = x + 1 elseif turns_taken % 4 == 3 then y = y + 1 end end var.megablock_x = x * 48 + 2 var.megablock_y = y * 48 out['red/signal-X'] = var.megablock_x + 2 out['red/signal-Y'] = var.megablock_y out['red/signal-W'] = 30 out['red/signal-H'] = 30 out['green/construction-robot'] = 109 out['green/signal-X'] = var.megablock_x out['green/signal-Y'] = var.megablock_y out['green/signal-W'] = 50 out['green/signal-H'] = 50 game.print('Surveying megatile ' .. (currently_constructed_megatiles + 1)) delay = 60 else var.is_surveying = false -- If we're currently building something, keep building it. -- Unless it's blueprint 109, that's the tree-clearing blueprint -- applied during surveying if green['construction-robot'] > 0 and green['construction-robot'] ~= 109 then newSignal = green['construction-robot'] -- The first Megatile should be nuclear elseif currently_constructed_megatiles == 1 then newSignal = 106 var.tilesBuilt = var.tilesBuilt + 8 -- If the megatile has uranium, or has abundant resources, -- it must be mined (we'll use a blank patch and let the -- smaller surveys divy it up) elseif green['uranium-ore'] > 100000 or green['uranium-ore'] + green['iron-ore'] + green['copper-ore'] + green['stone'] + green['coal'] > 1000000 then game.print((currently_constructed_megatiles + 1) .. '[img=item.laser-turret] [img=item.roboport]') newSignal = 1 -- Only build power megatiles if we need power elseif var.need_power then -- - Don't build more than one nuclear plant in the first 9 megatiles -- - Don't build nuclear if we're low on fuel cells -- - Don't build nuclear if we don't have at least 3 reactors -- in the logistic network (unless we're using infinichests, -- then go ahead, whatever) -- - Don't build nuclear if we built it in the last 2 megatiles if currently_constructed_megatiles > 10 and red['uranium-fuel-cell'] > 90 and (red['nuclear-reactor'] > 3 or (red['nuclear-reactor'] > -10000 and green['nuclear-reactor'] == 0)) and currently_constructed_megatiles >= var.last_nuclear_megatile + 1 then game.print((currently_constructed_megatiles + 1) .. '[img=item.nuclear-reactor] [img=item.steam-turbine]') newSignal = 106 var.last_nuclear_megatile = currently_constructed_megatiles + 1 else game.print((currently_constructed_megatiles + 1) .. '[img=item.solar-panel] [img=item.accumulator]') newSignal = 2 end var.need_power = false var.tilesBuilt = var.tilesBuilt + 8 elseif currently_constructed_megatiles < 9 then newSignal = 2 var.tilesBuilt = var.tilesBuilt + 8 -- If we're good on power, but light on oil products, -- build an oil processing megatile elseif lastSignal ~= 110 and (red['petroleum-gas-barrel'] < 0 or red['light-oil-barrel'] < 0 or red['heavy-oil-barrel'] < 0) then game.print((currently_constructed_megatiles + 1) .. '[img=item.oil-refinery] [img=fluid.petroleum-gas] [img=fluid.light-oil] [img=fluid.heavy-oil]') newSignal = 110 var.tilesBuilt = var.tilesBuilt + 8 -- If we're good on power and oil, build a blank megatile else game.print((currently_constructed_megatiles + 1) .. '[img=item.laser-turret] [img=item.roboport]') newSignal = 1 end var.is_paving = true out['construction-robot'] = newSignal lastSignal = newSignal local n = currently_constructed_megatiles local x = -1 local y = 0 local steps = 0 local max_steps = 1 local turns_taken = 0 for i = 2, n, 1 do steps = steps + 1 if steps == max_steps then steps = 0 turns_taken = turns_taken + 1 end if steps == 0 and turns_taken % 2 == 0 then max_steps = max_steps + 1 end if turns_taken % 4 == 0 then x = x - 1 elseif turns_taken % 4 == 1 then y = y - 1 elseif turns_taken % 4 == 2 then x = x + 1 elseif turns_taken % 4 == 3 then y = y + 1 end end -- Once the factory starts getting big, we want to build an -- artillery turret after each time we round a corner. var.need_artillery = var.need_artillery or (currently_constructed_megatiles > 36 and steps == max_steps - 1) var.megablock_x = x * 48 + 2 var.megablock_y = y * 48 out['signal-X'] = var.megablock_x out['signal-Y'] = var.megablock_y delay = 60 end -- If this isn't a megatile, check the tile and see if there are -- resources before building anything. elseif not var.is_surveying then var.is_surveying = true local n = (var.tilesBuilt % 8) + 1 local x = -1 local y = 0 local steps = 0 local max_steps = 1 local turns_taken = 0 for i = 2, n, 1 do steps = steps + 1 if steps == max_steps then steps = 0 turns_taken = turns_taken + 1 end if steps == 0 and turns_taken % 2 == 0 then max_steps = max_steps + 1 end if turns_taken % 4 == 0 then x = x - 1 elseif turns_taken % 4 == 1 then y = y - 1 elseif turns_taken % 4 == 2 then x = x + 1 elseif turns_taken % 4 == 3 then y = y + 1 end end out['red/signal-X'] = var.megablock_x + x * 16 + 2 out['red/signal-Y'] = var.megablock_y + y * 16 out['red/signal-W'] = 10 out['red/signal-H'] = 10 game.print('Surveying tile ' .. (var.tilesBuilt + 1)) delay = 60 else if green['construction-robot'] > 0 then game.print('Redoing this for some reason...'); newSignal = green['construction-robot'] else if (green['uranium-ore'] > 100000 and red['uranium-ore'] < 100000) then tagSignal = {type = "item", name = "uranium-ore"} game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. '[img=item.uranium-ore]') newSignal = 54 elseif (green['iron-ore'] > 100000 and red['iron-ore'] < 250000) or (green['copper-ore'] > 100000 and red['copper-ore'] < 250000) or (green['stone'] > 100000 and red['stone'] < 200000) or (green['coal'] > 100000 and red['coal'] < 250000) then local oreType = nil local maxAvailable = math.max(green['iron-ore'], green['copper-ore'], green['stone'], green['coal']) if green['iron-ore'] == maxAvailable then oreType = 'iron-ore' elseif green['copper-ore'] == maxAvailable then oreType = 'copper-ore' elseif green['stone'] == maxAvailable then oreType = 'stone' elseif green['coal'] == maxAvailable then oreType = 'coal' end tagSignal = {type = "item", name = oreType} game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. '[img=item.' .. oreType .. ']') newSignal = 53 elseif var.need_artillery then var.need_artillery = false tagSignal = {type = "item", name = 'artillery-targeting-remote'} newSignal = 114 game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. '[img=item.artillery-targeting-remote]') else local most_needed_item = choose_item_from_tiers() if most_needed_item ~= nil then tagSignal = {type = "item", name = most_needed_item.name} newSignal = most_needed_item.outSignal game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. '[img=item.' .. most_needed_item.name .. ']') elseif currently_constructed_research_tiles < MAX_RESEARCH_TILES and game.tick > var.researchDeadline then tagSignal = {type = "virtual", name = "signal-dot"} game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. ' Research!') newSignal = 9 var.researchDeadline = math.huge -- This is to prevent situations where we're waiting to see -- if we can build more research but we're having a power crisis elseif var.currently_in_power_shock then game.print(currently_constructed_megatiles .. ' * 8 + ' .. (var.tilesBuilt % 8 + 1) .. ' = ' .. (var.tilesBuilt + 1) .. ' Small Power Station') newSignal = 108 -- If there's truly nothing we can build, start a timer and -- if that's still the case when it's done, we'll build research. -- This is to prevent new research tiles from sneaking in -- during a really short pre-demand-shock period. elseif currently_constructed_research_tiles < MAX_RESEARCH_TILES and var.researchDeadline == math.huge then game.print('Setting a research deadline in ' .. (5 * 60) .. ' seconds...') var.researchDeadline = game.tick + (5 * 60 * 60) end end end out['construction-robot'] = newSignal lastSignal = newSignal local n = (var.tilesBuilt % 8) + 1 local x = -1 local y = 0 local steps = 0 local max_steps = 1 local turns_taken = 0 for i = 2, n, 1 do steps = steps + 1 if steps == max_steps then steps = 0 turns_taken = turns_taken + 1 end if steps == 0 and turns_taken % 2 == 0 then max_steps = max_steps + 1 end if turns_taken % 4 == 0 then x = x - 1 elseif turns_taken % 4 == 1 then y = y - 1 elseif turns_taken % 4 == 2 then x = x + 1 elseif turns_taken % 4 == 3 then y = y + 1 end end local tileX = var.megablock_x + x * 16 local tileY = var.megablock_y + y * 16 out['signal-X'] = tileX out['signal-Y'] = tileY if tagSignal ~= nil then _api.game.get_player(1).force.add_chart_tag(_api.game.surfaces .nauvis, { position = {x = tileX + PIN_OFFSET_X, y = tileY + PIN_OFFSET_Y}, icon = tagSignal }) end if green['construction-robot'] == 0 and newSignal ~= 0 then var.tilesBuilt = var.tilesBuilt + 1 var.is_surveying = false if var.researchDeadline ~= math.huge then game.print('Cancelling research deadline, ' .. math.ceil((var.researchDeadline - game.tick) / 60) .. ' seconds were remaining') var.researchDeadline = math.huge end end delay = 120 end else var.is_surveying = false end out['signal-P'] = lastSignal out['signal-check'] = (not currently_in_logistic_shock) and 1 or 0