Computercraft-Turtle-Quarry.../Quarry-Receiver.lua

1825 lines
70 KiB
Lua

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 <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