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