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