Advertisement
nitrogenfingers

npaintpro

Jan 30th, 2013
730
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][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][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.             local char = frames[sFrame].text[y][x]
  662.             if not char then char = " " end
  663.             line = line..char
  664.         end
  665.         file:write(line.."\n")
  666.     end
  667.     file:close()
  668. end
  669.  
  670. --[[Saves a animated paint file to the specified path
  671.     Params: path:string = The path to save the file to
  672.     Returns:nil
  673. ]]--
  674. local function saveNFA(path)
  675.     local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
  676.     if not fs.exists(sDir) then
  677.         fs.makeDir(sDir)
  678.     end
  679.    
  680.     local file = io.open(path, "w")
  681.     updateImageLims(true)
  682.     if not toplim then
  683.         file:close()
  684.         return
  685.     end
  686.     for i=1,#frames do
  687.         for y=1,botlim do
  688.             local line = ""
  689.             if frames[i][y] then
  690.                 for x=1,riglim do
  691.                     line = line..getHexOf(frames[i][y][x])
  692.                 end
  693.             end
  694.             file:write(line.."\n")
  695.         end
  696.         if i < #frames then file:write("~\n") end
  697.     end
  698.     file:close()
  699. end
  700.  
  701.  
  702. --[[Initializes the program, by loading in the paint file. Called at the start of each program.
  703.     Params: none
  704.     Returns:nil
  705. ]]--
  706. local function init()
  707.     if textEnabled then
  708.         loadNFT(sPath)
  709.         table.insert(ddModes, 2, { "text", "textpaint", name = "text"})
  710.     elseif animated then
  711.         loadNFA(sPath)
  712.         table.insert(ddModes, #ddModes, { "record", "play", name = "anim" })
  713.         table.insert(ddModes, #ddModes, { "go to", "remove", name = "frames"})
  714.         table.insert(ddModes[2], #ddModes[2], "blueprint on")
  715.         table.insert(ddModes[2], #ddModes[2], "layers on")
  716.     else
  717.         loadNFP(sPath)
  718.         table.insert(ddModes[2], #ddModes[2], "blueprint on")
  719.     end
  720.  
  721.     for i=0,15 do
  722.         table.insert(column, math.pow(2,i))
  723.     end
  724. end
  725.  
  726. --[[  
  727.             Section: Drawing  
  728. ]]--
  729.  
  730.  
  731. --[[Draws the rather superflous logo. Takes about 1 second, before user is able to move to the
  732.     actual program.
  733.     Params: none
  734.     Returns:nil
  735. ]]--
  736. local function drawLogo()
  737.     term.setBackgroundColour(colours.white)
  738.     term.clear()
  739.     drawPictureTable(logo, w/2 - #logo[1]/2, h/2 - #logo/2, colours.white)
  740.     term.setBackgroundColour(colours.white)
  741.     term.setTextColour(colours.black)
  742.     local msg = "NPaintPro"
  743.     term.setCursorPos(w/2 - #msg/2, h-3)
  744.     term.write(msg)
  745.     msg = "By NitrogenFingers"
  746.     term.setCursorPos(w/2 - #msg/2, h-2)
  747.     term.write(msg)
  748.    
  749.     os.pullEvent()
  750. end
  751.  
  752. --[[Clears the display to the alpha channel colour, draws the canvas, the image buffer and the selection
  753.     rectanlge if any of these things are present.
  754.     Params: none
  755.     Returns:nil
  756. ]]--
  757. local function drawCanvas()
  758.     --We have to readjust the position of the canvas if we're printing
  759.     turtlechar = "@"
  760.     if state == "active print" then
  761.         if layering == "up" then
  762.             if py >= 1 and py <= #frames then
  763.                 sFrame = py
  764.             end
  765.             if pz < sy then sy = pz
  766.             elseif pz > sy + h - 1 then sy = pz + h - 1 end
  767.             if px < sx then sx = px
  768.             elseif px > sx + w - 2 then sx = px + w - 2 end
  769.         else
  770.             if pz >= 1 and pz <= #frames then
  771.                 sFrame = pz
  772.             end
  773.            
  774.             if py < sy then sy = py
  775.             elseif py > sy + h - 1 then sy = py + h - 1 end
  776.             if px < sx then sx = px
  777.             elseif px > sx + w - 2 then sx = px + w - 2 end
  778.         end
  779.        
  780.         if pfx == 1 then turtlechar = ">"
  781.         elseif pfx == -1 then turtlechar = "<"
  782.         elseif pfz == 1 then turtlechar = "V"
  783.         elseif pfz == -1 then turtlechar = "^"
  784.         end
  785.     end
  786.  
  787.     --Picture next
  788.     local topLayer, botLayer
  789.     if layerDisplay then
  790.         topLayer = sFrame
  791.         botLayer = 1
  792.     else
  793.         topLayer,botLayer = sFrame,sFrame
  794.     end
  795.    
  796.     for currframe = botLayer,topLayer,1 do
  797.         for y=sy+1,sy+h-1 do
  798.             if frames[currframe][y] then
  799.                 for x=sx+1,sx+w-2 do
  800.                     term.setCursorPos(x-sx,y-sy)
  801.                     if frames[currframe][y][x] then
  802.                         term.setBackgroundColour(frames[currframe][y][x])
  803.                         if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then
  804.                             term.setTextColour(frames[currframe].textcol[y][x])
  805.                             term.write(frames[currframe].text[y][x])
  806.                         else
  807.                             term.write(" ")
  808.                         end
  809.                     else
  810.                         tileExists = false
  811.                         for i=currframe-1,botLayer,-1 do
  812.                             if frames[i][y][x] then
  813.                                 tileExists = true
  814.                                 break
  815.                             end
  816.                         end
  817.                        
  818.                         if not tileExists then
  819.                             if blueprint then
  820.                                 term.setBackgroundColour(colours.blue)
  821.                                 term.setTextColour(colours.white)
  822.                                 if x == sx+1 and y % 4 == 1 then
  823.                                     term.write(""..((y/4) % 10))
  824.                                 elseif y == sy + 1 and x % 4 == 1 then
  825.                                     term.write(""..((x/4) % 10))
  826.                                 elseif x % 2 == 1 and y % 2 == 1 then
  827.                                     term.write("+")
  828.                                 elseif x % 2 == 1 then
  829.                                     term.write("|")
  830.                                 elseif y % 2 == 1 then
  831.                                     term.write("-")
  832.                                 else
  833.                                     term.write(" ")
  834.                                 end
  835.                             else
  836.                                 term.setBackgroundColour(alphaC)
  837.                                 if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then
  838.                                     term.setTextColour(frames[currframe].textcol[y][x])
  839.                                     term.write(frames[currframe].text[y][x])
  840.                                 else
  841.                                     term.write(" ")
  842.                                 end
  843.                             end
  844.                         end
  845.                     end
  846.                 end
  847.             else
  848.                 for x=sx+1,sx+w-2 do
  849.                     term.setCursorPos(x-sx,y-sy)
  850.                    
  851.                     tileExists = false
  852.                     for i=currframe-1,botLayer,-1 do
  853.                         if frames[i][y] and frames[i][y][x] then
  854.                             tileExists = true
  855.                             break
  856.                         end
  857.                     end
  858.                    
  859.                     if not tileExists then
  860.                         if blueprint then
  861.                             term.setBackgroundColour(colours.blue)
  862.                             term.setTextColour(colours.white)
  863.                             if x == sx+1 and y % 4 == 1 then
  864.                                 term.write(""..((y/4) % 10))
  865.                             elseif y == sy + 1 and x % 4 == 1 then
  866.                                 term.write(""..((x/4) % 10))
  867.                             elseif x % 2 == 1 and y % 2 == 1 then
  868.                                 term.write("+")
  869.                             elseif x % 2 == 1 then
  870.                                 term.write("|")
  871.                             elseif y % 2 == 1 then
  872.                                 term.write("-")
  873.                             else
  874.                                 term.write(" ")
  875.                             end
  876.                         else
  877.                             term.setBackgroundColour(alphaC)
  878.                             term.write(" ")
  879.                         end
  880.                     end
  881.                 end
  882.             end
  883.         end
  884.     end
  885.    
  886.     --Then the printer, if he's on
  887.     if state == "active print" then
  888.         local bgColour = alphaC
  889.         if layering == "up" then
  890.             term.setCursorPos(px-sx,pz-sy)
  891.             if frames[sFrame] and frames[sFrame][pz-sy] and frames[sFrame][pz-sy][px-sx] then
  892.                 bgColour = frames[sFrame][pz-sy][px-sx]
  893.             elseif blueprint then bgColour = colours.blue end
  894.         else
  895.             term.setCursorPos(px-sx,py-sy)
  896.             if frames[sFrame] and frames[sFrame][py-sy] and frames[sFrame][py-sy][px-sx] then
  897.                 bgColour = frames[sFrame][py-sy][px-sx]
  898.             elseif blueprint then bgColour = colours.blue end
  899.         end
  900.        
  901.         term.setBackgroundColour(bgColour)
  902.         if bgColour == colours.black then term.setTextColour(colours.white)
  903.         else term.setTextColour(colours.black) end
  904.        
  905.         term.write(turtlechar)
  906.     end
  907.    
  908.     --Then the buffer
  909.     if selectrect then
  910.         if buffer and rectblink == 1 then
  911.         for y=selectrect.y1, math.min(selectrect.y2, selectrect.y1 + buffer.height-1) do
  912.             for x=selectrect.x1, math.min(selectrect.x2, selectrect.x1 + buffer.width-1) do
  913.                 if buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1] then
  914.                     term.setCursorPos(x+sx,y+sy)
  915.                     term.setBackgroundColour(buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1])
  916.                     term.write(" ")
  917.                 end
  918.             end
  919.         end
  920.         end
  921.    
  922.         --This draws the "selection" box
  923.         local add = nil
  924.         if buffer then
  925.             term.setBackgroundColour(colours.lightGrey)
  926.         else
  927.             term.setBackgroundColour(colours.grey)
  928.         end
  929.         for i=selectrect.x1, selectrect.x2 do
  930.             add = (i + selectrect.y1 + rectblink) % 2 == 0
  931.             term.setCursorPos(i-sx,selectrect.y1-sy)
  932.             if add then term.write(" ") end
  933.             add = (i + selectrect.y2 + rectblink) % 2 == 0
  934.             term.setCursorPos(i-sx,selectrect.y2-sy)
  935.             if add then term.write(" ") end
  936.         end
  937.         for i=selectrect.y1 + 1, selectrect.y2 - 1 do
  938.             add = (i + selectrect.x1 + rectblink) % 2 == 0
  939.             term.setCursorPos(selectrect.x1-sx,i-sy)
  940.             if add then term.write(" ") end
  941.             add = (i + selectrect.x2 + rectblink) % 2 == 0
  942.             term.setCursorPos(selectrect.x2-sx,i-sy)
  943.             if add then term.write(" ") end
  944.         end
  945.     end
  946. end
  947.  
  948. --[[Draws the colour picker on the right side of the screen, the colour pallette and the footer with any
  949.     messages currently being displayed
  950.     Params: none
  951.     Returns:nil
  952. ]]--
  953. local function drawInterface()
  954.     --Picker
  955.     for i=1,#column do
  956.         term.setCursorPos(w-1, i)
  957.         term.setBackgroundColour(column[i])
  958.         if state == "print" then
  959.             if i == 16 then
  960.                 term.setTextColour(colours.white)
  961.             else
  962.                 term.setTextColour(colours.black)
  963.             end
  964.             if requirementsDisplayed then
  965.                 if requiredMaterials[i] < 10 then term.write(" ") end
  966.                 term.setCursorPos(w-#tostring(requiredMaterials[i])+1, i)
  967.                 term.write(requiredMaterials[i])
  968.             else
  969.                 if i < 10 then term.write(" ") end
  970.                 term.write(i)
  971.             end
  972.         else
  973.             term.write("  ")
  974.         end
  975.     end
  976.     term.setCursorPos(w-1,#column+1)
  977.     term.setBackgroundColour(colours.black)
  978.     term.setTextColour(colours.red)
  979.     term.write("XX")
  980.     --Pallette
  981.     term.setCursorPos(w-1,h-1)
  982.     if not lSel then
  983.         term.setBackgroundColour(colours.black)
  984.         term.setTextColour(colours.red)
  985.         term.write("X")
  986.     else
  987.         term.setBackgroundColour(lSel)
  988.         term.setTextColour(lSel)
  989.         term.write(" ")
  990.     end
  991.     if not rSel then
  992.         term.setBackgroundColour(colours.black)
  993.         term.setTextColour(colours.red)
  994.         term.write("X")
  995.     else
  996.         term.setBackgroundColour(rSel)
  997.         term.setTextColour(rSel)
  998.         term.write(" ")
  999.     end
  1000.     --Footer
  1001.     if inMenu then return end
  1002.    
  1003.     term.setCursorPos(1, h)
  1004.     term.setBackgroundColour(colours.lightGrey)
  1005.     term.setTextColour(colours.grey)
  1006.     term.clearLine()
  1007.     if inDropDown then
  1008.         term.write(string.rep(" ", 6))
  1009.     else
  1010.         term.setBackgroundColour(colours.grey)
  1011.         term.setTextColour(colours.lightGrey)
  1012.         term.write("menu  ")
  1013.     end
  1014.     term.setBackgroundColour(colours.lightGrey)
  1015.     term.setTextColour(colours.grey)
  1016.     term.write(getStateMessage())
  1017.    
  1018.     local coords="X:"..sx.." Y:"..sy
  1019.     if animated then coords = coords.." Frame:"..sFrame.."/"..framecount.."   " end
  1020.     term.setCursorPos(w-#coords+1,h)
  1021.     if state == "play" then term.setBackgroundColour(colours.lime)
  1022.     elseif record then term.setBackgroundColour(colours.red) end
  1023.     term.write(coords)
  1024.    
  1025.     if animated then
  1026.         term.setCursorPos(w-1,h)
  1027.         term.setBackgroundColour(colours.grey)
  1028.         term.setTextColour(colours.lightGrey)
  1029.         term.write("<>")
  1030.     end
  1031. end
  1032.  
  1033. --[[Runs an interface where users can select topics of help. Will return once the user quits the help screen.
  1034.     Params: none
  1035.     Returns:nil
  1036. ]]--
  1037. local function drawHelpScreen()
  1038.     local selectedHelp = nil
  1039.     while true do
  1040.         term.setBackgroundColour(colours.lightGrey)
  1041.         term.clear()
  1042.         if not selectedHelp then
  1043.             term.setCursorPos(4, 1)
  1044.             term.setTextColour(colours.brown)
  1045.             term.write("Available modes (click for info):")
  1046.             for i=1,#helpTopics do
  1047.                 term.setCursorPos(2, 2 + i)
  1048.                 term.setTextColour(colours.black)
  1049.                 term.write(helpTopics[i].name)
  1050.                 if helpTopics[i].key then
  1051.                     term.setTextColour(colours.red)
  1052.                     term.write(" ("..helpTopics[i].key..")")
  1053.                 end
  1054.             end
  1055.             term.setCursorPos(4,h)
  1056.             term.setTextColour(colours.black)
  1057.             term.write("Press any key to exit")
  1058.         else
  1059.             term.setCursorPos(4,1)
  1060.             term.setTextColour(colours.brown)
  1061.             term.write(helpTopics[selectedHelp].name)
  1062.             if helpTopics[selectedHelp].key then
  1063.                 term.setTextColour(colours.red)
  1064.                 term.write(" ("..helpTopics[selectedHelp].key..")")
  1065.             end
  1066.             term.setCursorPos(1,3)
  1067.             term.setTextColour(colours.black)
  1068.             print(helpTopics[selectedHelp].message.."\n")
  1069.             for i=1,#helpTopics[selectedHelp].controls do
  1070.                 term.setTextColour(colours.brown)
  1071.                 term.write(helpTopics[selectedHelp].controls[i][1].." ")
  1072.                 term.setTextColour(colours.black)
  1073.                 print(helpTopics[selectedHelp].controls[i][2])
  1074.             end
  1075.         end
  1076.        
  1077.         local id,p1,p2,p3 = os.pullEvent()
  1078.        
  1079.         if id == "timer" then updateTimer(p1)
  1080.         elseif id == "key" then
  1081.             if selectedHelp then selectedHelp = nil
  1082.             else break end
  1083.         elseif id == "mouse_click" then
  1084.             if not selectedHelp then
  1085.                 if p3 >=3 and p3 <= 2+#helpTopics then
  1086.                     selectedHelp = p3-2
  1087.                 else break end
  1088.             else
  1089.                 selectedHelp = nil
  1090.             end
  1091.         end
  1092.     end
  1093. end
  1094.  
  1095. --[[Draws a message in the footer bar. A helper for DrawInterface, but can be called for custom messages, if the
  1096.     inMenu paramter is set to true while this is being done (remember to set it back when done!)
  1097.     Params: message:string = The message to be drawn
  1098.     Returns:nil
  1099. ]]--
  1100. local function drawMessage(message)
  1101.     term.setCursorPos(1,h)
  1102.     term.setBackgroundColour(colours.lightGrey)
  1103.     term.setTextColour(colours.grey)
  1104.     term.clearLine()
  1105.     term.write(message)
  1106. end
  1107.  
  1108. --[[
  1109.             Section: Generic Interfaces
  1110. ]]--
  1111.  
  1112.  
  1113. --[[One of my generic text printing methods, printing a message at a specified position with width and offset.
  1114.     No colour materials included.
  1115.     Params: msg:string = The message to print off-center
  1116.             height:number = The starting height of the message
  1117.             width:number = The limit as to how many characters long each line may be
  1118.             offset:number = The starting width offset of the message
  1119.     Returns:number the number of lines used in printing the message
  1120. ]]--
  1121. local function wprintOffCenter(msg, height, width, offset)
  1122.     local inc = 0
  1123.     local ops = 1
  1124.     while #msg - ops > width do
  1125.         local nextspace = 0
  1126.         while string.find(msg, " ", ops + nextspace) and
  1127.                 string.find(msg, " ", ops + nextspace) - ops < width do
  1128.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  1129.         end
  1130.         local ox,oy = term.getCursorPos()
  1131.         term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
  1132.         inc = inc + 1
  1133.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  1134.         ops = ops + nextspace
  1135.     end
  1136.     term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
  1137.     term.write(string.sub(msg, ops))
  1138.    
  1139.     return inc + 1
  1140. end
  1141.  
  1142. --[[Draws a message that must be clicked on or a key struck to be cleared. No options, so used for displaying
  1143.     generic information.
  1144.     Params: ctitle:string = The title of the confirm dialogue
  1145.             msg:string = The message displayed in the dialogue
  1146.     Returns:nil
  1147. ]]--
  1148. local function displayConfirmDialogue(ctitle, msg)
  1149.     local dialogoffset = 8
  1150.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  1151.     local lines = wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
  1152.    
  1153.     term.setCursorPos(dialogoffset, 3)
  1154.     term.setBackgroundColour(colours.grey)
  1155.     term.setTextColour(colours.lightGrey)
  1156.     term.write(string.rep(" ", w - dialogoffset * 2))
  1157.     term.setCursorPos(dialogoffset + (w - dialogoffset * 2)/2 - #ctitle/2, 3)
  1158.     term.write(ctitle)
  1159.     term.setTextColour(colours.grey)
  1160.     term.setBackgroundColour(colours.lightGrey)
  1161.     term.setCursorPos(dialogoffset, 4)
  1162.     term.write(string.rep(" ", w - dialogoffset * 2))
  1163.     for i=5,5+lines do
  1164.         term.setCursorPos(dialogoffset, i)
  1165.         term.write(" "..string.rep(" ", w - (dialogoffset) * 2 - 2).." ")
  1166.     end
  1167.     wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
  1168.    
  1169.     --In the event of a message, the player hits anything to continue
  1170.     while true do
  1171.         local id,key = os.pullEvent()
  1172.         if id == "timer" then updateTimer(key);
  1173.         elseif id == "key" or id == "mouse_click" or id == "mouse_drag" then break end
  1174.     end
  1175. end
  1176.  
  1177. --[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
  1178.     of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
  1179.     Params: x:int = the x position the menu should be displayed at
  1180.             y:int = the y position the menu should be displayed at
  1181.             options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
  1182.     Returns:string the selected menu option.
  1183. ]]--
  1184. local function displayDropDown(x, y, options)
  1185.     inDropDown = true
  1186.     --Figures out the dimensions of our thing
  1187.     local longestX = #options.name
  1188.     for i=1,#options do
  1189.         local currVal = options[i]
  1190.         if type(currVal) == "table" then currVal = currVal.name end
  1191.        
  1192.         longestX = math.max(longestX, #currVal)
  1193.     end
  1194.     local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
  1195.     local yOffset = math.max(0, #options - ((h-1) - y))
  1196.    
  1197.     local clickTimes = 0
  1198.     local tid = nil
  1199.     local selection = nil
  1200.     while clickTimes < 2 do
  1201.         drawCanvas()
  1202.         drawInterface()
  1203.        
  1204.         term.setCursorPos(x-xOffset,y-yOffset)
  1205.         term.setBackgroundColour(colours.grey)
  1206.         term.setTextColour(colours.lightGrey)
  1207.         term.write(options.name..string.rep(" ", longestX-#options.name + 2))
  1208.    
  1209.         for i=1,#options do
  1210.             term.setCursorPos(x-xOffset, y-yOffset+i)
  1211.             if i==selection and clickTimes % 2 == 0 then
  1212.                 term.setBackgroundColour(colours.grey)
  1213.                 term.setTextColour(colours.lightGrey)
  1214.             else
  1215.                 term.setBackgroundColour(colours.lightGrey)
  1216.                 term.setTextColour(colours.grey)
  1217.             end
  1218.             local currVal = options[i]
  1219.             if type(currVal) == "table" then
  1220.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
  1221.                 term.setBackgroundColour(colours.grey)
  1222.                 term.setTextColour(colours.lightGrey)
  1223.                 term.write(">")
  1224.             else
  1225.                 term.write(currVal..string.rep(" ", longestX-#currVal + 2))
  1226.             end
  1227.         end
  1228.        
  1229.         local id, p1, p2, p3 = os.pullEvent()
  1230.         if id == "timer" then
  1231.             if p1 == tid then
  1232.                 clickTimes = clickTimes + 1
  1233.                 if clickTimes > 2 then
  1234.                     break
  1235.                 else
  1236.                     tid = os.startTimer(0.1)
  1237.                 end
  1238.             else
  1239.                 updateTimer(p1)
  1240.                 drawCanvas()
  1241.                 drawInterface()
  1242.             end
  1243.         elseif id == "mouse_click" then
  1244.             if p2 >=x-xOffset and p2 <= x-xOffset + longestX + 1 and p3 >= y-yOffset+1 and p3 <= y-yOffset+#options then
  1245.                 selection = p3-(y-yOffset)
  1246.                 tid = os.startTimer(0.1)
  1247.             else
  1248.                 selection = ""
  1249.                 break
  1250.             end
  1251.         end
  1252.     end
  1253.    
  1254.     if type(selection) == "number" then
  1255.         selection = options[selection]
  1256.     end
  1257.    
  1258.     if type(selection) == "string" then
  1259.         inDropDown = false
  1260.         return selection
  1261.     elseif type(selection) == "table" then
  1262.         return displayDropDown(x, y, selection)
  1263.     end
  1264. end
  1265.  
  1266. --[[A custom io.read() function with a few differences- it limits the number of characters being printed,
  1267.     waits a 1/100th of a second so any keys still in the event library are removed before input is read and
  1268.     the timer for the selectionrectangle is continuously updated during the process.
  1269.     Params: lim:int = the number of characters input is allowed
  1270.     Returns:string the inputted string, trimmed of leading and tailing whitespace
  1271. ]]--
  1272. local function readInput(lim)
  1273.     term.setCursorBlink(true)
  1274.  
  1275.     local inputString = ""
  1276.     if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end
  1277.     local ox,oy = term.getCursorPos()
  1278.     --We only get input from the footer, so this is safe. Change if recycling
  1279.     term.setBackgroundColour(colours.lightGrey)
  1280.     term.setTextColour(colours.grey)
  1281.     term.write(string.rep(" ", lim))
  1282.     term.setCursorPos(ox, oy)
  1283.     --As events queue immediately, we may get an unwanted key... this will solve that problem
  1284.     local inputTimer = os.startTimer(0.01)
  1285.     local keysAllowed = false
  1286.    
  1287.     while true do
  1288.         local id,key = os.pullEvent()
  1289.        
  1290.         if keysAllowed then
  1291.             if id == "key" and key == 14 and #inputString > 0 then
  1292.                 inputString = string.sub(inputString, 1, #inputString-1)
  1293.                 term.setCursorPos(ox + #inputString,oy)
  1294.                 term.write(" ")
  1295.             elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then
  1296.                 break
  1297.             elseif id == "key" and key == keys.leftCtrl then
  1298.                 return ""
  1299.             elseif id == "char" and #inputString < lim then
  1300.                 inputString = inputString..key
  1301.             end
  1302.         end
  1303.        
  1304.         if id == "timer" then
  1305.             if key == inputTimer then
  1306.                 keysAllowed = true
  1307.             else
  1308.                 updateTimer(key)
  1309.                 drawCanvas()
  1310.                 drawInterface()
  1311.                 term.setBackgroundColour(colours.lightGrey)
  1312.                 term.setTextColour(colours.grey)
  1313.             end
  1314.         end
  1315.         term.setCursorPos(ox,oy)
  1316.         term.write(inputString)
  1317.         term.setCursorPos(ox + #inputString, oy)
  1318.     end
  1319.    
  1320.     while string.sub(inputString, 1, 1) == " " do
  1321.         inputString = string.sub(inputString, 2, #inputString)
  1322.     end
  1323.     while string.sub(inputString, #inputString, #inputString) == " " do
  1324.         inputString = string.sub(inputString, 1, #inputString-1)
  1325.     end
  1326.     term.setCursorBlink(false)
  1327.    
  1328.     return inputString
  1329. end
  1330.  
  1331. --[[  
  1332.             Section: Image tools
  1333. ]]--
  1334.  
  1335.  
  1336. --[[Copies all pixels beneath the selection rectangle into the image buffer. Empty buffers are converted to nil.
  1337.     Params: removeImage:bool = true if the image is to be erased after copying, false otherwise
  1338.     Returns:nil
  1339. ]]--
  1340. local function copyToBuffer(removeImage)
  1341.     buffer = { width = selectrect.x2 - selectrect.x1 + 1, height = selectrect.y2 - selectrect.y1 + 1, contents = { } }
  1342.    
  1343.     local containsSomething = false
  1344.     for y=1,buffer.height do
  1345.         buffer.contents[y] = { }
  1346.         local f,l = sFrame,sFrame
  1347.         if record then f,l = 1, framecount end
  1348.        
  1349.         for fra = f,l do
  1350.             if frames[fra][selectrect.y1 + y - 1] then
  1351.                 for x=1,buffer.width do
  1352.                     buffer.contents[y][x] = frames[sFrame][selectrect.y1 + y - 1][selectrect.x1 + x - 1]
  1353.                     if removeImage then frames[fra][selectrect.y1 + y - 1][selectrect.x1 + x - 1] = nil end
  1354.                     if buffer.contents[y][x] then containsSomething = true end
  1355.                 end
  1356.             end
  1357.         end
  1358.     end
  1359.     --I don't classify an empty buffer as a real buffer- confusing to the user.
  1360.     if not containsSomething then buffer = nil end
  1361. end
  1362.  
  1363. --[[Replaces all pixels under the selection rectangle with the image buffer (or what can be seen of it). Record-dependent.
  1364.     Params: removeBuffer:bool = true if the buffer is to be emptied after copying, false otherwise
  1365.     Returns:nil
  1366. ]]--
  1367. local function copyFromBuffer(removeBuffer)
  1368.     if not buffer then return end
  1369.  
  1370.     for y = 1, math.min(buffer.height,selectrect.y2-selectrect.y1+1) do
  1371.         local f,l = sFrame, sFrame
  1372.         if record then f,l = 1, framecount end
  1373.        
  1374.         for fra = f,l do
  1375.             if not frames[fra][selectrect.y1+y-1] then frames[fra][selectrect.y1+y-1] = { } end
  1376.             for x = 1, math.min(buffer.width,selectrect.x2-selectrect.x1+1) do
  1377.                 frames[fra][selectrect.y1+y-1][selectrect.x1+x-1] = buffer.contents[y][x]
  1378.             end
  1379.         end
  1380.     end
  1381.    
  1382.     if removeBuffer then buffer = nil end
  1383. end
  1384.  
  1385. --[[Moves the entire image (or entire animation) to the specified coordinates. Record-dependent.
  1386.     Params: newx:int = the X coordinate to move the image to
  1387.             newy:int = the Y coordinate to move the image to
  1388.     Returns:nil
  1389. ]]--
  1390. local function moveImage(newx,newy)
  1391.     if not leflim or not toplim then return end
  1392.     if newx <=0 or newy <=0 then return end
  1393.     local f,l = sFrame,sFrame
  1394.     if record then f,l = 1,framecount end
  1395.    
  1396.     for i=f,l do
  1397.         local newlines = { }
  1398.         for y,line in pairs(frames[i]) do
  1399.             newlines[y-toplim+newy] = { }
  1400.             for x,char in pairs(line) do
  1401.                 newlines[y-toplim+newy][x-leflim+newx] = char
  1402.             end
  1403.         end
  1404.         frames[i] = newlines
  1405.     end
  1406. end
  1407.  
  1408. --[[Prompts the user to clear the current frame or all frames. Record-dependent.,
  1409.     Params: none
  1410.     Returns:nil
  1411. ]]--
  1412. local function clearImage()
  1413.     inMenu = true
  1414.     if not animated then
  1415.         drawMessage("Clear image? Y/N: ")
  1416.     elseif record then
  1417.         drawMessage("Clear ALL frames? Y/N: ")
  1418.     else
  1419.         drawMessage("Clear current frame? Y/N :")
  1420.     end
  1421.     if string.find(string.upper(readInput(1)), "Y") then
  1422.         local f,l = sFrame,sFrame
  1423.         if record then f,l = 1,framecount end
  1424.        
  1425.         for i=f,l do
  1426.             frames[i] = { }
  1427.         end
  1428.     end
  1429.     inMenu = false
  1430. end
  1431.  
  1432. --[[A recursively called method (watch out for big calls!) in which every pixel of a set colour is
  1433.     changed to another colour. Does not work on the nil colour, for obvious reasons.
  1434.     Params: x:int = The X coordinate of the colour to flood-fill
  1435.             y:int = The Y coordinate of the colour to flood-fill
  1436.             targetColour:colour = the colour that is being flood-filled
  1437.             newColour:colour = the colour with which to replace the target colour
  1438.     Returns:nil
  1439. ]]--
  1440. local function floodFill(x, y, targetColour, newColour)
  1441.     if not newColour or not targetColour then return end
  1442.     local nodeList = { }
  1443.    
  1444.     table.insert(nodeList, {x = x, y = y})
  1445.    
  1446.     while #nodeList > 0 do
  1447.         local node = nodeList[1]
  1448.         if frames[sFrame][node.y] and frames[sFrame][node.y][node.x] == targetColour then
  1449.             frames[sFrame][node.y][node.x] = newColour
  1450.             table.insert(nodeList, { x = node.x + 1, y = node.y})
  1451.             table.insert(nodeList, { x = node.x, y = node.y + 1})
  1452.             if x > 1 then table.insert(nodeList, { x = node.x - 1, y = node.y}) end
  1453.             if y > 1 then table.insert(nodeList, { x = node.x, y = node.y - 1}) end
  1454.         end
  1455.         table.remove(nodeList, 1)
  1456.     end
  1457. end
  1458.  
  1459. --[[  
  1460.             Section: Animation Tools  
  1461. ]]--
  1462.  
  1463. --[[Enters play mode, allowing the animation to play through. Interface is restricted to allow this,
  1464.     and method only leaves once the player leaves play mode.
  1465.     Params: none
  1466.     Returns:nil
  1467. ]]--
  1468. local function playAnimation()
  1469.     state = "play"
  1470.     selectedrect = nil
  1471.    
  1472.     local animt = os.startTimer(animtime)
  1473.     repeat
  1474.         drawCanvas()
  1475.         drawInterface()
  1476.        
  1477.         local id,key,_,y = os.pullEvent()
  1478.        
  1479.         if id=="timer" then
  1480.             if key == animt then
  1481.                 animt = os.startTimer(animtime)
  1482.                 sFrame = (sFrame % framecount) + 1
  1483.             else
  1484.                 updateTimer(key)
  1485.             end
  1486.         elseif id=="key" then
  1487.             if key == keys.comma and animtime > 0.1 then animtime = animtime - 0.05
  1488.             elseif key == keys.period and animtime < 0.5 then animtime = animtime + 0.05
  1489.             elseif key == keys.space then state = "paint" end
  1490.         elseif id=="mouse_click" and y == h then
  1491.             state = "paint"
  1492.         end
  1493.     until state ~= "play"
  1494.     os.startTimer(0.5)
  1495. end
  1496.  
  1497. --[[Changes the selected frame (sFrame) to the chosen frame. If this frame is above the framecount,
  1498.     additional frames are created with a copy of the image on the selected frame.
  1499.     Params: newframe:int = the new frame to move to
  1500.     Returns:nil
  1501. ]]--
  1502. local function changeFrame(newframe)
  1503.     inMenu = true
  1504.     if not tonumber(newframe) then
  1505.         term.setCursorPos(1,h)
  1506.         term.setBackgroundColour(colours.lightGrey)
  1507.         term.setTextColour(colours.grey)
  1508.         term.clearLine()
  1509.    
  1510.         term.write("Go to frame: ")
  1511.         newframe = tonumber(readInput(2))
  1512.         if not newframe or newframe <= 0 then
  1513.             inMenu = false
  1514.             return
  1515.         end
  1516.     elseif newframe <= 0 then return end
  1517.    
  1518.     if newframe > framecount then
  1519.         for i=framecount+1,newframe do
  1520.             frames[i] = {}
  1521.             for y,line in pairs(frames[sFrame]) do
  1522.                 frames[i][y] = { }
  1523.                 for x,v in pairs(line) do
  1524.                     frames[i][y][x] = v
  1525.                 end
  1526.             end
  1527.         end
  1528.         framecount = newframe
  1529.     end
  1530.     sFrame = newframe
  1531.     inMenu = false
  1532. end
  1533.  
  1534. --[[Removes every frame leading after the frame passed in
  1535.     Params: frame:int the non-inclusive lower bounds of the delete
  1536.     Returns:nil
  1537. ]]--
  1538. local function removeFramesAfter(frame)
  1539.     inMenu = true
  1540.     if frame==framecount then return end
  1541.     drawMessage("Remove frames "..(frame+1).."/"..framecount.."? Y/N :")
  1542.     local answer = string.upper(readInput(1))
  1543.    
  1544.     if string.find(answer, string.upper("Y")) ~= 1 then
  1545.         inMenu = false
  1546.         return
  1547.     end
  1548.    
  1549.     for i=frame+1, framecount do
  1550.         frames[i] = nil
  1551.     end
  1552.     framecount = frame
  1553.     inMenu = false
  1554. end
  1555.  
  1556. --[[
  1557.             Section: Printing Tools
  1558. ]]--
  1559.  
  1560. --[[Constructs a new facing to the left of the current facing
  1561.     Params: curx:number = The facing on the X axis
  1562.             curz:number = The facing on the Z axis
  1563.             hand:string = The hand of the axis ("right" or "left")
  1564.     Returns:number,number = the new facing on the X and Z axis after a left turn
  1565. ]]--
  1566. local function getLeft(curx, curz)
  1567.     local hand = "left"
  1568.     if layering == "up" then hand = "right" end
  1569.    
  1570.     if hand == "right" then
  1571.         if curx == 1 then return 0,-1 end
  1572.         if curx == -1 then return 0,1 end
  1573.         if curz == 1 then return 1,0 end
  1574.         if curz == -1 then return -1,0 end
  1575.     else
  1576.         if curx == 1 then return 0,1 end
  1577.         if curx == -1 then return 0,-1 end
  1578.         if curz == 1 then return -1,0 end
  1579.         if curz == -1 then return 1,0 end
  1580.     end
  1581. end
  1582.  
  1583. --[[Constructs a new facing to the right of the current facing
  1584.     Params: curx:number = The facing on the X axis
  1585.             curz:number = The facing on the Z axis
  1586.             hand:string = The hand of the axis ("right" or "left")
  1587.     Returns:number,number = the new facing on the X and Z axis after a right turn
  1588. ]]--
  1589. local function getRight(curx, curz)
  1590.     local hand = "left"
  1591.     if layering == "up" then hand = "right" end
  1592.    
  1593.     if hand == "right" then
  1594.         if curx == 1 then return 0,1 end
  1595.         if curx == -1 then return 0,-1 end
  1596.         if curz == 1 then return -1,0 end
  1597.         if curz == -1 then return 1,0 end
  1598.     else
  1599.         if curx == 1 then return 0,-1 end
  1600.         if curx == -1 then return 0,1 end
  1601.         if curz == 1 then return 1,0 end
  1602.         if curz == -1 then return -1,0 end
  1603.     end
  1604. end
  1605.  
  1606.  
  1607. --[[Sends out a rednet signal requesting local printers, and will listen for any responses. Printers found are added to the
  1608.     printerList (for ID's) and printerNames (for names)
  1609.     Params: nil
  1610.     Returns:nil
  1611. ]]--
  1612. local function locatePrinters()
  1613.     printerList = { }
  1614.     printerNames = { name = "Printers" }
  1615.     local oldState = state
  1616.     state = "Locating printers, please wait...   "
  1617.     drawCanvas()
  1618.     drawInterface()
  1619.     state = oldState
  1620.    
  1621.     local modemOpened = false
  1622.     for k,v in pairs(rs.getSides()) do
  1623.         if peripheral.isPresent(v) and peripheral.getType(v) == "modem" then
  1624.             rednet.open(v)
  1625.             modemOpened = true
  1626.             break
  1627.         end
  1628.     end
  1629.    
  1630.     if not modemOpened then
  1631.         displayConfirmDialogue("Modem not found!", "No modem peripheral. Must have network modem to locate printers.")
  1632.         return false
  1633.     end
  1634.    
  1635.     rednet.broadcast("$3DPRINT IDENTIFY")
  1636.    
  1637.     while true do
  1638.         local id, msg = rsTimeReceive(1)
  1639.        
  1640.         if not id then break end
  1641.         if string.find(msg, "$3DPRINT IDACK") == 1 then
  1642.             msg = string.gsub(msg, "$3DPRINT IDACK ", "")
  1643.             table.insert(printerList, id)
  1644.             table.insert(printerNames, msg)
  1645.         end
  1646.     end
  1647.    
  1648.     if #printerList == 0 then
  1649.         displayConfirmDialogue("Printers not found!", "No active printers found in proximity of this computer.")
  1650.         return false
  1651.     else
  1652.         return true
  1653.     end
  1654. end
  1655.  
  1656. --[[Sends a request to the printer. Waits on a response and updates the state of the application accordingly.
  1657.     Params: command:string the command to send
  1658.             param:string a parameter to send, if any
  1659.     Returns:nil
  1660. ]]--
  1661. local function sendPC(command,param)
  1662.     local msg = "$PC "..command
  1663.     if param then msg = msg.." "..param end
  1664.     rednet.send(printerList[selectedPrinter], msg)
  1665.    
  1666.     while true do
  1667.         local id,key = rsTimeReceive()
  1668.         if id == printerList[selectedPrinter] then
  1669.             if key == "$3DPRINT ACK" then
  1670.                 break
  1671.             elseif key == "$3DPRINT DEP" then
  1672.                 displayConfirmDialogue("Printer Empty", "The printer has exhasted a material. Please refill slot "..param..
  1673.                     ", and click this message when ready to continue.")
  1674.                 rednet.send(printerList[selectedPrinter], msg)
  1675.             elseif key == "$3DPRINT OOF" then
  1676.                 displayConfirmDialogue("Printer Out of Fuel", "The printer has no fuel. Please replace the material "..
  1677.                     "in slot 1 with a fuel source, then click this message.")
  1678.                 rednet.send(printerList[selectedPrinter], "$PC SS 1")
  1679.                 id,key = rsTimeReceive()
  1680.                 rednet.send(printerList[selectedPrinter], "$PC RF")
  1681.                 id,key = rsTimeReceive()
  1682.                 rednet.send(printerList[selectedPrinter], msg)
  1683.             end
  1684.         end
  1685.     end
  1686.    
  1687.     --Changes to position are handled after the event has been successfully completed
  1688.     if command == "FW" then
  1689.         px = px + pfx
  1690.         pz = pz + pfz
  1691.     elseif command == "BK" then
  1692.         px = px - pfx
  1693.         pz = pz - pfz
  1694.     elseif command == "UP" then
  1695.         if layering == "up" then
  1696.             py = py + 1
  1697.         else
  1698.             py = py - 1
  1699.         end
  1700.     elseif command == "DW" then
  1701.         if layering == "up" then
  1702.             py = py - 1
  1703.         else    
  1704.             py = py + 1
  1705.         end
  1706.     elseif command == "TL" then
  1707.         pfx,pfz = getLeft(pfx,pfz)
  1708.     elseif command == "TR" then
  1709.         pfx,pfz = getRight(pfx,pfz)
  1710.     elseif command == "TU" then
  1711.         pfx = -pfx
  1712.         pfz = -pfz
  1713.     end
  1714.    
  1715.     drawCanvas()
  1716.     drawInterface()
  1717. end
  1718.  
  1719. --[[A printing function that commands the printer to turn to face the desired direction, if it is not already doing so
  1720.     Params: desx:number = the normalized x direction to face
  1721.             desz:number = the normalized z direction to face
  1722.     Returns:nil
  1723. ]]--
  1724. local function turnToFace(desx,desz)
  1725.     if desx ~= 0 then
  1726.         if pfx ~= desx then
  1727.             local temppfx,_ = getLeft(pfx,pfz)
  1728.             if temppfx == desx then
  1729.                 sendPC("TL")
  1730.             elseif temppfx == -desx then
  1731.                 sendPC("TR")
  1732.             else
  1733.                 sendPC("TU")
  1734.             end
  1735.         end
  1736.     else
  1737.         print("on the z axis")
  1738.         if pfz ~= desz then
  1739.             local _,temppfz = getLeft(pfx,pfz)
  1740.             if temppfz == desz then
  1741.                 sendPC("TL")
  1742.             elseif temppfz == -desz then
  1743.                 sendPC("TR")
  1744.             else
  1745.                 sendPC("TU")
  1746.             end
  1747.         end
  1748.     end
  1749. end
  1750.  
  1751. --[[Performs the print
  1752.     Params: nil
  1753.     Returns:nil
  1754. ]]--
  1755. local function performPrint()
  1756.     state = "active print"
  1757.     if layering == "up" then
  1758.         --An up layering starts our builder bot on the bottom left corner of our build
  1759.         px,py,pz = leflim, 0, botlim + 1
  1760.         pfx,pfz = 0,-1
  1761.        
  1762.         --We move him forward and up a bit from his original position.
  1763.         sendPC("FW")
  1764.         sendPC("UP")
  1765.         --For each layer that needs to be completed, we go up by one each time
  1766.         for layers=1,#frames do
  1767.             --We first decide if we're going forwards or back, depending on what side we're on
  1768.             local rowbot,rowtop,rowinc = nil,nil,nil
  1769.             if pz == botlim then
  1770.                 rowbot,rowtop,rowinc = botlim,toplim,-1
  1771.             else
  1772.                 rowbot,rowtop,rowinc = toplim,botlim,1
  1773.             end
  1774.            
  1775.             for rows = rowbot,rowtop,rowinc do
  1776.                 --Then we decide if we're going left or right, depending on what side we're on
  1777.                 local linebot,linetop,lineinc = nil,nil,nil
  1778.                 if px == leflim then
  1779.                     --Facing from the left side has to be easterly- it's changed here
  1780.                     turnToFace(1,0)
  1781.                     linebot,linetop,lineinc = leflim,riglim,1
  1782.                 else
  1783.                     --Facing from the right side has to be westerly- it's changed here
  1784.                     turnToFace(-1,0)
  1785.                     linebot,linetop,lineinc = riglim,leflim,-1
  1786.                 end
  1787.                
  1788.                 for lines = linebot,linetop,lineinc do
  1789.                     --We move our turtle forward, placing the right material at each step
  1790.                     local material = frames[py][pz][px]
  1791.                     if material then
  1792.                         material = math.log10(frames[py][pz][px])/math.log10(2) + 1
  1793.                         sendPC("SS", material)
  1794.                         sendPC("PD")
  1795.                     end
  1796.                     if lines ~= linetop then
  1797.                         sendPC("FW")
  1798.                     end
  1799.                 end
  1800.                
  1801.                 --The printer then has to do a U-turn, depending on which way he's facing and
  1802.                 --which way he needs to go
  1803.                 local temppfx,temppfz = getLeft(pfx,pfz)
  1804.                 if temppfz == rowinc and rows ~= rowtop then
  1805.                     sendPC("TL")
  1806.                     sendPC("FW")
  1807.                     sendPC("TL")
  1808.                 elseif temppfz == -rowinc and rows ~= rowtop then
  1809.                     sendPC("TR")
  1810.                     sendPC("FW")
  1811.                     sendPC("TR")
  1812.                 end
  1813.             end
  1814.             --Now at the end of a run he does a 180 and moves up to begin the next part of the print
  1815.             sendPC("TU")
  1816.             if layers ~= #frames then
  1817.                 sendPC("UP")
  1818.             end
  1819.         end
  1820.         --All done- now we head back to where we started.
  1821.         if px ~= leflim then
  1822.             turnToFace(-1,0)
  1823.             while px ~= leflim do
  1824.                 sendPC("FW")
  1825.             end
  1826.         end
  1827.         if pz ~= botlim then
  1828.             turnToFace(0,-1)
  1829.             while pz ~= botlim do
  1830.                 sendPC("BK")
  1831.             end
  1832.         end
  1833.         turnToFace(0,-1)
  1834.         sendPC("BK")
  1835.         while py > 0 do
  1836.             sendPC("DW")
  1837.         end
  1838.     else
  1839.         --The front facing is at the top-left corner, facing south not north
  1840.         px,py,pz = leflim, botlim, 1
  1841.         pfx,pfz = 0,1
  1842.         --We move the printer to the last layer- he prints from the back forwards
  1843.         while pz < #frames do
  1844.             sendPC("FW")
  1845.         end
  1846.        
  1847.         --For each layer in the frame we build our wall, the move back
  1848.         for layers = 1,#frames do
  1849.             --We first decide if we're going left or right based on our position
  1850.             local rowbot,rowtop,rowinc = nil,nil,nil
  1851.             if px == leflim then
  1852.                 rowbot,rowtop,rowinc = leflim,riglim,1
  1853.             else
  1854.                 rowbot,rowtop,rowinc = riglim,leflim,-1
  1855.             end
  1856.            
  1857.             for rows = rowbot,rowtop,rowinc do
  1858.                 --Then we decide if we're going up or down, depending on our given altitude
  1859.                 local linebot,linetop,lineinc = nil,nil,nil
  1860.                 if py == botlim then
  1861.                     linebot,linetop,lineinc = botlim,toplim,-1
  1862.                 else
  1863.                     linebot,linetop,lineinc = toplim,botlim,1
  1864.                 end
  1865.                
  1866.                 for lines = linebot,linetop,lineinc do
  1867.                 --We move our turtle up/down, placing the right material at each step
  1868.                     local material = frames[pz][py][px]
  1869.                     if material then
  1870.                         material = math.log10(frames[pz][py][px])/math.log10(2) + 1
  1871.                         sendPC("SS", material)
  1872.                         sendPC("PF")
  1873.                     end
  1874.                     if lines ~= linetop then
  1875.                         if lineinc == 1 then sendPC("DW")
  1876.                         else sendPC("UP") end
  1877.                     end
  1878.                 end
  1879.                    
  1880.                 if rows ~= rowtop then
  1881.                     turnToFace(rowinc,0)
  1882.                     sendPC("FW")
  1883.                     turnToFace(0,1)
  1884.                 end
  1885.             end
  1886.            
  1887.             if layers ~= #frames then
  1888.                 sendPC("TU")
  1889.                 sendPC("FW")
  1890.                 sendPC("TU")
  1891.             end
  1892.         end
  1893.         --He's easy to reset
  1894.         while px ~= leflim do
  1895.             turnToFace(-1,0)
  1896.             sendPC("FW")
  1897.         end
  1898.         turnToFace(0,1)
  1899.     end
  1900.    
  1901.     sendPC("DE")
  1902.    
  1903.     displayConfirmDialogue("Print complete", "The 3D print was successful.")
  1904. end
  1905.  
  1906. --[[  
  1907.             Section: Interface  
  1908. ]]--
  1909.  
  1910. --[[Runs the printing interface. Allows users to find/select a printer, the style of printing to perform and to begin the operation
  1911.     Params: none
  1912.     Returns:boolean true if printing was started, false otherwse
  1913. ]]--
  1914. local function runPrintInterface()
  1915.     calculateMaterials()
  1916.     --There's nothing on canvas yet!
  1917.     if not botlim then
  1918.         displayConfirmDialogue("Cannot Print Empty Canvas", "There is nothing on canvas that "..
  1919.                 "can be printed, and the operation cannot be completed.")
  1920.         return false
  1921.     end
  1922.     --No printers nearby
  1923.     if not locatePrinters() then
  1924.         return false
  1925.     end
  1926.    
  1927.     layering = "up"
  1928.     requirementsDisplayed = false
  1929.     selectedPrinter = 1
  1930.     while true do
  1931.         drawCanvas()
  1932.         term.setBackgroundColour(colours.lightGrey)
  1933.         for i=1,10 do
  1934.             term.setCursorPos(1,i)
  1935.             term.clearLine()
  1936.         end
  1937.         drawInterface()
  1938.         term.setBackgroundColour(colours.lightGrey)
  1939.         term.setTextColour(colours.black)
  1940.        
  1941.         local msg = "3D Printing"
  1942.         term.setCursorPos(w/2-#msg/2 - 2, 1)
  1943.         term.write(msg)
  1944.         term.setBackgroundColour(colours.grey)
  1945.         term.setTextColour(colours.lightGrey)
  1946.         if(requirementsDisplayed) then
  1947.             msg = "Count:"
  1948.         else
  1949.             msg = " Slot:"
  1950.         end
  1951.         term.setCursorPos(w-3-#msg, 1)
  1952.         term.write(msg)
  1953.         term.setBackgroundColour(colours.lightGrey)
  1954.         term.setTextColour(colours.black)
  1955.        
  1956.         term.setCursorPos(7, 2)
  1957.         term.write("Layering")
  1958.         drawPictureTable(layerUpIcon, 3, 3, colours.white)
  1959.         drawPictureTable(layerForwardIcon, 12, 3, colours.white)
  1960.         if layering == "up" then
  1961.             term.setBackgroundColour(colours.red)
  1962.         else
  1963.             term.setBackgroundColour(colours.lightGrey)
  1964.         end
  1965.         term.setCursorPos(3, 9)
  1966.         term.write("Upwards")
  1967.         if layering == "forward" then
  1968.             term.setBackgroundColour(colours.red)
  1969.         else
  1970.             term.setBackgroundColour(colours.lightGrey)
  1971.         end
  1972.         term.setCursorPos(12, 9)
  1973.         term.write("Forward")
  1974.        
  1975.         term.setBackgroundColour(colours.lightGrey)
  1976.         term.setTextColour(colours.black)
  1977.         term.setCursorPos(31, 2)
  1978.         term.write("Printer ID")
  1979.         term.setCursorPos(33, 3)
  1980.         if #printerList > 1 then
  1981.             term.setBackgroundColour(colours.grey)
  1982.             term.setTextColour(colours.lightGrey)
  1983.         else
  1984.             term.setTextColour(colours.red)
  1985.         end
  1986.         term.write(" "..printerNames[selectedPrinter].." ")
  1987.        
  1988.         term.setBackgroundColour(colours.grey)
  1989.         term.setTextColour(colours.lightGrey)
  1990.         term.setCursorPos(25, 10)
  1991.         term.write(" Cancel ")
  1992.         term.setCursorPos(40, 10)
  1993.         term.write(" Print ")
  1994.        
  1995.         local id, p1, p2, p3 = os.pullEvent()
  1996.        
  1997.         if id == "timer" then
  1998.             updateTimer(p1)
  1999.         elseif id == "mouse_click" then
  2000.             --Layering Buttons
  2001.             if p2 >= 3 and p2 <= 9 and p3 >= 3 and p3 <= 9 then
  2002.                 layering = "up"
  2003.             elseif p2 >= 12 and p2 <= 18 and p3 >= 3 and p3 <= 9 then
  2004.                 layering = "forward"
  2005.             --Count/Slot
  2006.             elseif p2 >= w - #msg - 3 and p2 <= w - 3 and p3 == 1 then
  2007.                 requirementsDisplayed = not requirementsDisplayed
  2008.             --Printer ID
  2009.             elseif p2 >= 33 and p2 <= 33 + #printerNames[selectedPrinter] and p3 == 3 and #printerList > 1 then
  2010.                 local chosenName = displayDropDown(33, 3, printerNames)
  2011.                 for i=1,#printerNames do
  2012.                     if printerNames[i] == chosenName then
  2013.                         selectedPrinter = i
  2014.                         break;
  2015.                     end
  2016.                 end
  2017.             --Print and Cancel
  2018.             elseif p2 >= 25 and p2 <= 32 and p3 == 10 then
  2019.                 break
  2020.             elseif p2 >= 40 and p2 <= 46 and p3 == 10 then
  2021.                 rednet.send(printerList[selectedPrinter], "$3DPRINT ACTIVATE")
  2022.                 ready = false
  2023.                 while true do
  2024.                     local id,msg = rsTimeReceive(10)
  2025.                    
  2026.                     if id == printerList[selectedPrinter] and msg == "$3DPRINT ACTACK" then
  2027.                         ready = true
  2028.                         break
  2029.                     end
  2030.                 end
  2031.                 if ready then
  2032.                     performPrint()
  2033.                     break
  2034.                 else
  2035.                     displayConfirmDialogue("Printer Didn't Respond", "The printer didn't respond to the activation command. Check to see if it's online")
  2036.                 end
  2037.             end
  2038.         end
  2039.     end
  2040.     state = "paint"
  2041. end
  2042.  
  2043. --[[This function changes the current paint program to another tool or mode, depending on user input. Handles
  2044.     any necessary changes in logic involved in that.
  2045.     Params: mode:string = the name of the mode to change to
  2046.     Returns:nil
  2047. ]]--
  2048. local function performSelection(mode)
  2049.     if not mode or mode == "" then return
  2050.    
  2051.     elseif mode == "help" then
  2052.         drawHelpScreen()
  2053.        
  2054.     elseif mode == "blueprint on" then
  2055.         blueprint = true
  2056.         ddModes[2][3] = "blueprint off"
  2057.        
  2058.     elseif mode == "blueprint off" then
  2059.         blueprint = false
  2060.         ddModes[2][3] = "blueprint on"
  2061.        
  2062.     elseif mode == "layers on" then
  2063.         layerDisplay = true
  2064.         ddModes[2][4] = "layers off"
  2065.    
  2066.     elseif mode == "layers off" then
  2067.         layerDisplay = false
  2068.         ddModes[2][4] = "layers on"
  2069.    
  2070.     elseif mode == "direction on" then
  2071.         printDirection = true
  2072.         ddModes[2][5] = "direction off"
  2073.        
  2074.     elseif mode == "direction off" then
  2075.         printDirection = false
  2076.         ddModes[2][5] = "direction on"
  2077.    
  2078.     elseif mode == "go to" then
  2079.         changeFrame()
  2080.    
  2081.     elseif mode == "remove" then
  2082.         removeFramesAfter(sFrame)
  2083.    
  2084.     elseif mode == "play" then
  2085.         playAnimation()
  2086.        
  2087.     elseif mode == "copy" then
  2088.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  2089.             copyToBuffer(false)
  2090.         end
  2091.    
  2092.     elseif mode == "cut" then
  2093.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  2094.             copyToBuffer(true)
  2095.         end
  2096.        
  2097.     elseif mode == "paste" then
  2098.         if selectrect and selectrect.x1 ~= selectrect.x2 then
  2099.             copyFromBuffer(false)
  2100.         end
  2101.        
  2102.     elseif mode == "hide" then
  2103.         selectrect = nil
  2104.         if state == "select" then state = "corner select" end
  2105.        
  2106.     elseif mode == "alpha to left" then
  2107.         if lSel then alphaC = lSel end
  2108.        
  2109.     elseif mode == "alpha to right" then
  2110.         if rSel then alphaC = rSel end
  2111.        
  2112.     elseif mode == "record" then
  2113.         record = not record
  2114.        
  2115.     elseif mode == "clear" then
  2116.         if state=="select" then buffer = nil
  2117.         else clearImage() end
  2118.    
  2119.     elseif mode == "select" then
  2120.         if state=="corner select" or state=="select" then
  2121.             state = "paint"
  2122.         elseif selectrect and selectrect.x1 ~= selectrect.x2 then
  2123.             state = "select"
  2124.         else
  2125.             state = "corner select"
  2126.         end
  2127.        
  2128.     elseif mode == "print" then
  2129.         state = "print"
  2130.         runPrintInterface()
  2131.         state = "paint"
  2132.        
  2133.     elseif mode == "save" then
  2134.         if animated then saveNFA(sPath)
  2135.         elseif textEnabled then saveNFT(sPath)
  2136.         else saveNFP(sPath) end
  2137.        
  2138.     elseif mode == "exit" then
  2139.         isRunning = false
  2140.    
  2141.     elseif mode ~= state then state = mode
  2142.     else state = "paint"
  2143.    
  2144.     end
  2145. end
  2146.  
  2147. --[[The main function of the program, reads and handles all events and updates them accordingly. Mode changes,
  2148.     painting to the canvas and general selections are done here.
  2149.     Params: none
  2150.     Returns:nil
  2151. ]]--
  2152. local function handleEvents()
  2153.     recttimer = os.startTimer(0.5)
  2154.     while isRunning do
  2155.         drawCanvas()
  2156.         drawInterface()
  2157.        
  2158.         if state == "text" then
  2159.             term.setCursorPos(textCurX - sx, textCurY - sy)
  2160.             term.setCursorBlink(true)
  2161.         end
  2162.        
  2163.         local id,p1,p2,p3 = os.pullEvent()
  2164.             term.setCursorBlink(false)
  2165.         if id=="timer" then
  2166.             updateTimer(p1)
  2167.         elseif id=="mouse_click" or id=="mouse_drag" then
  2168.             if p2 >=w-1 and p3 < #column+1 then
  2169.                 if p1==1 then lSel = column[p3]
  2170.                 else rSel = column[p3] end
  2171.             elseif p2 >=w-1 and p3==#column+1 then
  2172.                 if p1==1 then lSel = nil
  2173.                 else rSel = nil end
  2174.             elseif p2==w-1 and p3==h and animated then
  2175.                 changeFrame(sFrame-1)
  2176.             elseif p2==w and p3==h and animated then
  2177.                 changeFrame(sFrame+1)
  2178.             elseif p2 < w-10 and p3==h then
  2179.                 local sel = displayDropDown(1, h-1, ddModes)
  2180.                 performSelection(sel)
  2181.             elseif p2 < w-1 and p3 <= h-1 then
  2182.                 if state=="pippette" then
  2183.                     if p1==1 then
  2184.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  2185.                             lSel = frames[sFrame][p3+sy][p2+sx]
  2186.                         end
  2187.                     elseif p1==2 then
  2188.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  2189.                             rSel = frames[sFrame][p3+sy][p2+sx]
  2190.                         end
  2191.                     end
  2192.                 elseif state=="move" then
  2193.                     updateImageLims(record)
  2194.                     moveImage(p2,p3)
  2195.                 elseif state=="flood" then
  2196.                     if p1 == 1 and lSel and frames[sFrame][p3+sy]  then
  2197.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],lSel)
  2198.                     elseif p1 == 2 and rSel and frames[sFrame][p3+sy] then
  2199.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],rSel)
  2200.                     end
  2201.                 elseif state=="corner select" then
  2202.                     if not selectrect then
  2203.                         selectrect = { x1=p2+sx, x2=p2+sx, y1=p3+sy, y2=p3+sy }
  2204.                     elseif selectrect.x1 ~= p2+sx and selectrect.y1 ~= p3+sy then
  2205.                         if p2+sx<selectrect.x1 then selectrect.x1 = p2+sx
  2206.                         else selectrect.x2 = p2+sx end
  2207.                        
  2208.                         if p3+sy<selectrect.y1 then selectrect.y1 = p3+sy
  2209.                         else selectrect.y2 = p3+sy end
  2210.                        
  2211.                         state = "select"
  2212.                     end
  2213.                 elseif state=="textpaint" then
  2214.                     local paintCol = lSel
  2215.                     if p1 == 2 then paintCol = rSel end
  2216.                     if frames[sFrame].textcol[p3+sy] then
  2217.                         frames[sFrame].textcol[p3+sy][p2+sx] = paintCol
  2218.                     end
  2219.                 elseif state=="text" then
  2220.                     textCurX = p2 + sx
  2221.                     textCurY = p3 + sy
  2222.                 elseif state=="select" then
  2223.                     if p1 == 1 then
  2224.                         local swidth = selectrect.x2 - selectrect.x1
  2225.                         local sheight = selectrect.y2 - selectrect.y1
  2226.                    
  2227.                         selectrect.x1 = p2 + sx
  2228.                         selectrect.y1 = p3 + sy
  2229.                         selectrect.x2 = p2 + swidth + sx
  2230.                         selectrect.y2 = p3 + sheight + sy
  2231.                     elseif p1 == 2 and p2 < w-2 and p3 < h-1 then
  2232.                         inMenu = true
  2233.                         local sel = displayDropDown(p2, p3, srModes)
  2234.                         inMenu = false
  2235.                         performSelection(sel)
  2236.                     end
  2237.                 else
  2238.                     local f,l = sFrame,sFrame
  2239.                     if record then f,l = 1,framecount end
  2240.                     local bwidth = 0
  2241.                     if state == "brush" then bwidth = brushsize-1 end
  2242.                
  2243.                     for i=f,l do
  2244.                         for x = math.max(1,p2+sx-bwidth),p2+sx+bwidth do
  2245.                             for y = math.max(1,p3+sy-bwidth), p3+sy+bwidth do
  2246.                                 if math.abs(x - (p2+sx)) + math.abs(y - (p3+sy)) <= bwidth then
  2247.                                     if not frames[i][y] then frames[i][y] = {} end
  2248.                                     if p1==1 then frames[i][y][x] = lSel
  2249.                                     else frames[i][y][x] = rSel end
  2250.                                    
  2251.                                     if textEnabled then
  2252.                                         if not frames[i].text[y] then frames[i].text[y] = { } end
  2253.                                         if not frames[i].textcol[y] then frames[i].textcol[y] = { } end
  2254.                                     end
  2255.                                 end
  2256.                             end
  2257.                         end
  2258.                     end
  2259.                 end
  2260.             end
  2261.         elseif id=="char" then
  2262.             if state=="text" then
  2263.                 if not frames[sFrame][textCurY] then frames[sFrame][textCurY] = { } end
  2264.                 if not frames[sFrame].text[textCurY] then frames[sFrame].text[textCurY] = { } end
  2265.                 if not frames[sFrame].textcol[textCurY] then frames[sFrame].textcol[textCurY] = { } end
  2266.                
  2267.                 if rSel then frames[sFrame][textCurY][textCurX] = rSel end
  2268.                 if lSel then
  2269.                     frames[sFrame].text[textCurY][textCurX] = p1
  2270.                     frames[sFrame].textcol[textCurY][textCurX] = lSel
  2271.                 else
  2272.                     frames[sFrame].text[textCurY][textCurX] = " "
  2273.                     frames[sFrame].textcol[textCurY][textCurX] = rSel
  2274.                 end
  2275.                
  2276.                 textCurX = textCurX+1
  2277.                 if textCurX > w + sx - 2 then sx = textCurX - w + 2 end
  2278.             elseif tonumber(p1) then
  2279.                 if state=="brush" and tonumber(p1) > 1 then
  2280.                     brushsize = tonumber(p1)
  2281.                 elseif animated and tonumber(p1) > 0 then
  2282.                     changeFrame(tonumber(p1))
  2283.                 end
  2284.             end
  2285.         elseif id=="key" then
  2286.             --Text needs special handlers (all other keyboard shortcuts are of course reserved for typing)
  2287.             if state=="text" then
  2288.                 if p1==keys.backspace and textCurX > 1 then
  2289.                     textCurX = textCurX-1
  2290.                     frames[sFrame].text[textCurY][textCurX] = nil
  2291.                     frames[sFrame].textcol[textCurY][textCurX] = nil
  2292.                     if textCurX < sx then sx = textCurX end
  2293.                 elseif p1==keys.left and textCurX > 1 then
  2294.                     textCurX = textCurX-1
  2295.                     if textCurX-1 < sx then sx = textCurX-1 end
  2296.                 elseif p1==keys.right then
  2297.                     textCurX = textCurX+1
  2298.                     if textCurX > w + sx - 2 then sx = textCurX - w + 2 end
  2299.                 elseif p1==keys.up and textCurY > 1 then
  2300.                     textCurY = textCurY-1
  2301.                     if textCurY-1 < sy then sy = textCurY-1 end
  2302.                 elseif p1==keys.down then
  2303.                     textCurY = textCurY+1
  2304.                     if textCurY > h + sy - 1 then sy = textCurY - h + 1 end
  2305.                 end
  2306.            
  2307.             elseif p1==keys.leftCtrl then
  2308.                 local sel = displayDropDown(1, h-1, ddModes[#ddModes])
  2309.                 performSelection(sel)
  2310.             elseif p1==keys.leftAlt then
  2311.                 local sel = displayDropDown(1, h-1, ddModes[1])
  2312.                 performSelection(sel)
  2313.             elseif p1==keys.h then
  2314.                 performSelection("help")
  2315.             elseif p1==keys.x then
  2316.                 performSelection("cut")
  2317.             elseif p1==keys.c then
  2318.                 performSelection("copy")
  2319.             elseif p1==keys.v then
  2320.                 performSelection("paste")
  2321.             elseif p1==keys.z then
  2322.                 performSelection("clear")
  2323.             elseif p1==keys.s then
  2324.                 performSelection("select")
  2325.             elseif p1==keys.tab then
  2326.                 performSelection("hide")
  2327.             elseif p1==keys.q then
  2328.                 performSelection("alpha to left")
  2329.             elseif p1==keys.w then
  2330.                 performSelection("alpha to right")
  2331.             elseif p1==keys.f then
  2332.                 performSelection("flood")
  2333.             elseif p1==keys.b then
  2334.                 performSelection("brush")
  2335.             elseif p1==keys.m then
  2336.                 performSelection("move")
  2337.             elseif p1==keys.backslash and animated then
  2338.                 performSelection("record")
  2339.             elseif p1==keys.p then
  2340.                 performSelection("pippette")
  2341.             elseif p1==keys.g and animated then
  2342.                 performSelection("go to")
  2343.             elseif p1==keys.period and animated then
  2344.                 changeFrame(sFrame+1)
  2345.             elseif p1==keys.comma and animated then
  2346.                 changeFrame(sFrame-1)
  2347.             elseif p1==keys.r and animated then
  2348.                 performSelection("remove")
  2349.             elseif p1==keys.space and animated then
  2350.                 performSelection("play")
  2351.             elseif p1==keys.t and textEnabled then
  2352.                 performSelection("text")
  2353.                 sleep(0.01)
  2354.             elseif p1==keys.y and textEnabled then
  2355.                 performSelection("textpaint")
  2356.             elseif p1==keys.left then
  2357.                 if state == "move" then
  2358.                     updateImageLims(record)
  2359.                     moveImage(leflim-1,toplim)
  2360.                 elseif state=="select" and selectrect.x1 > 1 then
  2361.                     selectrect.x1 = selectrect.x1-1
  2362.                     selectrect.x2 = selectrect.x2-1
  2363.                 elseif sx > 0 then sx=sx-1 end
  2364.             elseif p1==keys.right then
  2365.                 if state == "move" then
  2366.                     updateImageLims(record)
  2367.                     moveImage(leflim+1,toplim)
  2368.                 elseif state=="select" then
  2369.                     selectrect.x1 = selectrect.x1+1
  2370.                     selectrect.x2 = selectrect.x2+1
  2371.                 else sx=sx+1 end
  2372.             elseif p1==keys.up then
  2373.                 if state == "move" then
  2374.                     updateImageLims(record)
  2375.                     moveImage(leflim,toplim-1)
  2376.                 elseif state=="select" and selectrect.y1 > 1 then
  2377.                     selectrect.y1 = selectrect.y1-1
  2378.                     selectrect.y2 = selectrect.y2-1
  2379.                 elseif sy > 0 then sy=sy-1 end
  2380.             elseif p1==keys.down then
  2381.                 if state == "move" then
  2382.                     updateImageLims(record)
  2383.                     moveImage(leflim,toplim+1)
  2384.                 elseif state=="select" then
  2385.                     selectrect.y1 = selectrect.y1+1
  2386.                     selectrect.y2 = selectrect.y2+1
  2387.                 else sy=sy+1 end
  2388.             end
  2389.         end
  2390.     end
  2391. end
  2392.  
  2393. --[[
  2394.             Section: Main  
  2395. ]]--
  2396.  
  2397. if not term.isColour() then
  2398.     print("For colour computers only")
  2399.     return
  2400. end
  2401.  
  2402. --Taken almost directly from edit (for consistency)
  2403. local tArgs = {...}
  2404.  
  2405. local ca = 1
  2406.  
  2407. if tArgs[ca] == "-a" then
  2408.     animated = true
  2409.     ca = ca + 1
  2410. end
  2411.  
  2412. if tArgs[ca] == "-t" then
  2413.     textEnabled = true
  2414.     ca = ca + 1
  2415. end
  2416.  
  2417. if #tArgs < ca then
  2418.     print("Usage: npaintpro [-a,-t] <path>")
  2419.     return
  2420. end
  2421.  
  2422. --Yeah you can't have animated text files YET... I haven't supported that, maybe later?
  2423. if animated and textEnabled then
  2424.     print("No support for animated text files- cannot have both -a and -t")
  2425. end
  2426.  
  2427. sPath = shell.resolve(tArgs[ca])
  2428. local bReadOnly = fs.isReadOnly(sPath)
  2429. if fs.exists(sPath) then
  2430.     if fs.isDir(sPath) then
  2431.         print("Cannot edit a directory.")
  2432.         return
  2433.     elseif string.find(sPath, ".nfp") ~= #sPath-3 and string.find(sPath, ".nfa") ~= #sPath-3 and
  2434.             string.find(sPath, ".nft") ~= #sPath-3 then
  2435.         print("Can only edit .nfp, .nft and .nfa files:",string.find(sPath, ".nfp"),#sPath-3)
  2436.         return
  2437.     end
  2438.    
  2439.     if string.find(sPath, ".nfa") == #sPath-3 then
  2440.         animated = true
  2441.     end
  2442.    
  2443.     if string.find(sPath, ".nft") == #sPath-3 then
  2444.         textEnabled = true
  2445.     end
  2446.    
  2447.     if string.find(sPath, ".nfp") == #sPath-3 and animated then
  2448.         print("Convert to nfa? Y/N")
  2449.         if string.find(string.lower(io.read()), "y") then
  2450.             local nsPath = string.sub(sPath, 1, #sPath-1).."a"
  2451.             fs.move(sPath, nsPath)
  2452.             sPath = nsPath
  2453.         else
  2454.             animated = false
  2455.         end
  2456.     end
  2457.    
  2458.     --Again this is possible, I just haven't done it. Maybe I will?
  2459.     if textEnabled and (string.find(sPath, ".nfp") == #sPath-3 or string.find(sPath, ".nfa") == #sPath-3) then
  2460.         print("Cannot convert to nft")
  2461.     end
  2462. else
  2463.     if not animated and not textEnabled and string.find(sPath, ".nfp") ~= #sPath-3 then
  2464.         sPath = sPath..".nfp"
  2465.     elseif animated and string.find(sPath, ".nfa") ~= #sPath-3 then
  2466.         sPath = sPath..".nfa"
  2467.     elseif textEnabled and string.find(sPath, ".nft") ~= #sPath-3 then
  2468.         sPath = sPath..".nft"
  2469.     end
  2470. end
  2471.  
  2472. drawLogo()
  2473. init()
  2474. handleEvents()
  2475.  
  2476. term.setBackgroundColour(colours.black)
  2477. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement