I made this a few months ago for our base (with Jon, Cran, Wiz, etc.) -- it lets you turn things in the base on/off with a GUI by right clicking the element on the screen. You can edit the layout in-game on the monitor. Setup information: You need to change the settings in npanel_conf.lua to point to a bundled cable. The control panel emits a pulse to toggle an external latch. I did this because latches retain their state immediately on chunk load. Put a MiscPeripherals note block adjacent to have it play a sound. You must put a lever or some redstone current to a side of the CC computer to have the screen turn on. Run npanel.lua. [h2]npanel.conf[/h2] Code: --[[ Node Panel Display by sk89q ]]-- dofile("npanel_conf.lua") dofile("serpent.lua") dofile("util.lua") local function validateColor(allowNone) return function(input) if allowNone and input == "" then return nil end if not colors[input] then return "Invalid color! Examples: red, blue, black, lightBlue, etc." end end end local Block = prototype({ __construct = function(self, x, y, text, color) self.x = x self.y = y self.text = text self.color = color or colors.lightBlue self.conn = {} self.cable = nil self.state = nil self:setText(text) end, setText = function(self, text) self.text = text -- calculate width of the block self._width = #self.text + 1 if self._width % 2 ~= 0 then self._width = self._width + 1 end end, onClick = function(self) if self.cable then -- bundled cable -- toggle the latch (external to the system) self.state = not self.state redstone.setBundledOutput(bundledSide, self.cable) os.sleep(0.2) redstone.setBundledOutput(bundledSide, 0) return "Toggled " .. self.text .. " to " .. (self.state and "ON" or "OFF") else return false end end, contains = function(self, x, y) return x >= -self._width / 2 + self.x and x <= self._width / 2 + self.x and y >= self.y - 1 and y <= self.y + 1 end, connect = function(self, to, color) local color = color or colors.white table.insert(self.conn, {to = to, color = color}) table.insert(to.conn, {to = self, color = color}) end, disconnect = function(self, to) for k, other in ipairs(self.conn) do if other.to == to then -- remove own connection table.remove(self.conn, k) -- remove the other's reference too for k, other in ipairs(to.conn) do if other.to == self then table.remove(to.conn, k) break end end return -- exit end end end, disconnectAll = function(self) for _, other in ipairs(self.conn) do -- remove the other's reference for k, o in ipairs(other.to.conn) do if o.to == self then table.remove(other.to.conn, k) break end end end self.conn = {} end, isConnected = function(self, to) for k, other in ipairs(self.conn) do if other.to == to then return true end end return false end, setCable = function(self, color) self.cable = color -- set the state field if only we have a color set if color then self.state = self.state or false else self.state = nil end return self end, apply = function(self, object) prototyptify(self, object) end, draw = function(self) term.setBackgroundColor(self._selected and colors.blue or self.color) term.setTextColor(colors.white) drawLine(-self._width / 2 + self.x + 1, self.y - 1, self._width / 2 + self.x - 1, self.y - 1, "-") drawLine(-self._width / 2 + self.x + 1, self.y + 1, self._width / 2 + self.x - 1, self.y + 1, "-") drawPixel(-self._width / 2 + self.x, self.y - 1, "+") drawPixel(self._width / 2 + self.x, self.y - 1, "+") drawPixel(-self._width / 2 + self.x, self.y + 1, "+") drawPixel(self._width / 2 + self.x, self.y + 1, "+") drawPixel(-self._width / 2 + self.x, self.y, "|") drawPixel(self._width / 2 + self.x, self.y, "|") term.setCursorPos(-self._width / 2 + self.x + 1, self.y) term.write(self.text) for i = 0, self._width - #self.text - 2 do term.write(" ") end -- draw state if self.state ~= nil then term.setCursorPos(-self._width / 2 + self.x + 1, self.y - 1) if self.state then term.setBackgroundColor(colors.green) term.setTextColor(colors.yellow) term.write("ON") else term.setBackgroundColor(colors.red) term.setTextColor(colors.yellow) term.write("OFF") end end end }) local Diagram = prototype({ __construct = function(self) self.blocks = {} self.seen = {} end, add = function(self, block) -- only add if we haven't seen this block if not self.seen[block] then self.seen[block] = true table.insert(self.blocks, block) for _, c in ipairs(block.conn) do self:add(c.to) -- add this block too end end end, remove = function(self, block) self.seen[block] = nil -- remove from self.blocks for k, v in ipairs(self.blocks) do if v == block then table.remove(self.blocks, k) return end end end, save = function(self, path) local data = serpent.dump(self.blocks, { valtypeignore = {["function"] = true}, compact = true }) -- save to file local f = fs.open(path, "w") f.write(data) f.close() end, load = function(self, path) -- save to file local f = fs.open(path, "r") if f then local data = f.readAll() f.close() self.blocks = loadstring(data)() -- need to re-initialize for _, block in ipairs(self.blocks) do Block:apply(block) end else -- no data to load then self.blocks = {} end end, intersect = function(self, x, y, seen, block) for _, block in ipairs(self.blocks) do if block:contains(x, y) then return block end end end }) local BasePane = prototype({ titleColor = colors.cyan, __construct = function(self, app) self.app = app end, readInput = function(self, name, validate) local input self:drawHint("Please type the input above") while true do self:drawStatus(name) redstone.setOutput(enableSide, true) input = trim(read()) redstone.setOutput(enableSide, false) if type(validate) == 'function' then local msg = validate(input) if msg then -- uh oh, error! self:drawHint(msg) else break -- ok end elseif validate then -- just verify non-blank if msg == "" then self:drawHint("Non-empty string required") else break -- ok end else break -- no validation needed end end -- ok! self:drawHint("") return input end, drawStatus = function(self, status) local w, h = term.getSize() drawLine(1, h - 1, w, h - 1, " ", colors.black) drawText(2, h - 1, status, colors.black, colors.yellow) end, drawHint = function(self, status) local w, h = term.getSize() drawLine(1, h, w, h, " ", self.titleColor) drawText(2, h, status, self.titleColor, colors.white) end, drawTitle = function(self, title) drawText(2, 1, " " .. title .. " ", self.titleColor, colors.white) end, drawDiagram = function(self) self:drawConnections() self:drawBlocks() end, drawBlocks = function(self) for _, block in ipairs(self.app.diagram.blocks) do block:draw() end end, drawConnection = function(self, x1, y1, x2, y2, color) drawLine(x1, y1, x2, y2, x1 == x2 and "|" or "-", color, colors.black) end, drawConnections = function(self) for _, block in ipairs(self.app.diagram.blocks) do for k, v in ipairs(block.conn) do self:drawConnection(block.x, block.y, v.to.x, v.to.y, v.color) end end end, draw = function(self) -- do nothing end, onPress = function(self, key) -- do nothing end, onClick = function(self, button, x, y) -- do nothing end, onDrag = function(self, button, x, y) -- do nothing end, }) local Display = prototype(BasePane, { draw = function(self) self.app:drawAll(function() local w, h = term.getSize() term.setBackgroundColor(colors.black) term.clear() self:drawTitle("SYSTEM OVERVIEW") self:drawStatus("Touch elements to toggle them off and on") self:drawHint("[E]dit Mode") self:drawDiagram() end) end, onPress = function(self, key) if key:lower() == "e" then self.app:show(self.app.editor) end end, onClick = function(self, button, x, y) local clicked = self.app.diagram:intersect(x, y) if clicked then local status = clicked:onClick() if status then self.app:drawAll(function() clicked:draw() self:drawStatus(status) self.app:save() -- better save state end) self.app.notebox.playNote(3, 24) end end end, }) local Editor = prototype(BasePane, { titleColor = colors.red, selected = nil, lastSelected = nil, draw = function(self) self.app:drawAll(function() local w, h = term.getSize() term.setBackgroundColor(colors.black) term.clear() self:drawTitle("BLOCK EDITOR") self:drawStatusBar() self:drawDiagram() end) end, drawStatusBar = function(self) if self.selected then self:drawHint("[I]nsert [D]elete [M]odify [C]onnect [S]ave") else self:drawHint("[I]nsert [S]ave") end end, select = function(self, block) -- swap selection local last = nil if self.selected then self.selected._selected = false last = self.selected end self.selected = block if block then block._selected = true end self.lastSelected = last return last end, onClick = function(self, button, x, y) local clicked = self.app.diagram:intersect(x, y) if clicked then local last = self:select(clicked) -- redraw self.app:drawAll(function() clicked:draw() if last then last:draw() -- unselected the last one, so redraw end self:drawStatus(clicked.text .. " selected" .. (clicked.cable and (" [color: " .. getColor(clicked.cable) .. "]") or "")) self:drawStatusBar() end) end end, onDrag = function(self, button, x, y) if self.selected then if x ~= self.selected.x or y ~= self.selected.y then self.selected.x = x self.selected.y = y self:draw() end end end, onPress = function(self, key) local lkey = key:lower() -- save/quit if lkey == "s" then self:select(nil) self.app:show(self.app.display) self.app:save() -- insert elseif lkey == "i" then local x, y = 2, 2 local name = self:readInput("Name: ", true) -- just non-blank local block = Block(x, y, name) self.app.diagram:add(block) self:select(block) self:draw() -- redraw all -- connect elseif lkey == "c" and self.selected then -- only if selected if self.lastSelected == self.selected then self:drawHint("Can't connect to itself, silly") elseif self.lastSelected then if self.selected:isConnected(self.lastSelected) then -- disconnect self.selected:disconnect(self.lastSelected, color) else -- connect local color = colors[self:readInput("Color: ", validateColor())] self.selected:connect(self.lastSelected, color) end self:draw() -- redraw all else self:drawHint("Select two blocks in sequence first") end -- delete elseif lkey == "d" and self.selected then -- only if selected local confirm = self:readInput("Are you sure? [y/n] ") if confirm:lower() == "y" then self.selected:disconnectAll() self.app.diagram:remove(self.selected) self:select(nil) end self:draw() -- modify elseif lkey == "m" and self.selected then -- only if selected -- set name local name = self:readInput("Name [blank for no change]: ") if name ~= "" then self.selected:setText(name) end -- bundled cable local color = colors[self:readInput( "Bundled cable [blank for none] color: ", validateColor(true))] -- allow none self.selected:setCable(color) self:draw() -- redraw all end end, }) local Application = prototype({ __construct = function(self) self.diagram = Diagram() self.notebox = peripheral.wrap(noteboxSide) self.terminals = { peripheral.wrap("top"), term.native } self:load() self.display = Display(self) self.editor = Editor(self) end, load = function(self) self.diagram:load(diagramFile) end, save = function(self) self.diagram:save(diagramFile) end, drawExternal = function(self, func) for _, m in ipairs(self.terminals) do if m ~= term.native then -- ignore native term.redirect(m) func() end end term.redirect(term.native) end, drawAll = function(self, func) local enabled = self:isEnabled() -- only draw if enabled for _, m in ipairs(self.terminals) do if m == term.native or enabled then term.redirect(m) func() end end term.redirect(term.native) end, show = function(self, view) self.view = view self.view:draw() end, isEnabled = function(self) return redstone.getInput(enableSide) end, processEvents = function(self) local active = true local wasOn = self:isEnabled() self:show(self.display) -- clear on start if needed if not wasOn then self:drawExternal(function() term.setBackgroundColor(colors.black) term.clear() end) end while active do local id, p1, p2, p3 = os.pullEvent() local on = self:isEnabled() if id == "redstone" then if wasOn ~= on then wasOn = on self:drawExternal(function() -- redraw if turned on if on then self.view:draw() else -- clear when off term.setBackgroundColor(colors.black) term.clear() end end) end elseif id == "key" then local key = p1 if key == 221 then active = false end elseif id == "char" then local ch = p1 self.view:onPress(ch) elseif id == "mouse_click" then local button, x, y = p1, p2, p3 self.view:onClick(button, x, y) elseif id == "mouse_drag" then local button, x, y = p1, p2, p3 self.view:onDrag(button, x, y) elseif id == "monitor_touch" and on then -- only if on! local side, x, y = p1, p2, p3 self.view:onClick(0, x, y) end end self:drawAll(function() term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clear() term.setCursorPos(1, 1) print("Node Panel exited") end) end, loadExample = function(self) local boiler = Block(25, 14, "Boiler"):setCable(colors.red) local engines = Block(10, 6, "Engines"):setCable(colors.white) local refinery = Block(10, 14, "Refinery"):setCable(colors.black) local mjStore = Block(25, 6, "MJ Store") local euStore = Block(40, 6, "EU Store") local factory = Block(40, 14, "Factory") boiler:connect(mjStore, colors.orange) engines:connect(mjStore, colors.orange) refinery:connect(engines, colors.yellow) mjStore:connect(euStore, colors.purple) euStore:connect(factory, colors.purple) self.diagram:add(boiler) end }) local app = Application() app:processEvents() [h2]npanel_conf.lua[/h2] Code: bundledSide = "bottom" noteboxSide = "right" diagramFile = "layout.txt" enableSide = "back" [h2]util.lua[/h2] Code: function prototype(parent, t) if not t then t = parent parent = nil end -- when there is no inheritance local t = t or {} local mt = { __index = t } setmetatable(t, { __call = function(_, ...) local obj = setmetatable({}, mt) if obj.__construct then obj:__construct(unpack(arg)) end return obj end, __index = parent }) return t end function prototyptify(cls, obj) local mt = {} mt.__index = cls setmetatable(obj, mt) return obj end function drawPixel(xPos, yPos, text) term.setCursorPos(xPos, yPos) term.write(text or " ") end -- from ComputerCraft function drawLine(startX, startY, endX, endY, text, bgColor, textColor) if bgColor then term.setBackgroundColor(bgColor) end if textColor then term.setTextColor(textColor) end startX = math.floor(startX) startY = math.floor(startY) endX = math.floor(endX) endY = math.floor(endY) if startX == endX and startY == endY then drawPixel(startX, startY, text) return end local minX = math.min(startX, endX) if minX == startX then minY = startY maxX = endX maxY = endY else minY = endY maxX = startX maxY = startY end local xDiff = maxX - minX local yDiff = maxY - minY if xDiff > math.abs(yDiff) then local y = minY local dy = yDiff / xDiff for x=minX,maxX do drawPixel(x, math.floor(y + 0.5), text) y = y + dy end else local x = minX local dx = xDiff / yDiff if maxY >= minY then for y=minY,maxY do drawPixel(math.floor(x + 0.5), y, text) x = x + dx end else for y=minY,maxY,-1 do drawPixel(math.floor(x + 0.5), y, text) x = x - dx end end end end function drawText(x, y, text, bgColor, textColor) term.setCursorPos(x, y) term.setBackgroundColor(bgColor or colors.black) term.setTextColor(textColor or colors.black) term.write(text) end function getColor(num) for k, v in pairs(colors) do if v == num then return k end end return "?" end function trim(s) return s:match"^%s*(.*)":match"(.-)%s*$" end [h2]serpent.lua[/h2] Code: local n, v = "serpent", 0.22 -- (C) 2012 Paul Kulchenko; MIT License local c, d = "Paul Kulchenko", "Serializer and pretty printer of Lua data types" local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} local badtype = {thread = true, userdata = true} local keyword, globals, G = {}, {}, (_G or _ENV) for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end for k,v in pairs(G) do globals[v] = k end -- build func to name mapping for _,g in ipairs({'coroutine', 'io', 'math', 'string', 'table', 'os'}) do for k,v in pairs(G[g]) do globals[v] = g..'.'..k end end local function s(t, opts) local name, indent, fatal = opts.name, opts.indent, opts.fatal local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) local comm = opts.comment and (tonumber(opts.comment) or math.huge) local seen, sref, syms, symn = {}, {}, {}, 0 local function gensym(val) return (tostring(val):gsub("[^%w]",""):gsub("(%d%w+)", function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end)) end local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s) or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] local n = name == nil and '' or name local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] local safe = plain and n or '['..safestr(n)..']' return (path or '')..(plain and path and '.' or '')..safe, safe end local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(o, n) local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} local function padnum(d) return ("%0"..maxn.."d"):format(d) end table.sort(o, function(a,b) return (o[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) < (o[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end local function val2str(t, name, indent, insref, path, plainindex, level) local ttype, level = type(t), (level or 0) local spath, sname = safename(path, name) local tag = plainindex and ((type(name) == "number") and '' or name..space..'='..space) or (name ~= nil and sname..space..'='..space or '') if seen[t] then -- if already seen and in sref processing, if insref then return tag..seen[t] end -- then emit right away table.insert(sref, spath..space..'='..space..seen[t]) return tag..'nil'..comment('ref', level) elseif badtype[ttype] then seen[t] = spath return tag..globerr(t, level) elseif ttype == 'function' then seen[t] = insref or spath local ok, res = pcall(string.dump, t) local func = ok and ((opts.nocode and "function() --[[..skipped..]] end" or "loadstring("..safestr(res)..",'@serialized')")..comment(t, level)) return tag..(func or globerr(t, level)) elseif ttype == "table" then if level >= maxl then return tag..'{}'..comment('max', level) end seen[t] = insref or spath -- set path to use as reference if getmetatable(t) and getmetatable(t).__tostring then return tag..val2str(tostring(t),nil,indent,false,nil,nil,level+1)..comment("meta", level) end if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty local maxn, o, out = #t, {}, {} for key = 1, maxn do table.insert(o, key) end for key in pairs(t) do if not o[key] then table.insert(o, key) end end if opts.sortkeys then alphanumsort(o, opts.sortkeys) end for n, key in ipairs(o) do local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing or opts.keyallow and not opts.keyallow[key] or opts.keynounderscore and type(key) == "string" and string.sub(key, 1, 1) == "_" or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types or sparse and value == nil then -- skipping nils; do nothing elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then if not seen[key] and not globals[key] then table.insert(sref, 'placeholder') sref[#sref] = 'local '..val2str(key,gensym(key),indent,gensym(key)) end table.insert(sref, 'placeholder') local path = seen[t]..'['..(seen[key] or globals[key] or gensym(key))..']' sref[#sref] = path..space..'='..space..(seen[value] or val2str(value,nil,indent,path)) else table.insert(out,val2str(value,key,indent,insref,seen[t],plainindex,level+1)) end end local prefix = string.rep(indent or '', level) local head = indent and '{\n'..prefix..indent or '{' local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) local tail = indent and "\n"..prefix..'}' or '}' return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level) else return tag..safestr(t) end -- handle all other types end local sepr = indent and "\n" or ";"..space local body = val2str(t, name, indent) -- this call also populates sref local tail = #sref>0 and table.concat(sref, sepr)..sepr or '' return not name and body or "do local "..body..sepr..tail.."return "..name..sepr.."end" end local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end serpent = { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
The program was made for 1.4.7 versions of CC and it's peripherals. So it's only natural that it won't work in 1.5 .
i might take a look see if i can get it working in 1.5 =p if i don't get distracted by the HUD i am writing (have a small digital clock written so far) updated: started working in it; seams that the only thing broken is the note block functionality; however the current bundled cables are not supported so unless we have some other cable i don't think its possible to fix.
The MFR cables work with exactly the same code as the RP2 ones did. Too bad I don't have a setup that really needs/is meant for this. looks like the error is somewhere in the terminal library where some function got changed. I haven't yet dug deep enough to know.
If mfr can be used like bundled cable i may have already fixed the bug; Will post it soon, i have to find out if it works
Have it working, it sends a pulse for 1 second so you need to have something to interpret the pulse, a toggle. Enjoy Note: a pulse goes out to the right now that will have to be wired to a note block If you want sound the config option for note block on right is n/a now this is due to an item being removed Only npanle.conf changed. Code: --[[ Node Panel Display by sk89q; update to 1.5 by fxstriker. ]]-- dofile("npanel_conf.lua") dofile("serpent.lua") dofile("util.lua") local function validateColor(allowNone) return function(input) if allowNone and input == "" then return nil end if not colors[input] then return "Invalid color! Examples: red, blue, black, lightBlue, etc." end end end local Block = prototype({ __construct = function(self, x, y, text, color) self.x = x self.y = y self.text = text self.color = color or colors.lightBlue self.conn = {} self.cable = nil self.state = nil self:setText(text) end, setText = function(self, text) self.text = text -- calculate width of the block self._width = #self.text + 1 if self._width % 2 ~= 0 then self._width = self._width + 1 end end, onClick = function(self) if self.cable then -- bundled cable -- toggle the latch (external to the system) self.state = not self.state redstone.setBundledOutput(bundledSide, self.cable) os.sleep(0.2) redstone.setBundledOutput(bundledSide, 0) return "Toggled " .. self.text .. " to " .. (self.state and "ON" or "OFF") else return false end end, contains = function(self, x, y) return x >= -self._width / 2 + self.x and x <= self._width / 2 + self.x and y >= self.y - 1 and y <= self.y + 1 end, connect = function(self, to, color) local color = color or colors.white table.insert(self.conn, {to = to, color = color}) table.insert(to.conn, {to = self, color = color}) end, disconnect = function(self, to) for k, other in ipairs(self.conn) do if other.to == to then -- remove own connection table.remove(self.conn, k) -- remove the other's reference too for k, other in ipairs(to.conn) do if other.to == self then table.remove(to.conn, k) break end end return -- exit end end end, disconnectAll = function(self) for _, other in ipairs(self.conn) do -- remove the other's reference for k, o in ipairs(other.to.conn) do if o.to == self then table.remove(other.to.conn, k) break end end end self.conn = {} end, isConnected = function(self, to) for k, other in ipairs(self.conn) do if other.to == to then return true end end return false end, setCable = function(self, color) self.cable = color -- set the state field if only we have a color set if color then self.state = self.state or false else self.state = nil end return self end, apply = function(self, object) prototyptify(self, object) end, draw = function(self) term.setBackgroundColor(self._selected and colors.blue or self.color) term.setTextColor(colors.white) drawLine(-self._width / 2 + self.x + 1, self.y - 1, self._width / 2 + self.x - 1, self.y - 1, "-") drawLine(-self._width / 2 + self.x + 1, self.y + 1, self._width / 2 + self.x - 1, self.y + 1, "-") drawPixel(-self._width / 2 + self.x, self.y - 1, "+") drawPixel(self._width / 2 + self.x, self.y - 1, "+") drawPixel(-self._width / 2 + self.x, self.y + 1, "+") drawPixel(self._width / 2 + self.x, self.y + 1, "+") drawPixel(-self._width / 2 + self.x, self.y, "|") drawPixel(self._width / 2 + self.x, self.y, "|") term.setCursorPos(-self._width / 2 + self.x + 1, self.y) term.write(self.text) for i = 0, self._width - #self.text - 2 do term.write(" ") end -- draw state if self.state ~= nil then term.setCursorPos(-self._width / 2 + self.x + 1, self.y - 1) if self.state then term.setBackgroundColor(colors.green) term.setTextColor(colors.yellow) term.write("ON") else term.setBackgroundColor(colors.red) term.setTextColor(colors.yellow) term.write("OFF") end end end }) local Diagram = prototype({ __construct = function(self) self.blocks = {} self.seen = {} end, add = function(self, block) -- only add if we haven't seen this block if not self.seen[block] then self.seen[block] = true table.insert(self.blocks, block) for _, c in ipairs(block.conn) do self:add(c.to) -- add this block too end end end, remove = function(self, block) self.seen[block] = nil -- remove from self.blocks for k, v in ipairs(self.blocks) do if v == block then table.remove(self.blocks, k) return end end end, save = function(self, path) local data = serpent.dump(self.blocks, { valtypeignore = {["function"] = true}, compact = true }) -- save to file local f = fs.open(path, "w") f.write(data) f.close() end, load = function(self, path) -- save to file local f = fs.open(path, "r") if f then local data = f.readAll() f.close() self.blocks = loadstring(data)() -- need to re-initialize for _, block in ipairs(self.blocks) do Block:apply(block) end else -- no data to load then self.blocks = {} end end, intersect = function(self, x, y, seen, block) for _, block in ipairs(self.blocks) do if block:contains(x, y) then return block end end end }) local BasePane = prototype({ titleColor = colors.cyan, __construct = function(self, app) self.app = app end, readInput = function(self, name, validate) local input self:drawHint("Please type the input above") while true do self:drawStatus(name) redstone.setOutput(enableSide, true) input = trim(read()) redstone.setOutput(enableSide, false) if type(validate) == 'function' then local msg = validate(input) if msg then -- uh oh, error! self:drawHint(msg) else break -- ok end elseif validate then -- just verify non-blank if msg == "" then self:drawHint("Non-empty string required") else break -- ok end else break -- no validation needed end end -- ok! self:drawHint("") return input end, drawStatus = function(self, status) local w, h = term.getSize() drawLine(1, h - 1, w, h - 1, " ", colors.black) drawText(2, h - 1, status, colors.black, colors.yellow) end, drawHint = function(self, status) local w, h = term.getSize() drawLine(1, h, w, h, " ", self.titleColor) drawText(2, h, status, self.titleColor, colors.white) end, drawTitle = function(self, title) drawText(2, 1, " " .. title .. " ", self.titleColor, colors.white) end, drawDiagram = function(self) self:drawConnections() self:drawBlocks() end, drawBlocks = function(self) for _, block in ipairs(self.app.diagram.blocks) do block:draw() end end, drawConnection = function(self, x1, y1, x2, y2, color) drawLine(x1, y1, x2, y2, x1 == x2 and "|" or "-", color, colors.black) end, drawConnections = function(self) for _, block in ipairs(self.app.diagram.blocks) do for k, v in ipairs(block.conn) do self:drawConnection(block.x, block.y, v.to.x, v.to.y, v.color) end end end, draw = function(self) -- do nothing end, onPress = function(self, key) -- do nothing end, onClick = function(self, button, x, y) -- do nothing end, onDrag = function(self, button, x, y) -- do nothing end, }) local Display = prototype(BasePane, { draw = function(self) self.app:drawAll(function() local w, h = term.getSize() term.setBackgroundColor(colors.black) term.clear() self:drawTitle("SYSTEM OVERVIEW") self:drawStatus("Touch elements to toggle them off and on") self:drawHint("[E]dit Mode") self:drawDiagram() end) end, onPress = function(self, key) if key:lower() == "e" then self.app:show(self.app.editor) end end, onClick = function(self, button, x, y) local clicked = self.app.diagram:intersect(x, y) if clicked then local status = clicked:onClick() if status then self.app:drawAll(function() clicked:draw() self:drawStatus(status) self.app:save() -- better save state end) --self.app.notebox.playNote(3, 24) --removed due to update breaking added pulse to right for sound if wanted. rs.setOutput("right",true) sleep(.5) rs.setOutput("right",false) end end end, }) local Editor = prototype(BasePane, { titleColor = colors.red, selected = nil, lastSelected = nil, draw = function(self) self.app:drawAll(function() local w, h = term.getSize() term.setBackgroundColor(colors.black) term.clear() self:drawTitle("BLOCK EDITOR") self:drawStatusBar() self:drawDiagram() end) end, drawStatusBar = function(self) if self.selected then self:drawHint("[I]nsert [D]elete [M]odify [C]onnect [S]ave") else self:drawHint("[I]nsert [S]ave") end end, select = function(self, block) -- swap selection local last = nil if self.selected then self.selected._selected = false last = self.selected end self.selected = block if block then block._selected = true end self.lastSelected = last return last end, onClick = function(self, button, x, y) local clicked = self.app.diagram:intersect(x, y) if clicked then local last = self:select(clicked) -- redraw self.app:drawAll(function() clicked:draw() if last then last:draw() -- unselected the last one, so redraw end self:drawStatus(clicked.text .. " selected" .. (clicked.cable and (" [color: " .. getColor(clicked.cable) .. "]") or "")) self:drawStatusBar() end) end end, onDrag = function(self, button, x, y) if self.selected then if x ~= self.selected.x or y ~= self.selected.y then self.selected.x = x self.selected.y = y self:draw() end end end, onPress = function(self, key) local lkey = key:lower() -- save/quit if lkey == "s" then self:select(nil) self.app:show(self.app.display) self.app:save() -- insert elseif lkey == "i" then local x, y = 2, 2 local name = self:readInput("Name: ", true) -- just non-blank local block = Block(x, y, name) self.app.diagram:add(block) self:select(block) self:draw() -- redraw all -- connect elseif lkey == "c" and self.selected then -- only if selected if self.lastSelected == self.selected then self:drawHint("Can't connect to itself, silly") elseif self.lastSelected then if self.selected:isConnected(self.lastSelected) then -- disconnect self.selected:disconnect(self.lastSelected, color) else -- connect local color = colors[self:readInput("Color: ", validateColor())] self.selected:connect(self.lastSelected, color) end self:draw() -- redraw all else self:drawHint("Select two blocks in sequence first") end -- delete elseif lkey == "d" and self.selected then -- only if selected local confirm = self:readInput("Are you sure? [y/n] ") if confirm:lower() == "y" then self.selected:disconnectAll() self.app.diagram:remove(self.selected) self:select(nil) end self:draw() -- modify elseif lkey == "m" and self.selected then -- only if selected -- set name local name = self:readInput("Name [blank for no change]: ") if name ~= "" then self.selected:setText(name) end -- bundled cable local color = colors[self:readInput( "Bundled cable [blank for none] color: ", validateColor(true))] -- allow none self.selected:setCable(color) self:draw() -- redraw all end end, }) local Application = prototype({ __construct = function(self) self.diagram = Diagram() self.notebox = peripheral.wrap(noteboxSide) self.terminals = { peripheral.wrap("top"), term.native } self:load() self.display = Display(self) self.editor = Editor(self) end, load = function(self) self.diagram:load(diagramFile) end, save = function(self) self.diagram:save(diagramFile) end, drawExternal = function(self, func) for _, m in ipairs(self.terminals) do if m ~= term.native then -- ignore native term.redirect(m) func() end end term.redirect(term.native) end, drawAll = function(self, func) local enabled = self:isEnabled() -- only draw if enabled for _, m in ipairs(self.terminals) do if m == term.native or enabled then term.redirect(m) func() end end term.redirect(term.native) end, show = function(self, view) self.view = view self.view:draw() end, isEnabled = function(self) return redstone.getInput(enableSide) end, processEvents = function(self) local active = true local wasOn = self:isEnabled() self:show(self.display) -- clear on start if needed if not wasOn then self:drawExternal(function() term.setBackgroundColor(colors.black) term.clear() end) end while active do local id, p1, p2, p3 = os.pullEvent() local on = self:isEnabled() if id == "redstone" then if wasOn ~= on then wasOn = on self:drawExternal(function() -- redraw if turned on if on then self.view:draw() else -- clear when off term.setBackgroundColor(colors.black) term.clear() end end) end elseif id == "key" then local key = p1 if key == 221 then active = false end elseif id == "char" then local ch = p1 self.view:onPress(ch) elseif id == "mouse_click" then local button, x, y = p1, p2, p3 self.view:onClick(button, x, y) elseif id == "mouse_drag" then local button, x, y = p1, p2, p3 self.view:onDrag(button, x, y) elseif id == "monitor_touch" and on then -- only if on! local side, x, y = p1, p2, p3 self.view:onClick(0, x, y) end end self:drawAll(function() term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clear() term.setCursorPos(1, 1) print("Node Panel exited") end) end, loadExample = function(self) local boiler = Block(25, 14, "Boiler"):setCable(colors.red) local engines = Block(10, 6, "Engines"):setCable(colors.white) local refinery = Block(10, 14, "Refinery"):setCable(colors.black) local mjStore = Block(25, 6, "MJ Store") local euStore = Block(40, 6, "EU Store") local factory = Block(40, 14, "Factory") boiler:connect(mjStore, colors.orange) engines:connect(mjStore, colors.orange) refinery:connect(engines, colors.yellow) mjStore:connect(euStore, colors.purple) euStore:connect(factory, colors.purple) self.diagram:add(boiler) end }) local app = Application() app:processEvents()
OMG, I've been waiting for you to release this for ages. Gave up on making it myself long ago (lazy lol)