Node Control Panel

Discussion in 'ComputerCraft Programming' started by sk89q, Apr 28, 2013.

  1. sk89q

    sk89q Administrator
    Staff Member

    Joined:
    Dec 1, 2011
    Messages:
    2,490
    Likes Received:
    1,504
    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.

    2013-04-28_11.41.43.png

    You can edit the layout in-game on the monitor.

    2013-04-28_11.41.30.png

    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 }
    
     
    Ali and tonius11 like this.
  2. DSoS

    DSoS Member

    Joined:
    Dec 6, 2012
    Messages:
    128
    Likes Received:
    18
    o_O very cool
     
  3. ZoomerX

    ZoomerX Sent to exile for no reason

    Joined:
    Feb 13, 2013
    Messages:
    223
    Likes Received:
    88
  4. Tonius

    Tonius 555 Eternal Server Error

    Joined:
    Jan 30, 2012
    Messages:
    480
    Likes Received:
    392
    :O Awesome!
     
  5. Neonbeta

    Neonbeta Person who did stuff and things

    Joined:
    Mar 2, 2012
    Messages:
    2,603
    Likes Received:
    757
    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 .
     
  6. fxstriker

    fxstriker Good Bye skcraft.

    Joined:
    Jan 14, 2012
    Messages:
    259
    Likes Received:
    35
    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.
     
  7. Azimath

    Azimath Master Zepplin Thief

    Joined:
    Jan 22, 2012
    Messages:
    30
    Likes Received:
    6
    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.
     
  8. fxstriker

    fxstriker Good Bye skcraft.

    Joined:
    Jan 14, 2012
    Messages:
    259
    Likes Received:
    35
    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 :p
     
  9. fxstriker

    fxstriker Good Bye skcraft.

    Joined:
    Jan 14, 2012
    Messages:
    259
    Likes Received:
    35
    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()
     
    
     
  10. LeiserGeist

    LeiserGeist Gamer, Software Developer

    Joined:
    Oct 26, 2012
    Messages:
    479
    Likes Received:
    116
    OMG, I've been waiting for you to release this for ages. Gave up on making it myself long ago (lazy lol)
     
  11. ghostpotato

    ghostpotato The Crimson King

    Joined:
    Jan 27, 2013
    Messages:
    92
    Likes Received:
    6
    O my word. Its beautiful.
    I wish I was smart enough to set it up for my base on 1.5.
     
  12. DeadClap

    DeadClap New Member

    Joined:
    Jun 28, 2014
    Messages:
    1
    Likes Received:
    0
    where is npanel.lua