424 lines
9.6 KiB
Lua
424 lines
9.6 KiB
Lua
--[[
|
|
tutel host
|
|
|
|
__
|
|
.,-;-;-,. /'_\
|
|
_/_/_/_|_\_\) /
|
|
'-<_><_><_><_>=/\
|
|
`/_/====/_/-'\_\
|
|
"" "" ""
|
|
]]
|
|
|
|
--[[
|
|
network variables
|
|
]]
|
|
local HOST_CHANNEL = 1337
|
|
local TURTLE_CHANNEL_BASE = 1338
|
|
|
|
local MSG_HEADER = 0x747574656C
|
|
|
|
local modem = peripheral.find("modem") or error("No modems found", 0)
|
|
modem.open(HOST_CHANNEL)
|
|
|
|
--[[
|
|
helper functions
|
|
]]
|
|
function wrapText(text, maxWidth)
|
|
if not text then return {} end
|
|
if maxWidth <= 0 then return { text } end
|
|
|
|
local lines = {}
|
|
local currentLine = ""
|
|
|
|
for word, newline in text:gmatch("([^%s\n]*)(%s*\n?)") do
|
|
if newline:find("\n") then
|
|
if currentLine ~= "" then
|
|
table.insert(lines, currentLine)
|
|
currentLine = ""
|
|
end
|
|
if word ~= "" then
|
|
table.insert(lines, word)
|
|
else
|
|
table.insert(lines, "")
|
|
end
|
|
elseif #currentLine + #word <= maxWidth then
|
|
if currentLine == "" then
|
|
currentLine = word
|
|
else
|
|
currentLine = currentLine .. " " .. word
|
|
end
|
|
else
|
|
if currentLine ~= "" then
|
|
table.insert(lines, currentLine)
|
|
currentLine = word
|
|
else
|
|
while #word > maxWidth do
|
|
table.insert(lines, word:sub(1, maxWidth))
|
|
word = word:sub(maxWidth + 1)
|
|
end
|
|
currentLine = word
|
|
end
|
|
end
|
|
end
|
|
|
|
if currentLine ~= "" then
|
|
table.insert(lines, currentLine)
|
|
end
|
|
|
|
return lines
|
|
end
|
|
|
|
--[[
|
|
logging
|
|
]]
|
|
local logTypes = {
|
|
["general"] = {
|
|
["BG"] = colours.green,
|
|
["FG"] = colours.white
|
|
},
|
|
|
|
["info"] = {
|
|
["BG"] = colours.blue,
|
|
["FG"] = colours.white
|
|
},
|
|
|
|
["warning"] = {
|
|
["BG"] = colours.orange,
|
|
["FG"] = colours.black,
|
|
["AffectContent"] = true
|
|
},
|
|
|
|
["error"] = {
|
|
["BG"] = colours.red,
|
|
["FG"] = colours.black,
|
|
["AffectContent"] = true
|
|
}
|
|
}
|
|
local log = {}
|
|
log.buffer = {}
|
|
|
|
for typeName, format in pairs(logTypes) do
|
|
log[typeName] = function(fmt, ...)
|
|
local entry = {
|
|
message = string.format(fmt, ...),
|
|
type = typeName
|
|
}
|
|
|
|
table.insert(log.buffer, entry)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
turtle tracking
|
|
]]
|
|
local turtles = {}
|
|
|
|
function registerTurtle(pos, fuel, id)
|
|
local channel = TURTLE_CHANNEL_BASE + #turtles
|
|
|
|
local turt = {
|
|
["channel"] = channel,
|
|
["index"] = #turtles,
|
|
["position"] = pos,
|
|
["fuel"] = fuel,
|
|
["status"] = "IDLE",
|
|
["id"] = id
|
|
}
|
|
|
|
table.insert(turtles, turt)
|
|
|
|
return turt
|
|
end
|
|
|
|
--[[
|
|
helper networking functions
|
|
]]
|
|
function sendToAll(name, msgData)
|
|
local message = {
|
|
["header"] = MSG_HEADER,
|
|
["name"] = name,
|
|
["data"] = msgData or {}
|
|
}
|
|
|
|
modem.transmit(0, 0, message)
|
|
end
|
|
|
|
--[[
|
|
command handling
|
|
]]
|
|
local commands = {}
|
|
function declareCommandHandler(name, handler, requiredKeys)
|
|
commands[name] = {
|
|
["keys"] = requiredKeys or {},
|
|
["handler"] = handler
|
|
}
|
|
end
|
|
|
|
-- scan
|
|
function handleScan(keys, id)
|
|
local turt = registerTurtle({ keys.positionX, keys.positionY, keys.positionZ }, keys.fuel, id)
|
|
|
|
log.info("Received turtle info (pos: %d, %d, %d | fuel: %d | channel: %d | id: %d)", turt.position[1],
|
|
turt.position[2], turt.position[3], turt.fuel, turt.channel, id)
|
|
|
|
sendToAll("REGISTER", { ["id"] = id, ["turtleChannel"] = turt.channel })
|
|
end
|
|
|
|
declareCommandHandler("SCAN", handleScan,
|
|
{
|
|
["positionX"] = "number",
|
|
["positionY"] = "number",
|
|
["positionZ"] = "number",
|
|
["fuel"] = "number"
|
|
})
|
|
|
|
--[[
|
|
event handling
|
|
]]
|
|
local eventListeners = {}
|
|
function registerEventListener(name, listener)
|
|
if not eventListeners[name] then eventListeners[name] = {} end
|
|
table.insert(eventListeners[name], listener)
|
|
end
|
|
|
|
function dispatchEvents(event, ...)
|
|
local eventTbl = eventListeners[event]
|
|
if not eventTbl then return end
|
|
|
|
for i = 1, #eventTbl, 1 do
|
|
eventTbl[i](...)
|
|
end
|
|
end
|
|
|
|
function handleMessages(side, channel, replyChannel, message, dist)
|
|
if channel ~= HOST_CHANNEL then return end
|
|
|
|
-- msg validation
|
|
if type(message) ~= "table" then return end
|
|
if not message.header or message.header ~= MSG_HEADER then return end
|
|
if not message.data then message.data = {} end
|
|
|
|
-- cmd validation
|
|
if not message.name then
|
|
log.warning("received nameless command?")
|
|
return
|
|
end
|
|
|
|
if not commands[message.name] then
|
|
log.error("unhandled command (%s)", message.name)
|
|
return
|
|
end
|
|
|
|
local cmd = commands[message.name]
|
|
for i, v in pairs(cmd.keys) do
|
|
if type(message.data[i]) ~= v and v ~= "optional" then
|
|
log.error("command %s requires key %s of type %s (was %s)", message.name, i, v, type(message[i]))
|
|
return
|
|
end
|
|
end
|
|
|
|
-- cmd execution
|
|
local success, err = pcall(cmd.handler, message.data, message.id)
|
|
if not success then
|
|
log.error("error occurred while executing command %s: %s", message.name, err)
|
|
end
|
|
|
|
log.info("processed command (%s)", message.name)
|
|
end
|
|
|
|
registerEventListener("modem_message", handleMessages)
|
|
|
|
--[[
|
|
key handling
|
|
]]
|
|
local GLFW_KEY_DOWN = 264
|
|
local GLFW_KEY_UP = 265
|
|
local GLFW_KEY_ENTER = 257
|
|
|
|
local logOffset = 0
|
|
|
|
function scrollLogs(key, isHeld)
|
|
if key == GLFW_KEY_DOWN then
|
|
logOffset = logOffset - 1
|
|
elseif key == GLFW_KEY_UP then
|
|
logOffset = logOffset + 1
|
|
end
|
|
|
|
if logOffset < 0 then logOffset = 0 end
|
|
if logOffset >= #log.buffer then logOffset = #log.buffer - 1 end
|
|
end
|
|
|
|
registerEventListener("key", scrollLogs)
|
|
|
|
--[[
|
|
console
|
|
]]
|
|
local commandBuffer = ""
|
|
function drawConsole(y)
|
|
|
|
end
|
|
|
|
|
|
function handleConsoleInput(char)
|
|
commandBuffer = commandBuffer..char
|
|
end
|
|
registerEventListener("char", handleConsoleInput)
|
|
|
|
function handleConsoleKeys(key)
|
|
if key == GLFW_KEY_ENTER then
|
|
-- executeCommand(commandBuffer)
|
|
log.info("Executed command: %s", commandBuffer)
|
|
commandBuffer = ""
|
|
end
|
|
end
|
|
registerEventListener("key", handleConsoleKeys)
|
|
|
|
--[[
|
|
drawing functions
|
|
]]
|
|
function drawLogs(x0, y0, x1, y1)
|
|
local w = x1 - x0
|
|
local h = y1 - y0
|
|
|
|
local drawnLines = 0
|
|
for i = #log.buffer - logOffset, 1, -1 do
|
|
if drawnLines > h then break end
|
|
|
|
local entry = log.buffer[i]
|
|
local format = logTypes[entry.type]
|
|
|
|
local prevColourBg = term.getBackgroundColour()
|
|
local prevColourFg = term.getTextColour()
|
|
|
|
local prefix = ("[%s]"):format(entry.type)
|
|
local prefixLength = #prefix
|
|
local maxMessageWidth = math.max(1, w - prefixLength)
|
|
|
|
local wrappedMessage = wrapText(tostring(entry.message), maxMessageWidth)
|
|
|
|
local messageBG, messageFG
|
|
if format.AffectContent then
|
|
messageBG = format.BG
|
|
messageFG = format.FG
|
|
else
|
|
messageBG = prevColourBg
|
|
messageFG = prevColourFg
|
|
end
|
|
|
|
for lineIdx = #wrappedMessage, 1, -1 do
|
|
if drawnLines > h then break end
|
|
|
|
local line = wrappedMessage[lineIdx]
|
|
|
|
term.setCursorPos(1, y1 - drawnLines)
|
|
|
|
if lineIdx == 1 then
|
|
term.setBackgroundColor(format.BG)
|
|
term.setTextColor(format.FG)
|
|
term.write(prefix)
|
|
else
|
|
term.setBackgroundColor(messageBG)
|
|
term.write((" "):rep(prefixLength))
|
|
end
|
|
|
|
term.setBackgroundColor(messageBG)
|
|
term.setTextColor(messageFG)
|
|
term.write(" ")
|
|
term.write(line)
|
|
|
|
local remaining = w - (prefixLength + #line)
|
|
if remaining > 0 then
|
|
term.write((" "):rep(remaining))
|
|
end
|
|
|
|
drawnLines = drawnLines + 1
|
|
end
|
|
|
|
term.setBackgroundColor(prevColourBg)
|
|
term.setTextColor(prevColourFg)
|
|
end
|
|
end
|
|
|
|
function drawStatusBar(y)
|
|
term.setCursorPos(1, y)
|
|
|
|
term.setBackgroundColor(colors.gray)
|
|
term.setTextColor(colors.white)
|
|
term.clearLine()
|
|
|
|
local str = ("turtles: %d"):format(#turtles)
|
|
term.write(str)
|
|
end
|
|
|
|
function drawCommandBar(w, y)
|
|
term.setCursorPos(1, y)
|
|
term.clearLine()
|
|
|
|
term.setBackgroundColor(colors.white)
|
|
term.setTextColor(colors.black)
|
|
|
|
term.write("$ ")
|
|
|
|
local startingIndex = commandBuffer:len() - w - 3
|
|
if startingIndex < 0 then
|
|
startingIndex = 0
|
|
end
|
|
|
|
term.write(commandBuffer:sub(startingIndex))
|
|
end
|
|
|
|
--[[
|
|
main loop functions
|
|
]]
|
|
function updateScreen()
|
|
local w, h = term.getSize()
|
|
|
|
local function drawTextCentered(text)
|
|
local x = (w / 2) - (text:len() / 2)
|
|
local _, curY = term.getCursorPos()
|
|
term.setCursorPos(x, curY)
|
|
term.write(text)
|
|
end
|
|
|
|
-- reset state and clear screen
|
|
term.setBackgroundColor(colors.black)
|
|
term.clear()
|
|
|
|
-- -- title bar
|
|
term.setCursorPos(1, 1)
|
|
|
|
term.setBackgroundColor(colors.lightGray)
|
|
term.setTextColour(colors.black)
|
|
|
|
term.clearLine()
|
|
drawTextCentered("tutel host controller")
|
|
|
|
term.setBackgroundColor(colors.black)
|
|
term.setTextColour(colors.white)
|
|
|
|
drawLogs(1, 2, w, h - 1)
|
|
drawStatusBar(2)
|
|
drawCommandBar(w, h)
|
|
end
|
|
|
|
function pollEvents()
|
|
local eventData = { os.pullEvent() }
|
|
|
|
local eventName = eventData[1]
|
|
table.remove(eventData, 1)
|
|
|
|
dispatchEvents(eventName, table.unpack(eventData))
|
|
end
|
|
|
|
--[[
|
|
main
|
|
]]
|
|
log.general("tutel host init")
|
|
log.info("broadcasting SCAN")
|
|
sendToAll("SCAN", { ["hostChannel"] = HOST_CHANNEL })
|
|
|
|
while true do
|
|
updateScreen()
|
|
pollEvents()
|
|
end
|