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