AE2 Pattern Checker
gelatine87 |
PRO |
10/24/25 06:21:30 PM UTC (Edited) |
0 ⭐ |
10381 👁️ |
Never ⏰ |
[minecraft, computercraft, MC, AE2, CC:T]
--[[
================================================================================
PATTERN DETECTOR — ME Bridge Crafting Watcher (CC:Tweaked / ComputerCraft)
================================================================================
Overview:
Monitors Applied Energistics crafting CPUs via an ME Bridge peripheral and
checks whether any *watchlisted* items are currently being crafted. If so,
it flips a redstone output (e.g., a lamp) and shows a status UI in color
terminals. It also logs to a file if enabled.
Key Features:
• Reads a watchlist from "watchlist.txt" (auto-creates a default if missing).
• Polls ME Bridge CPUs to detect active jobs for watchlisted items.
• Toggles a redstone line (CONFIG.redstoneSide) when any watchlisted item is
being crafted.
• Color terminal UI:
- Header banner
- Status bar showing redstone state
- Watchlist line (truncates if too wide)
- Crafting status list (with item, quantity, and CPU ID)
• Optional logging to a file (CONFIG.logFile).
Requirements:
• CC:Tweaked / ComputerCraft environment with a color-capable terminal (for UI).
• A connected ME Bridge peripheral (from Advanced Peripherals) on any side.
• Redstone output wired on the configured side (CONFIG.redstoneSide).
Usage:
• Place a computer next to or wired to an ME Bridge peripheral.
• Adjust CONFIG values below if needed.
• Create "watchlist.txt" with one item id per line (e.g. minecraft:stone).
• Run the program; it will show the UI and control redstone automatically.
Notes:
• If "watchlist.txt" is missing or empty, a default file with a few items
is created on first run.
• Logging is appended to CONFIG.logFile when CONFIG.enableLogging is true.
• Pressing Enter switches to a simple input mode placeholder (returns immediately).
================================================================================
]]
-- Configuration for polling rate, I/O files, redstone side, etc.
local CONFIG = {
pollInterval = 1.0, -- seconds between CPU checks when idle
redstoneSide = "top", -- side used for redstone output (e.g., "top", "left")
watchlistFile = "watchlist.txt", -- file with one item id per line (e.g., minecraft:stone)
logFile = "patters.log", -- log file path (appends); note: name kept as provided
enableLogging = true, -- toggle logging on/off
}
-- Last snapshot of CPU states to compare changes between iterations
local lastCpuSnapshot = nil
-- Loaded from watchlist file; entries are { name, displayName }
local watchlist = {}
--------------------------------------------------------------------------------
-- Logging helpers
--------------------------------------------------------------------------------
-- Writes a line to the screen and (optionally) to the log file.
local function log(msg)
print(msg)
if CONFIG.enableLogging then
local file = fs.open(CONFIG.logFile, "a")
if file then
file.write(msg .. "\n")
file.close()
end
end
end
-- Writes a colored line to screen (if color is available) and to log file.
-- Falls back to plain print on non-color terminals.
local function logColored(msg, color)
if term.isColor() then
term.setTextColor(color)
print(msg)
term.setTextColor(colors.white)
else
print(msg)
end
if CONFIG.enableLogging then
local file = fs.open(CONFIG.logFile, "a")
if file then
file.write(msg .. "\n")
file.close()
end
end
end
--------------------------------------------------------------------------------
-- Drawing helpers (UI)
--------------------------------------------------------------------------------
-- Draws a horizontal line (full terminal width) at row y in the given color.
local function drawLine(y, color)
if term.isColor() then
term.setTextColor(color)
term.setCursorPos(1, y)
term.write(string.rep("=", term.getSize()))
term.setTextColor(colors.white)
end
end
-- Draws a simple bordered box with optional title.
-- (Currently unused in the main loop but handy for future UI expansion.)
local function drawBox(x, y, width, height, title, color)
if term.isColor() then
term.setTextColor(color)
-- Top border
term.setCursorPos(x, y)
term.write("+" .. string.rep("-", width - 2) .. "+")
-- Title row (if provided)
if title then
term.setCursorPos(x, y + 1)
term.write("| " .. title .. string.rep(" ", width - #title - 3) .. "|")
end
-- Middle rows (skip title’s body line if present)
for i = 2, height - 1 do
if not title or i > 1 then
term.setCursorPos(x, y + i)
term.write("|" .. string.rep(" ", width - 2) .. "|")
end
end
-- Bottom border
term.setCursorPos(x, y + height)
term.write("+" .. string.rep("-", width - 2) .. "+")
term.setTextColor(colors.white)
end
end
-- Clears the screen and prints the top header banner.
local function showHeader()
if term.isColor() then
term.clear()
local termWidth, _ = term.getSize()
local title = "PATTERN DETECTOR"
local titleX = math.floor((termWidth - #title) / 2) + 1
term.setCursorPos(1, 1)
term.setBackgroundColor(colors.blue)
term.setTextColor(colors.white)
term.write(string.rep(" ", termWidth))
term.setCursorPos(titleX, 1)
term.write(title)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
end
end
-- Shows a centered status bar indicating whether the redstone output is ON/OFF.
local function showStatusBar(redstoneOn)
if term.isColor() then
local termWidth, _ = term.getSize()
local statusOn = "Redstone Lamp: ON"
local statusOff = "Redstone Lamp: OFF"
local status = redstoneOn and statusOn or statusOff
local color = redstoneOn and colors.green or colors.gray
local barY = 5
term.setCursorPos(1, barY)
term.setBackgroundColor(colors.black)
term.write(string.rep(" ", termWidth))
-- Centered text
local barX = math.floor((termWidth - #status) / 2) + 1
term.setCursorPos(barX, barY)
term.setTextColor(color)
term.write(status)
term.setTextColor(colors.white)
end
end
-- Renders the "Watchlist:" line (names joined by ", ", truncated if needed).
-- Falls back to logging in non-color terminals.
local function showWatchlist()
if term.isColor() then
local y = 7
local termWidth, _ = term.getSize()
local label = "Watchlist:"
local names = {}
for i, item in ipairs(watchlist) do
table.insert(names, item.displayName)
end
local namesStr = (#watchlist == 0) and "(empty)" or table.concat(names, ", ")
-- Compose and maybe truncate to terminal width
local line = label .. " " .. namesStr
if #line > termWidth then
local maxNamesLen = termWidth - #label - 4
if maxNamesLen < 0 then
maxNamesLen = 0
end
namesStr = namesStr:sub(1, maxNamesLen) .. "..."
line = label .. " " .. namesStr
end
-- Clear line then draw colored label + content
term.setCursorPos(1, y)
term.setBackgroundColor(colors.black)
term.write(string.rep(" ", termWidth))
term.setCursorPos(1, y)
term.setTextColor(colors.cyan)
term.write(label)
term.setTextColor(#watchlist == 0 and colors.orange or colors.white)
term.write(" " .. namesStr)
term.setTextColor(colors.white)
else
-- Non-color: print to log
if #watchlist == 0 then
log("Watchlist: (empty)")
else
local names = {}
for i, item in ipairs(watchlist) do
table.insert(names, item.displayName)
end
log("Watchlist: " .. table.concat(names, ", "))
end
end
end
-- Displays the crafting status block.
-- Lists each watchlisted item currently being crafted with quantity and CPU id.
local function showCraftingStatus(craftingPatterns)
if term.isColor() then
local termWidth, _ = term.getSize()
local title = "CRAFTING STATUS"
local y = 9
-- Clear an area that can fit title + up to N lines of jobs
local linesNeeded = 1 + math.max(1, #craftingPatterns)
for i = 0, linesNeeded do
term.setCursorPos(1, y + i)
term.setBackgroundColor(colors.black)
term.write(string.rep(" ", termWidth))
end
-- Title centered
local titleX = math.floor((termWidth - #title) / 2) + 1
term.setCursorPos(titleX, y)
term.setTextColor(colors.cyan)
term.write(title)
term.setTextColor(colors.white)
-- Lines with content or a single "no items" message
if #craftingPatterns > 0 then
local craftingLines = {}
local maxLineLen = 0
for i, crafting in ipairs(craftingPatterns) do
local line = "+ "
.. crafting.displayName
.. " x"
.. crafting.quantity
.. " (CPU "
.. crafting.cpuId
.. ")"
if #line > termWidth then
line = line:sub(1, termWidth - 3) .. "..."
end
craftingLines[i] = line
if #line > maxLineLen then
maxLineLen = #line
end
end
local blockWidth = maxLineLen
local blockX = math.floor((termWidth - blockWidth) / 2) + 1
for i, line in ipairs(craftingLines) do
term.setCursorPos(blockX, y + i)
term.setTextColor(colors.green)
term.write(line)
term.setTextColor(colors.white)
end
else
local msg = "- No watchlisted items being crafted"
if #msg > termWidth then
msg = msg:sub(1, termWidth - 3) .. "..."
end
local msgX = math.floor((termWidth - #msg) / 2) + 1
term.setCursorPos(msgX, y + 1)
term.setTextColor(colors.orange)
term.write(msg)
term.setTextColor(colors.white)
end
else
-- Non-color fallback to log output
log("CRAFTING STATUS:")
if #craftingPatterns > 0 then
for _, crafting in ipairs(craftingPatterns) do
log(" " .. crafting.displayName .. " x" .. crafting.quantity .. " (CPU " .. crafting.cpuId .. ")")
end
else
log("- No watchlisted items being crafted")
end
end
end
--------------------------------------------------------------------------------
-- Data loading / input
--------------------------------------------------------------------------------
-- Loads the watchlist file. If missing/empty, writes a default file and returns it.
local function loadWatchlist()
local items = {}
if fs.exists(CONFIG.watchlistFile) then
local file = fs.open(CONFIG.watchlistFile, "r")
if file then
local line = file.readLine()
while line do
-- Trim whitespace
local trimmed = line:gsub("^%s*(.-)%s*$", "%1")
-- Skip empty lines and comments starting with '#'
if trimmed ~= "" and not trimmed:match("^#") then
table.insert(items, {
name = trimmed,
-- Tidy display: drop common namespaces
displayName = trimmed:gsub("minecraft:", ""):gsub("ftbmaterials:", ""),
})
end
line = file.readLine()
end
file.close()
end
end
-- Provide a default list if nothing loaded, and create the file for convenience
if #items == 0 then
items = {
{ name = "minecraft:stone", displayName = "Stone" },
{ name = "minecraft:iron_ingot", displayName = "Iron Ingot" },
}
local file = fs.open(CONFIG.watchlistFile, "w")
if file then
for _, item in ipairs(items) do
file.write(item.name .. "\n")
end
file.close()
end
end
return items
end
-- Placeholder for handling typed user input when in "control mode".
-- Currently normalizes input but does nothing and exits control mode immediately.
local function handleUserInput()
local input = read()
if not input then
return false
end
-- Normalize
input = input:lower():gsub("^%s*(.-)%s*$", "%1")
-- No commands implemented; return false to exit control mode.
end
--------------------------------------------------------------------------------
-- ME Bridge integration
--------------------------------------------------------------------------------
-- Finds and returns the first wrapped peripheral that looks like an ME Bridge
-- (i.e., has getCraftingCPUs). Tries "bottom" first, then other sides.
local function findMeBridge()
if peripheral.isPresent("bottom") then
local bridge = peripheral.wrap("bottom")
if bridge and bridge.getCraftingCPUs then
return bridge
end
end
-- Try other sides if not at "bottom"
for _, side in ipairs({ "left", "right", "front", "back", "top" }) do
if peripheral.isPresent(side) then
local bridge = peripheral.wrap(side)
if bridge and bridge.getCraftingCPUs then
return bridge
end
end
end
return nil
end
-- Takes a snapshot of crafting CPUs with minimal fields needed for display/compare.
local function takeCpuSnapshot(bridge)
local snapshot = {}
if bridge.getCraftingCPUs then
-- Use pcall to be resilient against peripheral/API errors
local success, cpus = pcall(bridge.getCraftingCPUs, bridge)
if success and cpus then
for i, cpu in ipairs(cpus) do
snapshot[i] = {
isBusy = cpu.isBusy, -- boolean
name = cpu.name, -- string
storage = cpu.storage, -- number or details (depends on API)
craftingJob = cpu.craftingJob, -- table with job info
}
end
end
end
return snapshot
end
-- Inspects current CPU snapshot and collects jobs matching the watchlist.
-- Returns a list of crafting entries for the UI/log: { pattern, cpuId, cpuName, quantity, displayName }
local function checkCraftingPatterns(bridge)
local craftingPatterns = {}
-- Capture the current CPU states
local currentCpuSnapshot = takeCpuSnapshot(bridge)
-- Quick check: if no CPUs are busy, we can skip deeper analysis
local anyBusyCPUs = false
for cpuId, cpu in pairs(currentCpuSnapshot) do
if cpu.isBusy then
anyBusyCPUs = true
end
end
if not anyBusyCPUs then
lastCpuSnapshot = currentCpuSnapshot
return craftingPatterns
end
-- If we have a previous snapshot, scan active jobs for watchlist matches
if lastCpuSnapshot then
for cpuId, currentCpu in pairs(currentCpuSnapshot) do
if currentCpu.isBusy and currentCpu.craftingJob then
local job = currentCpu.craftingJob
if job.resource and job.resource.name then
-- Exact id match against the watchlist entry names
for _, watchlistItem in ipairs(watchlist) do
if job.resource.name == watchlistItem.name then
table.insert(craftingPatterns, {
pattern = watchlistItem,
cpuId = cpuId,
cpuName = currentCpu.name,
quantity = job.quantity,
displayName = job.resource.displayName or job.resource.name,
})
break
end
end
end
end
end
end
-- Persist snapshot for next compare
lastCpuSnapshot = currentCpuSnapshot
return craftingPatterns
end
--------------------------------------------------------------------------------
-- Redstone control
--------------------------------------------------------------------------------
-- Sets the redstone output to ON/OFF on the configured side.
local function setRedstoneLamp(on)
if on then
redstone.setOutput(CONFIG.redstoneSide, true)
else
redstone.setOutput(CONFIG.redstoneSide, false)
end
end
--------------------------------------------------------------------------------
-- Main loop
--------------------------------------------------------------------------------
-- Initializes UI, loads watchlist, finds the ME Bridge, and starts the monitor loop.
local function runMonitor()
showHeader()
watchlist = loadWatchlist()
local bridge = findMeBridge()
if not bridge then
logColored("ERROR: No ME Bridge found!", colors.red)
return
end
local lastState = false -- last redstone state we set
local controlMode = false -- toggled when user presses Enter (char "\r")
while true do
if controlMode then
-- If we ever implement commands, this is where they'd be handled
if handleUserInput() then
sleep(0.1)
else
-- Exit control mode and redraw the header
controlMode = false
showHeader()
end
else
-- Event polling for simple key/timer handling in color terminals
if term.isColor() then
local timer = os.startTimer(2)
local event, param = os.pullEvent()
if event == "timer" and param == timer then
-- Timer triggered; continue to work loop
elseif event == "char" and param == "\r" then
-- Enter pressed: enter control mode
controlMode = true
end
end
-- Check for watchlisted crafting jobs
local craftingPatterns = checkCraftingPatterns(bridge)
-- Redstone is ON iff we have any matching jobs
local currentState = #craftingPatterns > 0
-- Update redstone output only when it changes
if currentState ~= lastState then
setRedstoneLamp(currentState)
lastState = currentState
end
-- Refresh UI sections
showStatusBar(currentState)
showWatchlist()
showCraftingStatus(craftingPatterns)
-- Throttle loop to the configured poll interval
sleep(CONFIG.pollInterval)
end
end
end
-- Entry point
runMonitor()
Comments
0 B
|0 👍
/0 👎
0 B
|👍
/👎