CCCPaste

AE2 Connected with MineCol

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