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