MarcosKoco

3DnPaintPro

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