https://pastebin.com/raw/7Ksx4qUJ --Quarry Receiver Version 3.6.5 --Made by Civilwargeeky --[[ Recent Changes: Fixed bugs with hardcoded keys for CC: Tweaked ]] --Config local doDebug = false --For testing purposes local ySizes = 3 --There are 3 different Y Screen Sizes right now local quadEnabled = false --This is for the quadrotors mod by Lyqyd local autoRestart = false --If true, will reset screens instead of turning them off. For when reusing turtles. --Initializing Program-Wide Variables local expectedMessage = "Civil's Quarry" --Expected initial message local expectedFingerprint = "quarry" local replyMessage = "Turtle Quarry Receiver" --Message to respond to handshake with local replyFingerprint = "quarryReceiver" local stopMessage = "stop" local expectedFingerprint = "quarry" local themeFolder = "quarryResources/receiverThemes/" local modemSide --User can specify a modem side, but it is not necessary local modem --This will be the table for the modem local computer --The main screen is special. It gets defined first :3 local continue = true --This keeps the main while loop going local quadDirection = "north" local quadDirections = {n = "north", s = "south", e = "east", w = "west"} local quadBase, computerLocation local tArgs = {...} --These two are used by controller in main loop local commandString = "" --This will be a command string sent to turtle. This var is stored for display local lastCommand --If a command needs to be sent, this gets set local defaultSide local defaultCommand local stationsList = {} for i=1, #tArgs do --Parameters that must be set before rest of program for proper debugging local val = tArgs[i]:lower() if val == "-v" or val == "-verbose" then doDebug = true end if val == "-q" or val == "-quiet" then doDebug = false end end local keyMap = {[keys.space] = " ", [keys.minus] = "_", [keys.period] = ".", [keys.numPadDecimal] = "."} --This is for command string keyMap[keys.numPad0] = "0" keyMap[keys.numPad1] = "1" keyMap[keys.numPad2] = "2" keyMap[keys.numPad3] = "3" keyMap[keys.numPad4] = "4" keyMap[keys.numPad5] = "5" keyMap[keys.numPad6] = "6" keyMap[keys.numPad7] = "7" keyMap[keys.numPad8] = "8" keyMap[keys.numPad9] = "9" keyMap[keys.zero] = "0" keyMap[keys.one] = "1" keyMap[keys.two] = "2" keyMap[keys.three] = "3" keyMap[keys.four] = "4" keyMap[keys.five] = "5" keyMap[keys.six] = "6" keyMap[keys.seven] = "7" keyMap[keys.eight] = "8" keyMap[keys.nine] = "9" for a,b in pairs(keys) do --Add all letters from keys api if #a == 1 then keyMap[b] = a:upper() end end keyMap[keys.enter] = "enter" keyMap[keys.numPadEnter] = "enter" keyMap[keys.backspace] = "backspace" keyMap[keys.up] = "up" keyMap[keys.down] = "down" keyMap[keys.left] = "left" keyMap[keys.right] = "right" local helpResources = { --$$ is a new page main = [[$$Hello and welcome to Quarry Receiver Help! This goes over everything there is to know about the receiver Use the arrow keys to navigate! Press '0' to come back here! Press 'q' to quit! Press a section number at any time to go the beginning of that section 1. Basic Use 2. Parameters 3. Commands 4. Turtle Commands $$A secret page! You found it! Good job :) ]], [[$$Your turtle and you! To use this program, you need a wireless modem on both this computer and the turtle Make sure they are attached to both the turtle and this computer $$Using your new program! Once you have done that, start the turtle and when it says "Rednet?", say "Yes" Optionally, you can use the parameter "-rednet true" Then remember the channel it tells you to open. Come back to this computer, and run the program. Follow onscreen directions. Optionally, you can use the parameter "-receiveChannel" Check out the other help sections for more parameters $$Adding Screens! You can add screens with the "-screen" parameter or the "SCREEN" command An example would be "SCREEN LEFT 2 BLUE" for a screen on the left side with channel 2 and blue theme You can connect screens over wired modems. Attach a modem to the computer and screen, then right click the modem attached to the screen. Then you can say "SCREEN MONITOR_0" or whatever it says ]], [[$$Parameters! note: <> means required, [] means optional -help/help/-?/?/-usage/usage: That's this! -autoRestart [t/f]: If true, the receiver will not exit when all quarries are done and will automagically reconnect to new quarries With no argument, this is set to true. -receiveChannel/channel : Sets the main screen's receive channel -theme : sets the "default" theme that screens use when they don't have a set theme $$Parameters! note: <> means required, [] means optional -screen [channel] [theme]: makes a new screen on the given side with channel and theme example: -screen left 10 blue This adds a new screen on the left receiving channel 10 with a blue theme. -station [side]: makes the screen a "station" that monitors all screens. if no side, uses computer -auto [channel list]: This finds all attached monitors and initializes them (with channels) example: -auto 1 2 5 9 finds screens and gives them channels $$Parameters! note: <> means required, [] means optional -colorEditor: makes the main screen a color editor that just prints the current colors. Good for theme making current typeColors: default title, subtitle, pos, dim, extra, error, info, inverse, command, help, background -modem : Sets the modem side to side -v/-verbose: turns on debug -q/-quiet: turns off debug ]], [[$$Commands! COMMAND [screen] [text]: Sends text to the connected turtle. See turtle commands for valid commands SCREEN [side] [channel] [theme]: Adds a screen. You can also specify the channel and theme of the screen. REMOVE [screen]: Removes the selected screen (cannot remove the main screen) THEME [screen] [name]: Sets the theme of the given screen. THEME [screen] resets the screen to default theme $$Commands! THEME [name]: Sets the default theme. RECEIVE [screen] [channel]: Changes the receive channel of the given screen SEND [screen] [channel]: Changes the send channel of the given screen (for whatever reason) STATION [screen] [channel]: Sets the given screen to/from a station. If changing from a station, will set the screen's channel $$Commands! SET [text]: Sets a default command that can be backspaced. Useful for color editing or command sending Use SET with nothing after to remove text SIDE [screen]: Sets a default screen for "sided" commands. Any command that takes a [screen] is sided EXIT/QUIT: Quits the program gracefully $$Commands! 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 SAVETHEME [themeName] [fileName]: Saves the given theme as fileName for later use SAVETHEME [screen] [fileName]: Same as above but for a screen's theme AUTO [channelList]: Automatically searches for nearby screens, providing them sequentially with channels if a channel list is given Example Use: AUTO 1 2 5 9 $$Commands! HELP: Displays this again! VERBOSE: Turns debug on QUIET: Turns debug off ]], [[$$Turtle Commands! Stop: Stops the turtle where it is Return: The turtle will return to its starting point, drop off its load, and stop Drop: Turtle will immediately go and drop its inventory Pause: Pauses the turtle Resume: Resumes paused turtles Refuel: Turtle will schedule an emergency refuel This could take from fuelChest, or quadCopter or fuel in inventory (in that order) ]] } --Generic Functions-- local function debug(...) --if doDebug then return print(...) end --Basic if doDebug then print("\nDEBUG: ",...) os.pullEvent("char") end end local function clearScreen(x,y, periph) periph, x, y = periph or term, x or 1, y or 1 periph.clear() periph.setCursorPos(x,y) end local function swapKeyValue(tab) for a,b in pairs(tab) do tab[b] = a end return tab end local function copyTable(tab) local toRet = {} for a,b in pairs(tab) do toRet[a] = b end return toRet end local function checkChannel(num) num = tonumber(num) if not num then return false end if 1 <= num and num <= 65535 then return num end return false end local function truncate(text, xDim) if #text <= xDim then return text end return #text >= 4 and text:sub(1,xDim-3).."..." or text:sub(1,3) end local function align(text, xDim, direction, trunc) text = tostring(text or "None") if trunc == nil then trunc = true end if #text >= xDim and trunc then return truncate(text,xDim) end for i=1, xDim-#text do if direction == "right" then text = " "..text elseif direction == "left" then text = text.." " end end return text end local function alignR(text, xDim, trunc) return align(text, xDim, "right", trunc) end local function alignL(text, xDim, trunc) return align(text, xDim, "left", trunc) end local function center(text, xDim, char) if not xDim then error("Center: No dim given",2) end char = char or " " local a = (xDim-#text)/2 for i=1, a do text = char..text..char end return #text == xDim and text or text..char --If not full length, add a space end local function leftRight(first, second, dim) return alignL(tostring(first),dim-#tostring(second))..tostring(second) end local function roundNegative(num) --Rounds numbers up to 0 if num >= 0 then return num else return 0 end end local function testPeripheral(periph, periphFunc) if type(periph) ~= "table" then return false end if type(periph[periphFunc]) ~= "function" then return false end if periph[periphFunc]() == nil then --Expects string because the function could access nil return false end return true end local function initModem() --Sets up modem, returns true if modem exists if not testPeripheral(modem, "isWireless") then if modemSide then if peripheral.getType(modemSide) == "modem" then modem = peripheral.wrap(modemSide) if modem.isWireless and not modem.isWireless() then --Apparently this is a thing modem = nil return false end return true end end if peripheral.find then modem = peripheral.find("modem", function(side, obj) return obj.isWireless() end) end return modem and true or false end return true end --COLOR/THEME RELATED for a, b in pairs(colors) do --This is so commands color commands can be entered in one case colors[a:lower()] = b end colors.none = 0 --For adding things local requiredColors = {"default","title", "subtitle", "pos", "dim", "extra", "error", "info", "inverse", "command", "help", "background"} local function checkColor(name, text, back) --Checks if a given color works local flag = false for a, b in ipairs(requiredColors) do if b == name then flag = true break end end if not flag or not (tonumber(text) or colors[text]) or not (tonumber(back) or colors[back]) then return false end return true end local themes = {} --Loaded themes, gives each one a names local function newTheme(name) name = name:lower() or "none" local self = {name = name} self.addColor = function(self, colorName, text, back) --Colors are optional. Will default to "default" value. Make sure default is a color if colorName == "default" and (not text or not back) then return self end if not text then text = 0 end if not back then back = 0 end if not checkColor(colorName, text, back) then debug("Color check failed: ",name," ",text," ",back); return self end --Back or black because optional colorName = colorName or "none" self[colorName] = {text = text, background = back} return self --Allows for chaining :) end themes[name] = self return self end local function parseTheme(file) local addedTheme = newTheme(file:match("^.-\n") or "newTheme") --Initializes the new theme to the first line 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 for line in file:gmatch("[^\n]+\n") do --Go through all the color lines (besides first one) local args = {} for word in line:gmatch("%S+") do table.insert(args,word) end 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 end local flag = true --Make sure a theme has all required elements for a,b in ipairs(requiredColors) do if not addedTheme[b] then flag = false debug("Theme is missing color '",b,"'") end end if not flag then themes[addedTheme.name] = nil debug("Failed to load theme") return false end return addedTheme end --This is how adding colors will work --regex for adding from file: --(\w+) (\w+) (\w+) -- \:addColor\(\"\1\"\, \2\, \3\) newTheme("default") :addColor("default",colors.white, colors.black) :addColor("title", colors.green, colors.gray) :addColor("subtitle", colors.white, colors.black) :addColor("pos", colors.green, colors.black) :addColor("dim", colors.lightBlue, colors.black) :addColor("extra", colors.lightGray, colors.black) :addColor("error", colors.red, colors.white) :addColor("info", colors.blue, colors.lightGray) :addColor("inverse", colors.yellow, colors.blue) :addColor("command", colors.lightBlue, colors.black) :addColor("help", colors.cyan, colors.black) :addColor("background", colors.none, colors.none) newTheme("blue") :addColor("default",colors.white, colors.blue) :addColor("title", colors.lightBlue, colors.gray) :addColor("subtitle", 1, 2048) :addColor("pos", 16, 2048) :addColor("dim", colors.lime, 0) :addColor("extra", 8, 2048) :addColor("error", 8, 16384) :addColor("info", 2048, 256) :addColor("inverse", 2048, 1) :addColor("command", 2048, 8) :addColor("help", 16384, 1) :addColor("background", 1, 2048) newTheme("seagle") :addColor("default",colors.white, colors.black) :addColor("title", colors.white, colors.black) :addColor("subtitle", colors.red, colors.black) :addColor("pos", colors.gray, colors.black) :addColor("dim", colors.lightBlue, colors.black) :addColor("extra", colors.lightGray, colors.black) :addColor("error", colors.red, colors.white) :addColor("info", colors.blue, colors.lightGray) :addColor("inverse", colors.yellow, colors.lightGray) :addColor("command", colors.lightBlue, colors.black) :addColor("help", colors.red, colors.white) :addColor("background", colors.white, colors.black) newTheme("random") :addColor("default",colors.white, colors.black) :addColor("title", colors.pink, colors.blue) :addColor("subtitle", colors.black, colors.white) :addColor("pos", colors.green, colors.black) :addColor("dim", colors.lightBlue, colors.black) :addColor("extra", colors.lightGray, colors.lightBlue) :addColor("error", colors.white, colors.yellow) :addColor("info", colors.blue, colors.lightGray) :addColor("inverse", colors.yellow, colors.lightGray) :addColor("command", colors.green, colors.lightGray) :addColor("help", colors.white, colors.yellow) :addColor("background", colors.white, colors.red) newTheme("rainbow") :addColor("dim", 32, 0) :addColor("background", 16384, 0) :addColor("extra", 2048, 0) :addColor("info", 2048, 0) :addColor("inverse", 32, 0) :addColor("subtitle", 2, 0) :addColor("title", 16384, 0) :addColor("error", 1024, 0) :addColor("default", 1, 512) :addColor("command", 16, 0) :addColor("pos", 16, 0) :addColor("help", 2, 0) newTheme("green") :addColor("dim", 16384, 0) :addColor("background", 0, 0) :addColor("extra", 2048, 0) :addColor("info", 32, 256) :addColor("inverse", 8192, 1) :addColor("subtitle", 1, 0) :addColor("title", 8192, 128) :addColor("error", 16384, 32768) :addColor("default", 1, 8192) :addColor("command", 2048, 32) :addColor("pos", 16, 0) :addColor("help", 512, 32768) --If you modify a theme a bunch and want to save it local function saveTheme(theme, fileName) if not theme or not type(fileName) == "string" then return false end local file = fs.open(fileName,"w") if not file then return false end file.writeLine(fileName) for a,b in pairs(theme) do if type(b) == "table" then --If it contains color objects file.writeLine(a.." "..tostring(b.text).." "..tostring(b.background)) end end file.close() return true end --BUTTON CLASS local button = {} button.checkPoint = function(buttons, pos) --Returns a command or nil for a, b in pairs(buttons) do if pos[2] == b.line then if pos[1] >= b.xDim[1] and pos[1] <= b.xDim[2] then return b.command end end end end button.makeLine = function(buttons, sep, xDim) local toRet = "" for a, b in ipairs(buttons) do toRet = toRet..center(b.text, (b.xDim[2]-b.xDim[1]))..sep end return toRet:sub(1,-2).."" --Take off the last sep end button.new = function(line, xStart, xEnd, command, display) local toRet = {} setmetatable(toRet, {__index = button}) toRet.line = line toRet.xDim = {math.min(xStart, xEnd), math.max(xStart, xEnd)} toRet.command = command toRet.text = display return toRet end --==SCREEN CLASS FUNCTIONS== local screenClass = {} --This is the class for all monitor/screen objects screenClass.screens = {} --A simply numbered list of screens screenClass.sides = {} --A mapping of screens by their side attached screenClass.channels = {} --A mapping of receiving channels that have screens attached. Used for the receiver part screenClass.sizes = {{7,18,29,39,50}, {5,12,19} , computer = {51, 19}, turtle = {39,13}, pocket = {26,20}} screenClass.setTextColor = function(self, color) --Accepts raw color if color and self.term.isColor() then self.textColor = color self.term.setTextColor(color) return true end return false end screenClass.setBackgroundColor = function(self, color) --Accepts raw color if color and self.term.isColor() then self.backgroundColor = color self.term.setBackgroundColor(color) return true end return false end screenClass.setColor = function(self, color) --Wrapper, accepts themecolor objects if type(color) ~= "table" then error("Set color received a non-table",2) end local text, back = color.text, color.background if not text or text == 0 then text = self.theme.default.text end if not back or back == 0 then back = self.theme.default.background end return self:setTextColor(text) and self:setBackgroundColor(back) end screenClass.themeName = "default" --Setting super for fallback screenClass.theme = themes.default screenClass.rec = { --Initial values for all displayed numbers label = "Quarry Bot", id = 1, percent = 0, xPos = 0, zPos = 0, layersDone = 0, x = 0, z = 0, layers = 0, openSlots = 0, mined = 0, moved = 0, chestFull = false, isAtChest = false, isGoingToNextLayer = false, foundBedrock = false, fuel = 0, volume = 0, distance = 0, yPos = 0 } screenClass.new = function(side, receive, themeFile) local self = {} setmetatable(self, {__index = screenClass}) --Establish Hierarchy self.side = side if side == "computer" then self.term = term else self.term = peripheral.wrap(side) if not (self.term and peripheral.getType(side) == "monitor") then --Don't create an object if it doesn't exist if doDebug then error("No monitor on side "..tostring(side)) end self = nil --Save memory? return false end end --Channels and ids self.receive = tonumber(receive) --Receive Channel self.send = nil --Reply Channel, obtained in handshake self.id = #screenClass.screens+1 --Colors self.themeName = nil --Will be set by setTheme self.theme = nil self.isColor = self.term.isColor() --Just for convenience --Other Screen Properties self.dim = {self.term.getSize()} --Raw dimensions --Initializations self.isDone = false --Flag for when the turtle is done transmitting self.size = {} --Screen Size, assigned in setSize self.textColor = colors.white --Just placeholders until theme is loaded and run self.backColor = colors.black self.toPrint = {} self.isComputer = false self.isTurtle = false self.isPocket = false self.acceptsInput = false self.legacy = false --Whether it expects tables or strings self.rec = copyTable(screenClass.rec) screenClass.screens[self.id] = self screenClass.sides[self.side] = self if self.receive then modem.open(self.receive) --Modem should be defined by the time anything is open 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 end self:setSize() --Finish Initialization self:setTheme(themeFile) return self end screenClass.remove = function(tab) --Cleanup function if type(tab) == "number" then --Expects table, can take id (for no apparent reason) tab = screenClass.screens[tab] end tab:removeStation() if tab.side == "REMOVED" then return end if tab.side == "computer" then error("Tried removing computer screen",2) end --This should never happen tab:reset() --Clear screen tab:say("Removed", tab.theme.info, 1) --Let everyone know whats up screenClass.screens[tab.id] = {side = "REMOVED"} --Not nil because screw up len() screenClass.sides[tab.side] = nil tab:removeChannel() end --Init Functions screenClass.removeChannel = function(self) self.send = nil if self.receive then screenClass.channels[self.receive] = nil if modem and modem.isOpen(self.receive) then modem.close(self.receive) end self.receive = nil end self:setSize() end screenClass.setChannel = function(self, channel) if self.isStation then return false end --Don't want to set channel station self:removeChannel() if type(channel) == "number" then self.receive = channel screenClass.channels[self.receive] = self if modem and not modem.isOpen(channel) then modem.open(channel) end end self:setSize() --Sets proper draw function end screenClass.setStation = function(self) --Note: This only changes the "set" methods so that "update" methods remain intact per object :) self:removeChannel() if not self.isStation then --Just in case this gets called more than once self.isStation = true table.insert(stationsList,self) end self:setSize() end screenClass.removeStation = function(self) if self.isStation then for i=1, #stationsList do --No IDs so have to do a linear traversal if stationsList[i] == self then table.remove(stationsList, i) end end end self.isStation = false self:setSize() end screenClass.setSize = function(self) --Sets screen size if self.side ~= "computer" and not self.term then self.term = peripheral.wrap(self.side) end if not self.term.getSize() then --If peripheral is having problems/not there. Don't go further than term, otherwise index nil (maybe?) debug("There is no term...") self.updateDisplay = function() end --Do nothing on screen update, overrides class return true elseif self.isStation then self:setStationDisplay() elseif not self.receive then self:setBrokenDisplay() --This will prompt user to set channel elseif self.send then --This allows for class inheritance self:setNormalDisplay() --In case objects have special updateDisplay methods --Remove function in case it exists, defaults to super else --If the screen needs to have a handshake display self:setHandshakeDisplay() end self:resetButtons() self.dim = { self.term.getSize()} local tab = screenClass.sizes for a=1, 2 do --Want x and y dim for b=1, #tab[a] do --Go through all normal sizes, x and y individually if tab[a][b] <= self.dim[a] then --This will set size higher until false self.size[a] = b end end end local function isThing(toCheck, thing) --E.G. isThing(self.dim,"computer") return toCheck[1] == tab[thing][1] and toCheck[2] == tab[thing][2] end self.isComputer = isThing(self.dim, "computer") self.isTurtle = isThing(self.dim, "turtle") self.isPocket = isThing(self.dim, "pocket") self.acceptsInput = self.isComputer or self.isTurtle or self.isPocket return self end screenClass.setTheme = function(self, themeName, stopReset) if not themes[themeName] then --If we don't have it already, try to load it local fileName = themeName or ".." --.. returns false and I don't think you can name a file this if fs.exists(themeFolder) then fileName = themeFolder..fileName end if fs.exists(fileName) then debug("Loading theme: ",fileName) local file = fs.open(fileName, "r") if not file then debug("Could not load theme '",themeName,"' file not found") end parseTheme(file.readAll()) --Parses the text to make a theme, returns theme file.close() self.themeName = themeName:lower() --We can now set our themeName to the fileName else --Resets theme to super if not stopReset then --This exists so its possible to set default theme without breaking world self.themeName = nil self.theme = nil end return false end else self.themeName = themeName:lower() end self.theme = themes[self.themeName] --Now the theme is loaded or the function doesn't get here return true end --Adds text to the screen buffer screenClass.tryAddRaw = function(self, line, text, color, ...) --This will try to add text if Y dimension is a certain size local doAdd = {...} --booleans for small, medium, and large if type(text) ~= "string" then error("tryAddRaw got "..type(text)..", expected string",2) end if not text then debug("tryAddRaw got no string on line ",line) return false end if type(color) ~= "table" then error("tryAddRaw did not get a color",2) end --color = color or {text = colors.white} for i=1, ySizes do --As of now there are 3 Y sizes local test = doAdd[i] if test == nil then test = doAdd[#doAdd] end --Set it to the last known setting if doesn't exist if test and self.size[2] == i then --If should add this text for this screen size and the monitor is this size if #text <= self.dim[1] then self.toPrint[line] = {text = text, color = color} return true else debug("Tried adding '",text,"' on line ",line," but was too long: ",#text," vs ",self.dim[1]) end end end return false end screenClass.tryAdd = function(self, text, color,...) --Just a wrapper return self:tryAddRaw(#self.toPrint+1, text, color, ...) end screenClass.tryAddC = function(self, text, color, ...) --Centered text return self:tryAdd(center(text, self.dim[1]), color, ...) end screenClass.reset = function(self,color) color = color or self.theme.background self:setColor(color) self.term.clear() self.term.setCursorPos(1,1) end screenClass.say = function(self, text, color, line) local currColor = self.backgroundColor color = color or debug("Printing ",text," but had no themeColor: ",self.theme.name) or {} --Set default for nice error, alert that errors occur self:setColor(color) 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 if doDebug and #text > self.dim[1] then error("Tried printing: '"..text.."', but was too big") end self.term.setCursorPos(1,line) for i=1, self.dim[1]-#text do --This is so the whole line's background gets filled. text = text.." " end self.term.write(text) self.term.setCursorPos(1, line+1) end screenClass.pushScreenUpdates = function(self) for i=1, self.dim[2] do local tab = self.toPrint[i] if tab then self:say(tab.text, tab.color, i) end end self.term.setCursorPos(1,self.dim[2]) --So we can see errors end screenClass.resetButtons = function(self) self.buttons = {} end screenClass.addButton = function(self, button) self.buttons[#self.buttons+1] = button end screenClass.updateNormal = function(self) --This is the normal updateDisplay function local str = tostring self.toPrint = {} --Reset table local message, theme, x = self.rec, self.theme, self.dim[1] if not self.isDone then --Normally if self.size[1] == 1 then --Small Width Monitor if not self:tryAdd(message.label, theme.title, false, false, true) then --This will be a title, basically self:tryAdd("Quarry!", theme.title, false, false, true) end self:tryAdd("-Fuel-", theme.subtitle , false, true, true) if not self:tryAdd(str(message.fuel), theme.extra, false, true, true) then --The fuel number may be bigger than the screen self:tryAdd("A lot", theme.extra, false, true, true) end self:tryAdd("--%%%--", theme.subtitle, false, true, true) 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 self:tryAdd(center(str(message.percent).."%", x), theme.pos, true, false) --I want it to be centered on 1x1 self:tryAdd("--Pos--", theme.subtitle, false, true, true) self:tryAdd("X:"..alignR(str(message.xPos), 5), theme.pos, true) self:tryAdd("Z:"..alignR(str(message.zPos), 5), theme.pos , true) self:tryAdd("Y:"..alignR(str(message.layersDone), 5), theme.pos , true) 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 self:tryAdd(str(message.x).."x"..str(message.z), theme.dim , true, false) end self:tryAdd("--Dim--", theme.subtitle, false, true, true) self:tryAdd("X:"..alignR(str(message.x), 5), theme.dim, false, true, true) self:tryAdd("Z:"..alignR(str(message.z), 5), theme.dim, false, true, true) self:tryAdd("Y:"..alignR(str(message.layers), 5), theme.dim, false, true, true) self:tryAdd("-Extra-", theme.subtitle, false, false, true) self:tryAdd(alignR(textutils.formatTime(os.time()):gsub(" ","").."", 7), theme.extra, false, false, true) --Adds the current time, formatted, without spaces. self:tryAdd("Used:"..alignR(str(16-message.openSlots),2), theme.extra, false, false, true) self:tryAdd("Dug"..alignR(str(message.mined), 4), theme.extra, false, false, true) self:tryAdd("Mvd"..alignR(str(message.moved), 4), theme.extra, false, false, true) if message.status then self:tryAdd(alignL(message.status, x), theme.info, false, false, true) end if message.chestFull then self:tryAdd("ChstFll", theme.error, false, false, true) end end if self.size[1] == 2 then --Medium Monitor if not self:tryAdd(message.label, theme.title, false, false, true) then --This will be a title, basically self:tryAdd("Quarry!", theme.title, false, false, true) end self:tryAdd(center("Fuel",x,"-"), theme.subtitle , false, true, true) if not self:tryAdd(str(message.fuel), theme.extra, false, true, true) then --The fuel number may be bigger than the screen self.toPrint[#self.toPrint] = nil self:tryAdd("A lot", theme.extra, false, true, true) end self:tryAdd(str(message.percent).."% Complete", theme.pos , true) --This can be an example. Print (receivedMessage).percent in blue on all different screen sizes self:tryAdd(center("Pos",x,"-"), theme.subtitle, false, true, true) self:tryAdd(leftRight("X Coordinate:",message.xPos, x), theme.pos, true) self:tryAdd(leftRight("Z Coordinate:",message.zPos, x), theme.pos , true) self:tryAdd(leftRight("On Layer:",message.layersDone, x), theme.pos , true) 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 self:tryAdd(str(message.x).."x"..str(message.z).."x"..str(message.layers), theme.dim , true, false) end self:tryAdd(center("Dim",x,"-"), theme.subtitle, false, true, true) self:tryAdd(leftRight("Total X:", message.x, x), theme.dim, false, true, true) self:tryAdd(leftRight("Total Z:", message.z, x), theme.dim, false, true, true) self:tryAdd(leftRight("Total Layers:", message.layers, x), theme.dim, false, true, true) self:tryAdd(leftRight("Volume", message.volume, x), theme.dim, false, false, true) self:tryAdd(center("Extras",x,"-"), theme.subtitle, false, false, true) self:tryAdd(leftRight("Time: ", textutils.formatTime(os.time()):gsub(" ","").."", x), theme.extra, false, false, true) --Adds the current time, formatted, without spaces. self:tryAdd(leftRight("Used Slots:", 16-message.openSlots, x), theme.extra, false, false, true) self:tryAdd(leftRight("Blocks Mined:", message.mined, x), theme.extra, false, false, true) self:tryAdd(leftRight("Spaces Moved:", message.moved, x), theme.extra, false, false, not self.isPocket) if message.status then self:tryAdd(message.status, theme.info, false, false, true) end if message.chestFull then self:tryAdd("Chest Full, Fix It", theme.error, false, true, true) end end if self.size[1] >= 3 then --Large or larger screens if not self:tryAdd(message.label..alignR(" Turtle #"..str(message.id),x-#message.label), theme.title, true) then self:tryAdd("Your turtle's name is long...", theme.title, true) end self:tryAdd("Fuel: "..alignR(str(message.fuel),x-6), theme.extra, true) self:tryAdd("Percentage Done: "..alignR(str(message.percent).."%",x-17), theme.pos, true) local var1 = math.max(#str(message.x), #str(message.z), #str(message.layers)) local var2 = (x-6-var1+3)/3 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) 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) self:tryAdd("Volume: "..str(message.volume), theme.dim, false, true, true) self:tryAdd("",{}, false, false, true) self:tryAdd(center("____---- EXTRAS ----____",x), theme.subtitle, false, false, true) self:tryAdd(center("Time:"..alignR(textutils.formatTime(os.time()),10), x), theme.extra, false, true, true) self:tryAdd(center("Current Day: "..str(os.day()), x), theme.extra, false, false, true) self:tryAdd("Used Inventory Slots: "..alignR(str(16-message.openSlots),x-22), theme.extra, false, true, true) self:tryAdd("Blocks Mined: "..alignR(str(message.mined),x-14), theme.extra, false, true, true) self:tryAdd("Blocks Moved: "..alignR(str(message.moved),x-14), theme.extra, false, true, true) self:tryAdd("Distance to Turtle: "..alignR(str(message.distance), x-20), theme.extra, false, false, true) self:tryAdd("Actual Y Pos (Not Layer): "..alignR(str(message.yPos), x-26), theme.extra, false, false, true) if message.chestFull then self:tryAdd("Dropoff is Full, Please Fix", theme.error, false, true, true) end if message.foundBedrock then self:tryAdd("Found Bedrock! Please Check!!", theme.error, false, true, true) end if message.status then self:tryAdd("Status: "..message.status, theme.info, false, true, true) end if message.isAtChest then self:tryAdd("Turtle is at home chest", theme.info, false, true, true) end if message.isGoingToNextLayer then self:tryAdd("Turtle is going to next layer", theme.info, false, true, true) end end if self.term.isColor() and ((self.size[2] >= 2 and self.size[1] >= 3) or self.isPocket) then local line = self.acceptsInput and self.dim[2]-1 or self.dim[2] local part = math.floor(x/4) if #self.buttons == 0 then self:addButton(button.new(line, part*0, part*1-1, "drop","Drop")) self:addButton(button.new(line, part*1, part*2-1, "pause","Pause")) self:addButton(button.new(line, part*2, part*3-1, "return","Return")) self:addButton(button.new(line, part*3, part*4-1, "refuel","Refuel")) end self:tryAddRaw(line, button.makeLine(self.buttons,"|"):sub(1,self.isPocket and -2 or -1), theme.command, false, true) --Silly code because pocket breaks end else --If is done if self.size[1] == 1 then --Special case for small monitors self:tryAdd("Done", theme.title, true) if not self:tryAdd("Dug"..alignR(str(message.mined),4, false), theme.pos, true) then self:tryAdd("Dug", theme.pos, true) self:tryAdd(alignR(str(message.mined),x), theme.pos, true) end if not self:tryAdd("Fuel"..alignR(str(message.fuel),3, false), theme.pos, true) then self:tryAdd("Fuel", theme.pos, true) self:tryAdd(alignR(str(message.fuel),x), theme.pos, true) end self:tryAdd("-------", theme.subtitle, false,true,true) self:tryAdd("Turtle", theme.subtitle, false, true, true) self:tryAdd(center("is", x), theme.subtitle, false, true, true) self:tryAdd(center("Done!", x), theme.subtitle, false, true, true) else self:tryAdd("Done!", theme.title, true) self:tryAdd("Curr Fuel: "..str(message.fuel), theme.pos, true) if message.preciseTotals then local tab = {} for a,b in pairs(message.preciseTotals) do --Sorting the table a = a:match(":(.+)") if #tab == 0 then --Have to initialize or rest does nothing :) tab[1] = {a,b} else for i=1, #tab do --This is a really simple sort. Probably not very efficient, but I don't care. if b > tab[i][2] then --Gets the second value from the table, which is the itemCount table.insert(tab, i, {a,b}) break elseif i == #tab then --Insert at the end if not bigger than anything table.insert(tab,{a,b}) end end end end for i=1, #tab do --Print all the blocks in order local firstPart = "#"..tab[i][1]..": " 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 end else self:tryAdd("Blocks Dug: "..str(message.mined), theme.inverse, true) self:tryAdd("Cobble Dug: "..str(message.cobble), theme.pos, false, true, true) self:tryAdd("Fuel Dug: "..str(message.fuelblocks), theme.pos, false, true, true) self:tryAdd("Others Dug: "..str(message.other), theme.pos, false, true, true) end end end end screenClass.updateHandshake = function(self) self.toPrint = {} local half = math.ceil(self.dim[2]/2) if self.size[1] == 1 then --Not relying on the parameter system because less calls self:tryAddRaw(half-2, "Waiting", self.theme.error, true) self:tryAddRaw(half-1, "For Msg", self.theme.error, true) self:tryAddRaw(half, "On Chnl", self.theme.error, true) self:tryAddRaw(half+1, tostring(self.receive), self.theme.error, true) else local str = "for" if self.size[1] == 2 then str = "4" end--Just a small grammar change self:tryAddRaw(half-2, "", self.theme.error, true) --Filler self:tryAddRaw(half-1, center("Waiting "..str.." Message", self.dim[1]), self.theme.error, true) self:tryAddRaw(half, center("On Channel "..tostring(self.receive), self.dim[1]), self.theme.error, true) self:tryAddRaw(half+1, "",self.theme.error, true) end end screenClass.updateBroken = function(self) --If screen needs channel self.toPrint = {} if self.size[1] == 1 then self:tryAddC("No Rec", self.theme.pos, false, true, true) self:tryAddC("Channel", self.theme.pos, false, true, true) self:tryAddC("-------", self.theme.title, false, true, true) self:tryAddC("On Comp", self.theme.info, true) self:tryAddC("Type:", self.theme.info, true) self:tryAddC("RECEIVE", self.theme.command, true) if not self:tryAddC(self.side:upper(), self.theme.command, true) then --If we can't print the full side self:tryAddC("[side]",self.theme.command, true) end self:tryAddC("[Chnl]", self.theme.command, true) else self:tryAddC("No receiving", self.theme.pos, false, true, true) self:tryAddC("channel for", self.theme.pos, false, true, true) self:tryAddC("this screen", self.theme.pos, false, true, true) self:tryAddC("-----------------", self.theme.title, false, true, true) self:tryAddC("On main computer,", self.theme.info, true) self:tryAddC("Type:", self.theme.info, true) self:tryAdd("", self.theme.command, false, true, true) self:tryAddC('"""', self.theme.command, false, true, true) self:tryAddC("RECEIVE", self.theme.command, true) if not self:tryAddC(self.side:upper(), self.theme.command, true) then --If we can't print the full side self:tryAddC("[side]",self.theme.command, true) end self:tryAddC("[desired channel]", self.theme.command, true) self:tryAddC('"""', self.theme.command, false, true, true) end end screenClass.updateStation = function(self) self.toPrint = {} sepChar = "| " local part = math.floor((self.dim[1]-3*#sepChar - 3)/3) self:tryAdd(alignL("ID",3)..sepChar..alignL("Side",part)..sepChar..alignL("Channel",part)..sepChar..alignL("Theme",part), self.theme.title, true, true, true)--Headings local line = "" for i=1, self.dim[1] do line = line.."-" end self:tryAdd(line, self.theme.title, false, true, true) for a,b in ipairs(screenClass.screens) do if b.side ~= "REMOVED" then 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 end end end screenClass.updateDisplay = screenClass.updateNormal --Update screen method is normally this one --Misc screenClass.setNormalDisplay = function(self) self.updateDisplay = self.updateNormal --This defaults to super if doesn't exist end screenClass.setHandshakeDisplay = function(self) self.updateDisplay = self.updateHandshake --Sets update to handshake version, defaults to super if doesn't exist end screenClass.setBrokenDisplay = function(self) self.updateDisplay = self.updateBroken end screenClass.setStationDisplay = function(self) self.updateDisplay = self.updateStation end --Help Function. Goes so low so can see screenClass.theme local function displayHelp() local dummy = {term = term} --This will be a dummy "screnClass object" for setting color setmetatable(dummy, {__index = screenClass}) local theme = dummy.theme local tab = {} local indexOuter = "main" local indexInner = 1 for key, value in pairs(helpResources) do tab[key] = {} for a in value:gmatch("$$([^$]+)") do table.insert(tab[key], a) --Just inserting pages end end while true do dummy:setColor(theme.help) clearScreen(1,2) print(tab[indexOuter][indexInner]:match("\n(.+)")) --Print all but first line dummy:setColor(theme.title) dummy.term.setCursorPos(1,1) print(alignL(tab[indexOuter][indexInner]:match("[^\n]+") or "",({dummy.term.getSize()})[1])) --Print first line dummy:setColor(theme.info) local text = tostring(indexInner).."/"..tostring(#tab[indexOuter]) term.setCursorPos(({term.getSize()})[1]-#text,1) term.write(text) --Print the current page number local event, key = os.pullEvent("key") key = keyMap[key] if tonumber(key) and tab[tonumber(key)] then indexOuter = tonumber(key) indexInner = 1 elseif key == "Q" then os.pullEvent("char") --Capture extra event (note: this always works because only q triggers this) return true elseif key == "0" then --Go back to beginning indexOuter, indexInner = "main",1 elseif key == "up" and indexInner > 1 then indexInner = indexInner-1 elseif key == "down" and indexInner < #tab[indexOuter] then indexInner = indexInner + 1 end end end local function wrapPrompt(prefix, str, dim) --Used to wrap the commandString return prefix..str:sub(roundNegative(#str+#prefix-computer.dim[1]+2), -1).."_" --it is str + 2 because we add in the "_" end local function updateAllScreens() for a, b in pairs(screenClass.sides) do b:updateDisplay() b:reset() b:pushScreenUpdates() end end --Rednet local function newMessageID() return math.random(1,2000000000) --1 through 2 billion. Good enough solution end local function transmit(send, receive, message, legacy, fingerprint) fingerprint = fingerprint or replyFingerprint if legacy then modem.transmit(send, receive, message) else modem.transmit(send, receive, {message = message, id = newMessageID(), fingerprint = fingerprint}) end end --QuadRotor local function launchQuad(message) if quadEnabled and message.emergencyLocation then --This means the turtle is out of fuel. Also that it sent its two initial positions local movement = {} local function add(what) table.insert(movement,what) end add(quadDirection) --Get to the fuel chest add("suck") add(quadDirection) --So it can properly go down/up first 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 local distX, distY, distZ = dest[1]-orig[1], dest[2]-orig[2], dest[3]-orig[3] if firstMove then distX = distX - 3 * (quadDirection == "east" and 1 or (quadDirection == "west" and -1 or 0)) distZ = distZ - 3 * (quadDirection == "south" and 1 or (quadDirection == "north" and -1 or 0)) distY = distY - 1 --Because the quad is a block above the first thing elseif firstMove == false then local num = 2 if message.layersDone <= 1 then num = 1 end distY = distY + num * (distY < 0 and 1 or -1) --This is to be above the turtle and accounts for invert end add((distY > 0 and "up" or "down").." "..tostring(math.abs(distY))) add((distX > 0 and "east" or "west").." "..tostring(math.abs(distX))) add((distZ > 0 and "south" or "north").." "..tostring(math.abs(distZ))) if firstMove == false and message.layersDone > 1 then 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) end end debug("Location Types") debug(computerLocation) debug(message.firstPos) debug(message.secondPos) debug(message.emergencyLocation) go(message.firstPos, computerLocation, true) --Get to original position of turtle go(message.secondPos,message.firstPos) --Get into quarry go(message.emergencyLocation, message.secondPos, false) add("drop") add("return") for a,b in pairs(movement) do debug(a," ",b) end quadBase.flyQuad(movement) --Note, if there are no quadrotors, nothing will happen and the turtle will sit forever end end --==SET UP== clearScreen() print("Welcome to Quarry Receiver!") sleep(1) --==ARGUMENTS== --[[ Parameters: -help/-?/help/? -v/verbose --Turn on debugging -receiveChannel/channel [channel] --For only the main screen -theme --Sets a default theme -screen [side] [channel] [theme] -station -auto --Prompts for all sides, or you can supply a list of receive channels for random assignment! -colorEditor -quad [cardinal direction] --This looks for a quadrotor from the quadrotors mod. The direction is of the fuel chest. -autoRestart --Will reset any attached screen when done, instead of bricking them ]] --tArgs init local parameters = {} --Each command is stored with arguments local function addParam(value) val = value:lower() if val:match("^%-") then parameters[#parameters+1] = {val:sub(2)} --Starts a chain with the command. Can be unpacked later parameters[val:sub(2)] = {} --Needed for force/before/after parameters elseif parameterIndex ~= 0 then table.insert(parameters[#parameters], value) --value because arguments should be case sensitive for filenames table.insert(parameters[parameters[#parameters][1]], value) --Needed for force/after parameters end end for a,b in ipairs(tArgs) do addParam(b) end if parameters.theme then --This goes here so help can display in different theme :) screenClass:setTheme(parameters.theme[1]) end for a,b in ipairs(tArgs) do val = b:lower() if val == "help" or val == "-help" or val == "?" or val == "-?" or val == "usage" or val == "-usage" then displayHelp() --To make error("The End of Help",0) end end --Debug parameters if parameters.v or parameters.verbose then --Why not doDebug = true end for i=1,#parameters do debug("Parameter: ",parameters[i][1]) end --Options before screen loads if parameters.modem then modemSide = parameters.modem[1] end if parameters.quad then if not parameters.quad[1] then parameters.quad[1] = "direction doesn't exist" end local dir = parameters.quad[1]:lower():sub(1,1) if quadDirections[dir] then quadEnabled = true quadDirection = quadDirections[dir] else clearScreen() print("Please specify the cardinal direction your quad station is in") print("Make sure you have a quad station on one side with a chest behind it, forming a line") print("Like this: [computer] [station] [fuel chest]") print("The program will now terminate") error("",0) end end if parameters.autorestart then local val = parameters.autorstart[1] if not val then autoRestart = true --Assume no value = force true else val = val:sub(1,1):lower() autoRestart = not (val == "n" or val == "f") end end --Init Modem while not initModem() do clearScreen() print("No modem is connected, please attach one") if not peripheral.find then print("What side was that on?") modemSide = read() else os.pullEvent("peripheral") end end debug("Modem successfully connected!") local function autoDetect(channels) if type(channels) ~= "table" then channels = {} end local tab = peripheral.getNames() local index = 1 for i=1, #tab do if peripheral.getType(tab[i]) == "monitor" and not screenClass.sides[tab[i]] then screenClass.new(tab[i], channels[index]) --You can specify a list of channels in "auto" parameter index = index+1 end end end --Init QuadRotor Station if quadEnabled then local flag while not flag do for a,b in ipairs({"front","back","left","right","top"}) do if peripheral.isPresent(b) and peripheral.getType(b) == "quadbase" then quadBase = peripheral.wrap(b) end end clearScreen() if not quadBase then print("No QuadRotor Base Attached, please attach one") elseif quadBase.getQuadCount() == 0 then print("Please install at least one QuadRotor in the base") sleep(1) --Prevents screen flickering and overcalling gps else flag = true debug("QuadBase successfully connected!") end if not computerLocation and not gps.locate(5) then flag = false error("No GPS lock. Please make a GPS network to use quadrotors") else computerLocation = {gps.locate(5)} debug("GPS Location Acquired") end end end --Init Computer Screen Object (was defined at top) computer = screenClass.new("computer", (parameters.receivechannel and parameters.receivechannel[1]) or (parameters.channel and parameters.channel[1]))--This sets channel, checking if parameter exists computer.updateNormal = function(self) screenClass.updateNormal(self) computer:displayCommand() end computer.updateHandshake = function(self) --Not in setHandshake because that func checks object updateHandshake screenClass.updateHandshake(self) computer:displayCommand() end computer.updateBroken = function(self) screenClass.updateBroken(self) computer:displayCommand() end computer.updateStation = function(self)--This gets set in setSize screenClass.updateStation(self) self:displayCommand() end for i=1, #parameters do --Do actions for parameters that can be used multiple times local command, args = parameters[i][1], parameters[i] --For ease if command == "screen" then if not screenClass.sides[args[2]] then --Because this screwed up the computer local a = screenClass.new(args[2], args[3], args[4]) debug(type(a)) else debug("Overwriting existing screen settings for '",args[2],"'") local a = screenClass.sides[args[2]] a:setChannel(tonumber(args[3])) a:setTheme(args[4]) end end if command == "station" then --This will set the screen update to display stats on all other monitors if not args[2] or args[2]:lower() == "computer" then --Not below because it exists computer:setStation() --This handles setting updateNormal, setHandshakeDisplay, etc else local a = screenClass.new(args[2], nil, args[3]) --This means syntax is -station [side] [theme] if a then --If the screen actually exists a:setStation() end end end end if parameters.auto then --This must go after computer declaration so computer ID is 1 autoDetect(parameters.auto) addParam("-station") --Set computer as station addParam("computer") --Yes, I'm literally just feeding in more tArgs like from IO end computer.displayCommand = function(self) local sideString = ((defaultSide and " (") or "")..(defaultSide or "")..((defaultSide and ")") or "") if self.size == 1 then self:tryAddRaw(self.dim[2], wrapPrompt("Cmd"..sideString:sub(2,-2)..": ", commandString, self.dim[1]), self.theme.command, true) else self:tryAddRaw(self.dim[2], wrapPrompt("Command"..sideString..": ",commandString, self.dim[1]), self.theme.command, true) --This displays the last part of a string. end end --Initializing the computer screen if parameters.coloreditor then computer:removeChannel() --So it doesn't receive messages computer.isStation = true --So we can't assign a channel computer.updateNormal = function(self) --This is only for editing colors self.toPrint = {} for i=1, #requiredColors do self:tryAdd(requiredColors[i], self.theme[requiredColors[i]],true) end self:displayCommand() end computer.updateHandshake = computer.updateNormal computer.updateBroken = computer.updateNormal computer.updateStation = computer.updateNormal end computer:setSize() --Update changes made to display functions for a,b in pairs(screenClass.sides) do debug(a) end --==FINAL CHECKS== --If only one screen and computer has no channel, make it a station if #screenClass.screens > 1 and not computer.receive then debug("Only one screen, no comp channel. Setting station") computer:setStation() end --Updating all screen for first time and making sure channels are open for a, b in pairs(screenClass.sides) do b:setSize() b:updateDisplay()--Finish initialization process b:reset() b:pushScreenUpdates() end --Handshake will be handled in main loop --[[Workflow Wait for events modem_message if valid channel and valid message, update appropriate screen key if any letter, add to command string if room. if enter key if valid self command, execute command. Commands: command [side] [command] --If only one screen, then don't need channel. Send a command to a turtle screen [side] [channel] [theme] --Links a new screen to use. remove [side] --Removes a screen theme [themeName] --Sets the default theme theme [side] [themeName] --Changes this screen's theme savetheme [new name] [themeName] color [side/theme] [colorName] [textColor] [backgroundColor] side [side] --Sets a default side, added to prompts set [string] --Sets a default command, added to display immediately receive [side] [newChannel] --Changes the channel of the selected screen send [side] [newChannel] auto --Automatically adds screens not connected station --Sets the selected screen as a station (or resets if already a station) exit/quit/end peripheral_detach check what was lost, if modem, set to nil. If screen side, do screen:setSize() peripheral check if screen side already added reset screen size monitor_resize resize proper screen monitor_touch if screen already added select screen on main computer else add screen ]] --Modes: 1 - Sided, 2 - Not Sided, 3 - Both sided and not 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, auto = 2, verbose = 2, quiet = 2, station = 1} while continue do local event, par1, par2, par3, par4, par5 = os.pullEvent() ----MESSAGE HANDLING---- if event == "modem_message" and screenClass.channels[par2] then --If we got a message for a screen that exists local screen = screenClass.channels[par2] --For convenience if not screen.send then --This is the handshake debug("\nChecking handshake. Received: ",par4) local flag = false if par4 == expectedMessage then --Legacy quarries don't accept receiver dropping in mid-run screen.legacy = true --Accepts serialized tables flag = true 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 screen.legacy = false flag = true end if flag and (autoRestart or (not autoRestart and not screen.isDone)) then --We don't accept handshakes when we don't want autorestarts screen.isDone = false screen.rec = copyTable(screenClass.rec) --Need to reset this. Existing message from restart doesn't have everything debug("Screen ",screen.side," received a handshake") screen.send = par3 screen:setSize() --Resets update method to proper since channel is set debug("Sending back on ",screen.send) transmit(screen.send,screen.receive, replyMessage, screen.legacy) end else --Everything else is for regular messages local rec if screen.legacy then --We expect strings here if type(par4) == "string" then --Otherwise its not ours if par4 == "stop" then --This is the stop message. All other messages will be ending ones screen.isDone = true elseif par4 == expectedMessage then --We support dropping in mid-run debug("Screen ",screen.side," received mid-run handshake") transmit(screen.send,screen.receive, replyMessage, screen.legacy) elseif textutils.unserialize(par4) then rec = textutils.unserialize(par4) rec.distance = par5 end end elseif type(par4) == "table" and par4.fingerprint == expectedFingerprint then --Otherwise, we check if it is valid message if type(par4.message) == "table" then rec = par4.message if not par4.distance then --This is cool because it can add distances from the repeaters rec.distance = par5 else rec.distance = par4.distance + par5 end if rec.isDone then screen.isDone = true screen.send = nil --So that we can receive handshakes again. end elseif par4.message == expectedMessage then debug("Screen ",screen.side," received mid-run handshake") transmit(screen.send,screen.receive, replyMessage, screen.legacy) else debug("Message received did not contain table") end end if rec then rec.distance = math.floor(rec.distance) rec.label = rec.label or "Quarry!" screen.rec = rec --Set the table --Updating screen occurs outside of the if local toSend if screen.queuedMessage then toSend = screen.queuedMessage screen.queuedMessage = nil else toSend = replyMessage end if not screen.isDone then --Because then sendChannel doesn't exist transmit(screen.send,screen.receive, toSend, screen.legacy) --Send reply message for turtle end end end launchQuad(screen.rec) --Launch the Quad! (This only activates when turtle needs it) screen:updateDisplay() --isDone is queried inside this screen:reset(screen.theme.background) screen:pushScreenUpdates() --Actually write things to screen --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 :) ----KEY HANDLING---- elseif event == "key" and keyMap[par1] then local key = keyMap[par1] if key ~= "enter" then --If we aren't submitting a command if key == "backspace" then if #commandString > 0 then commandString = commandString:sub(1,-2) end elseif key == "up" then commandString = lastCommand or commandString --Set to last command, or do nothing if it doesn't exist elseif key == "down" then commandString = "" --If key down, clear elseif #key == 1 then commandString = commandString..key end --ALL THE COMMANDS else --If we are submitting a command lastCommand = commandString --For using up arrow local args = {} for a in commandString:gmatch("%S+") do --This captures all individual words in the command string args[#args+1] = a:lower() end local command = args[1] if validCommands[command] then --If it is a valid command... local commandType = validCommands[command] if commandType == 1 or commandType == 3 then --If the command requires a "side" like transmitting commands, versus setting a default if defaultSide then table.insert(args, 2, defaultSide) end local screen local test = screenClass.screens[tonumber(args[2])] if test and test.side ~= "REMOVED" then --This way we can specify IDs as well screen = test else screen = screenClass.sides[args[2]] end if screen then --If the side exists if command == "command" and screen.send then --If sending command to the turtle screen.queuedMessage = table.concat(args," ", 3) --Tells message handler to send appropriate message --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 end if command == "color" then screen.theme:addColor(args[3],colors[args[4]],colors[args[5]] ) updateAllScreens() --Because we are changing a theme color which others may have end if command == "theme" then screen:setTheme(args[3]) end if command == "send" then --This changes a send channel, and can also revert to handshake local chan = checkChannel(tonumber(args[3]) or -1) if chan then screen.send = chan else screen.send = nil end screen:setSize() --If on handshake, resets screen end if command == "receive" and not screen.isStation then local chan = checkChannel(tonumber(args[3]) or -1) if chan and not screenClass.channels[chan] then screen:setChannel(chan) screen:setSize() --Update broken status end end if command == "station" then if screen.isStation then screen:removeStation() else screen:setStation() end end if command == "remove" and screen.side ~= "computer" then --We don't want to remove the main display! print() screen:remove() else --Because if removed it does stupid things screen:reset() debug("here") screen:updateDisplay() debug("Here") screen:pushScreenUpdates() debug("Hereer") end end end if commandType == 2 or commandType == 3 then--Does not require a screen side if command == "screen" and peripheral.getType(args[2]) == "monitor" then --Makes sure there is a monitor on the screen side if not args[3] or not screenClass.channels[tonumber(args[3])] then --Make sure the channel doesn't already exist local mon = screenClass.new(args[2], args[3], args[4]) --args[3] is the channel and will set broken display if it doesn't exist --args[4] is the theme, and will default if doesn't exists. mon:updateDisplay() mon:reset() mon:pushScreenUpdates() end end if command == "theme" then screenClass:setTheme(args[2], true) --Otherwise this would set base theme to nil, erroring updateAllScreens() end if command == "color" and themes[args[2]] then themes[args[2]]:addColor(args[3],colors[args[4]],colors[args[5]]) updateAllScreens() --Because any screen could have this theme end if command == "side" then if screenClass.sides[args[2]] then defaultSide = args[2] else defaultSide = nil end end if command == "set" then if args[2] then defaultCommand = table.concat(args," ",2) defaultCommand = defaultCommand:upper() else defaultCommand = nil end end if command == "savetheme" then if saveTheme(themes[args[2]], args[3]) then computer:tryAddRaw(computer.dim[2]-1, "Save Theme Succeeded!", computer.theme.inverse, true) else computer:tryAddRaw(computer.dim[2]-1, "Save Theme Failed!", computer.theme.inverse, true) end computer:reset() computer:pushScreenUpdates() sleep(1) end if command == "auto" then local newTab = copyTable(args) --This is so we can pass all additional words as channel numbers table.remove(newTab, 1) autoDetect(newTab) updateAllScreens() end if command == "verbose" then doDebug = true end if command == "quiet" then doDebug = false end if command == "quit" or command == "exit" or command == "end" then continue = false end end else debug("\nInvalid Command") end if defaultCommand then commandString = defaultCommand.." " else commandString = "" end --Reset command string because it was sent end --Update computer display (computer is only one that displays command string computer:updateDisplay() --Note: Computer's method automatically adds commandString to last line if not continue then computer:tryAddRaw(computer.dim[2]-1,"Program Exiting", computer.theme.inverse, false, true, true) end computer:reset() computer:pushScreenUpdates() elseif event == "monitor_resize" then local screen = screenClass.sides[par1] if screen then screen:setSize() screen:updateDisplay() screen:reset() screen:pushScreenUpdates() end elseif event == "monitor_touch" then local screen = screenClass.sides[par1] debug("Side: ",par1," touched") if screen then --This part is copied from the "side" command local test = button.checkPoint(screen.buttons, {par2, par3}) if test then screen.queuedMessage = test else if not screen.receive then commandString = "RECEIVE "..par1:upper().." " end end else debug("Adding Screen") local mon = screenClass.new(par1) commandString = "RECEIVE "..mon.side:upper().." " mon:reset() mon:updateDisplay() mon:pushScreenUpdates() end computer:reset() computer:updateDisplay() computer:pushScreenUpdates() --Need to update computer for command string elseif event == "mouse_click" then screen = computer local test = button.checkPoint(screen.buttons, {par2, par3}) if test then screen.queuedMessage = test end elseif event == "peripheral_detach" then local screen = screenClass.sides[par1] if screen then screen:setSize() end --if screen then -- screen:remove() --end elseif event == "peripheral" then local screen = screenClass.sides[par1] if screen then screen:setSize() elseif peripheral.getType(par1) == "monitor" then commandString = "SCREEN "..par1:upper().." " end end local flag = false --Saying all screens are done, must disprove local count = 0 --We want it to wait if no screens have channels for a,b in pairs(screenClass.channels) do count = count + 1 if autoRestart or not b.isDone then flag = true end end if continue and count > 0 then --If its not already false from something else continue = flag end if #stationsList > 0 and event ~= "key" and event ~= "char" then --So screen is properly updated for a, b in ipairs(stationsList) do b:reset() b:updateDisplay() b:pushScreenUpdates() end end end sleep(1.5) for a in pairs(screenClass.channels) do modem.close(a) end for a, b in pairs(screenClass.sides) do if not b.isDone then --Otherwise we want it display the ending stats b:setTextColor(colors.white) b:setBackgroundColor(colors.black) b.term.clear() b.term.setCursorPos(1,1) end end local text --Fun :D if computer.isComputer then text = "SUPER COMPUTER OS 9000" elseif computer.isTurtle then text = "SUPER DIAMOND-MINING OS XXX" elseif computer.isPocket then text = "PoCkEt OOS AMAYZE 65" end if text and not computer.isDone then computer:say(text, computer.theme.title,1) else computer.term.setCursorPos(1,computer.dim[2]) computer.term.clearLine() end --Down here shut down all the channels, remove the saved file, other cleanup stuff