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