Advertisement
nitrogenfingers

npaintpro

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