Advertisement
austinv11

NPaintPro

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