-- AE2Warehouse.lua
-- Original Author: Chuck Burgess (Refined Storage)
-- Authors: Wian Grobler / Louw Erasmus (Applied Energistics 2 Integration)
-- Updated: 2025-10-31
-- required in game:
-- Advanced Peripherals mod (for Colony Integrator and ME Bridge)
-- Applied Energistics 2 mod
-- ME Bridge must be connected to an AE2 network with storage and crafting capabilities
-- ME Bridge must be connected to a ComputerCraft computer
-- Colony Integrator must be connected to the same computer
local logFile = "AE2Warehouse.log"
local time_between_runs = 30
-- Initialize Monitor
-- see: https://www.computercraft.info/wiki/Advanced_Monitor
local monitor = peripheral.find("monitor")
if not monitor then error("Monitor not found.") end
monitor.setTextScale(0.5)
monitor.clear()
monitor.setCursorPos(1, 1)
monitor.setCursorBlink(false)
print("Monitor initialized.")
-- Initialize ME Bridge (for Applied Energistics 2)
-- see: https://docs.advanced-peripherals.de/peripherals/me_bridge/
local bridge = peripheral.find("meBridge")
if not bridge then error("ME Bridge not found.") end
print("ME Bridge initialized.")
-- Initialize Colony Integrator
-- see: https://docs.advanced-peripherals.de/peripherals/colony_integrator/
local colony = peripheral.find("colonyIntegrator")
if not colony then error("Colony Integrator not found.") end
if not colony.isInColony() then error("Colony Integrator is not in a colony.") end
print("Colony Integrator initialized.")
-- Establish the direction to transport the items into the Warehouse based on
-- where the entanglement block is sitting
local storage = peripheral.find("entangled:tile")
if not storage then error("Warehouse storage not found.") end
local direction = ""
local names = peripheral.getNames()
for _, pos in ipairs(names) do
if peripheral.getType(pos) == "entangled:tile" then
-- Determine the direction (top, bottom, left, right, front, back)
-- You may need to adjust this based on your setup
direction = pos
break
end
end
print("Warehouse storage initialized at: " .. direction)
----------------------------------------------------------------------------
-- FUNCTIONS
----------------------------------------------------------------------------
--[[
Check if a table is empty
@param self table - The table to check
@return boolean - True if empty, false otherwise
]]
function table.empty(self)
for _ in pairs(self) do
return false
end
return true
end
--[[
Write data to log file
@param file handle - File handle to write to
@param data table - Data to serialize and write
@param blockTop string - Header text
@param blockBottom string - Footer text
@return void
]]
function writeToLog(file, data, blockTop, blockBottom)
file:write("\n")
file:write(blockTop)
file:write("\n")
file:write(textutils.serialize(data, { allow_repetitions = true }))
file:write("\n")
file:write(blockBottom)
file:write("\n")
end
--[[
Filter out equipment/tool requests that shouldn't be auto-fulfilled
@param request table - The work request to check
@return boolean - True if should be processed, false if should be skipped
]]
function processWorkRequestItem(request)
-- Skip tools, armor, and weapons
local skipCategories = {
"tool", "sword", "axe", "pickaxe", "shovel", "hoe",
"helmet", "chestplate", "leggings", "boots",
"bow", "crossbow", "shield", "shears", "flint_and_steel"
}
if not request.item or not request.item.name then
return false
end
local itemName = request.item.name:lower()
for _, category in ipairs(skipCategories) do
if itemName:find(category) then
return false
end
end
return true
end
--[[
Print justified text on monitor
@param mon peripheral - Monitor peripheral
@param y number - Y coordinate (row)
@param pos string - Position: "left", "center", or "right"
@param text string - Text to display
@param textcolor number - Color code
@return void
]]
function mPrintRowJustified(mon, y, pos, text, textcolor)
local w, h = mon.getSize()
local bg = mon.getBackgroundColor()
mon.setTextColor(textcolor)
mon.setBackgroundColor(bg)
if pos == "center" then
local x = math.floor((w - #text) / 2) + 1
mon.setCursorPos(x, y)
mon.write(text)
elseif pos == "right" then
local x = w - #text + 1
mon.setCursorPos(x, y)
mon.write(text)
else -- left
mon.setCursorPos(1, y)
mon.write(text)
end
end
--[[
Display timer and day/night cycle on monitor
@param mon peripheral - Monitor peripheral
@param t number - Current time
@return void
]]
function displayTimer(mon, t)
local w, h = mon.getSize()
local now = textutils.formatTime(t, false)
local cycle = "Day"
local cycle_color = colors.yellow
local timer_color = colors.white
if t >= 18.0 or t < 6.0 then
cycle = "Night"
cycle_color = colors.blue
end
mPrintRowJustified(mon, h, "left", " " .. cycle, cycle_color)
mPrintRowJustified(mon, h, "right", now .. " ", timer_color)
end
--[[
Parse colonist data from request target string
@param colonist string - Colonist target string (e.g., "Builder John Doe")
@return table - Parsed colonist data
]]
function createColonistData(colonist)
local title_words = {}
local words_in_name = 0
local colonist_job = "Unknown"
local colonist_name = colonist or "Unknown"
if colonist and colonist ~= "" then
local word_count = 0
for word in colonist:gmatch("%S+") do
word_count = word_count + 1
table.insert(title_words, word)
end
if word_count >= 2 then
colonist_job = title_words[1]
table.remove(title_words, 1)
colonist_name = table.concat(title_words, " ")
words_in_name = word_count - 1
elseif word_count == 1 then
colonist_job = title_words[1]
colonist_name = title_words[1]
end
end
return {
fullName = colonist,
titleWords = title_words,
job = colonist_job,
name = colonist_name,
wordsInName = words_in_name
}
end
--[[
Get all work requests from colony and log them
@param colony peripheral - Colony integrator peripheral
@return table - List of processed work requests
]]
function getWorkRequestList(colony)
local requestList = {}
local workRequests = colony.getRequests()
local file = fs.open(logFile, "w")
for w in pairs(workRequests) do
writeToLog(file, workRequests[w], "--- Request start ---", "--- Request end ---")
local name = workRequests[w].name or "Unknown"
local colonist = createColonistData(workRequests[w].target)
local desc = workRequests[w].desc or ""
local item = {}
local items = workRequests[w].items
if items and not table.empty(items) then
item.name = items[1].name
if items[1].nbt then
item.nbt = items[1].nbt
end
end
local needed = workRequests[w].count or 0
local newRecord = {
name = name,
desc = desc,
needed = needed,
item = item,
colonist = colonist
}
table.insert(requestList, newRecord)
writeToLog(file, newRecord, "--- Record start ---", "--- Record end ---")
end
file:close()
return requestList
end
--[[
Display list of requests on monitor
@param mon peripheral - Monitor peripheral
@param listName string - Title of the list
@param itemList table - List of items to display
@param startRow number - Starting row position
@return number - Next available row
]]
function displayList(mon, listName, itemList, startRow)
local row = startRow
mPrintRowJustified(mon, row, "center", listName, colors.white)
row = row + 1
if table.empty(itemList) then
mPrintRowJustified(mon, row, "center", "(none)", colors.gray)
row = row + 1
else
for e in pairs(itemList) do
local record = itemList[e]
local text = string.format("%d/%d %s", record.provided or 0, record.needed, record.name)
mPrintRowJustified(mon, row, "left", text, record.color)
mPrintRowJustified(mon, row, "right", record.colonist.name .. " ", record.color)
row = row + 1
end
end
row = row + 1
return row
end
--[[
Scan and process work requests using AE2 ME Bridge
@param colony peripheral - Colony integrator
@param bridge peripheral - ME Bridge
@param mon peripheral - Monitor
@param warehouse_direction string - Direction to export items
@return void
]]
function scanWorkRequests(colony, bridge, mon, warehouse_direction)
local builder_list = {}
local nonbuilder_list = {}
local equipment_list = {}
local requestList = getWorkRequestList(colony)
print(string.format("Processing %d work requests...", #requestList))
for i, request in ipairs(requestList) do
local color = colors.red
local provided = 0
-- Skip equipment/tool requests
if not processWorkRequestItem(request) then
request.color = colors.gray
request.provided = 0
table.insert(equipment_list, request)
print(string.format("Skipped: %s (equipment)", request.name))
else
-- Prepare item detail for AE2
local itemDetail = {
name = request.item.name,
count = request.needed
}
if request.item.nbt then
itemDetail.nbt = request.item.nbt
end
-- Check if item exists in AE2 system
local success, itemsInSystem = pcall(function() return bridge.getItem(itemDetail) end)
if success and itemsInSystem and itemsInSystem.amount >= request.needed then
-- Sufficient items available - export them
local exportSuccess, exported = pcall(function()
return bridge.exportItem(itemDetail, warehouse_direction)
end)
if exportSuccess and exported and exported > 0 then
provided = exported
color = colors.green
print(string.format("Exported: %d %s", exported, request.name))
else
color = colors.red
print(string.format("Failed to export: %s", request.name))
end
elseif success and itemsInSystem and itemsInSystem.amount > 0 then
-- Partial amount - export what's available and craft the rest
local partialItem = {
name = request.item.name,
count = itemsInSystem.amount
}
if request.item.nbt then
partialItem.nbt = request.item.nbt
end
local exportSuccess, exportResult = pcall(function()
return bridge.exportItem(partialItem, warehouse_direction)
end)
if exportSuccess and exportResult then
provided = exportResult
print(string.format("Partial export: %d/%d %s", provided, request.needed, request.name))
end
-- Try to craft remaining
local craftItem = {
name = request.item.name,
count = request.needed - provided
}
if request.item.nbt then
craftItem.nbt = request.item.nbt
end
local craftingSuccess, isCrafting = pcall(function()
return bridge.isItemCrafting(craftItem)
end)
if craftingSuccess and not isCrafting then
pcall(function() bridge.craftItem(craftItem) end)
print(string.format("Started crafting: %d %s", craftItem.count, request.name))
end
color = colors.yellow
else
-- Item not available - try to craft
local craftingSuccess, isCrafting = pcall(function()
return bridge.isItemCrafting(itemDetail)
end)
if craftingSuccess and not isCrafting then
local craftSuccess = pcall(function() bridge.craftItem(itemDetail) end)
if craftSuccess then
print(string.format("Started crafting: %d %s", request.needed, request.name))
color = colors.yellow
else
print(string.format("Cannot craft: %s (no pattern or resources)", request.name))
color = colors.red
end
else
print(string.format("Already crafting: %s", request.name))
color = colors.yellow
end
end
request.color = color
request.provided = provided
-- Categorize by job
if request.colonist.job == "Builder" then
table.insert(builder_list, request)
else
table.insert(nonbuilder_list, request)
end
end
end
-- Display on monitor
monitor.clear()
monitor.setCursorPos(1, 1)
local row = 1
row = displayList(mon, "=== BUILDERS ===", builder_list, row)
row = displayList(mon, "=== NON-BUILDERS ===", nonbuilder_list, row)
row = displayList(mon, "=== EQUIPMENT (Skipped) ===", equipment_list, row)
displayTimer(mon, os.time())
print(string.format("Scan complete: %d builders, %d non-builders, %d equipment",
#builder_list, #nonbuilder_list, #equipment_list))
end
----------------------------------------------------------------------------
-- MAIN LOOP
----------------------------------------------------------------------------
print("Starting AE2 Warehouse automation...")
print("Press Ctrl+T to terminate")
local timer_id = os.startTimer(time_between_runs)
while true do
local event, param1 = os.pullEvent()
if event == "timer" and param1 == timer_id then
local currentTime = os.time()
-- Only process during day (6:00 AM to 6:00 PM)
if currentTime >= 6.0 and currentTime < 18.0 then
print("=== Starting scan at " .. textutils.formatTime(currentTime, false) .. " ===")
local success, err = pcall(function()
scanWorkRequests(colony, bridge, monitor, direction)
end)
if not success then
print("ERROR: " .. tostring(err))
monitor.clear()
monitor.setCursorPos(1, 1)
mPrintRowJustified(monitor, 1, "center", "=== ERROR ===", colors.red)
mPrintRowJustified(monitor, 2, "center", "Check computer console", colors.red)
displayTimer(monitor, currentTime)
end
else
monitor.clear()
monitor.setCursorPos(1, 1)
mPrintRowJustified(monitor, 1, "center", "=== NIGHT TIME ===", colors.blue)
mPrintRowJustified(monitor, 2, "center", "Requests paused until morning", colors.gray)
displayTimer(monitor, currentTime)
print("Night time - paused")
end
timer_id = os.startTimer(time_between_runs)
elseif event == "monitor_touch" then
-- Manual refresh on touch
print("=== Manual refresh triggered ===")
local success, err = pcall(function()
scanWorkRequests(colony, bridge, monitor, direction)
end)
if not success then
print("ERROR: " .. tostring(err))
end
timer_id = os.startTimer(time_between_runs)
elseif event == "terminate" then
print("Terminating program...")
monitor.clear()
monitor.setCursorPos(1, 1)
mPrintRowJustified(monitor, 1, "center", "=== TERMINATED ===", colors.red)
break
end
end
print("AE2 Warehouse automation stopped.")