random-stuff/computercraft/oc-drone.lua

232 lines
9.3 KiB
Lua

local drone = component.proxy(component.list "drone"())
local net = component.proxy(component.list "internet"())
local wlan = component.proxy(component.list "modem"())
local comp = component.proxy(component.list "computer"())
wlan.setWakeMessage("poweron", true)
local central_point = { x = 297, y = 80, z = 294 }
local statuses = {
loading = { text = "LOAD", color = 0x000000 },
moving = { text = "GO", color = 0xFFFF00 },
idle = { text = "IDLE", color = 0x00FFFF },
error = { text = "ERROR", color = 0xFF0000 },
low_battery = { text = "ELOW", 0xFF8800 }
}
local function set_status(status)
local stat = statuses[status]
drone.setLightColor(stat.color or 0xFFFFFF)
drone.setStatusText((stat.text or status) .. (" "):rep(8))
end
set_status "loading"
comp.beep(600, 1)
local function energy()
return computer.energy() / computer.maxEnergy()
end
local GPS_PING_CHANNEL, GPS_RESPONSE_CHANNEL, TIMEOUT = 2048, 2047, 1
wlan.setStrength(math.huge)
local function fetch(url)
local res, err = net.request(url)
if not res then error(url .. " error: " .. err) end
local out = {}
while true do
local chunk, err = res.read()
if err then error(url .. " error: " .. err) end
if chunk then table.insert(out, chunk)
else return table.concat(out) end
end
end
local function round(v, m)
m = m or 1.0
return {
x = math.floor((v.x+(m*0.5))/m)*m,
y = math.floor((v.y+(m*0.5))/m)*m,
z = math.floor((v.z+(m*0.5))/m)*m
}
end
local function len(v)
return math.sqrt(v.x^2 + v.y^2 + v.z^2)
end
local function cross(v, b)
return {x = v.y*b.z-v.z*b.y, y = v.z*b.x-v.x*b.z, z = v.x*b.y-v.y*b.x}
end
local function dot(v, b)
return v.x*b.x + v.y*b.y + v.z*b.z
end
local function add(v, b)
return {x = v.x+b.x, y = v.y+b.y, z = v.z+b.z}
end
local function sub(v, b)
return {x = v.x-b.x, y = v.y-b.y, z = v.z-b.z}
end
local function mul(v, m)
return {x = v.x*m, y = v.y*m, z = v.z*m}
end
local function norm(v)
return mul(v, 1/len(v))
end
local function trilaterate(A, B, C)
local a2b = {x = B.x-A.x, y = B.y-A.y, z = B.z-A.z}
local a2c = {x = C.x-A.x, y = C.y-A.y, z = C.z-A.z}
if math.abs(dot(norm(a2b), norm(a2c))) > 0.999 then
return nil
end
local d = len(a2b)
local ex = norm(a2b)
local i = dot(ex, a2c)
local ey = norm(sub(mul(ex, i), a2c))
local j = dot(ey, a2c)
local ez = cross(ex, ey)
local r1 = A.d
local r2 = B.d
local r3 = C.d
local x = (r1^2 - r2^2 + d^2) / (2*d)
local y = (r1^2 - r3^2 - x^2 + (x-i)^2 + j^2) / (2*j)
local result = add(A, add(mul(ex, x), mul(ey, y)))
local zSquared = r1^2 - x^2 - y^2
if zSquared > 0 then
local z = math.sqrt(zSquared)
local result1 = add(result, mul(ez, z))
local result2 = add(result, mul(ez, z))
local rounded1, rounded2 = round(result1, 0.01), round(result2, 0.01)
if rounded1.x ~= rounded2.x or
rounded1.y ~= rounded2.y or
rounded1.z ~= rounded2.z then
return rounded1, rounded2
else
return rounded1
end
end
return round(result, 0.01)
end
local function narrow(p1, p2, fix)
local dist1 = math.abs(len(sub(p1, fix)) - fix.d)
local dist2 = math.abs(len(sub(p2, fix)) - fix.d)
if math.abs(dist1 - dist2) < 0.01 then
return p1, p2
elseif dist1 < dist2 then
return round(p1, 0.01)
else
return round(p2, 0.01)
end
end
local function locate(timeout)
local timeout = timeout or TIMEOUT
wlan.open(GPS_RESPONSE_CHANNEL)
wlan.broadcast(GPS_PING_CHANNEL, "PING")
local fixes = {}
local pos1, pos2 = nil, nil
local deadline = computer.uptime() + timeout
local dim
repeat
local event, _, from, port, distance, x, y, z, dimension = computer.pullSignal(deadline - computer.uptime())
if event == "modem_message" and port == GPS_RESPONSE_CHANNEL and x and y and z then
if type(dim) == "string" then dim = dimension end
local fix = {x = x, y = y, z = z, d = distance}
if fix.d == 0 then
pos1, pos2 = {fix.x, fix.y, fix.z}, nil
else
table.insert(fixes, fix)
if #fixes >= 3 then
if not pos1 then
pos1, pos2 = trilaterate(fixes[1], fixes[2], fixes[#fixes])
else
pos1, pos2 = narrow(pos1, pos2, fixes[#fixes])
end
end
end
if pos1 and not pos2 then
break
end
end
until computer.uptime() >= deadline
wlan.close(GPS_RESPONSE_CHANNEL)
if pos1 and pos2 then
return nil
elseif pos1 then
return pos1, dim
else
return nil
end
end
local a={["\\"]="\\\\",["\""]="\\\"",["\b"]="\\b",["\f"]="\\f",["\n"]="\\n",["\r"]="\\r",["\t"]="\\t"}local b={["\\/"]="/"}for c,d in pairs(a)do b[d]=c end;local e;local function f(...)local g={}for h=1,select("#",...)do g[select(h,...)]=true end;return g end;local i=f(" ","\t","\r","\n")local j=f(" ","\t","\r","\n","]","}",",")local k=f("\\","/",'"',"b","f","n","r","t","u")local l=f("true","false","null")local m={["true"]=true,["false"]=false,["null"]=nil}local function n(o,p,q,r)for h=p,#o do if q[o:sub(h,h)]~=r then return h end end;return#o+1 end;local function s(o,p,t)local u=1;local v=1;for h=1,p-1 do v=v+1;if o:sub(h,h)=="\n"then u=u+1;v=1 end end;error(string.format("%s at line %d col %d",t,u,v))end;local function w(x)local y=math.floor;if x<=0x7f then return string.char(x)elseif x<=0x7ff then return string.char(y(x/64)+192,x%64+128)elseif x<=0xffff then return string.char(y(x/4096)+224,y(x%4096/64)+128,x%64+128)elseif x<=0x10ffff then return string.char(y(x/262144)+240,y(x%262144/4096)+128,y(x%4096/64)+128,x%64+128)end;error(string.format("invalid unicode codepoint '%x'",x))end;local function z(A)local B=tonumber(A:sub(3,6),16)local C=tonumber(A:sub(9,12),16)if C then return w((B-0xd800)*0x400+C-0xdc00+0x10000)else return w(B)end end;local function D(o,h)local E=false;local F=false;local G=false;local H;for I=h+1,#o do local J=o:byte(I)if J<32 then s(o,I,"control character in string")end;if H==92 then if J==117 then local K=o:sub(I+1,I+5)if not K:find("%x%x%x%x")then s(o,I,"invalid unicode escape in string")end;if K:find("^[dD][89aAbB]")then F=true else E=true end else local L=string.char(J)if not k[L]then s(o,I,"invalid escape char '"..L.."' in string")end;G=true end;H=nil elseif J==34 then local A=o:sub(h+1,I-1)if F then A=A:gsub("\\u[dD][89aAbB]..\\u....",z)end;if E then A=A:gsub("\\u....",z)end;if G then A=A:gsub("\\.",b)end;return A,I+1 else H=J end end;s(o,h,"expected closing quote for string")end;local function M(o,h)local J=n(o,h,j)local A=o:sub(h,J-1)local x=tonumber(A)if not x then s(o,h,"invalid number '"..A.."'")end;return x,J end;local function N(o,h)local J=n(o,h,j)local O=o:sub(h,J-1)if not l[O]then s(o,h,"invalid literal '"..O.."'")end;return m[O],J end;local function P(o,h)local g={}local x=1;h=h+1;while 1 do local J;h=n(o,h,i,true)if o:sub(h,h)=="]"then h=h+1;break end;J,h=e(o,h)g[x]=J;x=x+1;h=n(o,h,i,true)local Q=o:sub(h,h)h=h+1;if Q=="]"then break end;if Q~=","then s(o,h,"expected ']' or ','")end end;return g,h end;local function R(o,h)local g={}h=h+1;while 1 do local S,T;h=n(o,h,i,true)if o:sub(h,h)=="}"then h=h+1;break end;if o:sub(h,h)~='"'then s(o,h,"expected string for key")end;S,h=e(o,h)h=n(o,h,i,true)if o:sub(h,h)~=":"then s(o,h,"expected ':' after key")end;h=n(o,h+1,i,true)T,h=e(o,h)g[S]=T;h=n(o,h,i,true)local Q=o:sub(h,h)h=h+1;if Q=="}"then break end;if Q~=","then s(o,h,"expected '}' or ','")end end;return g,h end;local U={['"']=D,["0"]=M,["1"]=M,["2"]=M,["3"]=M,["4"]=M,["5"]=M,["6"]=M,["7"]=M,["8"]=M,["9"]=M,["-"]=M,["t"]=N,["f"]=N,["n"]=N,["["]=P,["{"]=R}e=function(o,p)local Q=o:sub(p,p)local y=U[Q]if y then return y(o,p)end;s(o,p,"unexpected character '"..Q.."'")end;local function json_decode(o)if type(o)~="string"then error("expected argument of type string, got "..type(o))end;local g,p=e(o,n(o,1,i,true))p=n(o,p,i,true)if p<=#o then s(o,p,"trailing garbage")end;return g end
local function sleep(timeout)
local deadline = computer.uptime() + (timeout or 0)
repeat
computer.pullSignal(deadline - computer.uptime())
until computer.uptime() >= deadline
end
local function moveraw(x, y, z)
set_status "moving"
drone.move(x, y, z)
repeat
sleep(0.5)
until drone.getVelocity() < 0.1
set_status "idle"
end
local function move(pos)
moveraw(0, 32, 0)
moveraw(pos.x, pos.y, pos.z)
moveraw(0, -32, 0)
end
local function follow(currpos)
local data = json_decode(fetch "https://dynmap.codersnet.pw/up/world/world/1588704574112")
local possibles = {}
for _, p in pairs(data.players) do
local plrpos = { x = p.x, y = p.y, z = p.z }
local dist = len(sub(plrpos, central_point))
if dist < 100 and p.world == "world" then
table.insert(possibles, plrpos)
end
end
if #possibles == 0 then return false, "TNF" end
local targpos = possibles[math.random(1, #possibles)]
local offset = sub(targpos, currpos)
comp.beep(400, 0.5)
move(offset)
return true
end
while true do
set_status "idle"
local currpos = locate()
if currpos then
wlan.broadcast(1033, drone.name(), "LOC", currpos.x, currpos.y, currpos.z)
local offset_from_hub = sub(central_point, currpos)
if len(offset_from_hub) then
move(offset_from_hub)
else
local ok, err = follow(currpos)
if not ok then set_status "error" drone.setStatusText("E" .. err) sleep(10) end
sleep(1)
end
end
if energy() < 0.3 then
wlan.broadcast(1033, drone.name(), "LOW")
comp.beep(2000, 1.5)
status "low_battery"
move(sub(central_point, locate()))
end
end