-- 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.")