Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- ================================================================================
- 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()
Advertisement
Add Comment
Please, Sign In to add comment