· 4 years ago · May 16, 2021, 05:24 AM
1--Quarry Receiver Version 3.6.5
2--[[
3Recent Changes:
4 Fixed bugs with hardcoded keys for CC: Tweaked
5]]
6
7
8--Config
9local doDebug = false --For testing purposes
10local ySizes = 3 --There are 3 different Y Screen Sizes right now
11local quadEnabled = false --This is for the quadrotors mod by Lyqyd
12local autoRestart = false --If true, will reset screens instead of turning them off. For when reusing turtles.
13
14--Initializing Program-Wide Variables
15local expectedMessage = "Civil's Quarry" --Expected initial message
16local expectedFingerprint = "quarry"
17local replyMessage = "Turtle Quarry Receiver" --Message to respond to handshake with
18local replyFingerprint = "quarryReceiver"
19local stopMessage = "stop"
20local expectedFingerprint = "quarry"
21local themeFolder = "quarryResources/receiverThemes/"
22local modemSide --User can specify a modem side, but it is not necessary
23local modem --This will be the table for the modem
24local computer --The main screen is special. It gets defined first :3
25local continue = true --This keeps the main while loop going
26local quadDirection = "north"
27local quadDirections = {n = "north", s = "south", e = "east", w = "west"}
28local quadBase, computerLocation
29local tArgs = {...}
30--These two are used by controller in main loop
31local commandString = "" --This will be a command string sent to turtle. This var is stored for display
32local lastCommand --If a command needs to be sent, this gets set
33local defaultSide
34local defaultCommand
35local stationsList = {}
36
37for i=1, #tArgs do --Parameters that must be set before rest of program for proper debugging
38 local val = tArgs[i]:lower()
39 if val == "-v" or val == "-verbose" then
40 doDebug = true
41 end
42 if val == "-q" or val == "-quiet" then
43 doDebug = false
44 end
45end
46
47local keyMap = {[keys.space] = " ", [keys.minus] = "_", [keys.period] = ".", [keys.numPadDecimal] = "."} --This is for command string
48keyMap[keys.numPad0] = "0"
49keyMap[keys.numPad1] = "1"
50keyMap[keys.numPad2] = "2"
51keyMap[keys.numPad3] = "3"
52keyMap[keys.numPad4] = "4"
53keyMap[keys.numPad5] = "5"
54keyMap[keys.numPad6] = "6"
55keyMap[keys.numPad7] = "7"
56keyMap[keys.numPad8] = "8"
57keyMap[keys.numPad9] = "9"
58
59keyMap[keys.zero] = "0"
60keyMap[keys.one] = "1"
61keyMap[keys.two] = "2"
62keyMap[keys.three] = "3"
63keyMap[keys.four] = "4"
64keyMap[keys.five] = "5"
65keyMap[keys.six] = "6"
66keyMap[keys.seven] = "7"
67keyMap[keys.eight] = "8"
68keyMap[keys.nine] = "9"
69
70for a,b in pairs(keys) do --Add all letters from keys api
71 if #a == 1 then
72 keyMap[b] = a:upper()
73 end
74end
75keyMap[keys.enter] = "enter"
76keyMap[keys.numPadEnter] = "enter"
77keyMap[keys.backspace] = "backspace"
78keyMap[keys.up] = "up"
79keyMap[keys.down] = "down"
80keyMap[keys.left] = "left"
81keyMap[keys.right] = "right"
82
83local helpResources = { --$$ is a new page
84main = [[$$Hello and welcome to Quarry Receiver Help!
85
86This goes over everything there is to know about the receiver
87
88Use the arrow keys to navigate!
89Press '0' to come back here!
90Press 'q' to quit!
91
92Press a section number at any time to go the beginning of that section
93 1. Basic Use
94 2. Parameters
95 3. Commands
96 4. Turtle Commands
97
98$$A secret page!
99You found it! Good job :)
100]],
101[[$$Your turtle and you!
102
103To use this program, you need a wireless modem on both this computer and the turtle
104
105Make sure they are attached to both the turtle and this computer
106$$Using your new program!
107
108Once you have done that, start the turtle and when it says "Rednet?", say "Yes"
109 Optionally, you can use the parameter "-rednet true"
110Then remember the channel it tells you to open.
111
112Come back to this computer, and run the program. Follow onscreen directions.
113 Optionally, you can use the parameter "-receiveChannel"
114
115Check out the other help sections for more parameters
116$$Adding Screens!
117You can add screens with the "-screen" parameter or the "SCREEN" command
118An example would be "SCREEN LEFT 2 BLUE" for a screen on the left side with channel 2 and blue theme
119
120You can connect screens over wired modems. Attach a modem to the computer and screen, then right click the modem attached to the screen.
121Then you can say "SCREEN MONITOR_0" or whatever it says
122
123]],
124[[$$Parameters!
125 note: <> means required, [] means optional
126
127-help/help/-?/?/-usage/usage: That's this!
128
129-autoRestart [t/f]: If true, the receiver will not exit when all quarries are done and will automagically reconnect to new quarries
130 With no argument, this is set to true.
131
132-receiveChannel/channel <channel>: Sets the main screen's receive channel
133
134-theme <name>: sets the "default" theme that screens use when they don't have a set theme
135$$Parameters!
136 note: <> means required, [] means optional
137
138-screen <side> [channel] [theme]: makes a new screen on the given side with channel and theme
139 example: -screen left 10 blue This adds a new screen on the left receiving channel 10 with a blue theme.
140
141-station [side]: makes the screen a "station" that monitors all screens.
142 if no side, uses computer
143
144-auto [channel list]: This finds all attached monitors and initializes them (with channels)
145 example: -auto 1 2 5 9 finds screens and gives them channels
146$$Parameters!
147 note: <> means required, [] means optional
148
149-colorEditor: makes the main screen a color editor that just prints the current colors. Good for theme making
150current typeColors: default title, subtitle, pos, dim, extra, error, info, inverse, command, help, background
151
152-modem <side>: Sets the modem side to side
153
154-v/-verbose: turns on debug
155
156-q/-quiet: turns off debug
157]],
158[[$$Commands!
159
160COMMAND [screen] [text]: Sends text to the connected turtle. See turtle commands for valid commands
161
162SCREEN [side] [channel] [theme]: Adds a screen. You can also specify the channel and theme of the screen.
163
164REMOVE [screen]: Removes the selected screen (cannot remove the main screen)
165
166THEME [screen] [name]: Sets the theme of the given screen. THEME [screen] resets the screen to default theme
167$$Commands!
168
169THEME [name]: Sets the default theme.
170
171RECEIVE [screen] [channel]: Changes the receive channel of the given screen
172
173SEND [screen] [channel]: Changes the send channel of the given screen (for whatever reason)
174
175STATION [screen] [channel]: Sets the given screen to/from a station. If changing from a station, will set the screen's channel
176$$Commands!
177
178SET [text]: Sets a default command that can be backspaced. Useful for color editing or command sending
179 Use SET with nothing after to remove text
180
181SIDE [screen]: Sets a default screen for "sided" commands.
182 Any command that takes a [screen] is sided
183
184EXIT/QUIT: Quits the program gracefully
185$$Commands!
186
187COLOR [themeName] [typeColor] [textColor] [backColor]: Sets the the text and background colors of the given typeColor of the given theme. See notes on "colorEditor" parameter for more info
188
189SAVETHEME [themeName] [fileName]: Saves the given theme as fileName for later use
190
191SAVETHEME [screen] [fileName]: Same as above but for a screen's theme
192
193AUTO [channelList]: Automatically searches for nearby screens, providing them sequentially with channels if a channel list is given
194 Example Use: AUTO 1 2 5 9
195$$Commands!
196
197HELP: Displays this again!
198
199VERBOSE: Turns debug on
200
201QUIET: Turns debug off
202
203]],
204[[$$Turtle Commands!
205
206Stop: Stops the turtle where it is
207
208Return: The turtle will return to its starting point, drop off its load, and stop
209
210Drop: Turtle will immediately go and drop its inventory
211
212Pause: Pauses the turtle
213
214Resume: Resumes paused turtles
215
216Refuel: Turtle will schedule an emergency refuel
217 This could take from fuelChest, or quadCopter
218 or fuel in inventory (in that order)
219]]
220}
221
222--Generic Functions--
223local function debug(...)
224 --if doDebug then return print(...) end --Basic
225 if doDebug then
226 print("\nDEBUG: ",...)
227 os.pullEvent("char")
228 end
229end
230local function clearScreen(x,y, periph)
231 periph, x, y = periph or term, x or 1, y or 1
232 periph.clear()
233 periph.setCursorPos(x,y)
234end
235
236local function swapKeyValue(tab)
237 for a,b in pairs(tab) do
238 tab[b] = a
239 end
240 return tab
241end
242local function copyTable(tab)
243 local toRet = {}
244 for a,b in pairs(tab) do
245 toRet[a] = b
246 end
247 return toRet
248end
249local function checkChannel(num)
250 num = tonumber(num)
251 if not num then return false end
252 if 1 <= num and num <= 65535 then
253 return num
254 end
255 return false
256end
257local function truncate(text, xDim)
258 if #text <= xDim then return text end
259 return #text >= 4 and text:sub(1,xDim-3).."..." or text:sub(1,3)
260end
261local function align(text, xDim, direction, trunc)
262 text = tostring(text or "None")
263 if trunc == nil then trunc = true end
264 if #text >= xDim and trunc then return truncate(text,xDim) end
265 for i=1, xDim-#text do
266 if direction == "right" then
267 text = " "..text
268 elseif direction == "left" then
269 text = text.." "
270 end
271 end
272 return text
273end
274local function alignR(text, xDim, trunc)
275 return align(text, xDim, "right", trunc)
276end
277local function alignL(text, xDim, trunc)
278 return align(text, xDim, "left", trunc)
279end
280local function center(text, xDim, char)
281 if not xDim then error("Center: No dim given",2) end
282 char = char or " "
283 local a = (xDim-#text)/2
284 for i=1, a do
285 text = char..text..char
286 end
287 return #text == xDim and text or text..char --If not full length, add a space
288end
289local function leftRight(first, second, dim)
290 return alignL(tostring(first),dim-#tostring(second))..tostring(second)
291end
292local function roundNegative(num) --Rounds numbers up to 0
293 if num >= 0 then return num else return 0 end
294end
295
296
297local function testPeripheral(periph, periphFunc)
298 if type(periph) ~= "table" then return false end
299 if type(periph[periphFunc]) ~= "function" then return false end
300 if periph[periphFunc]() == nil then --Expects string because the function could access nil
301 return false
302 end
303 return true
304end
305
306local function initModem() --Sets up modem, returns true if modem exists
307 if not testPeripheral(modem, "isWireless") then
308 if modemSide then
309 if peripheral.getType(modemSide) == "modem" then
310 modem = peripheral.wrap(modemSide)
311 if modem.isWireless and not modem.isWireless() then --Apparently this is a thing
312 modem = nil
313 return false
314 end
315 return true
316 end
317 end
318 if peripheral.find then
319 modem = peripheral.find("modem", function(side, obj) return obj.isWireless() end)
320 end
321 return modem and true or false
322 end
323 return true
324end
325
326--COLOR/THEME RELATED
327for a, b in pairs(colors) do --This is so commands color commands can be entered in one case
328 colors[a:lower()] = b
329end
330colors.none = 0 --For adding things
331
332local requiredColors = {"default","title", "subtitle", "pos", "dim", "extra", "error", "info", "inverse", "command", "help", "background"}
333
334local function checkColor(name, text, back) --Checks if a given color works
335 local flag = false
336 for a, b in ipairs(requiredColors) do
337 if b == name then
338 flag = true
339 break
340 end
341 end
342 if not flag or not (tonumber(text) or colors[text]) or not (tonumber(back) or colors[back]) then return false end
343 return true
344end
345
346
347local themes = {} --Loaded themes, gives each one a names
348local function newTheme(name)
349 name = name:lower() or "none"
350 local self = {name = name}
351 self.addColor = function(self, colorName, text, back) --Colors are optional. Will default to "default" value. Make sure default is a color
352 if colorName == "default" and (not text or not back) then return self end
353 if not text then text = 0 end
354 if not back then back = 0 end
355 if not checkColor(colorName, text, back) then debug("Color check failed: ",name," ",text," ",back); return self end --Back or black because optional
356 colorName = colorName or "none"
357 self[colorName] = {text = text, background = back}
358 return self --Allows for chaining :)
359 end
360 themes[name] = self
361 return self
362end
363
364local function parseTheme(file)
365 local addedTheme = newTheme(file:match("^.-\n") or "newTheme") --Initializes the new theme to the first line
366 file:sub(file:find("\n") or 1) --If there is a newLine, this cuts everything before it. I don't care that the newLine is kept
367 for line in file:gmatch("[^\n]+\n") do --Go through all the color lines (besides first one)
368 local args = {}
369 for word in line:gmatch("%S+") do
370 table.insert(args,word)
371 end
372 addedTheme:addColor(args[1]:match("%a+") or "nothing", tonumber(args[2]) or colors[args[2]], tonumber(args[3]) or colors[args[3]]) --"nothing" will never get used, so its just lazy error prevention
373 end
374 local flag = true --Make sure a theme has all required elements
375 for a,b in ipairs(requiredColors) do
376 if not addedTheme[b] then
377 flag = false
378 debug("Theme is missing color '",b,"'")
379 end
380 end
381 if not flag then
382 themes[addedTheme.name] = nil
383 debug("Failed to load theme")
384 return false
385 end
386 return addedTheme
387end
388--This is how adding colors will work
389--regex for adding from file:
390--(\w+) (\w+) (\w+)
391-- \:addColor\(\"\1\"\, \2\, \3\)
392
393
394newTheme("default")
395 :addColor("default",colors.white, colors.black)
396 :addColor("title", colors.green, colors.gray)
397 :addColor("subtitle", colors.white, colors.black)
398 :addColor("pos", colors.green, colors.black)
399 :addColor("dim", colors.lightBlue, colors.black)
400 :addColor("extra", colors.lightGray, colors.black)
401 :addColor("error", colors.red, colors.white)
402 :addColor("info", colors.blue, colors.lightGray)
403 :addColor("inverse", colors.yellow, colors.blue)
404 :addColor("command", colors.lightBlue, colors.black)
405 :addColor("help", colors.cyan, colors.black)
406 :addColor("background", colors.none, colors.none)
407
408newTheme("blue")
409 :addColor("default",colors.white, colors.blue)
410 :addColor("title", colors.lightBlue, colors.gray)
411 :addColor("subtitle", 1, 2048)
412 :addColor("pos", 16, 2048)
413 :addColor("dim", colors.lime, 0)
414 :addColor("extra", 8, 2048)
415 :addColor("error", 8, 16384)
416 :addColor("info", 2048, 256)
417 :addColor("inverse", 2048, 1)
418 :addColor("command", 2048, 8)
419 :addColor("help", 16384, 1)
420 :addColor("background", 1, 2048)
421
422newTheme("seagle")
423 :addColor("default",colors.white, colors.black)
424 :addColor("title", colors.white, colors.black)
425 :addColor("subtitle", colors.red, colors.black)
426 :addColor("pos", colors.gray, colors.black)
427 :addColor("dim", colors.lightBlue, colors.black)
428 :addColor("extra", colors.lightGray, colors.black)
429 :addColor("error", colors.red, colors.white)
430 :addColor("info", colors.blue, colors.lightGray)
431 :addColor("inverse", colors.yellow, colors.lightGray)
432 :addColor("command", colors.lightBlue, colors.black)
433 :addColor("help", colors.red, colors.white)
434 :addColor("background", colors.white, colors.black)
435
436newTheme("random")
437 :addColor("default",colors.white, colors.black)
438 :addColor("title", colors.pink, colors.blue)
439 :addColor("subtitle", colors.black, colors.white)
440 :addColor("pos", colors.green, colors.black)
441 :addColor("dim", colors.lightBlue, colors.black)
442 :addColor("extra", colors.lightGray, colors.lightBlue)
443 :addColor("error", colors.white, colors.yellow)
444 :addColor("info", colors.blue, colors.lightGray)
445 :addColor("inverse", colors.yellow, colors.lightGray)
446 :addColor("command", colors.green, colors.lightGray)
447 :addColor("help", colors.white, colors.yellow)
448 :addColor("background", colors.white, colors.red)
449
450newTheme("rainbow")
451 :addColor("dim", 32, 0)
452 :addColor("background", 16384, 0)
453 :addColor("extra", 2048, 0)
454 :addColor("info", 2048, 0)
455 :addColor("inverse", 32, 0)
456 :addColor("subtitle", 2, 0)
457 :addColor("title", 16384, 0)
458 :addColor("error", 1024, 0)
459 :addColor("default", 1, 512)
460 :addColor("command", 16, 0)
461 :addColor("pos", 16, 0)
462 :addColor("help", 2, 0)
463
464newTheme("green")
465 :addColor("dim", 16384, 0)
466 :addColor("background", 0, 0)
467 :addColor("extra", 2048, 0)
468 :addColor("info", 32, 256)
469 :addColor("inverse", 8192, 1)
470 :addColor("subtitle", 1, 0)
471 :addColor("title", 8192, 128)
472 :addColor("error", 16384, 32768)
473 :addColor("default", 1, 8192)
474 :addColor("command", 2048, 32)
475 :addColor("pos", 16, 0)
476 :addColor("help", 512, 32768)
477
478
479--If you modify a theme a bunch and want to save it
480local function saveTheme(theme, fileName)
481 if not theme or not type(fileName) == "string" then return false end
482 local file = fs.open(fileName,"w")
483 if not file then return false end
484 file.writeLine(fileName)
485 for a,b in pairs(theme) do
486 if type(b) == "table" then --If it contains color objects
487 file.writeLine(a.." "..tostring(b.text).." "..tostring(b.background))
488 end
489 end
490 file.close()
491 return true
492end
493
494--BUTTON CLASS
495local button = {}
496
497button.checkPoint = function(buttons, pos) --Returns a command or nil
498 for a, b in pairs(buttons) do
499 if pos[2] == b.line then
500 if pos[1] >= b.xDim[1] and pos[1] <= b.xDim[2] then
501 return b.command
502 end
503 end
504 end
505end
506
507button.makeLine = function(buttons, sep, xDim)
508 local toRet = ""
509 for a, b in ipairs(buttons) do
510 toRet = toRet..center(b.text, (b.xDim[2]-b.xDim[1]))..sep
511 end
512 return toRet:sub(1,-2).."" --Take off the last sep
513end
514
515button.new = function(line, xStart, xEnd, command, display)
516 local toRet = {}
517 setmetatable(toRet, {__index = button})
518 toRet.line = line
519 toRet.xDim = {math.min(xStart, xEnd), math.max(xStart, xEnd)}
520 toRet.command = command
521 toRet.text = display
522 return toRet
523end
524
525
526--==SCREEN CLASS FUNCTIONS==
527local screenClass = {} --This is the class for all monitor/screen objects
528screenClass.screens = {} --A simply numbered list of screens
529screenClass.sides = {} --A mapping of screens by their side attached
530screenClass.channels = {} --A mapping of receiving channels that have screens attached. Used for the receiver part
531screenClass.sizes = {{7,18,29,39,50}, {5,12,19} , computer = {51, 19}, turtle = {39,13}, pocket = {26,20}}
532
533screenClass.setTextColor = function(self, color) --Accepts raw color
534 if color and self.term.isColor() then
535 self.textColor = color
536 self.term.setTextColor(color)
537 return true
538 end
539 return false
540end
541screenClass.setBackgroundColor = function(self, color) --Accepts raw color
542 if color and self.term.isColor() then
543 self.backgroundColor = color
544 self.term.setBackgroundColor(color)
545 return true
546 end
547 return false
548end
549screenClass.setColor = function(self, color) --Wrapper, accepts themecolor objects
550 if type(color) ~= "table" then error("Set color received a non-table",2) end
551 local text, back = color.text, color.background
552 if not text or text == 0 then text = self.theme.default.text end
553 if not back or back == 0 then back = self.theme.default.background end
554 return self:setTextColor(text) and self:setBackgroundColor(back)
555end
556
557screenClass.themeName = "default" --Setting super for fallback
558screenClass.theme = themes.default
559
560screenClass.rec = { --Initial values for all displayed numbers
561 label = "Quarry Bot",
562 id = 1,
563 percent = 0,
564 xPos = 0,
565 zPos = 0,
566 layersDone = 0,
567 x = 0,
568 z = 0,
569 layers = 0,
570 openSlots = 0,
571 mined = 0,
572 moved = 0,
573 chestFull = false,
574 isAtChest = false,
575 isGoingToNextLayer = false,
576 foundBedrock = false,
577 fuel = 0,
578 volume = 0,
579 distance = 0,
580 yPos = 0
581}
582
583screenClass.new = function(side, receive, themeFile)
584 local self = {}
585 setmetatable(self, {__index = screenClass}) --Establish Hierarchy
586 self.side = side
587 if side == "computer" then
588 self.term = term
589 else
590 self.term = peripheral.wrap(side)
591 if not (self.term and peripheral.getType(side) == "monitor") then --Don't create an object if it doesn't exist
592 if doDebug then
593 error("No monitor on side "..tostring(side))
594 end
595 self = nil --Save memory?
596 return false
597 end
598 end
599
600 --Channels and ids
601 self.receive = tonumber(receive) --Receive Channel
602 self.send = nil --Reply Channel, obtained in handshake
603 self.id = #screenClass.screens+1
604 --Colors
605 self.themeName = nil --Will be set by setTheme
606 self.theme = nil
607 self.isColor = self.term.isColor() --Just for convenience
608 --Other Screen Properties
609 self.dim = {self.term.getSize()} --Raw dimensions
610 --Initializations
611 self.isDone = false --Flag for when the turtle is done transmitting
612 self.size = {} --Screen Size, assigned in setSize
613 self.textColor = colors.white --Just placeholders until theme is loaded and run
614 self.backColor = colors.black
615 self.toPrint = {}
616 self.isComputer = false
617 self.isTurtle = false
618 self.isPocket = false
619 self.acceptsInput = false
620 self.legacy = false --Whether it expects tables or strings
621 self.rec = copyTable(screenClass.rec)
622
623 screenClass.screens[self.id] = self
624 screenClass.sides[self.side] = self
625 if self.receive then
626 modem.open(self.receive) --Modem should be defined by the time anything is open
627 screenClass.channels[self.receive] = self --If anyone ever asked, you could have multiple screens per channel, but its silly if no one ever needs it
628 end
629 self:setSize() --Finish Initialization
630 self:setTheme(themeFile)
631 return self
632end
633
634screenClass.remove = function(tab) --Cleanup function
635 if type(tab) == "number" then --Expects table, can take id (for no apparent reason)
636 tab = screenClass.screens[tab]
637 end
638 tab:removeStation()
639 if tab.side == "REMOVED" then return end
640 if tab.side == "computer" then error("Tried removing computer screen",2) end --This should never happen
641 tab:reset() --Clear screen
642 tab:say("Removed", tab.theme.info, 1) --Let everyone know whats up
643 screenClass.screens[tab.id] = {side = "REMOVED"} --Not nil because screw up len()
644 screenClass.sides[tab.side] = nil
645 tab:removeChannel()
646end
647
648--Init Functions
649screenClass.removeChannel = function(self)
650 self.send = nil
651 if self.receive then
652 screenClass.channels[self.receive] = nil
653 if modem and modem.isOpen(self.receive) then
654 modem.close(self.receive)
655 end
656 self.receive = nil
657 end
658 self:setSize()
659end
660
661screenClass.setChannel = function(self, channel)
662 if self.isStation then return false end --Don't want to set channel station
663 self:removeChannel()
664 if type(channel) == "number" then
665 self.receive = channel
666 screenClass.channels[self.receive] = self
667 if modem and not modem.isOpen(channel) then modem.open(channel) end
668 end
669 self:setSize() --Sets proper draw function
670end
671
672screenClass.setStation = function(self) --Note: This only changes the "set" methods so that "update" methods remain intact per object :)
673 self:removeChannel()
674 if not self.isStation then --Just in case this gets called more than once
675 self.isStation = true
676 table.insert(stationsList,self)
677 end
678 self:setSize()
679end
680
681screenClass.removeStation = function(self)
682 if self.isStation then
683 for i=1, #stationsList do --No IDs so have to do a linear traversal
684 if stationsList[i] == self then table.remove(stationsList, i) end
685 end
686 end
687 self.isStation = false
688 self:setSize()
689end
690
691screenClass.setSize = function(self) --Sets screen size
692 if self.side ~= "computer" and not self.term then self.term = peripheral.wrap(self.side) end
693 if not self.term.getSize() then --If peripheral is having problems/not there. Don't go further than term, otherwise index nil (maybe?)
694 debug("There is no term...")
695 self.updateDisplay = function() end --Do nothing on screen update, overrides class
696 return true
697 elseif self.isStation then
698 self:setStationDisplay()
699 elseif not self.receive then
700 self:setBrokenDisplay() --This will prompt user to set channel
701 elseif self.send then --This allows for class inheritance
702 self:setNormalDisplay() --In case objects have special updateDisplay methods --Remove function in case it exists, defaults to super
703 else --If the screen needs to have a handshake display
704 self:setHandshakeDisplay()
705 end
706 self:resetButtons()
707 self.dim = { self.term.getSize()}
708 local tab = screenClass.sizes
709 for a=1, 2 do --Want x and y dim
710 for b=1, #tab[a] do --Go through all normal sizes, x and y individually
711 if tab[a][b] <= self.dim[a] then --This will set size higher until false
712 self.size[a] = b
713 end
714 end
715 end
716 local function isThing(toCheck, thing) --E.G. isThing(self.dim,"computer")
717 return toCheck[1] == tab[thing][1] and toCheck[2] == tab[thing][2]
718 end
719 self.isComputer = isThing(self.dim, "computer")
720 self.isTurtle = isThing(self.dim, "turtle")
721 self.isPocket = isThing(self.dim, "pocket")
722 self.acceptsInput = self.isComputer or self.isTurtle or self.isPocket
723 return self
724end
725
726screenClass.setTheme = function(self, themeName, stopReset)
727 if not themes[themeName] then --If we don't have it already, try to load it
728 local fileName = themeName or ".." --.. returns false and I don't think you can name a file this
729 if fs.exists(themeFolder) then fileName = themeFolder..fileName end
730 if fs.exists(fileName) then
731 debug("Loading theme: ",fileName)
732 local file = fs.open(fileName, "r")
733 if not file then debug("Could not load theme '",themeName,"' file not found") end
734 parseTheme(file.readAll()) --Parses the text to make a theme, returns theme
735 file.close()
736 self.themeName = themeName:lower() --We can now set our themeName to the fileName
737 else
738 --Resets theme to super
739 if not stopReset then --This exists so its possible to set default theme without breaking world
740 self.themeName = nil
741 self.theme = nil
742 end
743 return false
744 end
745 else
746 self.themeName = themeName:lower()
747 end
748 self.theme = themes[self.themeName] --Now the theme is loaded or the function doesn't get here
749 return true
750end
751
752--Adds text to the screen buffer
753screenClass.tryAddRaw = function(self, line, text, color, ...) --This will try to add text if Y dimension is a certain size
754 local doAdd = {...} --booleans for small, medium, and large
755 if type(text) ~= "string" then error("tryAddRaw got "..type(text)..", expected string",2) end
756 if not text then
757 debug("tryAddRaw got no string on line ",line)
758 return false
759 end
760 if type(color) ~= "table" then error("tryAddRaw did not get a color",2) end
761 --color = color or {text = colors.white}
762 for i=1, ySizes do --As of now there are 3 Y sizes
763 local test = doAdd[i]
764 if test == nil then test = doAdd[#doAdd] end --Set it to the last known setting if doesn't exist
765 if test and self.size[2] == i then --If should add this text for this screen size and the monitor is this size
766 if #text <= self.dim[1] then
767 self.toPrint[line] = {text = text, color = color}
768 return true
769 else
770 debug("Tried adding '",text,"' on line ",line," but was too long: ",#text," vs ",self.dim[1])
771 end
772 end
773 end
774 return false
775end
776screenClass.tryAdd = function(self, text, color,...) --Just a wrapper
777 return self:tryAddRaw(#self.toPrint+1, text, color, ...)
778end
779screenClass.tryAddC = function(self, text, color, ...) --Centered text
780 return self:tryAdd(center(text, self.dim[1]), color, ...)
781end
782
783screenClass.reset = function(self,color)
784 color = color or self.theme.background
785 self:setColor(color)
786 self.term.clear()
787 self.term.setCursorPos(1,1)
788end
789screenClass.say = function(self, text, color, line)
790 local currColor = self.backgroundColor
791 color = color or debug("Printing ",text," but had no themeColor: ",self.theme.name) or {} --Set default for nice error, alert that errors occur
792 self:setColor(color)
793 local line = line or ({self.term.getCursorPos()})[2] or self:setSize() or 1 --If current yPos not found, sets screen size and moves cursor to 1
794 if doDebug and #text > self.dim[1] then error("Tried printing: '"..text.."', but was too big") end
795 self.term.setCursorPos(1,line)
796 for i=1, self.dim[1]-#text do --This is so the whole line's background gets filled.
797 text = text.." "
798 end
799 self.term.write(text)
800 self.term.setCursorPos(1, line+1)
801end
802screenClass.pushScreenUpdates = function(self)
803 for i=1, self.dim[2] do
804 local tab = self.toPrint[i]
805 if tab then
806 self:say(tab.text, tab.color, i)
807 end
808 end
809 self.term.setCursorPos(1,self.dim[2]) --So we can see errors
810end
811screenClass.resetButtons = function(self)
812 self.buttons = {}
813end
814screenClass.addButton = function(self, button)
815 self.buttons[#self.buttons+1] = button
816end
817
818screenClass.updateNormal = function(self) --This is the normal updateDisplay function
819 local str = tostring
820 self.toPrint = {} --Reset table
821 local message, theme, x = self.rec, self.theme, self.dim[1]
822 if not self.isDone then --Normally
823
824
825 if self.size[1] == 1 then --Small Width Monitor
826 if not self:tryAdd(message.label, theme.title, false, false, true) then --This will be a title, basically
827 self:tryAdd("Quarry!", theme.title, false, false, true)
828 end
829
830 self:tryAdd("-Fuel-", theme.subtitle , false, true, true)
831 if not self:tryAdd(str(message.fuel), theme.extra, false, true, true) then --The fuel number may be bigger than the screen
832 self:tryAdd("A lot", theme.extra, false, true, true)
833 end
834
835 self:tryAdd("--%%%--", theme.subtitle, false, true, true)
836 self:tryAdd(alignR(str(message.percent).."%", 7), theme.pos , false, true, true) --This can be an example. Print (receivedMessage).percent in blue on all different screen sizes
837 self:tryAdd(center(str(message.percent).."%", x), theme.pos, true, false) --I want it to be centered on 1x1
838
839 self:tryAdd("--Pos--", theme.subtitle, false, true, true)
840 self:tryAdd("X:"..alignR(str(message.xPos), 5), theme.pos, true)
841 self:tryAdd("Z:"..alignR(str(message.zPos), 5), theme.pos , true)
842 self:tryAdd("Y:"..alignR(str(message.layersDone), 5), theme.pos , true)
843
844 if not self:tryAdd(str(message.x).."x"..str(message.z).."x"..str(message.layers), theme.dim , true, false) then --If you can't display the y, then don't
845 self:tryAdd(str(message.x).."x"..str(message.z), theme.dim , true, false)
846 end
847 self:tryAdd("--Dim--", theme.subtitle, false, true, true)
848 self:tryAdd("X:"..alignR(str(message.x), 5), theme.dim, false, true, true)
849 self:tryAdd("Z:"..alignR(str(message.z), 5), theme.dim, false, true, true)
850 self:tryAdd("Y:"..alignR(str(message.layers), 5), theme.dim, false, true, true)
851
852 self:tryAdd("-Extra-", theme.subtitle, false, false, true)
853 self:tryAdd(alignR(textutils.formatTime(os.time()):gsub(" ","").."", 7), theme.extra, false, false, true) --Adds the current time, formatted, without spaces.
854 self:tryAdd("Used:"..alignR(str(16-message.openSlots),2), theme.extra, false, false, true)
855 self:tryAdd("Dug"..alignR(str(message.mined), 4), theme.extra, false, false, true)
856 self:tryAdd("Mvd"..alignR(str(message.moved), 4), theme.extra, false, false, true)
857 if message.status then
858 self:tryAdd(alignL(message.status, x), theme.info, false, false, true)
859 end
860 if message.chestFull then
861 self:tryAdd("ChstFll", theme.error, false, false, true)
862 end
863
864 end
865 if self.size[1] == 2 then --Medium Monitor
866 if not self:tryAdd(message.label, theme.title, false, false, true) then --This will be a title, basically
867 self:tryAdd("Quarry!", theme.title, false, false, true)
868 end
869
870 self:tryAdd(center("Fuel",x,"-"), theme.subtitle , false, true, true)
871 if not self:tryAdd(str(message.fuel), theme.extra, false, true, true) then --The fuel number may be bigger than the screen
872 self.toPrint[#self.toPrint] = nil
873 self:tryAdd("A lot", theme.extra, false, true, true)
874 end
875
876 self:tryAdd(str(message.percent).."% Complete", theme.pos , true) --This can be an example. Print (receivedMessage).percent in blue on all different screen sizes
877
878 self:tryAdd(center("Pos",x,"-"), theme.subtitle, false, true, true)
879 self:tryAdd(leftRight("X Coordinate:",message.xPos, x), theme.pos, true)
880 self:tryAdd(leftRight("Z Coordinate:",message.zPos, x), theme.pos , true)
881 self:tryAdd(leftRight("On Layer:",message.layersDone, x), theme.pos , true)
882
883 if not self:tryAdd("Size: "..str(message.x).."x"..str(message.z).."x"..str(message.layers), theme.dim , true, false) then --This is already here... I may as well give an alternative for those people with 1000^3quarries
884 self:tryAdd(str(message.x).."x"..str(message.z).."x"..str(message.layers), theme.dim , true, false)
885 end
886 self:tryAdd(center("Dim",x,"-"), theme.subtitle, false, true, true)
887 self:tryAdd(leftRight("Total X:", message.x, x), theme.dim, false, true, true)
888 self:tryAdd(leftRight("Total Z:", message.z, x), theme.dim, false, true, true)
889 self:tryAdd(leftRight("Total Layers:", message.layers, x), theme.dim, false, true, true)
890 self:tryAdd(leftRight("Volume", message.volume, x), theme.dim, false, false, true)
891
892 self:tryAdd(center("Extras",x,"-"), theme.subtitle, false, false, true)
893 self:tryAdd(leftRight("Time: ", textutils.formatTime(os.time()):gsub(" ","").."", x), theme.extra, false, false, true) --Adds the current time, formatted, without spaces.
894 self:tryAdd(leftRight("Used Slots:", 16-message.openSlots, x), theme.extra, false, false, true)
895 self:tryAdd(leftRight("Blocks Mined:", message.mined, x), theme.extra, false, false, true)
896 self:tryAdd(leftRight("Spaces Moved:", message.moved, x), theme.extra, false, false, not self.isPocket)
897 if message.status then
898 self:tryAdd(message.status, theme.info, false, false, true)
899 end
900 if message.chestFull then
901 self:tryAdd("Chest Full, Fix It", theme.error, false, true, true)
902 end
903 end
904 if self.size[1] >= 3 then --Large or larger screens
905 if not self:tryAdd(message.label..alignR(" Turtle #"..str(message.id),x-#message.label), theme.title, true) then
906 self:tryAdd("Your turtle's name is long...", theme.title, true)
907 end
908 self:tryAdd("Fuel: "..alignR(str(message.fuel),x-6), theme.extra, true)
909
910 self:tryAdd("Percentage Done: "..alignR(str(message.percent).."%",x-17), theme.pos, true)
911
912 local var1 = math.max(#str(message.x), #str(message.z), #str(message.layers))
913 local var2 = (x-6-var1+3)/3
914 self:tryAdd("Pos: "..alignR(" X:"..alignR(str(message.xPos),var1),var2)..alignR(" Z:"..alignR(str(message.zPos),var1),var2)..alignR(" Y:"..alignR(str(message.layersDone),var1),var2), theme.pos, true)
915 self:tryAdd("Size:"..alignR(" X:"..alignR(str(message.x),var1),var2)..alignR(" Z:"..alignR(str(message.z),var1),var2)..alignR(" Y:"..alignR(str(message.layers),var1),var2), theme.dim, true)
916 self:tryAdd("Volume: "..str(message.volume), theme.dim, false, true, true)
917 self:tryAdd("",{}, false, false, true)
918 self:tryAdd(center("____---- EXTRAS ----____",x), theme.subtitle, false, false, true)
919 self:tryAdd(center("Time:"..alignR(textutils.formatTime(os.time()),10), x), theme.extra, false, true, true)
920 self:tryAdd(center("Current Day: "..str(os.day()), x), theme.extra, false, false, true)
921 self:tryAdd("Used Inventory Slots: "..alignR(str(16-message.openSlots),x-22), theme.extra, false, true, true)
922 self:tryAdd("Blocks Mined: "..alignR(str(message.mined),x-14), theme.extra, false, true, true)
923 self:tryAdd("Blocks Moved: "..alignR(str(message.moved),x-14), theme.extra, false, true, true)
924 self:tryAdd("Distance to Turtle: "..alignR(str(message.distance), x-20), theme.extra, false, false, true)
925 self:tryAdd("Actual Y Pos (Not Layer): "..alignR(str(message.yPos), x-26), theme.extra, false, false, true)
926
927 if message.chestFull then
928 self:tryAdd("Dropoff is Full, Please Fix", theme.error, false, true, true)
929 end
930 if message.foundBedrock then
931 self:tryAdd("Found Bedrock! Please Check!!", theme.error, false, true, true)
932 end
933 if message.status then
934 self:tryAdd("Status: "..message.status, theme.info, false, true, true)
935 end
936 if message.isAtChest then
937 self:tryAdd("Turtle is at home chest", theme.info, false, true, true)
938 end
939 if message.isGoingToNextLayer then
940 self:tryAdd("Turtle is going to next layer", theme.info, false, true, true)
941 end
942
943
944
945 end
946 if self.term.isColor() and ((self.size[2] >= 2 and self.size[1] >= 3) or self.isPocket) then
947 local line = self.acceptsInput and self.dim[2]-1 or self.dim[2]
948 local part = math.floor(x/4)
949 if #self.buttons == 0 then
950 self:addButton(button.new(line, part*0, part*1-1, "drop","Drop"))
951 self:addButton(button.new(line, part*1, part*2-1, "pause","Pause"))
952 self:addButton(button.new(line, part*2, part*3-1, "return","Return"))
953 self:addButton(button.new(line, part*3, part*4-1, "refuel","Refuel"))
954 end
955 self:tryAddRaw(line, button.makeLine(self.buttons,"|"):sub(1,self.isPocket and -2 or -1), theme.command, false, true) --Silly code because pocket breaks
956 end
957 else --If is done
958 if self.size[1] == 1 then --Special case for small monitors
959 self:tryAdd("Done", theme.title, true)
960 if not self:tryAdd("Dug"..alignR(str(message.mined),4, false), theme.pos, true) then
961 self:tryAdd("Dug", theme.pos, true)
962 self:tryAdd(alignR(str(message.mined),x), theme.pos, true)
963 end
964 if not self:tryAdd("Fuel"..alignR(str(message.fuel),3, false), theme.pos, true) then
965 self:tryAdd("Fuel", theme.pos, true)
966 self:tryAdd(alignR(str(message.fuel),x), theme.pos, true)
967 end
968 self:tryAdd("-------", theme.subtitle, false,true,true)
969 self:tryAdd("Turtle", theme.subtitle, false, true, true)
970 self:tryAdd(center("is", x), theme.subtitle, false, true, true)
971 self:tryAdd(center("Done!", x), theme.subtitle, false, true, true)
972 else
973 self:tryAdd("Done!", theme.title, true)
974 self:tryAdd("Curr Fuel: "..str(message.fuel), theme.pos, true)
975 if message.preciseTotals then
976 local tab = {}
977 for a,b in pairs(message.preciseTotals) do --Sorting the table
978 a = a:match(":(.+)")
979 if #tab == 0 then --Have to initialize or rest does nothing :)
980 tab[1] = {a,b}
981 else
982 for i=1, #tab do --This is a really simple sort. Probably not very efficient, but I don't care.
983 if b > tab[i][2] then --Gets the second value from the table, which is the itemCount
984 table.insert(tab, i, {a,b})
985 break
986 elseif i == #tab then --Insert at the end if not bigger than anything
987 table.insert(tab,{a,b})
988 end
989 end
990 end
991 end
992 for i=1, #tab do --Print all the blocks in order
993 local firstPart = "#"..tab[i][1]..": "
994 self:tryAdd(firstPart..alignR(tab[i][2], x-#firstPart), (i%2 == 0) and theme.inverse or theme.info, true, true, true) --Switches the colors every time
995 end
996 else
997 self:tryAdd("Blocks Dug: "..str(message.mined), theme.inverse, true)
998 self:tryAdd("Cobble Dug: "..str(message.cobble), theme.pos, false, true, true)
999 self:tryAdd("Fuel Dug: "..str(message.fuelblocks), theme.pos, false, true, true)
1000 self:tryAdd("Others Dug: "..str(message.other), theme.pos, false, true, true)
1001 end
1002 end
1003 end
1004end
1005screenClass.updateHandshake = function(self)
1006 self.toPrint = {}
1007 local half = math.ceil(self.dim[2]/2)
1008 if self.size[1] == 1 then --Not relying on the parameter system because less calls
1009 self:tryAddRaw(half-2, "Waiting", self.theme.error, true)
1010 self:tryAddRaw(half-1, "For Msg", self.theme.error, true)
1011 self:tryAddRaw(half, "On Chnl", self.theme.error, true)
1012 self:tryAddRaw(half+1, tostring(self.receive), self.theme.error, true)
1013 else
1014 local str = "for"
1015 if self.size[1] == 2 then str = "4" end--Just a small grammar change
1016 self:tryAddRaw(half-2, "", self.theme.error, true) --Filler
1017 self:tryAddRaw(half-1, center("Waiting "..str.." Message", self.dim[1]), self.theme.error, true)
1018 self:tryAddRaw(half, center("On Channel "..tostring(self.receive), self.dim[1]), self.theme.error, true)
1019 self:tryAddRaw(half+1, "",self.theme.error, true)
1020 end
1021end
1022screenClass.updateBroken = function(self) --If screen needs channel
1023 self.toPrint = {}
1024 if self.size[1] == 1 then
1025 self:tryAddC("No Rec", self.theme.pos, false, true, true)
1026 self:tryAddC("Channel", self.theme.pos, false, true, true)
1027 self:tryAddC("-------", self.theme.title, false, true, true)
1028 self:tryAddC("On Comp", self.theme.info, true)
1029 self:tryAddC("Type:", self.theme.info, true)
1030 self:tryAddC("RECEIVE", self.theme.command, true)
1031 if not self:tryAddC(self.side:upper(), self.theme.command, true) then --If we can't print the full side
1032 self:tryAddC("[side]",self.theme.command, true)
1033 end
1034 self:tryAddC("[Chnl]", self.theme.command, true)
1035 else
1036 self:tryAddC("No receiving", self.theme.pos, false, true, true)
1037 self:tryAddC("channel for", self.theme.pos, false, true, true)
1038 self:tryAddC("this screen", self.theme.pos, false, true, true)
1039 self:tryAddC("-----------------", self.theme.title, false, true, true)
1040 self:tryAddC("On main computer,", self.theme.info, true)
1041 self:tryAddC("Type:", self.theme.info, true)
1042 self:tryAdd("", self.theme.command, false, true, true)
1043 self:tryAddC('"""', self.theme.command, false, true, true)
1044 self:tryAddC("RECEIVE", self.theme.command, true)
1045 if not self:tryAddC(self.side:upper(), self.theme.command, true) then --If we can't print the full side
1046 self:tryAddC("[side]",self.theme.command, true)
1047 end
1048 self:tryAddC("[desired channel]", self.theme.command, true)
1049 self:tryAddC('"""', self.theme.command, false, true, true)
1050 end
1051end
1052screenClass.updateStation = function(self)
1053 self.toPrint = {}
1054 sepChar = "| "
1055 local part = math.floor((self.dim[1]-3*#sepChar - 3)/3)
1056 self:tryAdd(alignL("ID",3)..sepChar..alignL("Side",part)..sepChar..alignL("Channel",part)..sepChar..alignL("Theme",part), self.theme.title, true, true, true)--Headings
1057 local line = ""
1058 for i=1, self.dim[1] do line = line.."-" end
1059 self:tryAdd(line, self.theme.title, false, true, true)
1060 for a,b in ipairs(screenClass.screens) do
1061 if b.side ~= "REMOVED" then
1062 self:tryAdd(alignL(b.id,3)..sepChar..alignL(b.side,part)..sepChar..alignL(b.receive, part)..sepChar..alignL(b.theme.name,part), self.theme.info, true, true, true)--Prints info about all screens
1063 end
1064 end
1065end
1066
1067screenClass.updateDisplay = screenClass.updateNormal --Update screen method is normally this one
1068
1069--Misc
1070screenClass.setNormalDisplay = function(self)
1071 self.updateDisplay = self.updateNormal --This defaults to super if doesn't exist
1072end
1073screenClass.setHandshakeDisplay = function(self)
1074 self.updateDisplay = self.updateHandshake --Sets update to handshake version, defaults to super if doesn't exist
1075end
1076screenClass.setBrokenDisplay = function(self)
1077 self.updateDisplay = self.updateBroken
1078end
1079screenClass.setStationDisplay = function(self)
1080 self.updateDisplay = self.updateStation
1081end
1082
1083--Help Function. Goes so low so can see screenClass.theme
1084local function displayHelp()
1085 local dummy = {term = term} --This will be a dummy "screnClass object" for setting color
1086 setmetatable(dummy, {__index = screenClass})
1087 local theme = dummy.theme
1088 local tab = {}
1089 local indexOuter = "main"
1090 local indexInner = 1
1091 for key, value in pairs(helpResources) do
1092 tab[key] = {}
1093 for a in value:gmatch("$$([^$]+)") do
1094 table.insert(tab[key], a) --Just inserting pages
1095 end
1096 end
1097 while true do
1098 dummy:setColor(theme.help)
1099 clearScreen(1,2)
1100 print(tab[indexOuter][indexInner]:match("\n(.+)")) --Print all but first line
1101 dummy:setColor(theme.title)
1102 dummy.term.setCursorPos(1,1)
1103 print(alignL(tab[indexOuter][indexInner]:match("[^\n]+") or "",({dummy.term.getSize()})[1])) --Print first line
1104 dummy:setColor(theme.info)
1105 local text = tostring(indexInner).."/"..tostring(#tab[indexOuter])
1106 term.setCursorPos(({term.getSize()})[1]-#text,1)
1107 term.write(text) --Print the current page number
1108 local event, key = os.pullEvent("key")
1109 key = keyMap[key]
1110 if tonumber(key) and tab[tonumber(key)] then
1111 indexOuter = tonumber(key)
1112 indexInner = 1
1113 elseif key == "Q" then
1114 os.pullEvent("char") --Capture extra event (note: this always works because only q triggers this)
1115 return true
1116 elseif key == "0" then --Go back to beginning
1117 indexOuter, indexInner = "main",1
1118 elseif key == "up" and indexInner > 1 then
1119 indexInner = indexInner-1
1120 elseif key == "down" and indexInner < #tab[indexOuter] then
1121 indexInner = indexInner + 1
1122 end
1123 end
1124
1125end
1126
1127
1128local function wrapPrompt(prefix, str, dim) --Used to wrap the commandString
1129 return prefix..str:sub(roundNegative(#str+#prefix-computer.dim[1]+2), -1).."_" --it is str + 2 because we add in the "_"
1130end
1131
1132local function updateAllScreens()
1133 for a, b in pairs(screenClass.sides) do
1134 b:updateDisplay()
1135 b:reset()
1136 b:pushScreenUpdates()
1137 end
1138end
1139--Rednet
1140local function newMessageID()
1141 return math.random(1,2000000000) --1 through 2 billion. Good enough solution
1142end
1143local function transmit(send, receive, message, legacy, fingerprint)
1144 fingerprint = fingerprint or replyFingerprint
1145 if legacy then
1146 modem.transmit(send, receive, message)
1147 else
1148 modem.transmit(send, receive, {message = message, id = newMessageID(), fingerprint = fingerprint})
1149 end
1150end
1151
1152--QuadRotor
1153local function launchQuad(message)
1154 if quadEnabled and message.emergencyLocation then --This means the turtle is out of fuel. Also that it sent its two initial positions
1155 local movement = {}
1156 local function add(what) table.insert(movement,what) end
1157 add(quadDirection) --Get to the fuel chest
1158 add("suck")
1159 add(quadDirection) --So it can properly go down/up first
1160 local function go(dest, orig, firstMove) --Goes to a place. firstMove because I'm lazy. Its for getting away from computer. If false, its the second move so go one above turtle. If nothing then nothing
1161 local distX, distY, distZ = dest[1]-orig[1], dest[2]-orig[2], dest[3]-orig[3]
1162 if firstMove then
1163 distX = distX - 3 * (quadDirection == "east" and 1 or (quadDirection == "west" and -1 or 0))
1164 distZ = distZ - 3 * (quadDirection == "south" and 1 or (quadDirection == "north" and -1 or 0))
1165 distY = distY - 1 --Because the quad is a block above the first thing
1166 elseif firstMove == false then
1167 local num = 2
1168 if message.layersDone <= 1 then
1169 num = 1
1170 end
1171 distY = distY + num * (distY < 0 and 1 or -1) --This is to be above the turtle and accounts for invert
1172 end
1173 add((distY > 0 and "up" or "down").." "..tostring(math.abs(distY)))
1174 add((distX > 0 and "east" or "west").." "..tostring(math.abs(distX)))
1175 add((distZ > 0 and "south" or "north").." "..tostring(math.abs(distZ)))
1176 if firstMove == false and message.layersDone > 1 then
1177 add(distY < 0 and "down" or "up") --This is so it goes into the turtle's proper layer (invert may or may not work, actually)
1178 end
1179 end
1180 debug("Location Types")
1181 debug(computerLocation)
1182 debug(message.firstPos)
1183 debug(message.secondPos)
1184 debug(message.emergencyLocation)
1185 go(message.firstPos, computerLocation, true) --Get to original position of turtle
1186 go(message.secondPos,message.firstPos) --Get into quarry
1187 go(message.emergencyLocation, message.secondPos, false)
1188
1189 add("drop")
1190 add("return")
1191 for a,b in pairs(movement) do
1192 debug(a," ",b)
1193 end
1194 quadBase.flyQuad(movement) --Note, if there are no quadrotors, nothing will happen and the turtle will sit forever
1195
1196 end
1197end
1198
1199--==SET UP==
1200clearScreen()
1201print("Welcome to Quarry Receiver!")
1202sleep(1)
1203
1204--==ARGUMENTS==
1205
1206--[[
1207Parameters:
1208 -help/-?/help/?
1209 -v/verbose --Turn on debugging
1210 -receiveChannel/channel [channel] --For only the main screen
1211 -theme --Sets a default theme
1212 -screen [side] [channel] [theme]
1213 -station
1214 -auto --Prompts for all sides, or you can supply a list of receive channels for random assignment!
1215 -colorEditor
1216 -quad [cardinal direction] --This looks for a quadrotor from the quadrotors mod. The direction is of the fuel chest.
1217 -autoRestart --Will reset any attached screen when done, instead of bricking them
1218]]
1219
1220--tArgs init
1221local parameters = {} --Each command is stored with arguments
1222
1223local function addParam(value)
1224 val = value:lower()
1225 if val:match("^%-") then
1226 parameters[#parameters+1] = {val:sub(2)} --Starts a chain with the command. Can be unpacked later
1227 parameters[val:sub(2)] = {} --Needed for force/before/after parameters
1228 elseif parameterIndex ~= 0 then
1229 table.insert(parameters[#parameters], value) --value because arguments should be case sensitive for filenames
1230 table.insert(parameters[parameters[#parameters][1]], value) --Needed for force/after parameters
1231 end
1232end
1233
1234for a,b in ipairs(tArgs) do
1235 addParam(b)
1236end
1237
1238if parameters.theme then --This goes here so help can display in different theme :)
1239 screenClass:setTheme(parameters.theme[1])
1240end
1241
1242for a,b in ipairs(tArgs) do
1243 val = b:lower()
1244 if val == "help" or val == "-help" or val == "?" or val == "-?" or val == "usage" or val == "-usage" then
1245 displayHelp() --To make
1246 error("The End of Help",0)
1247 end
1248end
1249
1250--Debug parameters
1251if parameters.v or parameters.verbose then --Why not
1252 doDebug = true
1253end
1254
1255for i=1,#parameters do
1256 debug("Parameter: ",parameters[i][1])
1257end
1258
1259--Options before screen loads
1260
1261if parameters.modem then
1262 modemSide = parameters.modem[1]
1263end
1264
1265if parameters.quad then
1266 if not parameters.quad[1] then parameters.quad[1] = "direction doesn't exist" end
1267 local dir = parameters.quad[1]:lower():sub(1,1)
1268 if quadDirections[dir] then
1269 quadEnabled = true
1270 quadDirection = quadDirections[dir]
1271 else
1272 clearScreen()
1273 print("Please specify the cardinal direction your quad station is in")
1274 print("Make sure you have a quad station on one side with a chest behind it, forming a line")
1275 print("Like this: [computer] [station] [fuel chest]")
1276 print("The program will now terminate")
1277 error("",0)
1278 end
1279end
1280
1281if parameters.autorestart then
1282 local val = parameters.autorstart[1]
1283 if not val then
1284 autoRestart = true --Assume no value = force true
1285 else
1286 val = val:sub(1,1):lower()
1287 autoRestart = not (val == "n" or val == "f")
1288 end
1289end
1290
1291--Init Modem
1292while not initModem() do
1293 clearScreen()
1294 print("No modem is connected, please attach one")
1295 if not peripheral.find then
1296 print("What side was that on?")
1297 modemSide = read()
1298 else
1299 os.pullEvent("peripheral")
1300 end
1301end
1302debug("Modem successfully connected!")
1303
1304local function autoDetect(channels)
1305 if type(channels) ~= "table" then channels = {} end
1306 local tab = peripheral.getNames()
1307 local index = 1
1308 for i=1, #tab do
1309 if peripheral.getType(tab[i]) == "monitor" and not screenClass.sides[tab[i]] then
1310 screenClass.new(tab[i], channels[index]) --You can specify a list of channels in "auto" parameter
1311 index = index+1
1312 end
1313 end
1314end
1315
1316--Init QuadRotor Station
1317if quadEnabled then
1318 local flag
1319 while not flag do
1320 for a,b in ipairs({"front","back","left","right","top"}) do
1321 if peripheral.isPresent(b) and peripheral.getType(b) == "quadbase" then
1322 quadBase = peripheral.wrap(b)
1323 end
1324 end
1325 clearScreen()
1326 if not quadBase then
1327 print("No QuadRotor Base Attached, please attach one")
1328 elseif quadBase.getQuadCount() == 0 then
1329 print("Please install at least one QuadRotor in the base")
1330 sleep(1) --Prevents screen flickering and overcalling gps
1331 else
1332 flag = true
1333 debug("QuadBase successfully connected!")
1334 end
1335 if not computerLocation and not gps.locate(5) then
1336 flag = false
1337 error("No GPS lock. Please make a GPS network to use quadrotors")
1338 else
1339 computerLocation = {gps.locate(5)}
1340 debug("GPS Location Acquired")
1341 end
1342 end
1343end
1344
1345--Init Computer Screen Object (was defined at top)
1346computer = screenClass.new("computer", (parameters.receivechannel and parameters.receivechannel[1]) or (parameters.channel and parameters.channel[1]))--This sets channel, checking if parameter exists
1347computer.updateNormal = function(self)
1348 screenClass.updateNormal(self)
1349 computer:displayCommand()
1350end
1351computer.updateHandshake = function(self) --Not in setHandshake because that func checks object updateHandshake
1352 screenClass.updateHandshake(self)
1353 computer:displayCommand()
1354end
1355computer.updateBroken = function(self)
1356 screenClass.updateBroken(self)
1357 computer:displayCommand()
1358end
1359computer.updateStation = function(self)--This gets set in setSize
1360 screenClass.updateStation(self)
1361 self:displayCommand()
1362end
1363
1364
1365for i=1, #parameters do --Do actions for parameters that can be used multiple times
1366 local command, args = parameters[i][1], parameters[i] --For ease
1367 if command == "screen" then
1368 if not screenClass.sides[args[2]] then --Because this screwed up the computer
1369 local a = screenClass.new(args[2], args[3], args[4])
1370 debug(type(a))
1371 else
1372 debug("Overwriting existing screen settings for '",args[2],"'")
1373 local a = screenClass.sides[args[2]]
1374 a:setChannel(tonumber(args[3]))
1375 a:setTheme(args[4])
1376 end
1377 end
1378 if command == "station" then --This will set the screen update to display stats on all other monitors
1379 if not args[2] or args[2]:lower() == "computer" then --Not below because it exists
1380 computer:setStation() --This handles setting updateNormal, setHandshakeDisplay, etc
1381 else
1382 local a = screenClass.new(args[2], nil, args[3]) --This means syntax is -station [side] [theme]
1383 if a then --If the screen actually exists
1384 a:setStation()
1385 end
1386 end
1387 end
1388end
1389
1390if parameters.auto then --This must go after computer declaration so computer ID is 1
1391 autoDetect(parameters.auto)
1392 addParam("-station") --Set computer as station
1393 addParam("computer") --Yes, I'm literally just feeding in more tArgs like from IO
1394end
1395
1396computer.displayCommand = function(self)
1397 local sideString = ((defaultSide and " (") or "")..(defaultSide or "")..((defaultSide and ")") or "")
1398 if self.size == 1 then
1399 self:tryAddRaw(self.dim[2], wrapPrompt("Cmd"..sideString:sub(2,-2)..": ", commandString, self.dim[1]), self.theme.command, true)
1400 else
1401 self:tryAddRaw(self.dim[2], wrapPrompt("Command"..sideString..": ",commandString, self.dim[1]), self.theme.command, true) --This displays the last part of a string.
1402 end
1403end
1404--Initializing the computer screen
1405if parameters.coloreditor then
1406
1407 computer:removeChannel() --So it doesn't receive messages
1408 computer.isStation = true --So we can't assign a channel
1409
1410 computer.updateNormal = function(self) --This is only for editing colors
1411 self.toPrint = {}
1412 for i=1, #requiredColors do
1413 self:tryAdd(requiredColors[i], self.theme[requiredColors[i]],true)
1414 end
1415 self:displayCommand()
1416 end
1417 computer.updateHandshake = computer.updateNormal
1418 computer.updateBroken = computer.updateNormal
1419 computer.updateStation = computer.updateNormal
1420end
1421computer:setSize() --Update changes made to display functions
1422
1423for a,b in pairs(screenClass.sides) do debug(a) end
1424
1425--==FINAL CHECKS==
1426
1427--If only one screen and computer has no channel, make it a station
1428if #screenClass.screens > 1 and not computer.receive then
1429 debug("Only one screen, no comp channel. Setting station")
1430 computer:setStation()
1431end
1432
1433--Updating all screen for first time and making sure channels are open
1434for a, b in pairs(screenClass.sides) do
1435 b:setSize()
1436 b:updateDisplay()--Finish initialization process
1437 b:reset()
1438 b:pushScreenUpdates()
1439end
1440
1441--Handshake will be handled in main loop
1442
1443--[[Workflow
1444 Wait for events
1445 modem_message
1446 if valid channel and valid message, update appropriate screen
1447 key
1448 if any letter, add to command string if room.
1449 if enter key
1450 if valid self command, execute command. Commands:
1451 command [side] [command] --If only one screen, then don't need channel. Send a command to a turtle
1452 screen [side] [channel] [theme] --Links a new screen to use.
1453 remove [side] --Removes a screen
1454 theme [themeName] --Sets the default theme
1455 theme [side] [themeName] --Changes this screen's theme
1456 savetheme [new name] [themeName]
1457 color [side/theme] [colorName] [textColor] [backgroundColor]
1458 side [side] --Sets a default side, added to prompts
1459 set [string] --Sets a default command, added to display immediately
1460 receive [side] [newChannel] --Changes the channel of the selected screen
1461 send [side] [newChannel]
1462 auto --Automatically adds screens not connected
1463 station --Sets the selected screen as a station (or resets if already a station)
1464 exit/quit/end
1465 peripheral_detach
1466 check what was lost, if modem, set to nil. If screen side, do screen:setSize()
1467 peripheral
1468 check if screen side already added
1469 reset screen size
1470 monitor_resize
1471 resize proper screen
1472 monitor_touch
1473 if screen already added
1474 select screen on main computer
1475 else
1476 add screen
1477
1478]]
1479
1480--Modes: 1 - Sided, 2 - Not Sided, 3 - Both sided and not
1481local validCommands = {command = 1, screen = 2, remove = 1, theme = 3, exit = 2, quit = 2, ["end"] = 2, color = 3, side = 2, set = 2, receive = 1, send = 1, savetheme = 2,
1482 auto = 2, verbose = 2, quiet = 2, station = 1}
1483while continue do
1484 local event, par1, par2, par3, par4, par5 = os.pullEvent()
1485 ----MESSAGE HANDLING----
1486 if event == "modem_message" and screenClass.channels[par2] then --If we got a message for a screen that exists
1487 local screen = screenClass.channels[par2] --For convenience
1488 if not screen.send then --This is the handshake
1489 debug("\nChecking handshake. Received: ",par4)
1490 local flag = false
1491 if par4 == expectedMessage then --Legacy quarries don't accept receiver dropping in mid-run
1492 screen.legacy = true --Accepts serialized tables
1493 flag = true
1494 elseif type(par4) == "table" and par4.fingerprint == expectedFingerprint then --Don't care about expected message, allows us to start receiver mid-run, fingerprint should be pretty specific
1495 screen.legacy = false
1496 flag = true
1497 end
1498
1499 if flag and (autoRestart or (not autoRestart and not screen.isDone)) then --We don't accept handshakes when we don't want autorestarts
1500 screen.isDone = false
1501 screen.rec = copyTable(screenClass.rec) --Need to reset this. Existing message from restart doesn't have everything
1502 debug("Screen ",screen.side," received a handshake")
1503 screen.send = par3
1504 screen:setSize() --Resets update method to proper since channel is set
1505 debug("Sending back on ",screen.send)
1506 transmit(screen.send,screen.receive, replyMessage, screen.legacy)
1507 end
1508
1509 else --Everything else is for regular messages
1510
1511 local rec
1512 if screen.legacy then --We expect strings here
1513 if type(par4) == "string" then --Otherwise its not ours
1514 if par4 == "stop" then --This is the stop message. All other messages will be ending ones
1515 screen.isDone = true
1516 elseif par4 == expectedMessage then --We support dropping in mid-run
1517 debug("Screen ",screen.side," received mid-run handshake")
1518 transmit(screen.send,screen.receive, replyMessage, screen.legacy)
1519 elseif textutils.unserialize(par4) then
1520 rec = textutils.unserialize(par4)
1521 rec.distance = par5
1522 end
1523 end
1524 elseif type(par4) == "table" and par4.fingerprint == expectedFingerprint then --Otherwise, we check if it is valid message
1525
1526 if type(par4.message) == "table" then
1527 rec = par4.message
1528 if not par4.distance then --This is cool because it can add distances from the repeaters
1529 rec.distance = par5
1530 else
1531 rec.distance = par4.distance + par5
1532 end
1533 if rec.isDone then
1534 screen.isDone = true
1535 screen.send = nil --So that we can receive handshakes again.
1536 end
1537 elseif par4.message == expectedMessage then
1538 debug("Screen ",screen.side," received mid-run handshake")
1539 transmit(screen.send,screen.receive, replyMessage, screen.legacy)
1540 else
1541 debug("Message received did not contain table")
1542 end
1543 end
1544
1545 if rec then
1546 rec.distance = math.floor(rec.distance)
1547 rec.label = rec.label or "Quarry!"
1548 screen.rec = rec --Set the table
1549 --Updating screen occurs outside of the if
1550 local toSend
1551 if screen.queuedMessage then
1552 toSend = screen.queuedMessage
1553 screen.queuedMessage = nil
1554 else
1555 toSend = replyMessage
1556 end
1557 if not screen.isDone then --Because then sendChannel doesn't exist
1558 transmit(screen.send,screen.receive, toSend, screen.legacy) --Send reply message for turtle
1559 end
1560 end
1561
1562 end
1563
1564 launchQuad(screen.rec) --Launch the Quad! (This only activates when turtle needs it)
1565
1566 screen:updateDisplay() --isDone is queried inside this
1567 screen:reset(screen.theme.background)
1568 screen:pushScreenUpdates() --Actually write things to screen
1569 --if screen.isDone and not autoRestart then screen:removeChannel() end --Don't receive any more messages. Allows turtle to think connected. Done after message sending so no error :)
1570
1571 ----KEY HANDLING----
1572 elseif event == "key" and keyMap[par1] then
1573 local key = keyMap[par1]
1574 if key ~= "enter" then --If we aren't submitting a command
1575 if key == "backspace" then
1576 if #commandString > 0 then
1577 commandString = commandString:sub(1,-2)
1578 end
1579 elseif key == "up" then
1580 commandString = lastCommand or commandString --Set to last command, or do nothing if it doesn't exist
1581 elseif key == "down" then
1582 commandString = "" --If key down, clear
1583 elseif #key == 1 then
1584 commandString = commandString..key
1585 end
1586 --ALL THE COMMANDS
1587 else --If we are submitting a command
1588 lastCommand = commandString --For using up arrow
1589 local args = {}
1590 for a in commandString:gmatch("%S+") do --This captures all individual words in the command string
1591 args[#args+1] = a:lower()
1592 end
1593 local command = args[1]
1594 if validCommands[command] then --If it is a valid command...
1595 local commandType = validCommands[command]
1596 if commandType == 1 or commandType == 3 then --If the command requires a "side" like transmitting commands, versus setting a default
1597 if defaultSide then table.insert(args, 2, defaultSide) end
1598 local screen
1599 local test = screenClass.screens[tonumber(args[2])]
1600 if test and test.side ~= "REMOVED" then --This way we can specify IDs as well
1601 screen = test
1602 else
1603 screen = screenClass.sides[args[2]]
1604 end
1605 if screen then --If the side exists
1606 if command == "command" and screen.send then --If sending command to the turtle
1607 screen.queuedMessage = table.concat(args," ", 3) --Tells message handler to send appropriate message
1608 --transmit(screen.send, screen.receive, table.concat(args," ", 3), screen.legacy) --This transmits all text in the command with spaces. Duh this is handled when we get message
1609 end
1610
1611 if command == "color" then
1612 screen.theme:addColor(args[3],colors[args[4]],colors[args[5]] )
1613 updateAllScreens() --Because we are changing a theme color which others may have
1614 end
1615 if command == "theme" then
1616 screen:setTheme(args[3])
1617 end
1618 if command == "send" then --This changes a send channel, and can also revert to handshake
1619 local chan = checkChannel(tonumber(args[3]) or -1)
1620 if chan then screen.send = chan else screen.send = nil end
1621 screen:setSize() --If on handshake, resets screen
1622 end
1623 if command == "receive" and not screen.isStation then
1624 local chan = checkChannel(tonumber(args[3]) or -1)
1625 if chan and not screenClass.channels[chan] then
1626 screen:setChannel(chan)
1627 screen:setSize() --Update broken status
1628 end
1629 end
1630 if command == "station" then
1631 if screen.isStation then screen:removeStation() else screen:setStation() end
1632 end
1633 if command == "remove" and screen.side ~= "computer" then --We don't want to remove the main display!
1634 print()
1635 screen:remove()
1636 else --Because if removed it does stupid things
1637 screen:reset()
1638 debug("here")
1639 screen:updateDisplay()
1640 debug("Here")
1641 screen:pushScreenUpdates()
1642 debug("Hereer")
1643 end
1644 end
1645 end
1646 if commandType == 2 or commandType == 3 then--Does not require a screen side
1647 if command == "screen" and peripheral.getType(args[2]) == "monitor" then --Makes sure there is a monitor on the screen side
1648 if not args[3] or not screenClass.channels[tonumber(args[3])] then --Make sure the channel doesn't already exist
1649 local mon = screenClass.new(args[2], args[3], args[4])
1650 --args[3] is the channel and will set broken display if it doesn't exist
1651 --args[4] is the theme, and will default if doesn't exists.
1652 mon:updateDisplay()
1653 mon:reset()
1654 mon:pushScreenUpdates()
1655 end
1656 end
1657 if command == "theme" then
1658 screenClass:setTheme(args[2], true) --Otherwise this would set base theme to nil, erroring
1659 updateAllScreens()
1660 end
1661 if command == "color" and themes[args[2]] then
1662 themes[args[2]]:addColor(args[3],colors[args[4]],colors[args[5]])
1663 updateAllScreens() --Because any screen could have this theme
1664 end
1665 if command == "side" then
1666 if screenClass.sides[args[2]] then
1667 defaultSide = args[2]
1668 else
1669 defaultSide = nil
1670 end
1671 end
1672 if command == "set" then
1673 if args[2] then
1674 defaultCommand = table.concat(args," ",2)
1675 defaultCommand = defaultCommand:upper()
1676 else
1677 defaultCommand = nil
1678 end
1679 end
1680 if command == "savetheme" then
1681 if saveTheme(themes[args[2]], args[3]) then
1682 computer:tryAddRaw(computer.dim[2]-1, "Save Theme Succeeded!", computer.theme.inverse, true)
1683 else
1684 computer:tryAddRaw(computer.dim[2]-1, "Save Theme Failed!", computer.theme.inverse, true)
1685 end
1686 computer:reset()
1687 computer:pushScreenUpdates()
1688 sleep(1)
1689 end
1690 if command == "auto" then
1691 local newTab = copyTable(args) --This is so we can pass all additional words as channel numbers
1692 table.remove(newTab, 1)
1693 autoDetect(newTab)
1694 updateAllScreens()
1695 end
1696 if command == "verbose" then doDebug = true end
1697 if command == "quiet" then doDebug = false end
1698 if command == "quit" or command == "exit" or command == "end" then
1699 continue = false
1700 end
1701 end
1702 else
1703 debug("\nInvalid Command")
1704 end
1705 if defaultCommand then commandString = defaultCommand.." " else commandString = "" end --Reset command string because it was sent
1706 end
1707
1708
1709 --Update computer display (computer is only one that displays command string
1710 computer:updateDisplay() --Note: Computer's method automatically adds commandString to last line
1711 if not continue then computer:tryAddRaw(computer.dim[2]-1,"Program Exiting", computer.theme.inverse, false, true, true) end
1712 computer:reset()
1713 computer:pushScreenUpdates()
1714
1715 elseif event == "monitor_resize" then
1716 local screen = screenClass.sides[par1]
1717 if screen then
1718 screen:setSize()
1719 screen:updateDisplay()
1720 screen:reset()
1721 screen:pushScreenUpdates()
1722 end
1723 elseif event == "monitor_touch" then
1724 local screen = screenClass.sides[par1]
1725 debug("Side: ",par1," touched")
1726 if screen then --This part is copied from the "side" command
1727 local test = button.checkPoint(screen.buttons, {par2, par3})
1728 if test then
1729 screen.queuedMessage = test
1730 else
1731 if not screen.receive then
1732 commandString = "RECEIVE "..par1:upper().." "
1733 end
1734 end
1735 else
1736 debug("Adding Screen")
1737 local mon = screenClass.new(par1)
1738 commandString = "RECEIVE "..mon.side:upper().." "
1739 mon:reset()
1740 mon:updateDisplay()
1741 mon:pushScreenUpdates()
1742
1743 end
1744 computer:reset()
1745 computer:updateDisplay()
1746 computer:pushScreenUpdates() --Need to update computer for command string
1747 elseif event == "mouse_click" then
1748 screen = computer
1749 local test = button.checkPoint(screen.buttons, {par2, par3})
1750 if test then
1751 screen.queuedMessage = test
1752 end
1753
1754 elseif event == "peripheral_detach" then
1755 local screen = screenClass.sides[par1]
1756 if screen then
1757 screen:setSize()
1758 end
1759 --if screen then
1760 -- screen:remove()
1761 --end
1762
1763 elseif event == "peripheral" then
1764 local screen = screenClass.sides[par1]
1765 if screen then
1766 screen:setSize()
1767 elseif peripheral.getType(par1) == "monitor" then
1768 commandString = "SCREEN "..par1:upper().." "
1769 end
1770
1771 end
1772
1773 local flag = false --Saying all screens are done, must disprove
1774 local count = 0 --We want it to wait if no screens have channels
1775 for a,b in pairs(screenClass.channels) do
1776 count = count + 1
1777 if autoRestart or not b.isDone then
1778 flag = true
1779 end
1780 end
1781 if continue and count > 0 then --If its not already false from something else
1782 continue = flag
1783 end
1784
1785 if #stationsList > 0 and event ~= "key" and event ~= "char" then --So screen is properly updated
1786 for a, b in ipairs(stationsList) do
1787 b:reset()
1788 b:updateDisplay()
1789 b:pushScreenUpdates()
1790 end
1791 end
1792
1793
1794end
1795
1796sleep(1.5)
1797for a in pairs(screenClass.channels) do
1798 modem.close(a)
1799end
1800for a, b in pairs(screenClass.sides) do
1801 if not b.isDone then --Otherwise we want it display the ending stats
1802 b:setTextColor(colors.white)
1803 b:setBackgroundColor(colors.black)
1804 b.term.clear()
1805 b.term.setCursorPos(1,1)
1806 end
1807end
1808
1809local text --Fun :D
1810if computer.isComputer then text = "SUPER COMPUTER OS 9000"
1811elseif computer.isTurtle then text = "SUPER DIAMOND-MINING OS XXX"
1812elseif computer.isPocket then text = "PoCkEt OOS AMAYZE 65"
1813end
1814if text and not computer.isDone then
1815 computer:say(text, computer.theme.title,1)
1816else
1817 computer.term.setCursorPos(1,computer.dim[2])
1818 computer.term.clearLine()
1819end
1820--Down here shut down all the channels, remove the saved file, other cleanup stuff