Compare commits
66 Commits
e29b4e9190
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 392dd8a68e | |||
| 9264c819ee | |||
| 1d4c1ca900 | |||
| e817d874c0 | |||
| 28c92e78c2 | |||
| c430904fa6 | |||
| 60db9abb31 | |||
| 16b1f474c4 | |||
| 8c46690f52 | |||
| 55abc526bf | |||
| 41a79d80a5 | |||
| 166fd3efc3 | |||
| a8d4d01378 | |||
| e47713e0c3 | |||
| b85f7e29f2 | |||
| d1900f8327 | |||
| 6486731698 | |||
| d72ca4a727 | |||
| 3b0555abd0 | |||
| 0162a776f8 | |||
| e22242c4e7 | |||
| 7ca69cf0a0 | |||
| 1ee6e9c0b8 | |||
| 973abbbfed | |||
| 45c499b6de | |||
| 9563262457 | |||
| 17cc45f5c3 | |||
| 11aa0db67b | |||
| ad4289910b | |||
| ad17715e4a | |||
| 868663d33f | |||
| e8c68ffded | |||
| 5aea3bb618 | |||
| 6fbb54c2cd | |||
| 283ee5cb71 | |||
| 9da79947d3 | |||
| c5ab37664b | |||
| 77da21f1e7 | |||
| b6b35cfd2f | |||
| 31d333333c | |||
| 3c7836c513 | |||
| 588f56075c | |||
| 67b1e7b7d9 | |||
| 709b7fb992 | |||
| 0465b2de5b | |||
| 7305789194 | |||
| dd117a7ca2 | |||
| cd2ff71b20 | |||
| 3c235a7513 | |||
| f29770e3f0 | |||
| 63c3e4e273 | |||
| 0d7e6eb8e5 | |||
| 08607c6497 | |||
| 6d454821f9 | |||
| 99b4fec189 | |||
| 5022f71398 | |||
| 55314b33b2 | |||
| a6ce7c8b48 | |||
| 4638994190 | |||
| 2f22573319 | |||
| 42f8d24b76 | |||
| aaa63e5293 | |||
| 091811ad3f | |||
| b0975519d1 | |||
| cc2988dbc8 | |||
| 3bcce0f50f |
@@ -103,7 +103,7 @@ function handleScan(keys)
|
|||||||
log.info("received scan, host channel assigned to %d, replying...", keys.hostChannel)
|
log.info("received scan, host channel assigned to %d, replying...", keys.hostChannel)
|
||||||
|
|
||||||
local x, y, z = gps.locate()
|
local x, y, z = gps.locate()
|
||||||
|
|
||||||
sendToHost("SCAN", {
|
sendToHost("SCAN", {
|
||||||
["positionX"] = x,
|
["positionX"] = x,
|
||||||
["positionY"] = y,
|
["positionY"] = y,
|
||||||
@@ -162,27 +162,38 @@ end
|
|||||||
declareCommandHandler("ABORT", handleAbort)
|
declareCommandHandler("ABORT", handleAbort)
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
main
|
event handling
|
||||||
]]
|
]]
|
||||||
function pollAndProcessMessages()
|
local eventListeners = {}
|
||||||
local eventType, side, channel, replyChannel, message, dist = os.pullEvent("modem_message")
|
local function registerEventListener(name, listener)
|
||||||
|
if not eventListeners[name] then eventListeners[name] = {} end
|
||||||
|
table.insert(eventListeners[name], listener)
|
||||||
|
end
|
||||||
|
|
||||||
-- ignore messages not from host, HOST_CHANNEL and REPLY_CHANNEL will get assigned after receiving SCAN on channel 0
|
local function dispatchEvents(event, ...)
|
||||||
|
local eventTbl = eventListeners[event]
|
||||||
|
if not eventTbl then return end
|
||||||
|
|
||||||
|
for i = 1, #eventTbl, 1 do
|
||||||
|
eventTbl[i](...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function handleMessages(side, channel, replyChannel, message, dist)
|
||||||
if TURTLE_CHANNEL and channel ~= TURTLE_CHANNEL then return end
|
if TURTLE_CHANNEL and channel ~= TURTLE_CHANNEL then return end
|
||||||
|
|
||||||
-- message validation
|
-- msg validation
|
||||||
if type(message) ~= "table" then return end
|
if type(message) ~= "table" then return end
|
||||||
if not message.header or message.header ~= MSG_HEADER then return end
|
if not message.header or message.header ~= MSG_HEADER then return end
|
||||||
if not message.data then message.data = {} end
|
if not message.data then message.data = {} end
|
||||||
|
|
||||||
-- command validation
|
-- cmd validation
|
||||||
if not message.name then
|
if not message.name then
|
||||||
log.warning("received nameless message")
|
log.warning("received nameless command?")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not commands[message.name] then
|
if not commands[message.name] then
|
||||||
log.error("unhandled message (%s)", message.name)
|
log.error("unhandled command (%s)", message.name)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -194,14 +205,33 @@ function pollAndProcessMessages()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
cmd.handler(message.data)
|
-- 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)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- message handling
|
|
||||||
log.info("processed command (%s)", message.name)
|
log.info("processed command (%s)", message.name)
|
||||||
end
|
end
|
||||||
|
registerEventListener("modem_message", handleMessages)
|
||||||
|
|
||||||
log.general("tutel client init")
|
--[[
|
||||||
|
event polling
|
||||||
|
]]
|
||||||
|
function pollEvents()
|
||||||
|
local eventData = { os.pullEvent() }
|
||||||
|
|
||||||
while true do
|
local eventName = eventData[1]
|
||||||
pollAndProcessMessages()
|
table.remove(eventData, 1)
|
||||||
|
|
||||||
|
dispatchEvents(eventName, table.unpack(eventData))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
main loop
|
||||||
|
]]
|
||||||
|
log.info("init")
|
||||||
|
while true do
|
||||||
|
pollEvents()
|
||||||
|
end
|
||||||
@@ -10,7 +10,15 @@
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
network variables
|
TO-DO List:
|
||||||
|
* Add argument / funcion type checks on callback styled register functions
|
||||||
|
* Make log screen scrolling work based on drawn lines not log indexes
|
||||||
|
* Rename network commands to messages, to avoid confusion with console commands
|
||||||
|
* Periodically check if turtle connections are valid
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
general variables
|
||||||
]]
|
]]
|
||||||
local HOST_CHANNEL = 1337
|
local HOST_CHANNEL = 1337
|
||||||
local TURTLE_CHANNEL_BASE = 1338
|
local TURTLE_CHANNEL_BASE = 1338
|
||||||
@@ -20,31 +28,56 @@ local MSG_HEADER = 0x747574656C
|
|||||||
local modem = peripheral.find("modem") or error("No modems found", 0)
|
local modem = peripheral.find("modem") or error("No modems found", 0)
|
||||||
modem.open(HOST_CHANNEL)
|
modem.open(HOST_CHANNEL)
|
||||||
|
|
||||||
|
local monitor = peripheral.find("monitor")
|
||||||
|
local scr = monitor or term
|
||||||
|
|
||||||
|
local shouldRun = true
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
helper functions
|
helper functions
|
||||||
]]
|
]]
|
||||||
function wrapText(text, maxWidth)
|
function wrapText(text, maxWidth)
|
||||||
|
if not text then return {} end
|
||||||
|
if maxWidth <= 0 then return { text } end
|
||||||
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local currentLine = ""
|
local currentLine = ""
|
||||||
for word in text:gmatch("%S+") do
|
|
||||||
if #currentLine + #word + 1 <= maxWidth then
|
for word, newline in text:gmatch("([^%s\n]*)(%s*\n?)") do
|
||||||
currentLine = currentLine .. (currentLine == "" and "" or " ") .. word
|
if newline:find("\n") then
|
||||||
else
|
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
|
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
|
while #word > maxWidth do
|
||||||
table.insert(lines, word:sub(1, maxWidth))
|
table.insert(lines, word:sub(1, maxWidth))
|
||||||
word = word:sub(maxWidth + 1)
|
word = word:sub(maxWidth + 1)
|
||||||
end
|
end
|
||||||
currentLine = word
|
currentLine = word
|
||||||
else
|
|
||||||
table.insert(lines, currentLine)
|
|
||||||
currentLine = word
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentLine ~= "" then
|
if currentLine ~= "" then
|
||||||
table.insert(lines, currentLine)
|
table.insert(lines, currentLine)
|
||||||
end
|
end
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,7 +171,7 @@ end
|
|||||||
function handleScan(keys, id)
|
function handleScan(keys, id)
|
||||||
local turt = registerTurtle({ keys.positionX, keys.positionY, keys.positionZ }, keys.fuel, 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],
|
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)
|
turt.position[2], turt.position[3], turt.fuel, turt.channel, id)
|
||||||
|
|
||||||
sendToAll("REGISTER", { ["id"] = id, ["turtleChannel"] = turt.channel })
|
sendToAll("REGISTER", { ["id"] = id, ["turtleChannel"] = turt.channel })
|
||||||
@@ -180,19 +213,19 @@ function handleMessages(side, channel, replyChannel, message, dist)
|
|||||||
|
|
||||||
-- cmd validation
|
-- cmd validation
|
||||||
if not message.name then
|
if not message.name then
|
||||||
log.warning("received nameless command?")
|
log.warning("Received nameless command?")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not commands[message.name] then
|
if not commands[message.name] then
|
||||||
log.error("unhandled command (%s)", message.name)
|
log.error("Unhandled command (%s)", message.name)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd = commands[message.name]
|
local cmd = commands[message.name]
|
||||||
for i, v in pairs(cmd.keys) do
|
for i, v in pairs(cmd.keys) do
|
||||||
if type(message.data[i]) ~= v and v ~= "optional" then
|
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]))
|
log.error("Command %s requires key %s of type %s (was %s)", message.name, i, v, type(message[i]))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -200,10 +233,10 @@ function handleMessages(side, channel, replyChannel, message, dist)
|
|||||||
-- cmd execution
|
-- cmd execution
|
||||||
local success, err = pcall(cmd.handler, message.data, message.id)
|
local success, err = pcall(cmd.handler, message.data, message.id)
|
||||||
if not success then
|
if not success then
|
||||||
log.error("error occurred while executing command %s: %s", message.name, err)
|
log.error("Error occurred while executing command %s: %s", message.name, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info("processed command (%s)", message.name)
|
log.info("Processed command (%s)", message.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
registerEventListener("modem_message", handleMessages)
|
registerEventListener("modem_message", handleMessages)
|
||||||
@@ -211,21 +244,153 @@ registerEventListener("modem_message", handleMessages)
|
|||||||
--[[
|
--[[
|
||||||
key handling
|
key handling
|
||||||
]]
|
]]
|
||||||
local logOffset = 0
|
local GLFW_KEY_DOWN = 264
|
||||||
|
local GLFW_KEY_UP = 265
|
||||||
|
local GLFW_KEY_ENTER = 257
|
||||||
|
local GLFW_KEY_BACKSPACE = 259
|
||||||
|
|
||||||
|
local logOffset = 0
|
||||||
|
|
||||||
function scrollLogs(key, isHeld)
|
function scrollLogs(key, isHeld)
|
||||||
if key == 264 then
|
if key == GLFW_KEY_DOWN then
|
||||||
logOffset = logOffset - 1
|
logOffset = logOffset - 1
|
||||||
elseif key == 265 then
|
elseif key == GLFW_KEY_UP then
|
||||||
logOffset = logOffset + 1
|
logOffset = logOffset + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if logOffset < 0 then logOffset = 0 end
|
|
||||||
if logOffset >= #log.buffer then logOffset = #log.buffer - 1 end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
registerEventListener("key", scrollLogs)
|
registerEventListener("key", scrollLogs)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
console
|
||||||
|
]]
|
||||||
|
local consoleCommands = {}
|
||||||
|
local commandBuffer = ""
|
||||||
|
|
||||||
|
function registerConsoleCommand(name, handler, desc, usage)
|
||||||
|
local cmd = {
|
||||||
|
["name"] = name,
|
||||||
|
["handler"] = handler,
|
||||||
|
["desc"] = desc or "",
|
||||||
|
["usage"] = usage or nil
|
||||||
|
}
|
||||||
|
consoleCommands[name] = cmd
|
||||||
|
end
|
||||||
|
|
||||||
|
function processCommand()
|
||||||
|
local cmd
|
||||||
|
local args = {}
|
||||||
|
|
||||||
|
for word in commandBuffer:gmatch("%S+") do
|
||||||
|
if not cmd then
|
||||||
|
cmd = word
|
||||||
|
else
|
||||||
|
table.insert(args, word)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if consoleCommands[cmd] then
|
||||||
|
log.info("> %s %s", cmd, table.concat(args, " "))
|
||||||
|
consoleCommands[cmd].handler(args)
|
||||||
|
else
|
||||||
|
log.warning("Unknown command: %s", cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
commandBuffer = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function handleConsoleInput(char)
|
||||||
|
commandBuffer = commandBuffer .. char
|
||||||
|
end
|
||||||
|
|
||||||
|
registerEventListener("char", handleConsoleInput)
|
||||||
|
|
||||||
|
function handleConsoleKeys(key)
|
||||||
|
if key == GLFW_KEY_ENTER and commandBuffer:len() > 0 then
|
||||||
|
processCommand()
|
||||||
|
elseif key == GLFW_KEY_BACKSPACE then
|
||||||
|
local len = commandBuffer:len()
|
||||||
|
if len >= 1 then
|
||||||
|
commandBuffer = commandBuffer:sub(0, len - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registerEventListener("key", handleConsoleKeys)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
console commands
|
||||||
|
]]
|
||||||
|
function echoCommand(args)
|
||||||
|
log.info(table.concat(args, " "))
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("echo", echoCommand, "Prints your message", "echo (string)")
|
||||||
|
|
||||||
|
function exitCommand(args)
|
||||||
|
shouldRun = false
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("exit", exitCommand, "Exits the program")
|
||||||
|
|
||||||
|
function scanCommand(args)
|
||||||
|
log.info("Broadcasting SCAN")
|
||||||
|
sendToAll("SCAN", { ["hostChannel"] = HOST_CHANNEL })
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("scan", scanCommand, "Sends out a scan command to pair unlinked turtles")
|
||||||
|
|
||||||
|
function clearCommand(args)
|
||||||
|
log.buffer = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("clear", clearCommand, "Clears the console")
|
||||||
|
|
||||||
|
function turtlesCommand(args)
|
||||||
|
if #turtles == 0 then
|
||||||
|
log.info("No turtles registered")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #turtles, 1 do
|
||||||
|
local turt = turtles[i]
|
||||||
|
|
||||||
|
local turtleMessage = ("Coords: %d, %d, %d - fuel: %d - channel: %d - id: %d - status: %s"):format(
|
||||||
|
turt.position[1],
|
||||||
|
turt.position[2], turt.position[3], turt.fuel, turt.channel, turt.id, turt.status)
|
||||||
|
|
||||||
|
log.info(turtleMessage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("turtles", turtlesCommand, "Lists all the linked turtles")
|
||||||
|
|
||||||
|
function helpCommand(args)
|
||||||
|
if #args == 0 then
|
||||||
|
log.info("----- commands -----")
|
||||||
|
for _, cmd in pairs(consoleCommands) do
|
||||||
|
log.info("%s - %s", cmd.name, cmd.desc)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local cmdName = args[1]
|
||||||
|
local cmd = consoleCommands[cmdName]
|
||||||
|
|
||||||
|
if not cmdName then
|
||||||
|
log.error("Unable to find command: %s", cmdName)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
log.info("---- %s ----", cmdName)
|
||||||
|
log.info(cmd.desc)
|
||||||
|
if cmd.usage then
|
||||||
|
log.info("Usage: %s", cmd.usage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registerConsoleCommand("help", helpCommand, "Prints out all the console commands or usage of a specified command",
|
||||||
|
"help or help (command name)")
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
drawing functions
|
drawing functions
|
||||||
]]
|
]]
|
||||||
@@ -233,8 +398,11 @@ function drawLogs(x0, y0, x1, y1)
|
|||||||
local w = x1 - x0
|
local w = x1 - x0
|
||||||
local h = y1 - y0
|
local h = y1 - y0
|
||||||
|
|
||||||
local prevColourBg = term.getBackgroundColour()
|
if #log.buffer == 0 then return end
|
||||||
local prevColourFg = term.getTextColour()
|
|
||||||
|
if logOffset < 0 then logOffset = 0 end
|
||||||
|
if logOffset >= #log.buffer then logOffset = #log.buffer - 1 end
|
||||||
|
if #log.buffer == 0 then logOffset = 0 end
|
||||||
|
|
||||||
local drawnLines = 0
|
local drawnLines = 0
|
||||||
for i = #log.buffer - logOffset, 1, -1 do
|
for i = #log.buffer - logOffset, 1, -1 do
|
||||||
@@ -243,18 +411,14 @@ function drawLogs(x0, y0, x1, y1)
|
|||||||
local entry = log.buffer[i]
|
local entry = log.buffer[i]
|
||||||
local format = logTypes[entry.type]
|
local format = logTypes[entry.type]
|
||||||
|
|
||||||
term.setCursorPos(1, y1 - drawnLines)
|
local prevColourBg = scr.getBackgroundColour()
|
||||||
|
local prevColourFg = scr.getTextColour()
|
||||||
|
|
||||||
term.setBackgroundColor(format["BG"])
|
local prefix = ("[%s]"):format(entry.type)
|
||||||
term.setTextColor(format["FG"])
|
|
||||||
|
|
||||||
local prefix = ("[%s] "):format(entry.type)
|
|
||||||
local prefixLength = #prefix
|
local prefixLength = #prefix
|
||||||
local maxMessageWidth = w - prefixLength
|
local maxMessageWidth = math.max(1, w - prefixLength)
|
||||||
|
|
||||||
if maxMessageWidth <= 0 then maxMessageWidth = 1 end
|
local wrappedMessage = wrapText(tostring(entry.message), maxMessageWidth)
|
||||||
|
|
||||||
local wrappedMessage = wrapText(entry.message, maxMessageWidth)
|
|
||||||
|
|
||||||
local messageBG, messageFG
|
local messageBG, messageFG
|
||||||
if format.AffectContent then
|
if format.AffectContent then
|
||||||
@@ -265,82 +429,102 @@ function drawLogs(x0, y0, x1, y1)
|
|||||||
messageFG = prevColourFg
|
messageFG = prevColourFg
|
||||||
end
|
end
|
||||||
|
|
||||||
for lineIdx, line in ipairs(wrappedMessage) do
|
for lineIdx = #wrappedMessage, 1, -1 do
|
||||||
if drawnLines > h then break end
|
if drawnLines > h then break end
|
||||||
|
|
||||||
local currentY = y1 - drawnLines
|
local line = wrappedMessage[lineIdx]
|
||||||
term.setCursorPos(1, currentY)
|
|
||||||
|
scr.setCursorPos(1, y1 - drawnLines)
|
||||||
|
|
||||||
if lineIdx == 1 then
|
if lineIdx == 1 then
|
||||||
term.setBackgroundColor(format.BG)
|
scr.setBackgroundColor(format.BG)
|
||||||
term.setTextColor(format.FG)
|
scr.setTextColor(format.FG)
|
||||||
term.write(prefix)
|
scr.write(prefix)
|
||||||
|
|
||||||
term.setBackgroundColor(messageBG)
|
|
||||||
term.setTextColor(messageFG)
|
|
||||||
term.write(line)
|
|
||||||
else
|
else
|
||||||
|
scr.setBackgroundColor(messageBG)
|
||||||
term.setBackgroundColor(messageBG)
|
scr.write((" "):rep(prefixLength))
|
||||||
term.setTextColor(messageFG)
|
|
||||||
term.write((" "):rep(prefixLength))
|
|
||||||
term.write(line)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
term.setBackgroundColor(messageBG)
|
scr.setBackgroundColor(messageBG)
|
||||||
term.setCursorPos(1 + prefixLength + #line, currentY)
|
scr.setTextColor(messageFG)
|
||||||
term.clearLine()
|
scr.write(" ")
|
||||||
|
scr.write(line)
|
||||||
|
|
||||||
|
local remaining = w - (prefixLength + #line)
|
||||||
|
if remaining > 0 then
|
||||||
|
scr.write((" "):rep(remaining))
|
||||||
|
end
|
||||||
|
|
||||||
drawnLines = drawnLines + 1
|
drawnLines = drawnLines + 1
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
term.setBackgroundColor(prevColourBg)
|
scr.setBackgroundColor(prevColourBg)
|
||||||
term.setTextColor(prevColourFg)
|
scr.setTextColor(prevColourFg)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawStatusBar(y)
|
function drawStatusBar(y)
|
||||||
term.setCursorPos(1, y)
|
scr.setCursorPos(1, y)
|
||||||
|
|
||||||
term.setBackgroundColor(colors.green)
|
scr.setBackgroundColor(colors.gray)
|
||||||
term.setTextColor(colors.white)
|
scr.setTextColor(colors.white)
|
||||||
term.clearLine()
|
scr.clearLine()
|
||||||
|
|
||||||
local str = ("turtles: %d"):format(#turtles)
|
local str = ("Turtles: %d"):format(#turtles)
|
||||||
term.write(str)
|
scr.write(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawCommandBar(w, y)
|
||||||
|
scr.setCursorPos(1, y)
|
||||||
|
|
||||||
|
scr.setBackgroundColor(colors.gray)
|
||||||
|
scr.setTextColor(colors.white)
|
||||||
|
|
||||||
|
scr.clearLine()
|
||||||
|
|
||||||
|
scr.write("$ ")
|
||||||
|
|
||||||
|
local startingIndex = commandBuffer:len() - w + 4
|
||||||
|
if startingIndex < 0 then
|
||||||
|
startingIndex = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
scr.write(commandBuffer:sub(startingIndex))
|
||||||
|
scr.setCursorBlink(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
main loop functions
|
main loop functions
|
||||||
]]
|
]]
|
||||||
function updateScreen()
|
function updateScreen()
|
||||||
local w, h = term.getSize()
|
local w, h = scr.getSize()
|
||||||
|
|
||||||
local function drawTextCentered(text)
|
local function drawTextCentered(text)
|
||||||
local x = (w / 2) - (text:len() / 2)
|
local x = (w / 2) - (text:len() / 2)
|
||||||
local _, curY = term.getCursorPos()
|
local _, curY = scr.getCursorPos()
|
||||||
term.setCursorPos(x, curY)
|
scr.setCursorPos(x, curY)
|
||||||
term.write(text)
|
scr.write(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset state and clear screen
|
-- reset state and clear screen
|
||||||
term.setBackgroundColor(colors.black)
|
scr.setBackgroundColor(colors.black)
|
||||||
term.clear()
|
scr.clear()
|
||||||
|
|
||||||
-- -- title bar
|
-- -- title bar
|
||||||
term.setCursorPos(1, 1)
|
scr.setCursorPos(1, 1)
|
||||||
|
|
||||||
term.setBackgroundColor(colors.lightGray)
|
scr.setBackgroundColor(colors.lightGray)
|
||||||
term.setTextColour(colors.black)
|
scr.setTextColour(colors.black)
|
||||||
|
|
||||||
term.clearLine()
|
scr.clearLine()
|
||||||
drawTextCentered("tutel host controller")
|
drawTextCentered("tutel host controller")
|
||||||
|
|
||||||
term.setBackgroundColor(colors.black)
|
scr.setBackgroundColor(colors.black)
|
||||||
term.setTextColour(colors.white)
|
scr.setTextColour(colors.white)
|
||||||
|
|
||||||
drawLogs(1, 2, w, h - 1)
|
drawLogs(1, 2, w, h - 1)
|
||||||
drawStatusBar(h)
|
drawStatusBar(2)
|
||||||
|
drawCommandBar(w, h)
|
||||||
end
|
end
|
||||||
|
|
||||||
function pollEvents()
|
function pollEvents()
|
||||||
@@ -355,11 +539,12 @@ end
|
|||||||
--[[
|
--[[
|
||||||
main
|
main
|
||||||
]]
|
]]
|
||||||
log.general("tutel host init")
|
log.general("Init")
|
||||||
log.info("broadcasting SCAN")
|
|
||||||
sendToAll("SCAN", { ["hostChannel"] = HOST_CHANNEL })
|
|
||||||
|
|
||||||
while true do
|
while shouldRun do
|
||||||
updateScreen()
|
updateScreen()
|
||||||
pollEvents()
|
pollEvents()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scr.clear()
|
||||||
|
scr.setCursorBlink(false)
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
if (mono_enumDomains() == nil) then LaunchMonoDataCollector() end
|
||||||
|
|
||||||
|
local is64Bit = targetIs64Bit()
|
||||||
|
local ARRAY_SIZE_OFF = is64Bit and 0x18 or 0x10
|
||||||
|
local ARRAY_DATA_OFF = is64Bit and 0x20 or 0x14
|
||||||
|
local PTR_SIZE = is64Bit and 8 or 4
|
||||||
|
|
||||||
|
local monoTypeToVartypeLookup = {
|
||||||
|
[0x02] = vtByte, -- BOOLEAN
|
||||||
|
[0x03] = vtWord, -- CHAR
|
||||||
|
[0x04] = vtByte, -- I1
|
||||||
|
[0x05] = vtByte, -- U1
|
||||||
|
[0x06] = vtWord, -- I2
|
||||||
|
[0x07] = vtWord, -- U2
|
||||||
|
[0x08] = vtDword, -- I4 (Int32)
|
||||||
|
[0x09] = vtDword, -- U4
|
||||||
|
[0x0a] = vtQword, -- I8
|
||||||
|
[0x0b] = vtQword, -- U8
|
||||||
|
[0x0c] = vtSingle, -- R4
|
||||||
|
[0x0d] = vtDouble, -- R8
|
||||||
|
[0x0e] = vtString, -- STRING
|
||||||
|
[0x0f] = vtPointer, -- PTR
|
||||||
|
[0x12] = vtPointer, -- CLASS
|
||||||
|
[0x14] = vtPointer -- ARRAY
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
generic helper functions
|
||||||
|
]]
|
||||||
|
function printf(fmt, ...) print(fmt:format(...)) end
|
||||||
|
|
||||||
|
function splitByComma(str)
|
||||||
|
if str == nil or str == "" then return {} end
|
||||||
|
local result = {}
|
||||||
|
for word in string.gmatch(str, "([^,]+)") do table.insert(result, word) end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
mono helper functions
|
||||||
|
]]
|
||||||
|
function dumpFields(class)
|
||||||
|
print("class fields:")
|
||||||
|
for i, v in pairs(mono_class_enumFields(class)) do
|
||||||
|
printf("\tname: %s - static: %s", v.name, tostring(v.isStatic))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getFieldFromName(class, name, shouldBeStatic)
|
||||||
|
local fields = mono_class_enumFields(class)
|
||||||
|
if not fields then return nil end
|
||||||
|
for i, v in pairs(fields) do
|
||||||
|
if v.isStatic == (shouldBeStatic == true) and v.name == name then
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print(("could not find field: %s"):format(name))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
mono class wrappers
|
||||||
|
]]
|
||||||
|
function MonoArray(addr)
|
||||||
|
if not addr or addr == 0 then error("attempted to instantiate MonoArray with invalid addr") end
|
||||||
|
|
||||||
|
local classId, className = mono_object_getClass(addr)
|
||||||
|
if not className then error("address did not point to valid object") end
|
||||||
|
if className:sub(-2) ~= "[]" then error("address did not point to array") end
|
||||||
|
|
||||||
|
local array = {
|
||||||
|
_address = addr,
|
||||||
|
getLength = function(self)
|
||||||
|
return readInteger(self._address + ARRAY_SIZE_OFF)
|
||||||
|
end,
|
||||||
|
getElement = function(self, idx)
|
||||||
|
if idx < 0 or idx >= self:getLength() then
|
||||||
|
error("invalid index")
|
||||||
|
end
|
||||||
|
return
|
||||||
|
readPointer(self._address + ARRAY_DATA_OFF + (idx * PTR_SIZE))
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(array, {
|
||||||
|
__tostring = function() return "Mono array instance" end,
|
||||||
|
__index = function(self, k)
|
||||||
|
local idx = tonumber(k)
|
||||||
|
if idx ~= nil then return self:getElement(idx) end
|
||||||
|
return rawget(self, k)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
return array
|
||||||
|
end
|
||||||
|
|
||||||
|
local stringClass = mono_findClass("System", "String")
|
||||||
|
local string_Length = getFieldFromName(stringClass, "_stringLength")
|
||||||
|
local string_FirstChar = getFieldFromName(stringClass, "_firstChar")
|
||||||
|
|
||||||
|
function MonoStringReader(addr)
|
||||||
|
if not addr or addr == 0 then return nil end
|
||||||
|
local classId, className = mono_object_getClass(addr)
|
||||||
|
if className ~= "String" then error("address did not point to string") end
|
||||||
|
|
||||||
|
local str = {
|
||||||
|
_address = addr,
|
||||||
|
getLength = function(self)
|
||||||
|
return readInteger(self._address + string_Length.offset)
|
||||||
|
end,
|
||||||
|
getValue = function(self)
|
||||||
|
return readString(self._address + string_FirstChar.offset,
|
||||||
|
self:getLength() * 2, true)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(str, {
|
||||||
|
__tostring = function(self) return self:getValue() end,
|
||||||
|
__index = str
|
||||||
|
})
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
function MonoFunctionHelper(fnName, fnId, isStatic)
|
||||||
|
local fn = {
|
||||||
|
_name = fnName,
|
||||||
|
_id = fnId,
|
||||||
|
_signatures = {},
|
||||||
|
_static = isStatic
|
||||||
|
}
|
||||||
|
|
||||||
|
if type(fnId) == "table" then
|
||||||
|
for i, v in ipairs(fnId) do
|
||||||
|
local sig = mono_method_getSignature(v)
|
||||||
|
table.insert(fn._signatures, {id = v, params = splitByComma(sig)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(fn, {
|
||||||
|
__call = function(self, ...)
|
||||||
|
local args = {...}
|
||||||
|
local methodArgs = args
|
||||||
|
local instancePtr = nil
|
||||||
|
|
||||||
|
if not self._static then
|
||||||
|
if #args == 0 then
|
||||||
|
error("Instance method " .. self._name ..
|
||||||
|
" requires instance pointer")
|
||||||
|
end
|
||||||
|
instancePtr = table.remove(methodArgs, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local activeId = nil
|
||||||
|
if type(self._id) == "table" then
|
||||||
|
for _, candidate in ipairs(self._signatures) do
|
||||||
|
if #candidate.params == #methodArgs then
|
||||||
|
activeId = candidate.id
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not activeId then
|
||||||
|
error("No overload for " .. self._name ..
|
||||||
|
" matches arg count " .. #methodArgs)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
activeId = self._id
|
||||||
|
end
|
||||||
|
|
||||||
|
local paramsInfo = mono_method_get_parameters(activeId)
|
||||||
|
local monoArgs = {}
|
||||||
|
for i = 1, #methodArgs do
|
||||||
|
local pType = paramsInfo.parameters[i].type
|
||||||
|
table.insert(monoArgs, {
|
||||||
|
type = monoTypeToVartypeLookup[pType] or vtPointer,
|
||||||
|
value = methodArgs[i]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return mono_invoke_method(mono_enumDomains()[1], activeId,
|
||||||
|
instancePtr, monoArgs)
|
||||||
|
end,
|
||||||
|
__tostring = function(self) return "MonoFunction: " .. self._name end
|
||||||
|
})
|
||||||
|
return fn
|
||||||
|
end
|
||||||
|
|
||||||
|
function MonoClass(namespace, name)
|
||||||
|
local classId = mono_findClass(namespace, name)
|
||||||
|
if not classId then error("Could not find class " .. name) end
|
||||||
|
|
||||||
|
local domain = mono_enumDomains()[1]
|
||||||
|
local staticAddr = mono_class_getStaticFieldAddress(domain, classId)
|
||||||
|
local methodsRaw = mono_class_enumMethods(classId)
|
||||||
|
|
||||||
|
local methodsSorted = {}
|
||||||
|
for _, v in pairs(methodsRaw) do
|
||||||
|
if methodsSorted[v.name] then
|
||||||
|
if type(methodsSorted[v.name]) ~= "table" then
|
||||||
|
methodsSorted[v.name] = {methodsSorted[v.name]}
|
||||||
|
end
|
||||||
|
table.insert(methodsSorted[v.name], v.method)
|
||||||
|
else
|
||||||
|
methodsSorted[v.name] = v.method
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local methodsWrapped = {}
|
||||||
|
for mName, mId in pairs(methodsSorted) do
|
||||||
|
local checkId = type(mId) == "table" and mId[1] or mId
|
||||||
|
local flags = mono_method_getFlags(checkId)
|
||||||
|
local isStatic = bAnd(flags, 0x0010) ~= 0
|
||||||
|
methodsWrapped[mName] = MonoFunctionHelper(mName, mId, isStatic)
|
||||||
|
end
|
||||||
|
|
||||||
|
local classObj = {
|
||||||
|
_id = classId,
|
||||||
|
_name = name,
|
||||||
|
_ns = namespace,
|
||||||
|
_fieldInfo = {},
|
||||||
|
_staticAddr = staticAddr,
|
||||||
|
_methods = methodsWrapped,
|
||||||
|
getStaticFieldOffset = function(self, name)
|
||||||
|
local field = getFieldFromName(self._id, name, true)
|
||||||
|
return field and field.offset or nil
|
||||||
|
end,
|
||||||
|
getStaticField = function(self, offset)
|
||||||
|
return readPointer(self._staticAddr + offset)
|
||||||
|
end,
|
||||||
|
getMethod = function(self, methodName)
|
||||||
|
local m = self._methods[methodName]
|
||||||
|
if not m then
|
||||||
|
error("Method " .. methodName .. " not found!")
|
||||||
|
end
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return setmetatable(classObj, {
|
||||||
|
__index = function(self, key)
|
||||||
|
return self._methods[key] or rawget(self, key)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local typeClass = MonoClass("System", "Type")
|
||||||
|
function Type(typeName)
|
||||||
|
local typeStr = mono_new_string(typeName)
|
||||||
|
local getType = typeClass:getMethod("GetType")
|
||||||
|
local typeRet = getType(typeStr)
|
||||||
|
|
||||||
|
print(type(typeRet), typeRet)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
game
|
||||||
|
]]
|
||||||
|
local GameDataClass = MonoClass("", "GameData")
|
||||||
|
local instanceOffset = GameDataClass:getStaticFieldOffset("Instance")
|
||||||
|
|
||||||
|
printf("GameData class id: %s", tostring(GameDataClass))
|
||||||
|
printf("GameData.Instance field offset: %x", instanceOffset or 0)
|
||||||
|
|
||||||
|
local gameDataInstance = GameDataClass:getStaticField(instanceOffset)
|
||||||
|
printf("GameData: %x", gameDataInstance or 0)
|
||||||
|
|
||||||
|
local alterFunds = GameDataClass:getMethod("CmdAlterFunds")
|
||||||
|
printf("GameData.CmdAlterFunds: %x", alterFunds._id or 0)
|
||||||
|
alterFunds(gameDataInstance, 13371337)
|
||||||
Reference in New Issue
Block a user