Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --Quarry Receiver Version 3.6.5
- --Made by MrJohnDowe
- --[[
- Recent Changes:
- Fixed bugs with hardcoded keys for CC: Tweaked
- ]]
- if fs.exists("QuarryReceiver")then
- shell.run("rename QuarryReceiver startup.lua")
- end
- --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 = true --If true, will reset screens instead of turning them off. For when reusing turtles.
- --Initializing Program-Wide Variables
- local expectedMessage = "Dowe'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 <channel>: Sets the main screen's receive channel
- -theme <name>: sets the "default" theme that screens use when they don't have a set theme
- $$Parameters!
- note: <> means required, [] means optional
- -screen <side> [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 <side>: 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
Add Comment
Please, Sign In to add comment