CHANNEL_GPS = 65534 CHANNEL_SNMP = 999 local function trilaterate(A, B, C) local a2b = B.pos - A.pos local a2c = C.pos - A.pos if math.abs(a2b:normalize():dot(a2c:normalize())) > 0.999 then return nil end local d = a2b:length() local ex = a2b:normalize() local i = ex:dot(a2c) local ey = (a2c - ex * i):normalize() local j = ey:dot(a2c) local ez = ex:cross(ey) local r1 = A.distance local r2 = B.distance local r3 = C.distance local x = (r1 * r1 - r2 * r2 + d * d) / (2 * d) local y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j) local result = A.pos + ex * x + ey * y local zSquared = r1 * r1 - x * x - y * y if zSquared > 0 then local z = math.sqrt(zSquared) local result1 = result + ez * z local result2 = result - ez * z return result1, result2 end return result end -- from Opus somewhere local function permutation(tbl, n) local function permgen(a, n) if n == 0 then coroutine.yield(a) else for i=1,n do a[n], a[i] = a[i], a[n] permgen(a, n - 1) a[n], a[i] = a[i], a[n] end end end local co = coroutine.create(function() permgen(tbl, n) end) return function() local _, res = coroutine.resume(co) return res end end local known_opus_devices = { ["6_4_milo_2"] = { -3182, 62, -5125, dimension = "overworld" }, ["NationalCenterForMissingTurtles #1"] = { -3174, 73, -5124, dimension = "overworld" }, ["computer_19171"] = { -3178, 64, -5130, dimension = "overworld" }, ["RangerStore"] = { 217, 72, 123, dimension = "overworld" }, ["AlexMilo"] = { -1785, 50, -2759, dimension = "overworld" }, ["Solar-newmilo"] = { -3073, 78, -3008, dimension = "overworld" }, ["ScorchMilo"] = { 269, 58, 421, dimension = "overworld" }, ["scorchsfurninator"] = { 269, 54, 421, dimension = "overworld" }, ["OMGFurni"] = { 4874, 76, -1701, dimension = "overworld" }, ["computer_21867"] = { -7118, 52, -7354, dimension = "overworld" }, ["LeClercMilo"] = { -7116, 50, -7357, dimension = "overworld" }, ["manager_of_ground_trap"] = { 291, 31, -11, dimension = "overworld" }, ["CodedPythonMilo"] = { -2597, 65, 4998, dimension = "overworld" }, ["DistantMilo2"] = { -417, 80, -3049, dimension = "overworld" }, ["NationalCenterForMissingTurtles #2"] = { 6, 35, -41, dimension = "nether" }, ["CobbleGen69"] = { 3996, 52, 2900, dimension = "end" }, ["GTech Storage"] = { 3955, 35, -2914, dimension = "end" }, ["NationalCenterForMissingTurtles #3"] = { 4858, 75, 1975, dimension = "end" }, ["BoomStorage"] = { -6016, 71, 1248, dimension = "overworld" }, ["TS-shack"] = { 69, 69, -69, dimension = "nether" } } local state = { debug = false, fixes = {}, modem_side = nil, channel_gps_was_closed = nil, channel_snmp_was_closed = nil, listener_running = false, use_saved_fixes = false, mse_threshold = 0.01, passive = false, max_fixes = 4, max_fix_age = nil, actual_position = nil -- for testing of Opus device positions } function initialize(modem_side) -- Find a modem if modem_side == nil then for _, side in ipairs(rs.getSides()) do if peripheral.getType(side) == "modem" and peripheral.call(side, "isWireless") then modem_side = side break end end end if modem_side == nil then if state.debug then print("No wireless modem attached") end return nil end if state.debug then print("Using", modem_side, "modem") end state.modem_side = modem_side local modem = peripheral.wrap(modem_side) state.channel_gps_was_closed = false if not modem.isOpen(CHANNEL_GPS) then modem.open(CHANNEL_GPS) state.channel_was_closed = true if state.debug then print "Opened GPS" end end state.channel_snmp_was_closed = false if not modem.isOpen(CHANNEL_SNMP) then modem.open(CHANNEL_SNMP) state.channel_snmp_was_closed = true if state.debug then print "Opened SNMP" end end end function teardown() if state.modem_side and state.channel_gps_was_closed then peripheral.call(state.modem_side, "close", CHANNEL_GPS) end if state.modem_side and state.channel_snmp_was_closed then peripheral.call(state.modem_side, "close", CHANNEL_SNMP) end state.modem_side = nil end function listener() state.listener_running = true while true do local e, side, channel, reply_channel, message, distance = os.pullEvent "modem_message" if e == "modem_message" then if side == state.modem_side and distance then local fix if channel == CHANNEL_GPS and reply_channel == CHANNEL_GPS and type(message) == "table" and #message == 3 and tonumber(message[1]) and tonumber(message[2]) and tonumber(message[3]) and message[1] == message[1] and message[2] == message[2] and message[3] == message[3] then local vec = vector.new(message[1], message[2], message[3]) fix = { pos = vec, dim = message.dimension, src = "gps:" .. tostring(vec) } elseif channel == CHANNEL_SNMP and type(message) == "table" and type(message.status) == "string" and type(message.label) == "string" and #message.status <= 100 and #message.label <= 32 then local data = known_opus_devices[message.label] if state.debug and data then print(("Got Opus message %d %s %s"):format(reply_channel, message.label, message.status)) end if data then fix = { pos = vector.new(unpack(data)), dim = data.dimension, src = "opus:" .. message.label } if state.actual_position then local disrepancy = (fix.pos - state.actual_position):length() - distance if disrepancy > 0.1 then print("Disrepancy of ", disrepancy, "on", message.label) end end end end if fix then local time = os.clock() fix.time = time fix.distance = distance for i, old_fix in pairs(state.fixes) do if tostring(old_fix.pos) == tostring(fix.pos) then table.remove(state.fixes, i) if state.debug then print("Duplicate fix, dropping old") end end if state.max_fix_age then if time > state.max_fix_age + fix.time then table.remove(state.fixes, i) if state.debug then print("Fix over max age") end end end end if state.debug then print(fix.distance .. " metres from " .. tostring(fix.pos)) if fix.dim then print("Dimension", fix.dim) end end table.insert(state.fixes, fix) if #state.fixes > state.max_fixes then table.remove(state.fixes, 1) end os.queueEvent "fix_acquired" end end end end end function configure(args) for k, v in pairs(args) do state[k] = v end end function locate(timeout, _debug) state.debug = _debug -- Let command computers use their magic fourth-wall-breaking special abilities if commands then return commands.getBlockPosition() end if not state.modem_side then initialize() end if state.debug then print("Finding position...") end if not state.passive then peripheral.call(state.modem_side, "transmit", CHANNEL_GPS, CHANNEL_GPS, "PING") end local spawn_listener = not state.listener_running local pos local dimension if state.use_saved_fixes == false then state.fixes = {} end local fns = { function() sleep(timeout or 1) end, function() while true do os.pullEvent "fix_acquired" for _, fix in pairs(state.fixes) do if fix.distance == 0 then if state.debug then print("Distance 0 to", fix.pos) end pos = fix.pos return end end if state.debug then print("Fixes at", #state.fixes) end local candidate_positions = {} local dimvotes = {} for _, fix in pairs(state.fixes) do if fix.dim then dimvotes[fix.dim] = (dimvotes[fix.dim] or 0) + 1 end end local best for dim, votes in pairs(dimvotes) do if best == nil or votes > best then dimension = dim end end if #state.fixes >= 3 then for fixes in permutation(state.fixes, 3) do local pos1, pos2 = trilaterate(fixes[1], fixes[2], fixes[3]) if pos1 and pos1.x == pos1.x and pos1.y == pos1.y and pos1.z == pos1.z then candidate_positions[tostring(pos1)] = pos1 end if pos2 and pos2.x == pos2.x and pos2.y == pos2.y and pos2.z == pos2.z then candidate_positions[tostring(pos2)] = pos2 end end local best_error, best_candidate for key, candidate in pairs(candidate_positions) do local total_square_error = 0 for _, fix in pairs(state.fixes) do total_square_error = total_square_error + ((candidate - fix.pos):length() - fix.distance)^2 end local mean_square_error = total_square_error / #state.fixes if best_error == nil or mean_square_error < best_error then best_candidate = candidate best_error = mean_square_error end end if best_error < state.mse_threshold and (#state.fixes > 3 or #candidate_positions == 1) then if state.debug then print("Best candidate position is", best_candidate, "with error", best_error) end pos = best_candidate return else if state.debug then print("Position fix above error threshold:", best_candidate, best_error) end end end end end } if spawn_listener then table.insert(fns, listener) end parallel.waitForAny(unpack(fns)) if spawn_listener then state.listener_running = false end teardown() if pos then if state.debug then print("Position is " .. pos.x .. "," .. pos.y .. "," .. pos.z) end if state.debug and dimension then print("Dimension is", dimension) end return pos.x, pos.y, pos.z, dimension else if debug then print("Could not determine position") end return nil end end return { CHANNEL_GPS = CHANNEL_GPS, locate = locate, configure = configure, listener = listener }