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