programcreator

Add-On Programs: nPaintPro by nitrogenfingers

Feb 17th, 2015
1,571
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 63.46 KB | None | 0 0
  1. --[[
  2.         NPaintPro
  3.         By NitrogenFingers
  4. ]]--
  5.  
  6. --The screen size
  7. local w,h = term.getSize()
  8. --Whether or not the program is currently waiting on user input
  9. local inMenu = false
  10. --Whether or not a drop down menu is active
  11. local inDropDown = false
  12. --Whether or not animation tools are enabled (use -a to turn them on)
  13. local animated = false
  14. --Whether or not "blueprint" display mode is on
  15. local blueprint = false
  16. --Whether or not the "layer" display is on
  17. local layerDisplay = false
  18. --Whether or not the "direction" display is on
  19. local printDirection = false
  20. --The tool/mode npaintpro is currently in. Default is "paint"
  21. --For a list of modes, check out the help file
  22. local state = "paint"
  23. --Whether or not the program is presently running
  24. local isRunning = true
  25. --The rednet address of the 3D printer, if one has been attached
  26. local printer = nil
  27.  
  28. --The list of every frame, containing every image in the picture/animation
  29. --Note: nfp files always have the picture at frame 1
  30. local frames = { }
  31. --How many frames are currently in the given animation.
  32. local frameCount = 1
  33. --The Colour Picker column
  34. local column = {}
  35. --The currently selected left and right colours
  36. local lSel,rSel = colours.white,nil
  37. --The amount of scrolling on the X and Y axis
  38. local sx,sy = 0,0
  39. --The alpha channel colour
  40. --Change this to change default canvas colour
  41. local alphaC = colours.yellow
  42. --The currently selected frame. Default is 1
  43. local sFrame = 1
  44. --The contents of the image buffer- contains contents, width and height
  45. local buffer = nil
  46. --The position, width and height of the selection rectangle
  47. local selectrect = nil
  48.  
  49. --The currently calculated required materials
  50. local requiredMaterials = {}
  51. --Whether or not required materials are being displayed in the pallette
  52. local requirementsDisplayed = false
  53. --A list of the rednet ID's all in-range printers located
  54. local printerList = { }
  55. --A list of the names of all in-range printers located. Same as the printerList in reference
  56. local printerNames = { }
  57. --The selected printer
  58. local selectedPrinter = 1
  59. --The X,Y,Z and facing of the printer
  60. local px,py,pz,pfx,pfz = 0,0,0,0,0
  61. --The form of layering used
  62. local layering = "up"
  63.  
  64. --The animation state of the selection rectangle and image buffer
  65. local rectblink = 0
  66. --The ID for the timer
  67. local recttimer = nil
  68. --The radius of the brush tool
  69. local brushsize = 3
  70. --Whether or not "record" mode is activated (animation mode only)
  71. local record = false
  72. --The time between each frame when in play mode (animation mode only)
  73. local animtime = 0.3
  74.  
  75. --A list of hexidecimal conversions from numbers to hex digits
  76. local hexnums = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f" }
  77. --The NPaintPro logo (divine, isn't it?)
  78. local logo = {
  79. "fcc              3   339";
  80. " fcc          9333    33";
  81. "  fcc        933 333  33";
  82. "   fcc       933  33  33";
  83. "    fcc      933   33 33";
  84. "     c88     333   93333";
  85. "     888     333    9333";
  86. "      333 3  333     939";
  87. }
  88. --The Layer Up and Layer Forward printing icons
  89. local layerUpIcon = {
  90.     "0000000";
  91.     "0088880";
  92.     "0888870";
  93.     "07777f0";
  94.     "0ffff00";
  95.     "0000000";
  96. }
  97. local layerForwardIcon = {
  98.     "0000000";
  99.     "000fff0";
  100.     "00777f0";
  101.     "0888700";
  102.     "0888000";
  103.     "0000000";
  104. }
  105. --The available menu options in the ctrl menu
  106. local mChoices = {"Save","Exit"}
  107. --The available modes from the dropdown menu- tables indicate submenus (include a name!)
  108. local ddModes = { { "paint", "brush", "pippette", "flood", "move", "clear", "select", name = "painting" }, { "alpha to left", "alpha to right", "blueprint on", "layers on", name = "display" }, "help", { "print", "save", "exit", name = "file" }, name = "menu" }
  109. --The available modes from the selection right-click menu
  110. local srModes = { "cut", "copy", "paste", "clear", "hide", name = "selection" }
  111. --The list of available help topics for each mode 127
  112. local helpTopics = {
  113.     [1] = {
  114.         name = "Paint Mode",
  115.         key = nil,
  116.         animonly = false,
  117.         message = "The default mode for NPaintPro, for painting pixels."
  118.         .." Controls here that are not overridden will apply for all other modes. Leaving a mode by selecting that mode "
  119.         .." again will always send the user back to paint mode.",
  120.         controls = {
  121.             { "Arrow keys", "Scroll the canvas" },
  122.             { "Left Click", "Paint/select left colour" },
  123.             { "Right Click", "Paint/select right colour" },
  124.             { "Z Key", "Clear image on screen" },
  125.             { "Tab Key", "Hide selection rectangle if visible" },
  126.             { "Q Key", "Set alpha mask to left colour" },
  127.             { "W Key", "Set alpha mask to right colour" },
  128.             { "Number Keys", "Swich between frames 1-9" },
  129.             { "</> keys", "Move to the next/last frame" },
  130.             { "R Key", "Removes every frame after the current frame"}
  131.         }
  132.     },
  133.     [2] = {
  134.         name = "Brush Mode",
  135.         key = "b",
  136.         animonly = false,
  137.         message = "Brush mode allows painting a circular area of variable diameter rather than a single pixel, working in "..
  138.         "the exact same way as paint mode in all other regards.",
  139.         controls = {
  140.             { "Left Click", "Paints a brush blob with the left colour" },
  141.             { "Right Click", "Paints a brush blob with the right colour" },
  142.             { "Number Keys", "Changes the radius of the brush blob from 2-9" }
  143.         }
  144.     },
  145.     [3] = {
  146.         name = "Pippette Mode",
  147.         key = "p",
  148.         animonly = false,
  149.         message = "Pippette mode allows the user to click the canvas and set the colour clicked to the left or right "..
  150.         "selected colour, for later painting.",
  151.         controls = {
  152.             { "Left Click", "Sets clicked colour to the left selected colour" },
  153.             { "Right Click", "Sets clicked colour to the right selected colour" }
  154.         }
  155.     },
  156.     [4] = {
  157.         name = "Move Mode",
  158.         key = "m",
  159.         animonly = false,
  160.         message = "Mode mode allows the moving of the entire image on the screen. This is especially useful for justifying"..
  161.         " the image to the top-left for animations or game assets.",
  162.         controls = {
  163.             { "Left/Right Click", "Moves top-left corner of image to selected square" },
  164.             { "Arrow keys", "Moves image one pixel in any direction" }
  165.         }
  166.     },
  167.     [5] = {
  168.         name = "Flood Mode (NYI)",
  169.         key = "f",
  170.         animonly = false,
  171.         message = "Flood mode allows the changing of an area of a given colour to that of the selected colour. "..
  172.         "The tool uses a flood4 algorithm and will not fill diagonally. Transparency cannot be flood filled.",
  173.         controls = {
  174.             { "Left Click", "Flood fills selected area to left colour" },
  175.             { "Right Click", "Flood fills selected area to right colour" }
  176.         }
  177.     },
  178.     [6] = {
  179.         name = "Select Mode",
  180.         key = "s",
  181.         animonly = false,
  182.         message = "Select mode allows the creation and use of the selection rectangle, to highlight specific areas on "..
  183.         "the screen and perform operations on the selected area of the image. The selection rectangle can contain an "..
  184.         "image on the clipboard- if it does, the image will flash inside the rectangle, and the rectangle edges will "..
  185.         "be light grey instead of dark grey.",
  186.         controls = {
  187.             { "C Key", "Copy: Moves selection into the clipboard" },
  188.             { "X Key", "Cut: Clears canvas under the rectangle, and moves it into the clipboard" },
  189.             { "V Key", "Paste: Copys clipboard to the canvas" },
  190.             { "Z Key", "Clears clipboard" },
  191.             { "Left Click", "Moves top-left corner of rectangle to selected pixel" },
  192.             { "Right Click", "Opens selection menu" },
  193.             { "Arrow Keys", "Moves rectangle one pixel in any direction" }
  194.         }
  195.     },
  196.     [7] = {
  197.         name = "Corner Select Mode",
  198.         key = nil,
  199.         animonly = false,
  200.         message = "If a selection rectangle isn't visible, this mode is selected automatically. It allows the "..
  201.         "defining of the corners of the rectangle- one the top-left and bottom-right corners have been defined, "..
  202.         "NPaintPro switches to selection mode. Note rectangle must be at least 2 pixels wide and high.",
  203.         controls = {
  204.             { "Left/Right Click", "Defines a corner of the selection rectangle" }
  205.         }
  206.     },
  207.     [8] = {
  208.         name = "Play Mode",
  209.         key = "space",
  210.         animonly = true,
  211.         message = "Play mode will loop through each frame in your animation at a constant rate. Editing tools are "..
  212.         "locked in this mode, and the coordinate display will turn green to indicate it is on.",
  213.         controls = {
  214.             { "</> Keys", "Increases/Decreases speed of the animation" },
  215.             { "Space Bar", "Returns to paint mode" }
  216.         }
  217.     },
  218.     [9] = {
  219.         name = "Record Mode",
  220.         key = "\\",
  221.         animonly = true,
  222.         message = "Record mode is not a true mode, but influences how other modes work. Changes made that modify the "..
  223.         "canvas in record mode will affect ALL frames in the animation. The coordinates will turn red to indicate that "..
  224.         "record mode is on.",
  225.         controls = {
  226.             { "", "Affects:" },
  227.             { "- Paint Mode", "" },
  228.             { "- Brush Mode", "" },
  229.             { "- Cut and Paste in Select Mode", ""},
  230.             { "- Move Mode", ""}
  231.         }
  232.     },
  233.     [10] = {
  234.         name = "Help Mode",
  235.         key = "h",
  236.         animonly = false,
  237.         message = "Displays this help screen. Clicking on options will display help on that topic. Clicking out of the screen"..
  238.         " will leave this mode.",
  239.         controls = {
  240.             { "Left/Right Click", "Displays a topic/Leaves the mode" }
  241.         }
  242.     },
  243.     [11] = {
  244.         name = "File Mode",
  245.         keys = nil,
  246.         animonly = false,
  247.         message = "Clicking on the mode display at the bottom of the screen will open the options menu. Here you can"..
  248.         " activate all of the modes in the program with a simple mouse click. Pressing left control will open up the"..
  249.         " file menu automatically.",
  250.         controls = {
  251.             { "leftCtrl", "Opens the file menu" },
  252.             { "leftAlt", "Opens the paint menu" }
  253.            
  254.         }
  255.     }
  256. }
  257. --The "bounds" of the image- the first/last point on both axes where a pixel appears
  258. local toplim,botlim,leflim,riglim = nil,nil,nil,nil
  259. --The selected path
  260. local sPath = nil
  261.  
  262. --[[  
  263.             Section:  Helpers      
  264. ]]--
  265.  
  266. --[[Converts a colour parameter into a single-digit hex coordinate for the colour
  267.     Params: colour:int = The colour to be converted
  268.     Returns:string A string conversion of the colour
  269. ]]--
  270. local function getHexOf(colour)
  271.     if not colour or not tonumber(colour) then
  272.         return " "
  273.     end
  274.     local value = math.log(colour)/math.log(2)
  275.     if value > 9 then
  276.         value = hexnums[value]
  277.     end
  278.     return value
  279. end
  280.  
  281. --[[Converts a hex digit into a colour value
  282.     Params: hex:?string = the hex digit to be converted
  283.     Returns:string A colour value corresponding to the hex, or nil if the character is invalid
  284. ]]--
  285. local function getColourOf(hex)
  286.     local value = tonumber(hex, 16)
  287.     if not value then return nil end
  288.     value = math.pow(2,value)
  289.     return value
  290. end
  291.  
  292. --[[Finds the biggest and smallest bounds of the image- the outside points beyond which pixels do not appear
  293.     These values are assigned to the "lim" parameters for access by other methods
  294.     Params: forAllFrames:bool = True if all frames should be used to find bounds, otherwise false or nil
  295.     Returns:nil
  296. ]]--
  297. local function updateImageLims(forAllFrames)
  298.     local f,l = sFrame,sFrame
  299.     if forAllFrames == true then f,l = 1,framecount end
  300.    
  301.     toplim,botlim,leflim,riglim = nil,nil,nil,nil
  302.     for locf = f,l do
  303.         for y,_ in pairs(frames[locf]) do
  304.             for x,_ in pairs(frames[locf][y]) do
  305.                 if frames[locf][y][x] ~= nil then
  306.                     if leflim == nil or x < leflim then leflim = x end
  307.                     if toplim == nil or y < toplim then toplim = y end
  308.                     if riglim == nil or x > riglim then riglim = x end
  309.                     if botlim == nil or y > botlim then botlim = y end
  310.                 end
  311.             end
  312.         end
  313.     end
  314. end
  315.  
  316. --[[Determines how much of each material is required for a print. Done each time printing is called.
  317.     Params: none
  318.     Returns:table A complete list of how much of each material is required.
  319. ]]--
  320. function calculateMaterials()
  321.     updateImageLims(animated)
  322.     requiredMaterials = {}
  323.     for i=1,16 do
  324.         requiredMaterials[i] = 0
  325.     end
  326.    
  327.     if not toplim then return end
  328.    
  329.     for i=1,#frames do
  330.         for y = toplim, botlim do
  331.             for x = leflim, riglim do
  332.                 if type(frames[i][y][x]) == "number" then
  333.                     requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] =
  334.                         requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] + 1
  335.                 end
  336.             end
  337.         end
  338.     end
  339. end
  340.  
  341.  
  342. --[[Updates the rectangle blink timer. Should be called anywhere events are captured, along with a timer capture.
  343.     Params: nil
  344.     Returns:nil
  345. ]]--
  346. local function updateTimer(id)
  347.     if id == recttimer then
  348.         recttimer = os.startTimer(0.5)
  349.         rectblink = (rectblink % 2) + 1
  350.     end
  351. end
  352.  
  353. --[[Constructs a message based on the state currently selected
  354.     Params: nil
  355.     Returns:string A message regarding the state of the application
  356. ]]--
  357. local function getStateMessage()
  358.     local msg = " "..string.upper(string.sub(state, 1, 1))..string.sub(state, 2, #state).." mode"
  359.     if state == "brush" then msg = msg..", size="..brushsize end
  360.     return msg
  361. end
  362.  
  363. --[[Calls the rednet_message event, but also looks for timer events to keep then
  364.     system timer ticking.
  365.     Params: timeout:number how long before the event times out
  366.     Returns:number the id of the sender
  367.            :number the message send
  368. ]]--
  369. local function rsTimeReceive(timeout)
  370.     local timerID
  371.     if timeout then timerID = os.startTimer(timeout) end
  372.    
  373.     local id,key,msg = nil,nil
  374.     while true do
  375.         id,key,msg = os.pullEvent()
  376.        
  377.         if id == "timer" then
  378.             if key == timerID then return
  379.             else updateTimer(key) end
  380.         end
  381.         if id == "rednet_message" then
  382.             return key,msg
  383.         end
  384.     end
  385. end
  386.  
  387. --[[Draws a picture, in paint table format on the screen
  388.     Params: image:table = the image to display
  389.             xinit:number = the x position of the top-left corner of the image
  390.             yinit:number = the y position of the top-left corner of the image
  391.             alpha:number = the color to display for the alpha channel. Default is white.
  392.     Returns:nil
  393. ]]--
  394. local function drawPictureTable(image, xinit, yinit, alpha)
  395.     if not alpha then alpha = 1 end
  396.     for y=1,#image do
  397.         for x=1,#image[y] do
  398.             term.setCursorPos(xinit + x-1, yinit + y-1)
  399.             local col = getColourOf(string.sub(image[y], x, x))
  400.             if not col then col = alpha end
  401.             term.setBackgroundColour(col)
  402.             term.write(" ")
  403.         end
  404.     end
  405. end
  406.  
  407. --[[  
  408.             Section: Loading  
  409. ]]--
  410.  
  411. --[[Loads a non-animted paint file into the program
  412.     Params: path:string = The path in which the file is located
  413.     Returns:nil
  414. ]]--
  415. local function loadNFP(path)
  416.     sFrame = 1
  417.     frames[sFrame] = { }
  418.     if fs.exists(path) then
  419.         local file = io.open(path, "r" )
  420.         local sLine = file:read()
  421.         local num = 1
  422.         while sLine do
  423.             table.insert(frames[sFrame], num, {})
  424.             for i=1,#sLine do
  425.                 frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
  426.             end
  427.             num = num+1
  428.             sLine = file:read()
  429.         end
  430.         file:close()
  431.     end
  432. end
  433.  
  434. --[[Saves a non-animated paint file to the specified path
  435.     Params: path:string = The path to save the file to
  436.     Returns:nil
  437. ]]--
  438. local function saveNFP(path)
  439.     local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
  440.     if not fs.exists(sDir) then
  441.         fs.makeDir(sDir)
  442.     end
  443.  
  444.     local file = io.open(path, "w")
  445.     updateImageLims(false)
  446.     if not toplim then
  447.         file:close()
  448.         return
  449.     end
  450.     for y=1,botlim do
  451.         local line = ""
  452.         if frames[sFrame][y] then
  453.             for x=1,riglim do
  454.                 line = line..getHexOf(frames[sFrame][y][x])
  455.             end
  456.         end
  457.         file:write(line.."\n")
  458.     end
  459.     file:close()
  460. end
  461.  
  462. --[[Loads an animated paint file into the program
  463.     Params: path:string = The path in which the file is located
  464.     Returns:nil
  465. ]]--
  466. local function loadNFA(path)
  467.     frames[sFrame] = { }
  468.     if fs.exists(path) then
  469.         local file = io.open(path, "r" )
  470.         local sLine = file:read()
  471.         local num = 1
  472.         while sLine do
  473.             table.insert(frames[sFrame], num, {})
  474.             if sLine == "~" then
  475.                 sFrame = sFrame + 1
  476.                 frames[sFrame] = { }
  477.                 num = 1
  478.             else
  479.                 for i=1,#sLine do
  480.                     frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
  481.                 end
  482.                 num = num+1
  483.             end
  484.             sLine = file:read()
  485.         end
  486.         file:close()
  487.     end
  488.     framecount = sFrame
  489.     sFrame = 1
  490. end
  491.  
  492. --[[Saves a animated paint file to the specified path
  493.     Params: path:string = The path to save the file to
  494.     Returns:nil
  495. ]]--
  496. local function saveNFA(path)
  497.     local file = io.open(path, "w")
  498.     updateImageLims(true)
  499.     if not toplim then
  500.         file:close()
  501.         return
  502.     end
  503.     for i=1,#frames do
  504.         for y=1,botlim do
  505.             local line = ""
  506.             if frames[i][y] then
  507.                 for x=1,riglim do
  508.                     line = line..getHexOf(frames[i][y][x])
  509.                 end
  510.             end
  511.             file:write(line.."\n")
  512.         end
  513.         if i < #frames then file:write("~\n") end
  514.     end
  515.     file:close()
  516. end
  517.  
  518. --[[Initializes the program, by loading in the paint file. Called at the start of each program.
  519.     Params: none
  520.     Returns:nil
  521. ]]--
  522. local function init()
  523.     if animated then
  524.         loadNFA(sPath)
  525.         table.insert(ddModes, #ddModes, { "record", "play", name = "anim" })
  526.         table.insert(ddModes, #ddModes, { "go to", "remove", name = "frames"})
  527.     else loadNFP(sPath) end
  528.  
  529.     for i=0,15 do
  530.         table.insert(column, math.pow(2,i))
  531.     end
  532. end
  533.  
  534. --[[  
  535.             Section: Drawing  
  536. ]]--
  537.  
  538.  
  539. --[[Draws the rather superflous logo. Takes about 1 second, before user is able to move to the
  540.     actual program.
  541.     Params: none
  542.     Returns:nil
  543. ]]--
  544. local function drawLogo()
  545.     term.setBackgroundColour(colours.white)
  546.     term.clear()
  547.     drawPictureTable(logo, w/2 - #logo[1]/2, h/2 - #logo/2, colours.white)
  548.     term.setBackgroundColour(colours.white)
  549.     term.setTextColour(colours.black)
  550.     local msg = "NPaintPro"
  551.     term.setCursorPos(w/2 - #msg/2, h-3)
  552.     term.write(msg)
  553.     msg = "By NitrogenFingers"
  554.     term.setCursorPos(w/2 - #msg/2, h-2)
  555.     term.write(msg)
  556.     msg = "Thanks to faubiguy for testing"
  557.     term.setCursorPos(w/2 - #msg/2, h)
  558.     term.write(msg)
  559.    
  560.     os.pullEvent()
  561. end
  562.  
  563. --[[Clears the display to the alpha channel colour, draws the canvas, the image buffer and the selection
  564.     rectanlge if any of these things are present.
  565.     Params: none
  566.     Returns:nil
  567. ]]--
  568. local function drawCanvas()
  569.     --We have to readjust the position of the canvas if we're printing
  570.     turtlechar = "@"
  571.     if state == "active print" then
  572.         if layering == "up" then
  573.             if py >= 1 and py <= #frames then
  574.                 sFrame = py
  575.             end
  576.             if pz < sy then sy = pz
  577.             elseif pz > sy + h - 1 then sy = pz + h - 1 end
  578.             if px < sx then sx = px
  579.             elseif px > sx + w - 2 then sx = px + w - 2 end
  580.         else
  581.             if pz >= 1 and pz <= #frames then
  582.                 sFrame = pz
  583.             end
  584.            
  585.             if py < sy then sy = py
  586.             elseif py > sy + h - 1 then sy = py + h - 1 end
  587.             if px < sx then sx = px
  588.             elseif px > sx + w - 2 then sx = px + w - 2 end
  589.         end
  590.        
  591.         if pfx == 1 then turtlechar = ">"
  592.         elseif pfx == -1 then turtlechar = "<"
  593.         elseif pfz == 1 then turtlechar = "V"
  594.         elseif pfz == -1 then turtlechar = "^"
  595.         end
  596.     end
  597.  
  598.     --Picture next
  599.     local topLayer, botLayer
  600.     if layerDisplay then
  601.         topLayer = sFrame
  602.         botLayer = 1
  603.     else
  604.         topLayer,botLayer = sFrame,sFrame
  605.     end
  606.    
  607.     for currframe = botLayer,topLayer,1 do
  608.         for y=sy+1,sy+h-1 do
  609.             if frames[currframe][y] then
  610.                 for x=sx+1,sx+w-2 do
  611.                     term.setCursorPos(x-sx,y-sy)
  612.                     if frames[currframe][y][x] then
  613.                         term.setBackgroundColour(frames[currframe][y][x])
  614.                         term.write(" ")
  615.                     else
  616.                         tileExists = false
  617.                         for i=currframe-1,botLayer,-1 do
  618.                             if frames[i][y][x] then
  619.                                 tileExists = true
  620.                                 break
  621.                             end
  622.                         end
  623.                        
  624.                         if not tileExists then
  625.                             if blueprint then
  626.                                 term.setBackgroundColour(colours.blue)
  627.                                 term.setTextColour(colours.white)
  628.                                 if x == sx+1 and y % 4 == 1 then
  629.                                     term.write(""..((y/4) % 10))
  630.                                 elseif y == sy + 1 and x % 4 == 1 then
  631.                                     term.write(""..((x/4) % 10))
  632.                                 elseif x % 2 == 1 and y % 2 == 1 then
  633.                                     term.write("+")
  634.                                 elseif x % 2 == 1 then
  635.                                     term.write("|")
  636.                                 elseif y % 2 == 1 then
  637.                                     term.write("-")
  638.                                 else
  639.                                     term.write(" ")
  640.                                 end
  641.                             else
  642.                                 term.setBackgroundColour(alphaC)
  643.                                 term.write(" ")
  644.                             end
  645.                         end
  646.                     end
  647.                 end
  648.             else
  649.                 for x=sx+1,sx+w-2 do
  650.                     term.setCursorPos(x-sx,y-sy)
  651.                    
  652.                     tileExists = false
  653.                     for i=currframe-1,botLayer,-1 do
  654.                         if frames[i][y] and frames[i][y][x] then
  655.                             tileExists = true
  656.                             break
  657.                         end
  658.                     end
  659.                    
  660.                     if not tileExists then
  661.                         if blueprint then
  662.                             term.setBackgroundColour(colours.blue)
  663.                             term.setTextColour(colours.white)
  664.                             if x == sx+1 and y % 4 == 1 then
  665.                                 term.write(""..((y/4) % 10))
  666.                             elseif y == sy + 1 and x % 4 == 1 then
  667.                                 term.write(""..((x/4) % 10))
  668.                             elseif x % 2 == 1 and y % 2 == 1 then
  669.                                 term.write("+")
  670.                             elseif x % 2 == 1 then
  671.                                 term.write("|")
  672.                             elseif y % 2 == 1 then
  673.                                 term.write("-")
  674.                             else
  675.                                 term.write(" ")
  676.                             end
  677.                         else
  678.                             term.setBackgroundColour(alphaC)
  679.                             term.write(" ")
  680.                         end
  681.                     end
  682.                 end
  683.             end
  684.         end
  685.     end
  686.    
  687.     --Then the printer, if he's on
  688.     if state == "active print" then
  689.         local bgColour = alphaC
  690.         if layering == "up" then
  691.             term.setCursorPos(px-sx,pz-sy)
  692.             if frames[sFrame] and frames[sFrame][pz-sy] and frames[sFrame][pz-sy][px-sx] then
  693.                 bgColour = frames[sFrame][pz-sy][px-sx]
  694.             elseif blueprint then bgColour = colours.blue end
  695.         else
  696.             term.setCursorPos(px-sx,py-sy)
  697.             if frames[sFrame] and frames[sFrame][py-sy] and frames[sFrame][py-sy][px-sx] then
  698.                 bgColour = frames[sFrame][py-sy][px-sx]
  699.             elseif blueprint then bgColour = colours.blue end
  700.         end
  701.        
  702.         term.setBackgroundColour(bgColour)
  703.         if bgColour == colours.black then term.setTextColour(colours.white)
  704.         else term.setTextColour(colours.black) end
  705.        
  706.         term.write(turtlechar)
  707.     end
  708.    
  709.     --Then the buffer
  710.     if selectrect then
  711.         if buffer and rectblink == 1 then
  712.         for y=selectrect.y1, math.min(selectrect.y2, selectrect.y1 + buffer.height-1) do
  713.             for x=selectrect.x1, math.min(selectrect.x2, selectrect.x1 + buffer.width-1) do
  714.                 if buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1] then
  715.                     term.setCursorPos(x+sx,y+sy)
  716.                     term.setBackgroundColour(buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1])
  717.                     term.write(" ")
  718.                 end
  719.             end
  720.         end
  721.         end
  722.    
  723.         --This draws the "selection" box
  724.         local add = nil
  725.         if buffer then
  726.             term.setBackgroundColour(colours.lightGrey)
  727.         else
  728.             term.setBackgroundColour(colours.grey)
  729.         end
  730.         for i=selectrect.x1, selectrect.x2 do
  731.             add = (i + selectrect.y1 + rectblink) % 2 == 0
  732.             term.setCursorPos(i-sx,selectrect.y1-sy)
  733.             if add then term.write(" ") end
  734.             add = (i + selectrect.y2 + rectblink) % 2 == 0
  735.             term.setCursorPos(i-sx,selectrect.y2-sy)
  736.             if add then term.write(" ") end
  737.         end
  738.         for i=selectrect.y1 + 1, selectrect.y2 - 1 do
  739.             add = (i + selectrect.x1 + rectblink) % 2 == 0
  740.             term.setCursorPos(selectrect.x1-sx,i-sy)
  741.             if add then term.write(" ") end
  742.             add = (i + selectrect.x2 + rectblink) % 2 == 0
  743.             term.setCursorPos(selectrect.x2-sx,i-sy)
  744.             if add then term.write(" ") end
  745.         end
  746.     end
  747. end
  748.  
  749. --[[Draws the colour picker on the right side of the screen, the colour pallette and the footer with any
  750.     messages currently being displayed
  751.     Params: none
  752.     Returns:nil
  753. ]]--
  754. local function drawInterface()
  755.     --Picker
  756.     for i=1,#column do
  757.         term.setCursorPos(w-1, i)
  758.         term.setBackgroundColour(column[i])
  759.         if state == "print" then
  760.             if i == 16 then
  761.                 term.setTextColour(colours.white)
  762.             else
  763.                 term.setTextColour(colours.black)
  764.             end
  765.             if requirementsDisplayed then
  766.                 if requiredMaterials[i] < 10 then term.write(" ") end
  767.                 term.setCursorPos(w-#tostring(requiredMaterials[i])+1, i)
  768.                 term.write(requiredMaterials[i])
  769.             else
  770.                 if i < 10 then term.write(" ") end
  771.                 term.write(i)
  772.             end
  773.         else
  774.             term.write("  ")
  775.         end
  776.     end
  777.     term.setCursorPos(w-1,#column+1)
  778.     term.setBackgroundColour(colours.black)
  779.     term.setTextColour(colours.red)
  780.     term.write("XX")
  781.     --Pallette
  782.     term.setCursorPos(w-1,h-1)
  783.     if not lSel then
  784.         term.setBackgroundColour(colours.black)
  785.         term.setTextColour(colours.red)
  786.         term.write("X")
  787.     else
  788.         term.setBackgroundColour(lSel)
  789.         term.setTextColour(lSel)
  790.         term.write(" ")
  791.     end
  792.     if not rSel then
  793.         term.setBackgroundColour(colours.black)
  794.         term.setTextColour(colours.red)
  795.         term.write("X")
  796.     else
  797.         term.setBackgroundColour(rSel)
  798.         term.setTextColour(rSel)
  799.         term.write(" ")
  800.     end
  801.     --Footer
  802.     if inMenu then return end
  803.    
  804.     term.setCursorPos(1, h)
  805.     term.setBackgroundColour(colours.lightGrey)
  806.     term.setTextColour(colours.grey)
  807.     term.clearLine()
  808.     if inDropDown then
  809.         term.write(string.rep(" ", 6))
  810.     else
  811.         term.setBackgroundColour(colours.grey)
  812.         term.setTextColour(colours.lightGrey)
  813.         term.write("menu  ")
  814.     end
  815.     term.setBackgroundColour(colours.lightGrey)
  816.     term.setTextColour(colours.grey)
  817.     term.write(getStateMessage())
  818.    
  819.     local coords="X:"..sx.." Y:"..sy
  820.     if animated then coords = coords.." Frame:"..sFrame.."/"..framecount.."   " end
  821.     term.setCursorPos(w-#coords+1,h)
  822.     if state == "play" then term.setBackgroundColour(colours.lime)
  823.     elseif record then term.setBackgroundColour(colours.red) end
  824.     term.write(coords)
  825.    
  826.     if animated then
  827.         term.setCursorPos(w-1,h)
  828.         term.setBackgroundColour(colours.grey)
  829.         term.setTextColour(colours.lightGrey)
  830.         term.write("<>")
  831.     end
  832. end
  833.  
  834. --[[Runs an interface where users can select topics of help. Will return once the user quits the help screen.
  835.     Params: none
  836.     Returns:nil
  837. ]]--
  838. local function drawHelpScreen()
  839.     local selectedHelp = nil
  840.     while true do
  841.         term.setBackgroundColour(colours.lightGrey)
  842.         term.clear()
  843.         if not selectedHelp then
  844.             term.setCursorPos(4, 1)
  845.             term.setTextColour(colours.brown)
  846.             term.write("Available modes (click for info):")
  847.             for i=1,#helpTopics do
  848.                 term.setCursorPos(2, 2 + i)
  849.                 term.setTextColour(colours.black)
  850.                 term.write(helpTopics[i].name)
  851.                 if helpTopics[i].key then
  852.                     term.setTextColour(colours.red)
  853.                     term.write(" ("..helpTopics[i].key..")")
  854.                 end
  855.             end
  856.             term.setCursorPos(4,h)
  857.             term.setTextColour(colours.black)
  858.             term.write("Press any key to exit")
  859.         else
  860.             term.setCursorPos(4,1)
  861.             term.setTextColour(colours.brown)
  862.             term.write(helpTopics[selectedHelp].name)
  863.             if helpTopics[selectedHelp].key then
  864.                 term.setTextColour(colours.red)
  865.                 term.write(" ("..helpTopics[selectedHelp].key..")")
  866.             end
  867.             term.setCursorPos(1,3)
  868.             term.setTextColour(colours.black)
  869.             print(helpTopics[selectedHelp].message.."\n")
  870.             for i=1,#helpTopics[selectedHelp].controls do
  871.                 term.setTextColour(colours.brown)
  872.                 term.write(helpTopics[selectedHelp].controls[i][1].." ")
  873.                 term.setTextColour(colours.black)
  874.                 print(helpTopics[selectedHelp].controls[i][2])
  875.             end
  876.         end
  877.        
  878.         local id,p1,p2,p3 = os.pullEvent()
  879.        
  880.         if id == "timer" then updateTimer(p1)
  881.         elseif id == "key" then
  882.             if selectedHelp then selectedHelp = nil
  883.             else break end
  884.         elseif id == "mouse_click" then
  885.             if not selectedHelp then
  886.                 if p3 >=3 and p3 <= 2+#helpTopics then
  887.                     selectedHelp = p3-2
  888.                 else break end
  889.             else
  890.                 selectedHelp = nil
  891.             end
  892.         end
  893.     end
  894. end
  895.  
  896. --[[Draws a message in the footer bar. A helper for DrawInterface, but can be called for custom messages, if the
  897.     inMenu paramter is set to true while this is being done (remember to set it back when done!)
  898.     Params: message:string = The message to be drawn
  899.     Returns:nil
  900. ]]--
  901. local function drawMessage(message)
  902.     term.setCursorPos(1,h)
  903.     term.setBackgroundColour(colours.lightGrey)
  904.     term.setTextColour(colours.grey)
  905.     term.clearLine()
  906.     term.write(message)
  907. end
  908.  
  909. --[[
  910.             Section: Generic Interfaces
  911. ]]--
  912.  
  913.  
  914. --[[One of my generic text printing methods, printing a message at a specified position with width and offset.
  915.     No colour materials included.
  916.     Params: msg:string = The message to print off-center
  917.             height:number = The starting height of the message
  918.             width:number = The limit as to how many characters long each line may be
  919.             offset:number = The starting width offset of the message
  920.     Returns:number the number of lines used in printing the message
  921. ]]--
  922. local function wprintOffCenter(msg, height, width, offset)
  923.     local inc = 0
  924.     local ops = 1
  925.     while #msg - ops > width do
  926.         local nextspace = 0
  927.         while string.find(msg, " ", ops + nextspace) and
  928.                 string.find(msg, " ", ops + nextspace) - ops < width do
  929.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  930.         end
  931.         local ox,oy = term.getCursorPos()
  932.         term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
  933.         inc = inc + 1
  934.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  935.         ops = ops + nextspace
  936.     end
  937.     term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
  938.     term.write(string.sub(msg, ops))
  939.    
  940.     return inc + 1
  941. end
  942.  
  943. --[[Draws a message that must be clicked on or a key struck to be cleared. No options, so used for displaying
  944.     generic information.
  945.     Params: ctitle:string = The title of the confirm dialogue
  946.             msg:string = The message displayed in the dialogue
  947.     Returns:nil
  948. ]]--
  949. local function displayConfirmDialogue(ctitle, msg)
  950.     local dialogoffset = 8
  951.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  952.     local lines = wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
  953.    
  954.     term.setCursorPos(dialogoffset, 3)
  955.     term.setBackgroundColour(colours.grey)
  956.     term.setTextColour(colours.lightGrey)
  957.     term.write(string.rep(" ", w - dialogoffset * 2))
  958.     term.setCursorPos(dialogoffset + (w - dialogoffset * 2)/2 - #ctitle/2, 3)
  959.     term.write(ctitle)
  960.     term.setTextColour(colours.grey)
  961.     term.setBackgroundColour(colours.lightGrey)
  962.     term.setCursorPos(dialogoffset, 4)
  963.     term.write(string.rep(" ", w - dialogoffset * 2))
  964.     for i=5,5+lines do
  965.         term.setCursorPos(dialogoffset, i)
  966.         term.write(" "..string.rep(" ", w - (dialogoffset) * 2 - 2).." ")
  967.     end
  968.     wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
  969.    
  970.     --In the event of a message, the player hits anything to continue
  971.     while true do
  972.         local id,key = os.pullEvent()
  973.         if id == "timer" then updateTimer(key);
  974.         elseif id == "key" or id == "mouse_click" or id == "mouse_drag" then break end
  975.     end
  976. end
  977.  
  978. --[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
  979.     of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
  980.     Params: x:int = the x position the menu should be displayed at
  981.             y:int = the y position the menu should be displayed at
  982.             options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
  983.     Returns:string the selected menu option.
  984. ]]--
  985. local function displayDropDown(x, y, options)
  986.     inDropDown = true
  987.     --Figures out the dimensions of our thing
  988.     local longestX = #options.name
  989.     for i=1,#options do
  990.         local currVal = options[i]
  991.         if type(currVal) == "table" then currVal = currVal.name end
  992.        
  993.         longestX = math.max(longestX, #currVal)
  994.     end
  995.     local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
  996.     local yOffset = math.max(0, #options - ((h-1) - y))
  997.    
  998.     local clickTimes = 0
  999.     local tid = nil
  1000.     local selection = nil
  1001.     while clickTimes < 2 do
  1002.         drawCanvas()
  1003.         drawInterface()
  1004.        
  1005.         term.setCursorPos(x-xOffset,y-yOffset)
  1006.         term.setBackgroundColour(colours.grey)
  1007.         term.setTextColour(colours.lightGrey)
  1008.         term.write(options.name..string.rep(" ", longestX-#options.name + 2))
  1009.    
  1010.         for i=1,#options do
  1011.             term.setCursorPos(x-xOffset, y-yOffset+i)
  1012.             if i==selection and clickTimes % 2 == 0 then
  1013.                 term.setBackgroundColour(colours.grey)
  1014.                 term.setTextColour(colours.lightGrey)
  1015.             else
  1016.                 term.setBackgroundColour(colours.lightGrey)
  1017.                 term.setTextColour(colours.grey)
  1018.             end
  1019.             local currVal = options[i]
  1020.             if type(currVal) == "table" then
  1021.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
  1022.                 term.setBackgroundColour(colours.grey)
  1023.                 term.setTextColour(colours.lightGrey)
  1024.                 term.write(">")
  1025.             else
  1026.                 term.write(currVal..string.rep(" ", longestX-#currVal + 2))
  1027.             end
  1028.         end
  1029.        
  1030.         local id, p1, p2, p3 = os.pullEvent()
  1031.         if id == "timer" then
  1032.             if p1 == tid then
  1033.                 clickTimes = clickTimes + 1
  1034.                 if clickTimes > 2 then
  1035.                     break
  1036.                 else
  1037.                     tid = os.startTimer(0.1)
  1038.                 end
  1039.             else
  1040.                 updateTimer(p1)
  1041.                 drawCanvas()
  1042.                 drawInterface()
  1043.             end
  1044.         elseif id == "mouse_click" then
  1045.             if p2 >=x-xOffset and p2 <= x-xOffset + longestX + 1 and p3 >= y-yOffset+1 and p3 <= y-yOffset+#options then
  1046.                 selection = p3-(y-yOffset)
  1047.                 tid = os.startTimer(0.1)
  1048.             else
  1049.                 selection = ""
  1050.                 break
  1051.             end
  1052.         end
  1053.     end
  1054.    
  1055.     if type(selection) == "number" then
  1056.         selection = options[selection]
  1057.     end
  1058.    
  1059.     if type(selection) == "string" then
  1060.         inDropDown = false
  1061.         return selection
  1062.     elseif type(selection) == "table" then
  1063.         return displayDropDown(x, y, selection)
  1064.     end
  1065. end
  1066.  
  1067. --[[A custom io.read() function with a few differences- it limits the number of characters being printed,
  1068.     waits a 1/100th of a second so any keys still in the event library are removed before input is read and
  1069.     the timer for the selectionrectangle is continuously updated during the process.
  1070.     Params: lim:int = the number of characters input is allowed
  1071.     Returns:string the inputted string, trimmed of leading and tailing whitespace
  1072. ]]--
  1073. local function readInput(lim)
  1074.     term.setCursorBlink(true)
  1075.  
  1076.     local inputString = ""
  1077.     if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end
  1078.     local ox,oy = term.getCursorPos()
  1079.     --We only get input from the footer, so this is safe. Change if recycling
  1080.     term.setBackgroundColour(colours.lightGrey)
  1081.     term.setTextColour(colours.grey)
  1082.     term.write(string.rep(" ", lim))
  1083.     term.setCursorPos(ox, oy)
  1084.     --As events queue immediately, we may get an unwanted key... this will solve that problem
  1085.     local inputTimer = os.startTimer(0.01)
  1086.     local keysAllowed = false
  1087.    
  1088.     while true do
  1089.         local id,key = os.pullEvent()
  1090.        
  1091.         if keysAllowed then
  1092.             if id == "key" and key == 14 and #inputString > 0 then
  1093.                 inputString = string.sub(inputString, 1, #inputString-1)
  1094.                 term.setCursorPos(ox + #inputString,oy)
  1095.                 term.write(" ")
  1096.             elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then
  1097.                 break
  1098.             elseif id == "key" and key == keys.leftCtrl then
  1099.                 return ""
  1100.             elseif id == "char" and #inputString < lim then
  1101.                 inputString = inputString..key
  1102.             end
  1103.         end
  1104.        
  1105.         if id == "timer" then
  1106.             if key == inputTimer then
  1107.                 keysAllowed = true
  1108.             else
  1109.                 updateTimer(key)
  1110.                 drawCanvas()
  1111.                 drawInterface()
  1112.                 term.setBackgroundColour(colours.lightGrey)
  1113.                 term.setTextColour(colours.grey)
  1114.             end
  1115.         end
  1116.         term.setCursorPos(ox,oy)
  1117.         term.write(inputString)
  1118.         term.setCursorPos(ox + #inputString, oy)
  1119.     end
  1120.    
  1121.     while string.sub(inputString, 1, 1) == " " do
  1122.         inputString = string.sub(inputString, 2, #inputString)
  1123.     end
  1124.     while string.sub(inputString, #inputString, #inputString) == " " do
  1125.         inputString = string.sub(inputString, 1, #inputString-1)
  1126.     end
  1127.     term.setCursorBlink(false)
  1128.    
  1129.     return inputString
  1130. end
  1131.  
  1132. --[[  
  1133.             Section: Image tools
  1134. ]]--
  1135.  
  1136.  
  1137. --[[Copies all pixels beneath the selection rectangle into the image buffer. Empty buffers are converted to nil.
  1138.     Params: removeImage:bool = true if the image is to be erased after copying, false otherwise
  1139.     Returns:nil
  1140. ]]--
  1141. local function copyToBuffer(removeImage)
  1142.     buffer = { width = selectrect.x2 - selectrect.x1 + 1, height = selectrect.y2 - selectrect.y1 + 1, contents = { } }
  1143.    
  1144.     local containsSomething = false
  1145.     for y=1,buffer.height do
  1146.         buffer.contents[y] = { }
  1147.         local f,l = sFrame,sFrame
  1148.         if record then f,l = 1, framecount end
  1149.        
  1150.         for fra = f,l do
  1151.             if frames[fra][selectrect.y1 + y - 1] then
  1152.                 for x=1,buffer.width do
  1153.                     buffer.contents[y][x] = frames[sFrame][selectrect.y1 + y - 1][selectrect.x1 + x - 1]
  1154.                     if removeImage then frames[fra][selectrect.y1 + y - 1][selectrect.x1 + x - 1] = nil end
  1155.                     if buffer.contents[y][x] then containsSomething = true end
  1156.                 end
  1157.             end
  1158.         end
  1159.     end
  1160.     --I don't classify an empty buffer as a real buffer- confusing to the user.
  1161.     if not containsSomething then buffer = nil end
  1162. end
  1163.  
  1164. --[[Replaces all pixels under the selection rectangle with the image buffer (or what can be seen of it). Record-dependent.
  1165.     Params: removeBuffer:bool = true if the buffer is to be emptied after copying, false otherwise
  1166.     Returns:nil
  1167. ]]--
  1168. local function copyFromBuffer(removeBuffer)
  1169.     if not buffer then return end
  1170.  
  1171.     for y = 1, math.min(buffer.height,selectrect.y2-selectrect.y1+1) do
  1172.         local f,l = sFrame, sFrame
  1173.         if record then f,l = 1, framecount end
  1174.        
  1175.         for fra = f,l do
  1176.             if not frames[fra][selectrect.y1+y-1] then frames[fra][selectrect.y1+y-1] = { } end
  1177.             for x = 1, math.min(buffer.width,selectrect.x2-selectrect.x1+1) do
  1178.                 frames[fra][selectrect.y1+y-1][selectrect.x1+x-1] = buffer.contents[y][x]
  1179.             end
  1180.         end
  1181.     end
  1182.    
  1183.     if removeBuffer then buffer = nil end
  1184. end
  1185.  
  1186. --[[Moves the entire image (or entire animation) to the specified coordinates. Record-dependent.
  1187.     Params: newx:int = the X coordinate to move the image to
  1188.             newy:int = the Y coordinate to move the image to
  1189.     Returns:nil
  1190. ]]--
  1191. local function moveImage(newx,newy)
  1192.     if not leflim or not toplim then return end
  1193.     if newx <=0 or newy <=0 then return end
  1194.     local f,l = sFrame,sFrame
  1195.     if record then f,l = 1,framecount end
  1196.    
  1197.     for i=f,l do
  1198.         local newlines = { }
  1199.         for y,line in pairs(frames[i]) do
  1200.             newlines[y-toplim+newy] = { }
  1201.             for x,char in pairs(line) do
  1202.                 newlines[y-toplim+newy][x-leflim+newx] = char
  1203.             end
  1204.         end
  1205.         frames[i] = newlines
  1206.     end
  1207. end
  1208.  
  1209. --[[Prompts the user to clear the current frame or all frames. Record-dependent.,
  1210.     Params: none
  1211.     Returns:nil
  1212. ]]--
  1213. local function clearImage()
  1214.     inMenu = true
  1215.     if not animated then
  1216.         drawMessage("Clear image? Y/N: ")
  1217.     elseif record then
  1218.         drawMessage("Clear ALL frames? Y/N: ")
  1219.     else
  1220.         drawMessage("Clear current frame? Y/N :")
  1221.     end
  1222.     if string.find(string.upper(readInput(1)), "Y") then
  1223.         local f,l = sFrame,sFrame
  1224.         if record then f,l = 1,framecount end
  1225.        
  1226.         for i=f,l do
  1227.             frames[i] = { }
  1228.         end
  1229.     end
  1230.     inMenu = false
  1231. end
  1232.  
  1233. --[[A recursively called method (watch out for big calls!) in which every pixel of a set colour is
  1234.     changed to another colour. Does not work on the nil colour, for obvious reasons.
  1235.     Params: x:int = The X coordinate of the colour to flood-fill
  1236.             y:int = The Y coordinate of the colour to flood-fill
  1237.             targetColour:colour = the colour that is being flood-filled
  1238.             newColour:colour = the colour with which to replace the target colour
  1239.     Returns:nil
  1240. ]]--
  1241. local function floodFill(x, y, targetColour, newColour)
  1242.     if not newColour or not targetColour then return end
  1243.     local nodeList = { }
  1244.    
  1245.     table.insert(nodeList, {x = x, y = y})
  1246.    
  1247.     while #nodeList > 0 do
  1248.         local node = nodeList[1]
  1249.         if frames[sFrame][node.y] and frames[sFrame][node.y][node.x] == targetColour then
  1250.             frames[sFrame][node.y][node.x] = newColour
  1251.             table.insert(nodeList, { x = node.x + 1, y = node.y})
  1252.             table.insert(nodeList, { x = node.x, y = node.y + 1})
  1253.             if x > 1 then table.insert(nodeList, { x = node.x - 1, y = node.y}) end
  1254.             if y > 1 then table.insert(nodeList, { x = node.x, y = node.y - 1}) end
  1255.         end
  1256.         table.remove(nodeList, 1)
  1257.     end
  1258. end
  1259.  
  1260. --[[  
  1261.             Section: Animation Tools  
  1262. ]]--
  1263.  
  1264. --[[Enters play mode, allowing the animation to play through. Interface is restricted to allow this,
  1265.     and method only leaves once the player leaves play mode.
  1266.     Params: none
  1267.     Returns:nil
  1268. ]]--
  1269. local function playAnimation()
  1270.     state = "play"
  1271.     selectedrect = nil
  1272.    
  1273.     local animt = os.startTimer(animtime)
  1274.     repeat
  1275.         drawCanvas()
  1276.         drawInterface()
  1277.        
  1278.         local id,key,_,y = os.pullEvent()
  1279.        
  1280.         if id=="timer" then
  1281.             if key == animt then
  1282.                 animt = os.startTimer(animtime)
  1283.                 sFrame = (sFrame % framecount) + 1
  1284.             else
  1285.                 updateTimer(key)
  1286.             end
  1287.         elseif id=="key" then
  1288.             if key == keys.comma and animtime > 0.1 then animtime = animtime - 0.05
  1289.             elseif key == keys.period and animtime < 0.5 then animtime = animtime + 0.05
  1290.             elseif key == keys.space then state = "paint" end
  1291.         elseif id=="mouse_click" and y == h then
  1292.             state = "paint"
  1293.         end
  1294.     until state ~= "play"
  1295.     os.startTimer(0.5)
  1296. end
  1297.  
  1298. --[[Changes the selected frame (sFrame) to the chosen frame. If this frame is above the framecount,
  1299.     additional frames are created with a copy of the image on the selected frame.
  1300.     Params: newframe:int = the new frame to move to
  1301.     Returns:nil
  1302. ]]--
  1303. local function changeFrame(newframe)
  1304.     inMenu = true
  1305.     if not tonumber(newframe) then
  1306.         term.setCursorPos(1,h)
  1307.         term.setBackgroundColour(colours.lightGrey)
  1308.         term.setTextColour(colours.grey)
  1309.         term.clearLine()
  1310.    
  1311.         term.write("Go to frame: ")
  1312.         newframe = tonumber(readInput(2))
  1313.         if not newframe or newframe <= 0 then
  1314.             inMenu = false
  1315.             return
  1316.         end
  1317.     elseif newframe <= 0 then return end
  1318.    
  1319.     if newframe > framecount then
  1320.         for i=framecount+1,newframe do
  1321.             frames[i] = {}
  1322.             for y,line in pairs(frames[sFrame]) do
  1323.                 frames[i][y] = { }
  1324.                 for x,v in pairs(line) do
  1325.                     frames[i][y][x] = v
  1326.                 end
  1327.             end
  1328.         end
  1329.         framecount = newframe
  1330.     end
  1331.     sFrame = newframe
  1332.     inMenu = false
  1333. end
  1334.  
  1335. --[[Removes every frame leading after the frame passed in
  1336.     Params: frame:int the non-inclusive lower bounds of the delete
  1337.     Returns:nil
  1338. ]]--
  1339. local function removeFramesAfter(frame)
  1340.     inMenu = true
  1341.     if frame==framecount then return end
  1342.     drawMessage("Remove frames "..(frame+1).."/"..framecount.."? Y/N :")
  1343.     local answer = string.upper(readInput(1))
  1344.    
  1345.     if string.find(answer, string.upper("Y")) ~= 1 then
  1346.         inMenu = false
  1347.         return
  1348.     end
  1349.    
  1350.     for i=frame+1, framecount do
  1351.         frames[i] = nil
  1352.     end
  1353.     framecount = frame
  1354.     inMenu = false
  1355. end
  1356.  
  1357. --[[
  1358.             Section: Printing Tools
  1359. ]]--
  1360.  
  1361. --[[Constructs a new facing to the left of the current facing
  1362.     Params: curx:number = The facing on the X axis
  1363.             curz:number = The facing on the Z axis
  1364.             hand:string = The hand of the axis ("right" or "left")
  1365.     Returns:number,number = the new facing on the X and Z axis after a left turn
  1366. ]]--
  1367. local function getLeft(curx, curz)
  1368.     local hand = "left"
  1369.     if layering == "up" then hand = "right" end
  1370.    
  1371.     if hand == "right" then
  1372.         if curx == 1 then return 0,-1 end
  1373.         if curx == -1 then return 0,1 end
  1374.         if curz == 1 then return 1,0 end
  1375.         if curz == -1 then return -1,0 end
  1376.     else
  1377.         if curx == 1 then return 0,1 end
  1378.         if curx == -1 then return 0,-1 end
  1379.         if curz == 1 then return -1,0 end
  1380.         if curz == -1 then return 1,0 end
  1381.     end
  1382. end
  1383.  
  1384. --[[Constructs a new facing to the right of the current facing
  1385.     Params: curx:number = The facing on the X axis
  1386.             curz:number = The facing on the Z axis
  1387.             hand:string = The hand of the axis ("right" or "left")
  1388.     Returns:number,number = the new facing on the X and Z axis after a right turn
  1389. ]]--
  1390. local function getRight(curx, curz)
  1391.     local hand = "left"
  1392.     if layering == "up" then hand = "right" end
  1393.    
  1394.     if hand == "right" then
  1395.         if curx == 1 then return 0,1 end
  1396.         if curx == -1 then return 0,-1 end
  1397.         if curz == 1 then return -1,0 end
  1398.         if curz == -1 then return 1,0 end
  1399.     else
  1400.         if curx == 1 then return 0,-1 end
  1401.         if curx == -1 then return 0,1 end
  1402.         if curz == 1 then return 1,0 end
  1403.         if curz == -1 then return -1,0 end
  1404.     end
  1405. end
  1406.  
  1407.  
  1408. --[[Sends out a rednet signal requesting local printers, and will listen for any responses. Printers found are added to the
  1409.     printerList (for ID's) and printerNames (for names)
  1410.     Params: nil
  1411.     Returns:nil
  1412. ]]--
  1413. local function locatePrinters()
  1414.     printerList = { }
  1415.     printerNames = { name = "Printers" }
  1416.     local oldState = state
  1417.     state = "Locating printers, please wait...   "
  1418.     drawCanvas()
  1419.     drawInterface()
  1420.     state = oldState
  1421.    
  1422.     local modemOpened = false
  1423.     for k,v in pairs(rs.getSides()) do
  1424.         if peripheral.isPresent(v) and peripheral.getType(v) == "modem" then
  1425.             rednet.open(v)
  1426.             modemOpened = true
  1427.             break
  1428.         end
  1429.     end
  1430.    
  1431.     if not modemOpened then
  1432.         displayConfirmDialogue("Modem not found!", "No modem peripheral. Must have network modem to locate printers.")
  1433.         return false
  1434.     end
  1435.    
  1436.     rednet.broadcast("$3DPRINT IDENTIFY")
  1437.    
  1438.     while true do
  1439.         local id, msg = rsTimeReceive(1)
  1440.        
  1441.         if not id then break end
  1442.         if string.find(msg, "$3DPRINT IDACK") == 1 then
  1443.             msg = string.gsub(msg, "$3DPRINT IDACK ", "")
  1444.             table.insert(printerList, id)
  1445.             table.insert(printerNames, msg)
  1446.         end
  1447.     end
  1448.    
  1449.     if #printerList == 0 then
  1450.         displayConfirmDialogue("Printers not found!", "No active printers found in proximity of this computer.")
  1451.         return false
  1452.     else
  1453.         return true
  1454.     end
  1455. end
  1456.  
  1457. --[[Sends a request to the printer. Waits on a response and updates the state of the application accordingly.
  1458.     Params: command:string the command to send
  1459.             param:string a parameter to send, if any
  1460.     Returns:nil
  1461. ]]--
  1462. local function sendPC(command,param)
  1463.     local msg = "$PC "..command
  1464.     if param then msg = msg.." "..param end
  1465.     rednet.send(printerList[selectedPrinter], msg)
  1466.    
  1467.     while true do
  1468.         local id,key = rsTimeReceive()
  1469.         if id == printerList[selectedPrinter] then
  1470.             if key == "$3DPRINT ACK" then
  1471.                 break
  1472.             elseif key == "$3DPRINT DEP" then
  1473.                 displayConfirmDialogue("Printer Empty", "The printer has exhasted a material. Please refill slot "..param..
  1474.                     ", and click this message when ready to continue.")
  1475.                 rednet.send(printerList[selectedPrinter], msg)
  1476.             end
  1477.         end
  1478.     end
  1479.    
  1480.     --Changes to position are handled after the event has been successfully completed
  1481.     if command == "FW" then
  1482.         px = px + pfx
  1483.         pz = pz + pfz
  1484.     elseif command == "BK" then
  1485.         px = px - pfx
  1486.         pz = pz - pfz
  1487.     elseif command == "UP" then
  1488.         if layering == "up" then
  1489.             py = py + 1
  1490.         else
  1491.             py = py - 1
  1492.         end
  1493.     elseif command == "DW" then
  1494.         if layering == "up" then
  1495.             py = py - 1
  1496.         else    
  1497.             py = py + 1
  1498.         end
  1499.     elseif command == "TL" then
  1500.         pfx,pfz = getLeft(pfx,pfz)
  1501.     elseif command == "TR" then
  1502.         pfx,pfz = getRight(pfx,pfz)
  1503.     elseif command == "TU" then
  1504.         pfx = -pfx
  1505.         pfz = -pfz
  1506.     end
  1507.    
  1508.     drawCanvas()
  1509.     drawInterface()
  1510. end
  1511.  
  1512. --[[A printing function that commands the printer to turn to face the desired direction, if it is not already doing so
  1513.     Params: desx:number = the normalized x direction to face
  1514.             desz:number = the normalized z direction to face
  1515.     Returns:nil
  1516. ]]--
  1517. local function turnToFace(desx,desz)
  1518.     if desx ~= 0 then
  1519.         if pfx ~= desx then
  1520.             local temppfx,_ = getLeft(pfx,pfz)
  1521.             if temppfx == desx then
  1522.                 sendPC("TL")
  1523.             elseif temppfx == -desx then
  1524.                 sendPC("TR")
  1525.             else
  1526.                 sendPC("TU")
  1527.             end
  1528.         end
  1529.     else
  1530.         print("on the z axis")
  1531.         if pfz ~= desz then
  1532.             local _,temppfz = getLeft(pfx,pfz)
  1533.             if temppfz == desz then
  1534.                 sendPC("TL")
  1535.             elseif temppfz == -desz then
  1536.                 sendPC("TR")
  1537.             else
  1538.                 sendPC("TU")
  1539.             end
  1540.         end
  1541.     end
  1542. end
  1543.  
  1544. --[[Performs the print
  1545.     Params: nil
  1546.     Returns:nil
  1547. ]]--
  1548. local function performPrint()
  1549.     state = "active print"
  1550.     if layering == "up" then
  1551.         --An up layering starts our builder bot on the bottom left corner of our build
  1552.         px,py,pz = leflim, 0, botlim + 1
  1553.         pfx,pfz = 0,-1
  1554.        
  1555.         --We move him forward and up a bit from his original position.
  1556.         sendPC("FW")
  1557.         sendPC("UP")
  1558.         --For each layer that needs to be completed, we go up by one each time
  1559.         for layers=1,#frames do
  1560.             --We first decide if we're going forwards or back, depending on what side we're on
  1561.             local rowbot,rowtop,rowinc = nil,nil,nil
  1562.             if pz == botlim then
  1563.                 rowbot,rowtop,rowinc = botlim,toplim,-1
  1564.             else
  1565.                 rowbot,rowtop,rowinc = toplim,botlim,1
  1566.             end
  1567.            
  1568.             for rows = rowbot,rowtop,rowinc do
  1569.                 --Then we decide if we're going left or right, depending on what side we're on
  1570.                 local linebot,linetop,lineinc = nil,nil,nil
  1571.                 if px == leflim then
  1572.                     --Facing from the left side has to be easterly- it's changed here
  1573.                     turnToFace(1,0)
  1574.                     linebot,linetop,lineinc = leflim,riglim,1
  1575.                 else
  1576.                     --Facing from the right side has to be westerly- it's changed here
  1577.                     turnToFace(-1,0)
  1578.                     linebot,linetop,lineinc = riglim,leflim,-1
  1579.                 end
  1580.                
  1581.                 for lines = linebot,linetop,lineinc do
  1582.                     --We move our turtle forward, placing the right material at each step
  1583.                     local material = frames[py][pz][px]
  1584.                     if material then
  1585.                         material = math.log10(frames[py][pz][px])/math.log10(2) + 1
  1586.                         sendPC("SS", material)
  1587.                         sendPC("PD")
  1588.                     end
  1589.                     if lines ~= linetop then
  1590.                         sendPC("FW")
  1591.                     end
  1592.                 end
  1593.                
  1594.                 --The printer then has to do a U-turn, depending on which way he's facing and
  1595.                 --which way he needs to go
  1596.                 local temppfx,temppfz = getLeft(pfx,pfz)
  1597.                 if temppfz == rowinc and rows ~= rowtop then
  1598.                     sendPC("TL")
  1599.                     sendPC("FW")
  1600.                     sendPC("TL")
  1601.                 elseif temppfz == -rowinc and rows ~= rowtop then
  1602.                     sendPC("TR")
  1603.                     sendPC("FW")
  1604.                     sendPC("TR")
  1605.                 end
  1606.             end
  1607.             --Now at the end of a run he does a 180 and moves up to begin the next part of the print
  1608.             sendPC("TU")
  1609.             if layers ~= #frames then
  1610.                 sendPC("UP")
  1611.             end
  1612.         end
  1613.         --All done- now we head back to where we started.
  1614.         if px ~= leflim then
  1615.             turnToFace(-1,0)
  1616.             while px ~= leflim do
  1617.                 sendPC("FW")
  1618.             end
  1619.         end
  1620.         if pz ~= botlim then
  1621.             turnToFace(0,-1)
  1622.             while pz ~= botlim do
  1623.                 sendPC("BK")
  1624.             end
  1625.         end
  1626.         turnToFace(0,-1)
  1627.         sendPC("BK")
  1628.         while py > 0 do
  1629.             sendPC("DW")
  1630.         end
  1631.     else
  1632.         --The front facing is at the top-left corner, facing south not north
  1633.         px,py,pz = leflim, botlim, 1
  1634.         pfx,pfz = 0,1
  1635.         --We move the printer to the last layer- he prints from the back forwards
  1636.         while pz < #frames do
  1637.             sendPC("FW")
  1638.         end
  1639.        
  1640.         --For each layer in the frame we build our wall, the move back
  1641.         for layers = 1,#frames do
  1642.             --We first decide if we're going left or right based on our position
  1643.             local rowbot,rowtop,rowinc = nil,nil,nil
  1644.             if px == leflim then
  1645.                 rowbot,rowtop,rowinc = leflim,riglim,1
  1646.             else
  1647.                 rowbot,rowtop,rowinc = riglim,leflim,-1
  1648.             end
  1649.            
  1650.             for rows = rowbot,rowtop,rowinc do
  1651.                 --Then we decide if we're going up or down, depending on our given altitude
  1652.                 local linebot,linetop,lineinc = nil,nil,nil
  1653.                 if py == botlim then
  1654.                     linebot,linetop,lineinc = botlim,toplim,-1
  1655.                 else
  1656.                     linebot,linetop,lineinc = toplim,botlim,1
  1657.                 end
  1658.                
  1659.                 for lines = linebot,linetop,lineinc do
  1660.                 --We move our turtle up/down, placing the right material at each step
  1661.                     local material = frames[pz][py][px]
  1662.                     if material then
  1663.                         material = math.log10(frames[pz][py][px])/math.log10(2) + 1
  1664.                         sendPC("SS", material)
  1665.                         sendPC("PF")
  1666.                     end
  1667.                     if lines ~= linetop then
  1668.                         if lineinc == 1 then sendPC("DW")
  1669.                         else sendPC("UP") end
  1670.                     end
  1671.                 end
  1672.                    
  1673.                 if rows ~= rowtop then
  1674.                     turnToFace(rowinc,0)
  1675.                     sendPC("FW")
  1676.                     turnToFace(0,1)
  1677.                 end
  1678.             end
  1679.            
  1680.             if layers ~= #frames then
  1681.                 sendPC("TU")
  1682.                 sendPC("FW")
  1683.                 sendPC("TU")
  1684.             end
  1685.         end
  1686.         --He's easy to reset
  1687.         while px ~= leflim do
  1688.             turnToFace(-1,0)
  1689.             sendPC("FW")
  1690.         end
  1691.         turnToFace(0,1)
  1692.     end
  1693.    
  1694.     sendPC("DE")
  1695.    
  1696.     displayConfirmDialogue("Print complete", "The 3D print was successful.")
  1697. end
  1698.  
  1699. --[[  
  1700.             Section: Interface  
  1701. ]]--
  1702.  
  1703. --[[Runs the printing interface. Allows users to find/select a printer, the style of printing to perform and to begin the operation
  1704.     Params: none
  1705.     Returns:boolean true if printing was started, false otherwse
  1706. ]]--
  1707. local function runPrintInterface()
  1708.     if not locatePrinters() then
  1709.         return false
  1710.     end
  1711.    
  1712.     calculateMaterials()
  1713.     layering = "up"
  1714.     requirementsDisplayed = false
  1715.     selectedPrinter = 1
  1716.     while true do
  1717.         drawCanvas()
  1718.         term.setBackgroundColour(colours.lightGrey)
  1719.         for i=1,10 do
  1720.             term.setCursorPos(1,i)
  1721.             term.clearLine()
  1722.         end
  1723.         drawInterface()
  1724.         term.setBackgroundColour(colours.lightGrey)
  1725.         term.setTextColour(colours.black)
  1726.        
  1727.         local msg = "3D Printing"
  1728.         term.setCursorPos(w/2-#msg/2 - 2, 1)
  1729.         term.write(msg)
  1730.         term.setBackgroundColour(colours.grey)
  1731.         term.setTextColour(colours.lightGrey)
  1732.         if(requirementsDisplayed) then
  1733.             msg = "Count:"
  1734.         else
  1735.             msg = " Slot:"
  1736.         end
  1737.         term.setCursorPos(w-3-#msg, 1)
  1738.         term.write(msg)
  1739.         term.setBackgroundColour(colours.lightGrey)
  1740.         term.setTextColour(colours.black)
  1741.        
  1742.         term.setCursorPos(7, 2)
  1743.         term.write("Layering")
  1744.         drawPictureTable(layerUpIcon, 3, 3, colours.white)
  1745.         drawPictureTable(layerForwardIcon, 12, 3, colours.white)
  1746.         if layering == "up" then
  1747.             term.setBackgroundColour(colours.red)
  1748.         else
  1749.             term.setBackgroundColour(colours.lightGrey)
  1750.         end
  1751.         term.setCursorPos(3, 9)
  1752.         term.write("Upwards")
  1753.         if layering == "forward" then
  1754.             term.setBackgroundColour(colours.red)
  1755.         else
  1756.             term.setBackgroundColour(colours.lightGrey)
  1757.         end
  1758.         term.setCursorPos(12, 9)
  1759.         term.write("Forward")
  1760.        
  1761.         term.setBackgroundColour(colours.lightGrey)
  1762.         term.setTextColour(colours.black)
  1763.         term.setCursorPos(31, 2)
  1764.         term.write("Printer ID")
  1765.         term.setCursorPos(33, 3)
  1766.         if #printerList > 1 then
  1767.             term.setBackgroundColour(colours.grey)
  1768.             term.setTextColour(colours.lightGrey)
  1769.         else
  1770.             term.setTextColour(colours.red)
  1771.         end
  1772.         term.write(" "..printerNames[selectedPrinter].." ")
  1773.        
  1774.         term.setBackgroundColour(colours.grey)
  1775.         term.setTextColour(colours.lightGrey)
  1776.         term.setCursorPos(25, 10)
  1777.         term.write(" Cancel ")
  1778.         term.setCursorPos(40, 10)
  1779.         term.write(" Print ")
  1780.        
  1781.         local id, p1, p2, p3 = os.pullEvent()
  1782.        
  1783.         if id == "timer" then
  1784.             updateTimer(p1)
  1785.         elseif id == "mouse_click" then
  1786.             --Layering Buttons
  1787.             if p2 >= 3 and p2 <= 9 and p3 >= 3 and p3 <= 9 then
  1788.                 layering = "up"
  1789.             elseif p2 >= 12 and p2 <= 18 and p3 >= 3 and p3 <= 9 then
  1790.                 layering = "forward"
  1791.             --Count/Slot
  1792.             elseif p2 >= w - #msg - 3 and p2 <= w - 3 and p3 == 1 then
  1793.                 requirementsDisplayed = not requirementsDisplayed
  1794.             --Printer ID
  1795.             elseif p2 >= 33 and p2 <= 33 + #printerNames[selectedPrinter] and p3 == 3 and #printerList > 1 then
  1796.                 local chosenName = displayDropDown(33, 3, printerNames)
  1797.                 for i=1,#printerNames do
  1798.                     if printerNames[i] == chosenName then
  1799.                         selectedPrinter = i
  1800.                         break;
  1801.                     end
  1802.                 end
  1803.             --Print and Cancel
  1804.             elseif p2 >= 25 and p2 <= 32 and p3 == 10 then
  1805.                 break
  1806.             elseif p2 >= 40 and p2 <= 46 and p3 == 10 then
  1807.                 rednet.send(printerList[selectedPrinter], "$3DPRINT ACTIVATE")
  1808.                 ready = false
  1809.                 while true do
  1810.                     local id,msg = rsTimeReceive(10)
  1811.                    
  1812.                     if id == printerList[selectedPrinter] and msg == "$3DPRINT ACTACK" then
  1813.                         ready = true
  1814.                         break
  1815.                     end
  1816.                 end
  1817.                 if ready then
  1818.                     performPrint()
  1819.                     break
  1820.                 else
  1821.                     displayConfirmDialogue("Printer Didn't Respond", "The printer didn't respond to the activation command. Check to see if it's online")
  1822.                 end
  1823.             end
  1824.         end
  1825.     end
  1826.     state = "paint"
  1827. end
  1828.  
  1829. --[[This function changes the current paint program to another tool or mode, depending on user input. Handles
  1830.     any necessary changes in logic involved in that.
  1831.     Params: mode:string = the name of the mode to change to
  1832.     Returns:nil
  1833. ]]--
  1834. local function performSelection(mode)
  1835.     if not mode or mode == "" then return
  1836.    
  1837.     elseif mode == "help" then
  1838.         drawHelpScreen()
  1839.        
  1840.     elseif mode == "blueprint on" then
  1841.         blueprint = true
  1842.         ddModes[2][3] = "blueprint off"
  1843.        
  1844.     elseif mode == "blueprint off" then
  1845.         blueprint = false
  1846.         ddModes[2][3] = "blueprint on"
  1847.        
  1848.     elseif mode == "layers on" then
  1849.         layerDisplay = true
  1850.         ddModes[2][4] = "layers off"
  1851.    
  1852.     elseif mode == "layers off" then
  1853.         layerDisplay = false
  1854.         ddModes[2][4] = "layers on"
  1855.    
  1856.     elseif mode == "direction on" then
  1857.         printDirection = true
  1858.         ddModes[2][5] = "direction off"
  1859.        
  1860.     elseif mode == "direction off" then
  1861.         printDirection = false
  1862.         ddModes[2][5] = "direction on"
  1863.    
  1864.     elseif mode == "go to" then
  1865.         changeFrame()
  1866.    
  1867.     elseif mode == "remove" then
  1868.         removeFramesAfter(sFrame)
  1869.    
  1870.     elseif mode == "play" then
  1871.         playAnimation()
  1872.        
  1873.     elseif mode == "copy" then
  1874.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  1875.             copyToBuffer(false)
  1876.         end
  1877.    
  1878.     elseif mode == "cut" then
  1879.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  1880.             copyToBuffer(true)
  1881.         end
  1882.        
  1883.     elseif mode == "paste" then
  1884.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  1885.             copyFromBuffer(false)
  1886.         end
  1887.        
  1888.     elseif mode == "hide" then
  1889.         selectrect = nil
  1890.         if state == "select" then state = "corner select" end
  1891.        
  1892.     elseif mode == "alpha to left" then
  1893.         if lSel then alphaC = lSel end
  1894.        
  1895.     elseif mode == "alpha to right" then
  1896.         if rSel then alphaC = rSel end
  1897.        
  1898.     elseif mode == "record" then
  1899.         record = not record
  1900.        
  1901.     elseif mode == "clear" then
  1902.         if state=="select" then buffer = nil
  1903.         else clearImage() end
  1904.    
  1905.     elseif mode == "select" then
  1906.         if state=="corner select" or state=="select" then
  1907.             state = "paint"
  1908.         elseif selectrect and selectrect.x1 ~= selectrect.x2 then
  1909.             state = "select"
  1910.         else
  1911.             state = "corner select"
  1912.         end
  1913.        
  1914.     elseif mode == "print" then
  1915.         state = "print"
  1916.         runPrintInterface()
  1917.         state = "paint"
  1918.        
  1919.     elseif mode == "save" then
  1920.         if animated then saveNFA(sPath)
  1921.         else saveNFP(sPath) end
  1922.        
  1923.     elseif mode == "exit" then
  1924.         isRunning = false
  1925.    
  1926.     elseif mode ~= state then state = mode
  1927.     else state = "paint"
  1928.     end
  1929. end
  1930.  
  1931. --[[The main function of the program, reads and handles all events and updates them accordingly. Mode changes,
  1932.     painting to the canvas and general selections are done here.
  1933.     Params: none
  1934.     Returns:nil
  1935. ]]--
  1936. local function handleEvents()
  1937.     recttimer = os.startTimer(0.5)
  1938.     while isRunning do
  1939.         drawCanvas()
  1940.         drawInterface()
  1941.         local id,p1,p2,p3 = os.pullEvent()
  1942.         if id=="timer" then
  1943.             updateTimer(p1)
  1944.         elseif id=="mouse_click" or id=="mouse_drag" then
  1945.             if p2 >=w-1 and p3 < #column+1 then
  1946.                 if p1==1 then lSel = column[p3]
  1947.                 else rSel = column[p3] end
  1948.             elseif p2 >=w-1 and p3==#column+1 then
  1949.                 if p1==1 then lSel = nil
  1950.                 else rSel = nil end
  1951.             elseif p2==w-1 and p3==h and animated then
  1952.                 changeFrame(sFrame-1)
  1953.             elseif p2==w and p3==h and animated then
  1954.                 changeFrame(sFrame+1)
  1955.             elseif p2 < w-10 and p3==h then
  1956.                 local sel = displayDropDown(1, h-1, ddModes)
  1957.                 performSelection(sel)
  1958.             elseif p2 < w-1 and p3 <= h-1 then
  1959.                 if state=="pippette" then
  1960.                     if p1==1 then
  1961.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1962.                             lSel = frames[sFrame][p3+sy][p2+sx]
  1963.                         end
  1964.                     elseif p1==2 then
  1965.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1966.                             rSel = frames[sFrame][p3+sy][p2+sx]
  1967.                         end
  1968.                     end
  1969.                 elseif state=="move" then
  1970.                     updateImageLims(record)
  1971.                     moveImage(p2,p3)
  1972.                 elseif state=="flood" then
  1973.                     if p1 == 1 and lSel and frames[sFrame][p3+sy]  then
  1974.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],lSel)
  1975.                     elseif p1 == 2 and rSel and frames[sFrame][p3+sy] then
  1976.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],rSel)
  1977.                     end
  1978.                 elseif state=="corner select" then
  1979.                     if not selectrect then
  1980.                         selectrect = { x1=p2+sx, x2=p2+sx, y1=p3+sy, y2=p3+sy }
  1981.                     elseif selectrect.x1 ~= p2+sx and selectrect.y1 ~= p3+sy then
  1982.                         if p2+sx<selectrect.x1 then selectrect.x1 = p2+sx
  1983.                         else selectrect.x2 = p2+sx end
  1984.                        
  1985.                         if p3+sy<selectrect.y1 then selectrect.y1 = p3+sy
  1986.                         else selectrect.y2 = p3+sy end
  1987.                        
  1988.                         state = "select"
  1989.                     end
  1990.                 elseif state=="select" then
  1991.                     if p1 == 1 then
  1992.                         local swidth = selectrect.x2 - selectrect.x1
  1993.                         local sheight = selectrect.y2 - selectrect.y1
  1994.                    
  1995.                         selectrect.x1 = p2 + sx
  1996.                         selectrect.y1 = p3 + sy
  1997.                         selectrect.x2 = p2 + swidth + sx
  1998.                         selectrect.y2 = p3 + sheight + sy
  1999.                     elseif p1 == 2 and p2 < w-2 and p3 < h-1 then
  2000.                         inMenu = true
  2001.                         local sel = displayDropDown(p2, p3, srModes)
  2002.                         inMenu = false
  2003.                         performSelection(sel)
  2004.                     end
  2005.                 else
  2006.                     local f,l = sFrame,sFrame
  2007.                     if record then f,l = 1,framecount end
  2008.                     local bwidth = 0
  2009.                     if state == "brush" then bwidth = brushsize-1 end
  2010.                
  2011.                     for i=f,l do
  2012.                         for x = math.max(1,p2+sx-bwidth),p2+sx+bwidth do
  2013.                             for y = math.max(1,p3+sy-bwidth), p3+sy+bwidth do
  2014.                                 if math.abs(x - (p2+sx)) + math.abs(y - (p3+sy)) <= bwidth then
  2015.                                     if not frames[i][y] then frames[i][y] = {} end
  2016.                                     if p1==1 then frames[i][y][x] = lSel
  2017.                                     else frames[i][y][x] = rSel end
  2018.                                 end
  2019.                             end
  2020.                         end
  2021.                     end
  2022.                 end
  2023.             end
  2024.         elseif id=="key" then
  2025.             if p1==keys.leftCtrl then
  2026.                 local sel = displayDropDown(1, h-1, ddModes[#ddModes])
  2027.                 performSelection(sel)
  2028.             elseif p1==keys.leftAlt then
  2029.                 local sel = displayDropDown(1, h-1, ddModes[1])
  2030.                 performSelection(sel)
  2031.             elseif p1==keys.h then
  2032.                 performSelection("help")
  2033.             elseif p1==keys.x then
  2034.                 performSelection("cut")
  2035.             elseif p1==keys.c then
  2036.                 performSelection("copy")
  2037.             elseif p1==keys.v then
  2038.                 performSelection("paste")
  2039.             elseif p1==keys.z then
  2040.                 performSelection("clear")
  2041.             elseif p1==keys.s then
  2042.                 performSelection("select")
  2043.             elseif p1==keys.tab then
  2044.                 performSelection("hide")
  2045.             elseif p1==keys.q then
  2046.                 performSelection("alpha to left")
  2047.             elseif p1==keys.w then
  2048.                 performSelection("alpha to right")
  2049.             elseif p1==keys.f then
  2050.                 performSelection("flood")
  2051.             elseif p1==keys.b then
  2052.                 performSelection("brush")
  2053.             elseif p1==keys.m then
  2054.                 performSelection("move")
  2055.             elseif p1==keys.backslash and animated then
  2056.                 performSelection("record")
  2057.             elseif p1==keys.p then
  2058.                 performSelection("pippette")
  2059.             elseif p1==keys.g and animated then
  2060.                 performSelection("go to")
  2061.             elseif p1==keys.period and animated then
  2062.                 changeFrame(sFrame+1)
  2063.             elseif p1==keys.comma and animated then
  2064.                 changeFrame(sFrame-1)
  2065.             elseif p1==keys.r and animated then
  2066.                 performSelection("remove")
  2067.             elseif p1==keys.space and animated then
  2068.                 performSelection("play")
  2069.             elseif p1==keys.left then
  2070.                 if state == "move" then
  2071.                     updateImageLims(record)
  2072.                     moveImage(leflim-1,toplim)
  2073.                 elseif state=="select" and selectrect.x1 > 1 then
  2074.                     selectrect.x1 = selectrect.x1-1
  2075.                     selectrect.x2 = selectrect.x2-1
  2076.                 elseif sx > 0 then sx=sx-1 end
  2077.             elseif p1==keys.right then
  2078.                 if state == "move" then
  2079.                     updateImageLims(record)
  2080.                     moveImage(leflim+1,toplim)
  2081.                 elseif state=="select" then
  2082.                     selectrect.x1 = selectrect.x1+1
  2083.                     selectrect.x2 = selectrect.x2+1
  2084.                 else sx=sx+1 end
  2085.             elseif p1==keys.up then
  2086.                 if state == "move" then
  2087.                     updateImageLims(record)
  2088.                     moveImage(leflim,toplim-1)
  2089.                 elseif state=="select" and selectrect.y1 > 1 then
  2090.                     selectrect.y1 = selectrect.y1-1
  2091.                     selectrect.y2 = selectrect.y2-1
  2092.                 elseif sy > 0 then sy=sy-1 end
  2093.             elseif p1==keys.down then
  2094.                 if state == "move" then
  2095.                     updateImageLims(record)
  2096.                     moveImage(leflim,toplim+1)
  2097.                 elseif state=="select" then
  2098.                     selectrect.y1 = selectrect.y1+1
  2099.                     selectrect.y2 = selectrect.y2+1
  2100.                 else sy=sy+1 end
  2101.             end
  2102.         elseif id=="char" and tonumber(p1) then
  2103.             if state=="brush" and tonumber(p1) > 1 then
  2104.                 brushsize = tonumber(p1)
  2105.             elseif tonumber(p1) > 0 then
  2106.                 changeFrame(tonumber(p1))
  2107.             end
  2108.         end
  2109.     end
  2110. end
  2111.  
  2112. --[[  
  2113.             Section: Main  
  2114. ]]--
  2115.  
  2116. --Taken almost directly from edit (for consistency)
  2117. local tArgs = {...}
  2118.  
  2119. local ca = 1
  2120.  
  2121. if tArgs[ca] == "-a" then
  2122.     animated = true
  2123.     ca = ca + 1
  2124. end
  2125.  
  2126. if #tArgs < ca then
  2127.     print("Usage: npaintpro [-a] <path>")
  2128.     return
  2129. end
  2130.  
  2131. sPath = shell.resolve(tArgs[ca])
  2132. local bReadOnly = fs.isReadOnly(sPath)
  2133. if fs.exists(sPath) then
  2134.     if fs.isDir(sPath) then
  2135.         print("Cannot edit a directory.")
  2136.         return
  2137.     elseif string.find(sPath, ".nfp") ~= #sPath-3 and string.find(sPath, ".nfa") ~= #sPath-3 then
  2138.         print("Can only edit .nfp and nfa files:",string.find(sPath, ".nfp"),#sPath-3)
  2139.         return
  2140.     end
  2141.    
  2142.     if string.find(sPath, ".nfa") == #sPath-3 then
  2143.         animated = true
  2144.     end
  2145.    
  2146.     if string.find(sPath, ".nfp") == #sPath-3 and animated then
  2147.         print("Convert to nfa? Y/N")
  2148.         if string.find(string.lower(io.read()), "y") then
  2149.             local nsPath = string.sub(sPath, 1, #sPath-1).."a"
  2150.             fs.move(sPath, nsPath)
  2151.             sPath = nsPath
  2152.         else
  2153.             animated = false
  2154.         end
  2155.     end
  2156. else
  2157.     if not animated and string.find(sPath, ".nfp") ~= #sPath-3 then
  2158.         sPath = sPath..".nfp"
  2159.     elseif animated and string.find(sPath, ".nfa") ~= #sPath-3 then
  2160.         sPath = sPath..".nfa"
  2161.     end
  2162. end
  2163.  
  2164. if not term.isColour() then
  2165.     print("For colour computers only")
  2166.     return
  2167. end
  2168.  
  2169. drawLogo()
  2170. init()
  2171. handleEvents()
  2172.  
  2173. term.setBackgroundColour(colours.black)
  2174. shell.run("clear")
Add Comment
Please, Sign In to add comment