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