Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local args = { ... }
- local argument = args[1]
- local TurtleRegistry = {}
- TurtleRegistry.__index = TurtleRegistry
- function TurtleRegistry.new()
- local self = setmetatable({}, TurtleRegistry)
- self.register = {}
- local registry = settings.get("registry") or {}
- for id, entry in pairs(registry) do
- self.register[id] = {
- uuid = entry.uuid,
- channel = entry.channel,
- }
- end
- return self
- end
- -- https://gist.github.com/jrus/3197011
- function TurtleRegistry:GenerateRegisterUUID()
- local template = "STIDxxxx-xxxx-xxxx"
- return string.gsub(template, "[x]", function(c)
- return string.format("%x", math.random(0, 0xf))
- end)
- end
- function TurtleRegistry:Register(id)
- if self.register[id] then
- return false
- end
- local uuid = self:GenerateRegisterUUID()
- self.register[id] = {
- uuid = uuid,
- channel = (os.getComputerID() * id) % 65536,
- }
- settings.set("registry", self.register)
- settings.save()
- return uuid
- end
- function TurtleRegistry:GetEntryByUUID(uuid)
- for id, entry in pairs(self.register) do
- if entry.uuid == uuid then
- return entry, id
- end
- end
- end
- function TurtleRegistry:GetEntryByID(id)
- return self.register[id]
- end
- function TurtleRegistry:GetEntryByChannel(channel)
- for id, entry in pairs(self.register) do
- if entry.channel == channel then
- return entry, id
- end
- end
- end
- function TurtleRegistry:Delete(id)
- self.register[id] = nil
- settings.set("registry", self.register)
- settings.save()
- end
- function TurtleRegistry:Export(id)
- if not self.register[id] then
- return ""
- end
- local list = {}
- for i, v in pairs(self.register[id]) do
- if type(v) == "string" then
- table.insert(list, i .. "='" .. v .. "'")
- else
- table.insert(list, i .. "=" .. v)
- end
- end
- return "{" .. table.concat(list, ",") .. ",cid=" .. os.getComputerID() .. "}"
- end
- local Network = {}
- Network.__index = Network
- function Network.new(registry, modem)
- local self = setmetatable({}, Network)
- if registry == nil then
- error("Unable to create network, no turtle registry provided", 2)
- end
- if modem == nil then
- error("Unable to create network, no modem provided", 2)
- end
- self.registry = registry
- self.DEFAULT_CHANNEL = 42424
- self.expectedPackets = {}
- self.outgoingPackets = {}
- self.connections = {}
- self.responseTime = 3
- self.modems = { modem }
- return self
- end
- function Network:Listen()
- while true do
- local event, id, channel, reply, message = os.pullEvent()
- if argument == "debug" then
- print("l", event, id, channel, message)
- end
- if event == "network_disconnect" then
- self.expectedPackets = {}
- self.outgoingPackets = {}
- break
- end
- if event == "timer" or event == "modem_message" then
- local closed = {}
- for i = 1, #self.expectedPackets do
- local packet = self.expectedPackets[i]
- if event == "timer" and id == packet.timeout then
- table.remove(self.expectedPackets, i)
- i = i - 1
- end
- if event == "modem_message" and channel == packet.channel and message == packet.message then
- table.remove(self.expectedPackets, i)
- table.insert(closed, channel)
- os.queueEvent(packet.uuid)
- os.cancelTimer(packet.timeout)
- i = i - 1
- end
- end
- for i = 1, #self.connections do
- local connection = self.connections[i]
- if event == "timer" and connection.timeoutID == id then
- table.remove(self.connections, i)
- os.queueEvent("connection_end", connection.targetUUID)
- self:CancelRepeatedPacket(connection.signalUUID)
- i = i - 1
- connection.status = 0
- if connection.timeoutListener then
- connection.timeoutListener(connection)
- end
- table.insert(closed, channel)
- elseif event == "modem_message" and channel == connection.channel then
- os.cancelTimer(connection.timeoutID)
- connection.timeoutID = os.startTimer(self.responseTime)
- if connection.status == 0 and message == connection.message then
- connection.status = 1
- elseif connection.status == 1 and message == connection.message then
- connection.status = 2
- connection.timeout = self.responseTime
- os.queueEvent("network_connect", connection.UUID)
- if connection.connectedListener then
- connection.connectedListener(connection)
- end
- elseif connection.status == 2 and message ~= connection.message then
- os.queueEvent("connection_update", connection.targetUUID, message)
- end
- end
- end
- for i = 1, #closed do
- local required = false
- for _, packet in pairs(self.expectedPackets) do
- local channel = closed[i]
- if packet.channel == channel then
- required = true
- break
- end
- end
- if not required then
- self:CloseChannel(channel)
- end
- end
- if event == "timer" then
- for i, packet in pairs(self.outgoingPackets) do
- if packet.timerID == id then
- if self:Transmit(packet.channel, packet.message, packet.wireless) then
- local timer = os.startTimer(packet.period)
- packet.timerID = timer
- if argument == "debug" then
- print("u", "send", packet.channel, packet.message, packet.wireless)
- end
- else
- table.remove(self.outgoingPackets, i)
- end
- break
- end
- end
- end
- end
- end
- end
- -- https://gist.github.com/jrus/3197011
- function Network:GenerateEventUUID()
- local template = "STNPxxxx-xxxx-xxxx"
- return string.gsub(template, "[x]", function(c)
- return string.format("%x", math.random(0, 0xf))
- end)
- end
- function Network:Stop()
- os.queueEvent("network_disconnect")
- end
- function Network:WaitForAll(timeout)
- local timerID = os.startTimer(timeout or 60)
- while #self.expectedPackets > 0 do
- local event, id = os.pullEvent()
- if event == "timer" and id == timerID then
- break
- end
- end
- return #self.expectedPackets == 0
- end
- function Network:WaitForBatch(batch, timeout)
- local events = {}
- local result = {}
- for i, packet in pairs(batch) do
- table.insert(
- events,
- Network:Expect(packet.channel, packet.message, timeout, function(success)
- result[i] = success
- return packet.callback(success)
- end)
- )
- end
- parallel.waitForAll(unpack(events))
- return result
- end
- function Network:Wait(channel, message, timeout, callback)
- return self:Expect(channel, message, timeout, callback)()
- end
- function Network:Expect(channel, message, timeout, callback)
- local packetUUID = self:GenerateEventUUID()
- local timerID = timeout == false and -1 or os.startTimer(timeout or 30)
- self:OpenChannel(channel)
- table.insert(self.expectedPackets, {
- channel = channel,
- message = message,
- timeout = timerID,
- uuid = packetUUID,
- })
- return function()
- while true do
- local event, id = os.pullEvent()
- if event == "timer" and id == timerID then
- if callback then
- return callback(false)
- end
- return false
- end
- if event == packetUUID then
- if callback then
- return callback(false)
- end
- return true
- end
- end
- end
- end
- function Network:HasModem(otherModem)
- for i, modem in pairs(self.modems) do
- for channel = 1, 65535 do
- if not (modem.isOpen(channel) or otherModem.isOpen(channel)) then
- modem.open(channel)
- local newOpen = otherModem.isOpen(channel)
- modem.close(channel)
- return newOpen
- end
- end
- end
- return false
- end
- function Network:AddModem(newModem)
- if self:HasModem(newModem) then
- return false
- end
- table.insert(self.modems, newModem)
- return true
- end
- function Network:OpenChannel(channel, wireless)
- if wireless == nil then
- wireless = true
- end
- for _, modem in pairs(self.modems) do
- if
- ((modem.isWireless() and wireless) or (not modem.isWireless() and not wireless)) and modem.isOpen(channel)
- then
- return true
- end
- end
- for _, modem in pairs(self.modems) do
- if
- ((modem.isWireless() and wireless) or (not modem.isWireless() and not wireless))
- and pcall(modem.open, channel)
- then
- return true
- end
- end
- error("Too many channels open, no modems available", 2)
- end
- function Network:CloseChannel(channel)
- for _, modem in pairs(self.modems) do
- if modem.isOpen(channel) then
- modem.close(channel)
- end
- end
- end
- function Network:Transmit(channel, message, wireless)
- if #self.modems == 0 then
- error("No modem available to transmit message", 2)
- end
- if wireless == nil then
- wireless = true
- end
- for _, modem in pairs(self.modems) do
- local isWireless = select(2, pcall(modem.isWireless))
- if (isWireless and wireless) or not (isWireless or wireless) then
- modem.transmit(channel, channel, message)
- return true
- end
- end
- return false
- end
- function Network:SendRepeatedPacket(channel, message, period, wireless)
- local uuid = self:GenerateEventUUID()
- local timerID = os.startTimer(period)
- if self:Transmit(channel, message, wireless) then
- table.insert(self.outgoingPackets, {
- uuid = uuid,
- timerID = timerID,
- channel = channel,
- message = message,
- period = period,
- wireless = wireless,
- })
- else
- os.cancelTimer(timerID)
- return
- end
- return uuid
- end
- function Network:CancelRepeatedPacket(uuid)
- for i, packet in pairs(self.outgoingPackets) do
- if packet.uuid == uuid then
- os.cancelTimer(packet.timerID)
- table.remove(self.outgoingPackets, i)
- return true
- end
- end
- return false
- end
- function Network:IsConnected(turtleUUID)
- for _, con in pairs(self.connections) do
- if con.targetUUID == turtleUUID and con.status == 2 then
- return true, con.UUID
- end
- end
- return false, nil
- end
- function Network:ConnectToTurtle(turtleUUID, timeout, timeoutListener, connectedListener)
- local connected, uid = self:IsConnected(turtleUUID)
- if connected then
- return uid
- end
- local entry, id = self.registry:GetEntryByUUID(turtleUUID)
- if entry == nil then
- error("Turtle UUID not registered to this system", 2)
- end
- local message = self:Obfuscate(entry.uuid .. "-CONNECT", id)
- local timerID = os.startTimer(timeout or 300)
- local UUID = self:GenerateEventUUID()
- self:OpenChannel(entry.channel, true)
- table.insert(self.connections, {
- status = 0,
- timeoutID = timerID,
- timeout = timeout or 300,
- channel = entry.channel,
- message = message,
- signalUUID = self:SendRepeatedPacket(entry.channel, message, 1, true),
- targetUUID = entry.uuid,
- timeoutListener = timeoutListener,
- connectedListener = connectedListener,
- UUID = UUID,
- })
- return function()
- while true do
- local event, id = os.pullEvent()
- if event == "timer" and id == timerID then
- return nil
- end
- if event == "network_connect" and id == UUID then
- return UUID
- end
- end
- end
- end
- function Network:GetListenerHandle()
- return function()
- self:Listen()
- end
- end
- function Network:Obfuscate(str, id, reverse)
- local lid = os.getComputerID()
- math.randomseed(lid >= id and (lid * lid + lid + id) or (lid + id * id))
- if #str % 2 == 1 then
- str = str .. ";"
- end
- local keys = {}
- local mul = reverse and 2 or 1
- local len = #str / (2 * mul)
- for i = 1, 16 do
- local key = {}
- for i = 1, len do
- table.insert(key, math.random(0, 255))
- end
- keys[i] = key
- end
- local function xor8(x, y)
- return (math.floor(x / 0x80) == math.floor(y / 0x80) and 0 or 0x80)
- + (math.floor(x / 0x40) % 2 == math.floor(y / 0x40) % 2 and 0x00 or 0x40)
- + (math.floor(x / 0x20) % 2 == math.floor(y / 0x20) % 2 and 0x00 or 0x20)
- + (math.floor(x / 0x10) % 2 == math.floor(y / 0x10) % 2 and 0x00 or 0x10)
- + (math.floor(x / 0x08) % 2 == math.floor(y / 0x08) % 2 and 0x00 or 0x08)
- + (math.floor(x / 0x04) % 2 == math.floor(y / 0x04) % 2 and 0x00 or 0x04)
- + (math.floor(x / 0x02) % 2 == math.floor(y / 0x02) % 2 and 0x00 or 0x02)
- + (x % 2 == y % 2 and 0x00 or 0x01)
- end
- local function xor(x, y)
- local o = {}
- for i = 1, #x do
- table.insert(o, xor8(x[i], y[i]))
- end
- return o
- end
- local L, R = {}, {}
- for i = 1, #str, mul do
- local chunk = reverse and tonumber(str:sub(i, i + 1), 16) or str:byte(i)
- if i <= #str / 2 then
- table.insert(L, chunk)
- else
- table.insert(R, chunk)
- end
- end
- if not reverse then
- for i = 1, #keys do
- L, R = R, xor(L, xor(keys[i], R))
- end
- else
- for i = #keys, 1, -1 do
- L, R = R, xor(L, xor(keys[i], R))
- end
- end
- local out = ""
- for i = 1, len do
- if reverse then
- out = out .. string.char(R[i])
- else
- out = out .. ("%02x"):format(R[i])
- end
- end
- for i = 1, len do
- if reverse then
- if not (L[i] == 59 and i == len) then
- out = out .. string.char(L[i])
- end
- else
- out = out .. ("%02x"):format(L[i])
- end
- end
- return out
- end
- function Network:ReadData(s)
- --[[
- data format
- entries split by :
- status=1:key=value:ins=1,3,2#scan|
- until the ins key, where we
- split by #
- ]]
- local info = {}
- for chunk in s:gmatch("[^:]+") do
- local bits = {}
- for bit in chunk:gmatch("[^=]+") do
- table.insert(bits, bit)
- end
- if #bits ~= 2 or not bits[1]:match("%a") then
- return
- end
- local key = bits[1]
- if key == "ins" then
- local ins = {}
- for i in bits[2]:gmatch("[^/]+") do
- local dat = {}
- for k in i:gmatch("[^#]+") do
- table.insert(dat, k)
- end
- if #dat ~= 2 or not dat[2]:match("%a") then
- return
- end
- local coord = {}
- for dir in dat[1]:gmatch("[^,]+") do
- local n = tonumber(dir)
- if n == nil then
- return
- end
- table.insert(coord, n)
- end
- if #coord ~= 3 then
- return
- end
- table.insert(ins, {
- pos = coord,
- action = dat[2],
- })
- end
- info.ins = ins
- else
- local anum = tonumber(bits[2])
- info[key] = anum ~= nil and anum or bits[2]
- end
- end
- return info
- end
- function Network:SendCommand(turtleUUID, data)
- if not self:IsConnected(turtleUUID) then
- return false
- end
- local chunks = {}
- for i, v in pairs(data) do
- if type(v) ~= "table" then
- table.insert(chunks, tostring(i) .. "=" .. tostring(v))
- else
- local ins = {}
- for k, n in pairs(v) do
- if n.direction == nil then
- table.insert(ins, table.concat(n.position, ",") .. ",~#" .. n.action)
- else
- local dstr = ""
- if n.direction[1] == 0 then
- if n.direction[3] == -1 then
- dstr = "N"
- else
- dstr = "S"
- end
- else
- if n.direction[1] == -1 then
- dstr = "W"
- else
- dstr = "E"
- end
- end
- table.insert(ins, table.concat(n.position, ",") .. "," .. dstr .. "#" .. n.action)
- end
- end
- table.insert(chunks, "ins=" .. table.concat(ins, "/"))
- end
- end
- local entry, id = self.registry:GetEntryByUUID(turtleUUID)
- local msg = self:Obfuscate(table.concat(chunks, ":"), id)
- self:Transmit(entry.channel, msg, true)
- end
- function Network:ConnectionExpect(turtleUUID, callback)
- if not self:IsConnected(turtleUUID) then
- return
- end
- local entry, id = self.registry:GetEntryByUUID(turtleUUID)
- return function()
- while true do
- local event, uuid, message = os.pullEvent()
- if event == "connection_end" and uuid == turtleUUID then
- callback(false)
- return false
- elseif event == "connection_update" and uuid == turtleUUID then
- local data = self:Obfuscate(message, id, true)
- local read = self:ReadData(data)
- if read ~= nil then
- callback(true, read)
- return true, read
- end
- end
- end
- end
- end
- function Network:ConnectionWait(turtleUUID, callback)
- return self:ConnectionExpect(turtleUUID, callback)()
- end
- function Network:EndConnection(turtleUUID)
- for i, v in pairs(self.connections) do
- if v.targetUUID == turtleUUID then
- table.remove(self.connections, i)
- return true
- end
- end
- return false
- end
- local modem = peripheral.find("modem", function(_, o)
- return o.isWireless()
- end)
- local registry = TurtleRegistry.new()
- local network = Network.new(registry, modem)
- local function registrationSetup(process)
- local lan = peripheral.find("modem", function(_, o)
- return not o.isWireless()
- end)
- if not lan then
- error("Please connect the computer to the registration network", 2)
- end
- network:AddModem(lan)
- local drive = peripheral.find("drive")
- if not drive then
- error("Make sure a drive is connected to the network, did you right click it?", 2)
- end
- if not drive.isDiskPresent() then
- error("Please place a floppy disk into the drive", 2)
- end
- network:OpenChannel(network.DEFAULT_CHANNEL, false)
- print("Searching for a turtle...")
- while true do
- local event, name = os.pullEvent()
- if event == "peripheral" then
- local t = peripheral.getType(name)
- if lan.isPresentRemote(name) then
- if t == "turtle" then
- process(lan, drive, name)
- end
- end
- elseif event == "peripheral_detach" then
- local t = peripheral.getType(name)
- if t == "drive" then
- drive = peripheral.find("drive")
- if not drive then
- error("Connection lost to the registration drive", 2)
- end
- elseif t == "modem" then
- lan = peripheral.find("modem", function(_, o)
- return not o.isWireless()
- end)
- if not lan then
- error("Lost connection to the registration network", 2)
- end
- end
- end
- end
- end
- local function registrationProcess()
- registrationSetup(function(lan, drive, name)
- local id = lan.callRemote(name, "getID")
- if not registry:GetEntryByID(id) then
- print("Unregistered turtle found, attempting connection...")
- local conUUID = network:SendRepeatedPacket(network.DEFAULT_CHANNEL, "ST-REG", 1, false)
- local success = network:Wait(network.DEFAULT_CHANNEL, "ST-REG-CONFIRM", 60)
- network:CancelRepeatedPacket(conUUID)
- if not drive.isDiskPresent() then
- error("Floppy disk not present, unable to continue registration", 2)
- end
- if success then
- print("Connection successful, transferring data...")
- local turtleUUID = registry:Register(id)
- local entry = registry:GetEntryByUUID(turtleUUID)
- local disk = fs.open("disk/reg", "w")
- disk.write(registry:Export(id))
- disk.close()
- network:OpenChannel(entry.channel, false)
- local regConfirmUUID = network:SendRepeatedPacket(network.DEFAULT_CHANNEL, "ST-REG-FINISH", 1, false)
- local success = network:Wait(entry.channel, "ST-REG-SUCCESS", 20)
- network:CancelRepeatedPacket(regConfirmUUID)
- if success then
- print(("Turtle %s successfully registered"):format(turtleUUID))
- else
- registry:Delete(id)
- print("Unable to confirm successful registration, please try again")
- end
- else
- print("Failed to connect to the turtle, remove the turtle and try again")
- end
- else
- print("Turtle already registered with this network")
- end
- print("Searching for a turtle...")
- end)
- end
- local function deregistrationProcess()
- registrationSetup(function(lan, drive, name)
- local id = lan.callRemote(name, "getID")
- local entry = registry:GetEntryByID(id)
- if entry then
- print("Registered turtle found, attempting connection...")
- network:OpenChannel(entry.channel, false)
- local conUUID = network:SendRepeatedPacket(entry.channel, "ST-DEREG", 1, false)
- local success = network:Wait(entry.channel, "ST-DEREG-CONFIRM", 60)
- network:CancelRepeatedPacket(conUUID)
- if success then
- network:Transmit(entry.channel, "ST-DEREG-SUCCESS", false)
- print("Deregistration successful")
- registry:Delete(id)
- else
- print("Failed to connect to the turtle, remove the turtle and try again")
- end
- else
- print("Turtle is not registered with this network")
- end
- print("Searching for a turtle...")
- end)
- end
- parallel.waitForAll(network:GetListenerHandle(), function()
- if argument == "register" then
- registrationProcess()
- return
- elseif argument == "deregister" then
- deregistrationProcess()
- return
- end
- if
- not (tonumber(args[1]) and tonumber(args[2]) and tonumber(args[3]) and tonumber(args[4]) and tonumber(args[5]))
- then
- error("Insufficient arguments, ensure the chunk size x and z, as well as the computer position is provided", 2)
- end
- local cpos = { tonumber(args[3]), tonumber(args[4]), tonumber(args[5]) }
- local size = { tonumber(args[1]), tonumber(args[2]) }
- local connected = {}
- local events = {}
- local total = 0
- print("Attempting connection to turtles...")
- for i, v in pairs(registry.register) do
- total = total + 1
- table.insert(
- events,
- network:ConnectToTurtle(v.uuid, 60, nil, function()
- table.insert(connected, v.uuid)
- end)
- )
- end
- parallel.waitForAll(unpack(events))
- print("Connected " .. #connected .. " / " .. total)
- network.responseTime = 10
- print("Locating turtles...")
- local ready = 0
- local done = {}
- local locations = {}
- for i, v in pairs(network.connections) do
- network:SendCommand(v.targetUUID, { locate = true, cx = cpos[1], cy = cpos[2], cz = cpos[3] })
- table.insert(
- done,
- network:ConnectionExpect(v.targetUUID, function(success, data)
- if success then
- ready = ready + 1
- for i, v in pairs(data) do
- print(i, v)
- end
- locations[v.targetUUID] = {
- position = { x = data.x, y = data.y, z = data.z },
- direction = { x = data.dx, y = data.dy, z = data.dz },
- }
- else
- network:EndConnection(v.targetUUID)
- end
- end)
- )
- end
- parallel.waitForAll(unpack(done))
- print("Starting process with " .. ready .. " / " .. total .. " turtles")
- for i, v in pairs(locations) do
- print(i, v.position.x, v.position.y, v.position.z, v.direction.x, v.direction.y, v.direction.z)
- end
- if ready == 0 then
- error("No turtles to do job, try again", 2)
- end
- local pos = { { -73, 96, -25 }, { -56, 96, -25 } }
- for i, v in pairs(network.connections) do
- network:SendCommand(
- v.targetUUID,
- { ins = { {
- action = "move",
- position = pos[i],
- } }, status = "start" }
- )
- end
- -- local success = network:ConnectToTurtle(registry:GetEntryByID(13).uuid, 60, function()
- -- print("timed out sadge")
- -- end, function()
- -- print("connect wooo")
- -- end)()
- -- if success then
- -- print("sending")
- -- network:SendCommand(registry:GetEntryByID(13).uuid, { fuel = true })
- -- print("waiting")
- -- network:ConnectionWait(registry:GetEntryByID(13).uuid, function(success, data)
- -- if success then
- -- print("returned data")
- -- print(data.fuel)
- -- end
- -- end)
- -- end
- -- if settings.get("")
- end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement