Advertisement
nitrogenfingers

npaintpro

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