Advertisement
nitrogenfingers

npaintpro

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