--Originally by MysticT
--TODO: Implement HTTP Publishing total amount of GPS Requests served
local tLocations = {}
local tKnownLocations = {}
local bDebug = false
--Download config API
local function pastebinGet(code,path)
local sCode = code
local sFile = path
local sPath = shell.resolve( sFile )
if fs.exists( sPath ) then
fs.delete(sPath)
end
local response = http.get(
"http://pastebin.com/raw.php?i="..textutils.urlEncode( sCode )
)
if response then
local sResponse = response.readAll()
response.close()
local file = fs.open( sPath, "w" )
file.write( sResponse )
file.close()
return true
else
return false
end
end
if not http then
print( "GPS requires the HTTP API" )
print( "Set enableAPI_http to 1 in mod_ComputerCraft.cfg" )
return false
end
if not fs.exists("config") then
if pastebinGet("fkcMT9CE","config") then
else
print("Something went wrong! No response!")
return false
end
end
-- Helper functions
local function clear()
term.clear()
term.setCursorPos(1, 1)
end
local function table_size(t)
local i = 0
for _,_ in pairs(t) do
i = i + 1
end
return i
end
local function connect()
for _,s in ipairs(rs.getSides()) do
if peripheral.isPresent(s) and peripheral.getType(s) == "modem" then
rednet.open(s)
print( "No modem active. Opening "..sFreeSide.." modem" )
return true
end
end
print("No modem attached")
return false
end
local function _print(...)
if bDebug then
print(...)
end
end
local function vector_equal(v1, v2)
return v1.x == v2.x and v1.y == v2.y and v1.z == v2.z
end
-- Load/Save functions
local function save()
_print("Saving locations...")
local file = fs.open("locations.dat", "w")
if file then
for id, loc in pairs(tKnownLocations) do
file.writeLine(tostring(id)..": "..tostring(loc.x)..", "..tostring(loc.y)..", "..tostring(loc.z))
end
file.close()
_print("Locations saved")
return true
end
_print("Error saving locations")
return false
end
local function load()
_print("Loading locations")
local file = fs.open("locations.dat", "r")
if file then
local i = 0
local sLine = file.readLine()
while sLine do
local sId, sX, sY, sZ = string.match(sLine, "(%d+): (%-?%d+), (%-?%d+), (%-?%d+)")
local id, x, y, z = tonumber(sId), tonumber(sX), tonumber(sY), tonumber(sZ)
if id and x and y and z then
tKnownLocations[id] = vector.new(x, y, z)
i = i + 1
end
sLine = file.readLine()
end
file.close()
_print(n, " locations loaded")
return true, i
end
_print("Error loading locations")
return false
end
-- Location functions
local function trilaterate(A, B, C)
local a2b = B.position - A.position
local a2c = C.position - A.position
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.position + (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)
local rounded1, rounded2 = result1:round(), result2:round()
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
return result1, result2
else
return rounded1
end
end
return result:round()
end
local function narrow(p1, p2, fix)
local dist1 = math.abs((p1 - fix.position):length() - fix.distance)
local dist2 = math.abs((p2 - fix.position):length() - fix.distance)
if math.abs(dist1 - dist2) < 0.05 then
return p1, p2
elseif dist1 < dist2 then
return p1:round()
else
return p2:round()
end
end
function locate(id)
local tFixes = {}
local p1, p2
for _,t in pairs(tLocations[id]) do
table.insert(tFixes, { ["position"] = vector.new(t.x, t.y, t.z), ["distance"] = t.d })
end
local i = 3
while (p1 == nil or p2 ~= nil) and i <= #tFixes do
if not p1 then
p1, p2 = trilaterate(tFixes[1], tFixes[2], tFixes[i])
else
p1, p2 = narrow(p1, p2, tFixes[i])
end
i = i + 1
end
if p1 and p2 then
_print("Ambiguous position")
_print("Could be "..p1.x..","..p1.y..","..p1.z.." or "..p2.x..","..p2.y..","..p2.z)
elseif p1 then
if not tKnownLocations[id] or not vector_equal(tKnownLocations[id], p1) then
print("Location added:")
print(id, ": ", p1)
tKnownLocations[id] = p1
save()
end
else
_print("Couldn't determine location for ", id)
end
end
local function addLocation(id, t)
if not tLocations[t.id] then
tLocations[t.id] = {}
end
local tLoc = {}
tLoc.x = t.x
tLoc.y = t.y
tLoc.z = t.z
tLoc.d = t.d
tLocations[t.id][id] = tLoc
if table_size(tLocations[t.id]) >= 3 then
_print("Trying to locate #", t.id)
locate(t.id)
end
end
-- Event handlers
local function handle_message(id, msg)
local st = string.match(msg, "<LOCATION> (.+)")
if st then
local t = textutils.unserialize(st)
if t and type(t) == "table" then
_print("Location received from ", id, ":")
_print(t.id, ": ", "(", t.x, ", ", t.y, ", ", t.z, ", ", t.d, ")")
addLocation(id, t)
end
end
end
local function handle_char(c)
c = string.lower(c)
if c == "l" then
print("Locations:")
for id, loc in pairs(tKnownLocations) do
print(id, ": ", loc)
end
elseif c == "c" then
clear()
end
end
-- Start program
local function host()
os.loadAPI("config")
config.load(nil,"config") --forgot to define multiple arguments
local nMasterID = tonumber(config.readVal("mId"))
local tLoc = {}
tLoc.x = tonumber(config.readVal("x"))
tLoc.y = tonumber(config.readVal("y"))
tLoc.z = tonumber(config.readVal("z"))
config.save()
os.unloadAPI("config")
if not nMasterID or not tLoc.x or not tLoc.y or not tLoc.z then
printUsage()
return
end
clear()
if not connect() then
return
end
print("==================================================")
print("Powered by G&K Industries. Contact representative, gknova61 if any issues.")
print("==================================================")
print( "Position is "..tLoc.x..","..tLoc.y..","..tLoc.z )
print( "Serving GPS requests" )
local nServed = 0
--print("Waiting for messages...")
while true do
local id, msg, dist = rednet.receive()
--print("Message received from ", id, " at ", dist, " meters.")
if msg == "PING" then
rednet.send(id,textutils.serialize({tLoc.x,tLoc.y,tLoc.z}))
nServed = nServed + 1
if nServed > 1 then
local x,y = term.getCursorPos()
term.setCursorPos(1,y-1)
end
print(nServed.." GPS Requests served")
end
tLoc.id = id
tLoc.d = dist
rednet.send(nMasterID, "<LOCATION> "..textutils.serialize(tLoc))
end
end
local function printUsage()
local sName = fs.getName(shell.getRunningProgram())
print("Usage:")
print(sName, " receive [debug]")
print(sName, " host id x y z")
end
local tArgs = {...}
local sAction = tArgs[1]
if sAction and not fs.exists("config") then
printUsage()
return false
elseif not sAction and fs.exists("config") then
host()
elseif sAction == "host" and not fs.exists("config") then
if #tArgs < 5 then
printUsage()
return false
end
local mId = tArgs[2]
local x = tArgs[3]
local y = tArgs[4]
local z = tArgs[5]
os.loadAPI("config")
config.load(nil,"config")
config.writeVal("mId",mId)
config.writeVal("x",x)
config.writeVal("y",y)
config.writeVal("z",z)
config.save()
os.unloadAPI("config")
end
if sAction == "receive" then
if #tArgs > 1 then
if string.lower(tArgs[2]) == "debug" then
bDebug = true
else
printUsage()
return
end
end
clear()
if not connect() then
return
end
local ok, n = load()
if ok then
print("Loaded ", n, " locations")
end
print("Press l to list located computers")
print("Press c to clear the screen")
while true do
local evt, arg1, arg2 = os.pullEvent()
if evt == "rednet_message" then
handle_message(arg1, arg2)
elseif evt == "char" then
handle_char(arg1)
end
end
end