2023-11-13 19:37:05 +00:00
|
|
|
local username = settings.get "username" or "gollark"
|
|
|
|
local ni = peripheral.wrap "back"
|
|
|
|
local speaker = peripheral.find "speaker"
|
|
|
|
local modem = peripheral.find "modem"
|
|
|
|
local offload_laser = settings.get "offload_laser"
|
|
|
|
local w, h = ni.canvas().getSize()
|
|
|
|
if _G.thing_group then
|
|
|
|
pcall(_G.thing_group.remove)
|
|
|
|
end
|
|
|
|
if _G.canvas3d_group then
|
|
|
|
pcall(_G.canvas3d_group.clear)
|
|
|
|
pcall(_G.canvas3d_group.remove)
|
|
|
|
end
|
|
|
|
local group
|
|
|
|
local group_3d
|
|
|
|
local function initialize_group_thing()
|
|
|
|
if group then pcall(group.remove) end
|
|
|
|
if group_3d then pcall(group_3d.remove) end
|
|
|
|
group = ni.canvas().addGroup({ w - 70, 10 })
|
|
|
|
ni.canvas3d().clear()
|
|
|
|
group_3d = ni.canvas3d().create()
|
|
|
|
_G.thing_group = group
|
|
|
|
_G.canvas3d_group = group_3d
|
|
|
|
end
|
|
|
|
initialize_group_thing()
|
|
|
|
|
|
|
|
local targets = {}
|
|
|
|
|
|
|
|
local use_spudnet = offload_laser
|
|
|
|
|
|
|
|
local spudnet_send, spudnet_background
|
|
|
|
if use_spudnet then
|
|
|
|
print "SPUDNET interface loading."
|
|
|
|
spudnet_send, spudnet_background = require "ni-ctl_spudnet_interface"()
|
|
|
|
end
|
|
|
|
|
|
|
|
local function offload_protocol(...)
|
|
|
|
spudnet_send { "exec", {...} }
|
|
|
|
end
|
|
|
|
|
|
|
|
local function is_target(name)
|
|
|
|
for target, type in pairs(targets) do
|
|
|
|
if name:lower():match(target) then return type end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function vector_sqlength(self)
|
|
|
|
return self.x * self.x + self.y * self.y + self.z * self.z
|
|
|
|
end
|
|
|
|
|
|
|
|
local function project(line_start, line_dir, point)
|
|
|
|
local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir)
|
|
|
|
return line_start + line_dir * t, t
|
|
|
|
end
|
|
|
|
|
|
|
|
local function calc_yaw_pitch(v)
|
|
|
|
local x, y, z = v.x, v.y, v.z
|
|
|
|
local pitch = -math.atan2(y, math.sqrt(x * x + z * z))
|
|
|
|
local yaw = math.atan2(-x, z)
|
|
|
|
return math.deg(yaw), math.deg(pitch)
|
|
|
|
end
|
|
|
|
|
|
|
|
local settings_cfg = {
|
|
|
|
brake = { type = "bool", default = true, shortcode = "b" },
|
|
|
|
counter = { type = "bool", default = false, shortcode = "c" }, -- counterattack
|
|
|
|
highlight = { type = "bool", default = false, shortcode = "h" },
|
|
|
|
dodge = { type = "bool", default = true, shortcode = "d" },
|
|
|
|
power = { type = "number", default = 5, max = 5, min = 0.5 }, -- laser power
|
|
|
|
flight = { type = "string", default = "std", shortcode = "f", alias = { fly = true } },
|
|
|
|
drill = { type = "bool", default = false, shortcode = "D", persist = false },
|
|
|
|
show_acceleration = { type = "bool", default = false },
|
|
|
|
pitch_controls = { type = "bool", default = false },
|
|
|
|
ext_highlight = { type = "bool", default = false }
|
|
|
|
}
|
|
|
|
local SAVEFILE = "ni-ctl-settings"
|
|
|
|
if fs.exists(SAVEFILE) then
|
|
|
|
local f = fs.open(SAVEFILE, "r")
|
|
|
|
for key, value in pairs(textutils.unserialise(f.readAll())) do
|
|
|
|
settings_cfg[key].value = value
|
|
|
|
end
|
|
|
|
f.close()
|
|
|
|
end
|
|
|
|
local gsettings = {}
|
|
|
|
setmetatable(gsettings, {
|
|
|
|
__index = function(_, key)
|
|
|
|
local cfg = settings_cfg[key]
|
|
|
|
if cfg.value == nil then return cfg.default else return cfg.value end
|
|
|
|
end,
|
|
|
|
__newindex = function(_, key, value)
|
|
|
|
print("set", key, "to", value)
|
|
|
|
settings_cfg[key].value = value
|
|
|
|
if settings_cfg[key].persist ~= false then
|
|
|
|
local kv = {}
|
|
|
|
for key, cfg in pairs(settings_cfg) do
|
|
|
|
kv[key] = cfg.value
|
|
|
|
end
|
|
|
|
local f = fs.open(SAVEFILE, "w")
|
|
|
|
f.write(textutils.serialise(kv))
|
|
|
|
f.close()
|
|
|
|
end
|
|
|
|
os.queueEvent "settings_change"
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
|
|
|
local work_queue = {}
|
|
|
|
|
|
|
|
local addressed_lasers = {}
|
|
|
|
local function bool_to_yn(b)
|
|
|
|
if b == true then return "y"
|
|
|
|
elseif b == false then return "n"
|
|
|
|
else return "?" end
|
|
|
|
end
|
|
|
|
|
|
|
|
local status_lines = {}
|
|
|
|
local notices = {}
|
|
|
|
local function push_notice(t)
|
|
|
|
table.insert(notices, { t, os.epoch "utc" })
|
|
|
|
end
|
|
|
|
|
|
|
|
local function lase(entity)
|
|
|
|
local target_location = entity.s
|
|
|
|
for i = 1, 5 do
|
|
|
|
target_location = entity.s + entity.v * (target_location:length() / 1.5)
|
|
|
|
end
|
|
|
|
local y, p = calc_yaw_pitch(target_location)
|
|
|
|
if offload_laser then offload_protocol("fire", y, p, gsettings.power) else ni.fire(y, p, gsettings.power) end
|
|
|
|
end
|
|
|
|
|
|
|
|
local user_meta
|
|
|
|
local fast_mode_reqs = {}
|
|
|
|
|
|
|
|
local colortheme = {
|
|
|
|
status = 0xFFFFFFFF,
|
|
|
|
notice = 0xFF8800FF,
|
|
|
|
follow = 0xFF00FFFF,
|
|
|
|
watch = 0xFFFF00FF,
|
|
|
|
laser = 0xFF0000FF,
|
|
|
|
entity = 0x00FFFFFF,
|
|
|
|
select = 0x00FF00FF
|
|
|
|
}
|
|
|
|
|
|
|
|
local function schedule(fn, time, uniquename)
|
|
|
|
if uniquename then
|
|
|
|
work_queue[uniquename] = { os.clock() + time, fn }
|
|
|
|
else
|
|
|
|
table.insert(work_queue, { os.clock() + time, fn })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function direction_vector(yaw, pitch)
|
|
|
|
return vector.new(
|
|
|
|
-math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)),
|
|
|
|
-math.sin(math.rad(pitch)),
|
|
|
|
math.cos(math.rad(yaw)) * math.cos(math.rad(pitch))
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
local function ni.launch(yaw, pitch, power)
|
|
|
|
ni.ni.launch(yaw, pitch, power)
|
|
|
|
if user_meta then
|
|
|
|
local impulse = vector.new(
|
|
|
|
-math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)),
|
|
|
|
math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)),
|
|
|
|
-math.sin(math.rad(pitch))
|
|
|
|
)
|
|
|
|
if user_meta.isElytraFlying then
|
|
|
|
impulse = 0.4 * impulse
|
|
|
|
end
|
|
|
|
user_meta.velocity = user_meta.velocity + (impulse * power)
|
|
|
|
end
|
|
|
|
end]]
|
|
|
|
|
|
|
|
local gravity_motion_offset = 0.07840001
|
|
|
|
|
|
|
|
--[[
|
|
|
|
local inav_position = nil
|
|
|
|
local inav_delta = nil
|
|
|
|
local scaler = 20
|
|
|
|
|
|
|
|
local function navigation()
|
|
|
|
while true do
|
|
|
|
local real_pos = vector.new(gps.locate())
|
|
|
|
if inav_position then
|
|
|
|
print(inav_position - real_pos)
|
|
|
|
local real_delta = (inav_position - real_pos):length()
|
|
|
|
local delta_size = inav_delta:length()
|
|
|
|
print("calculated delta was", delta_size / real_delta, "of real")
|
|
|
|
end
|
|
|
|
inav_position = real_pos
|
|
|
|
inav_delta = vector.new(0, 0, 0)
|
|
|
|
sleep(3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
]]
|
|
|
|
|
|
|
|
local hud_entities = {}
|
|
|
|
local function render_hud()
|
|
|
|
local flags = {}
|
|
|
|
for key, cfg in pairs(settings_cfg) do
|
|
|
|
if cfg.shortcode and cfg.type == "bool" then
|
|
|
|
if gsettings[key] then
|
|
|
|
table.insert(flags, cfg.shortcode)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
table.sort(flags)
|
|
|
|
status_lines.flags = "Flags: " .. table.concat(flags)
|
|
|
|
|
|
|
|
local i = 0
|
|
|
|
local ok, err = pcall(group.clear)
|
|
|
|
if not ok then
|
|
|
|
initialize_group_thing()
|
|
|
|
end
|
|
|
|
local time = os.epoch "utc"
|
|
|
|
for _, text in pairs(status_lines) do
|
|
|
|
group.addText({ 0, i * 7 }, text, colortheme.status, 0.6)
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
for ix, info in pairs(notices) do
|
|
|
|
if time >= (info[2] + 2000) then notices[ix] = nil end
|
|
|
|
group.addText({ 0, i * 7 }, info[1], colortheme.notice, 0.6)
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
for thing, count in pairs(hud_entities) do
|
|
|
|
local text = thing
|
|
|
|
if count ~= 1 then text = text .. " " .. count end
|
|
|
|
group.addText({ 0, i * 7 }, text, colortheme[is_target(thing) or "entity"], 0.6)
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local last_velocity
|
|
|
|
local last_time
|
|
|
|
local integrated_position = vector.new(0, 0, 0)
|
|
|
|
|
|
|
|
local function update_motion_vars(new_meta)
|
|
|
|
local time = os.clock()
|
|
|
|
if user_meta then
|
|
|
|
-- walking hack
|
|
|
|
if not (user_meta.isFlying or user_meta.isElytraFlying) then
|
|
|
|
if user_meta.isInWater then new_meta.motionY = new_meta.motionY + 0.02
|
|
|
|
else new_meta.motionY = new_meta.motionY + gravity_motion_offset end
|
|
|
|
end
|
|
|
|
user_meta.velocity = vector.new(new_meta.motionX, new_meta.motionY, new_meta.motionZ)
|
|
|
|
user_meta.real_velocity = vector.new(new_meta.deltaPosX, new_meta.deltaPosY, new_meta.deltaPosZ)
|
|
|
|
integrated_position = integrated_position + user_meta.real_velocity
|
|
|
|
user_meta.motionX = new_meta.motionX
|
|
|
|
user_meta.motionY = new_meta.motionY
|
|
|
|
user_meta.motionZ = new_meta.motionZ
|
|
|
|
user_meta.pitch = new_meta.pitch
|
|
|
|
user_meta.yaw = new_meta.yaw
|
|
|
|
if last_time and last_velocity then
|
|
|
|
local timestep = time - last_time
|
|
|
|
user_meta.acceleration = (user_meta.velocity - last_velocity) / timestep
|
|
|
|
end
|
|
|
|
last_velocity = user_meta.velocity
|
|
|
|
end
|
|
|
|
last_time = time
|
|
|
|
end
|
|
|
|
|
|
|
|
local function scan_entities()
|
|
|
|
while true do
|
|
|
|
fast_mode_reqs.laser = false
|
|
|
|
fast_mode_reqs.acting = false
|
|
|
|
local entities = ni.sense()
|
|
|
|
local maybe_players = {}
|
|
|
|
hud_entities = {}
|
|
|
|
local lasers = {}
|
|
|
|
|
|
|
|
for _, entity in pairs(entities) do
|
|
|
|
entity.s = vector.new(entity.x, entity.y, entity.z)
|
|
|
|
entity.v = vector.new(entity.motionX, entity.motionY, entity.motionZ)
|
2023-12-01 15:48:21 +00:00
|
|
|
if entity.deltaPosX then
|
|
|
|
entity.v = vector.new(entity.deltaPosX, entity.deltaPosY, entity.deltaPosZ)
|
|
|
|
end
|
2023-11-13 19:37:05 +00:00
|
|
|
if entity.displayName ~= username then
|
|
|
|
hud_entities[entity.displayName] = (hud_entities[entity.displayName] or 0) + 1
|
|
|
|
end
|
|
|
|
if entity.displayName ~= username and entity.displayName == entity.name and (math.floor(entity.yaw) ~= entity.yaw and math.floor(entity.pitch) ~= entity.pitch) then -- player, quite possibly
|
|
|
|
entity.v = entity.v + vector.new(0, gravity_motion_offset, 0)
|
|
|
|
table.insert(maybe_players, entity)
|
|
|
|
end
|
|
|
|
if entity.name == "plethora:laser" then
|
|
|
|
fast_mode_reqs.laser = true
|
|
|
|
end
|
|
|
|
if entity.name == "plethora:laser" and not addressed_lasers[entity.id] then
|
|
|
|
local closest_approach, param = project(entity.s, entity.v - user_meta.velocity, vector.new(0, 0, 0))
|
|
|
|
if param > 0 and vector_sqlength(closest_approach) < 5 then
|
|
|
|
push_notice "Laser detected"
|
|
|
|
fast_mode_reqs.laser = true
|
|
|
|
local time_to_impact = (entity.s:length() / (entity.v - user_meta.velocity):length()) / 20
|
|
|
|
print("got inbound laser", time_to_impact, vector_sqlength(closest_approach), param)
|
|
|
|
addressed_lasers[entity.id] = true
|
|
|
|
if gsettings.dodge then
|
|
|
|
schedule(function()
|
|
|
|
push_notice "Executing dodging"
|
|
|
|
local dir2d = vector.new(entity.motionX - user_meta.motionX, 0, entity.motionZ - user_meta.motionZ)
|
|
|
|
local perpendicular_dir2d = vector.new(1, 0, -dir2d.x / dir2d.z)
|
|
|
|
-- NaN contingency measures
|
|
|
|
if perpendicular_dir2d.x ~= perpendicular_dir2d.x or perpendicular_dir2d.z ~= perpendicular_dir2d.z then
|
|
|
|
perpendicular_dir2d = vector.new(-dir2d.z / dir2d.x, 0, 1)
|
|
|
|
end
|
|
|
|
if perpendicular_dir2d.x ~= perpendicular_dir2d.x or perpendicular_dir2d.z ~= perpendicular_dir2d.z then
|
|
|
|
local theta = math.random() * math.pi * 2
|
|
|
|
perpendicular_dir2d = vector.new(math.cos(theta), 0, math.sin(theta))
|
|
|
|
end
|
|
|
|
local y, p = calc_yaw_pitch(perpendicular_dir2d)
|
|
|
|
if math.random(1, 2) == 1 then p = -p end
|
|
|
|
ni.launch(y, p, 3)
|
|
|
|
end, math.max(0, time_to_impact / 2 - 0.1))
|
|
|
|
end
|
|
|
|
schedule(function() addressed_lasers[entity.id] = false end, 15)
|
|
|
|
table.insert(lasers, entity)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for _, laser in pairs(lasers) do
|
|
|
|
for _, player in pairs(maybe_players) do
|
|
|
|
local closest_approach, param = project(laser.s, laser.v, player.s)
|
|
|
|
print(player.displayName, closest_approach, param)
|
|
|
|
if param < 0 and vector_sqlength(closest_approach - player.s) < 8 and gsettings.counter then
|
|
|
|
print("execute counterattack", player.displayName)
|
|
|
|
push_notice(("Counterattack %s"):format(player.displayName))
|
|
|
|
targets[player.displayName:lower()] = "laser"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
render_hud()
|
|
|
|
|
|
|
|
pcall(function()
|
|
|
|
group_3d.clear()
|
|
|
|
group_3d.recenter()
|
|
|
|
end)
|
|
|
|
|
|
|
|
for _, entity in pairs(entities) do
|
|
|
|
local action = is_target(entity.displayName)
|
|
|
|
if action then
|
|
|
|
if action == "laser" then
|
|
|
|
schedule(function() lase(entity) end, 0, entity.id)
|
|
|
|
elseif action == "watch" then
|
|
|
|
schedule(function() ni.look(calc_yaw_pitch(entity.s)) end, 0, entity.id)
|
|
|
|
elseif action == "follow" then
|
|
|
|
schedule(function()
|
|
|
|
local y, p = calc_yaw_pitch(entity.s)
|
|
|
|
ni.launch(y, p, math.min(entity.s:length() / 24, 2))
|
|
|
|
end, 0, entity.id)
|
|
|
|
end
|
|
|
|
fast_mode_reqs.acting = true
|
|
|
|
end
|
|
|
|
if gsettings.highlight and hud_entities[entity.displayName] and hud_entities[entity.displayName] < 20 then
|
|
|
|
local color = colortheme[action or "entity"]
|
|
|
|
local object = group_3d.addBox(entity.x - 0.25, entity.y - 0.25, entity.z - 0.25)
|
|
|
|
object.setColor(color)
|
|
|
|
object.setAlpha(128)
|
|
|
|
object.setDepthTested(false)
|
|
|
|
object.setSize(0.5, 0.5, 0.5)
|
|
|
|
if gsettings.ext_highlight then
|
|
|
|
local frame = group_3d.addFrame({entity.x - 0.25, entity.y + 0.25, entity.z - 0.25})
|
|
|
|
frame.setDepthTested(false)
|
|
|
|
frame.addText({0, 0}, entity.displayName, nil, 3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local fast_mode = false
|
|
|
|
for _, m in pairs(fast_mode_reqs) do
|
|
|
|
fast_mode = fast_mode or m
|
|
|
|
end
|
|
|
|
|
|
|
|
--status_lines.fast_mode = "Fast scan: " .. bool_to_yn(fast_mode)
|
|
|
|
|
|
|
|
if fast_mode then sleep(0.1) else sleep(0.2) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local flight_shortcodes = {
|
|
|
|
o = "off",
|
|
|
|
b = "brake",
|
|
|
|
h = "hpower",
|
|
|
|
l = "lpower",
|
|
|
|
s = "std",
|
|
|
|
a = "align",
|
|
|
|
v = "hover"
|
|
|
|
}
|
|
|
|
|
|
|
|
local flight_powers = {
|
|
|
|
std = 1,
|
|
|
|
lpower = 0.5,
|
|
|
|
hpower = 4,
|
|
|
|
align = 1,
|
|
|
|
hover = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
local flight_target = nil
|
|
|
|
|
|
|
|
local function xz_plane(v)
|
|
|
|
return vector.new(v.x, 0, v.z)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- As far as I can tell, a speed of more than 10 in the X/Z plane causes a reset of your velocity by the server and thus horrible rubberbanding.
|
|
|
|
local function maxvel_compensatory_launch(yaw, pitch, power)
|
|
|
|
local effective_power = (user_meta and user_meta.isElytraFlying) and (power * 0.4) or power
|
|
|
|
local impulse = direction_vector(yaw, pitch) * effective_power
|
|
|
|
local power_over_velocity_limit = math.max(xz_plane(user_meta.velocity + impulse):length() - 10, 0)
|
|
|
|
if user_meta and user_meta.isElytraFlying then
|
|
|
|
power = power - power_over_velocity_limit / 0.4
|
|
|
|
else
|
|
|
|
power = power - power_over_velocity_limit
|
|
|
|
end
|
|
|
|
power = math.min(math.max(power, 0), 4)
|
|
|
|
if power > 0 then
|
|
|
|
ni.launch(yaw, pitch, power)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function run_flight()
|
|
|
|
if flight_shortcodes[gsettings.flight] then gsettings.flight = flight_shortcodes[gsettings.flight] end
|
|
|
|
local disp = gsettings.flight
|
|
|
|
if user_meta.deltaPosY < -0.3 and gsettings.brake then
|
|
|
|
ni.launch(0, 270, math.max(0.4, math.min(4, -user_meta.motionY / 1.5)))
|
|
|
|
--ni.launch(0, 270, 0.4)
|
|
|
|
--end
|
|
|
|
fast_mode_reqs.flying = true
|
|
|
|
disp = disp .. " F"
|
|
|
|
else
|
|
|
|
fast_mode_reqs.flying = false
|
|
|
|
end
|
|
|
|
if gsettings.flight == "std" or gsettings.flight == "hpower" or gsettings.flight == "lpower" or gsettings.flight == "align" or gsettings.flight == "hover" then
|
|
|
|
if user_meta.isElytraFlying or user_meta.isSneaking then
|
|
|
|
fast_mode_reqs.flying = true
|
|
|
|
end
|
|
|
|
if user_meta.isElytraFlying ~= user_meta.isSneaking then
|
|
|
|
if not user_meta.isAirborne and user_meta.pitch < -15 then
|
|
|
|
push_notice "Fast takeoff"
|
|
|
|
ni.launch(0, 270, 1)
|
|
|
|
end
|
|
|
|
local power = flight_powers[gsettings.flight]
|
|
|
|
if user_meta.isInWater then
|
|
|
|
power = power * 2
|
|
|
|
end
|
|
|
|
local yaw, pitch = user_meta.yaw, user_meta.pitch
|
|
|
|
if pitch == 90 and gsettings.pitch_controls then
|
|
|
|
local y, p = calc_yaw_pitch(-user_meta.velocity)
|
|
|
|
ni.launch(y, p, math.min(user_meta.velocity:length(), 4))
|
|
|
|
else
|
|
|
|
local raw_direction = direction_vector(yaw, pitch)
|
|
|
|
local impulse = vector.new(raw_direction.x, raw_direction.y * 1.5, raw_direction.z) * power
|
|
|
|
impulse = impulse + vector.new(0, 0.1, 0)
|
|
|
|
local y, p = calc_yaw_pitch(impulse)
|
|
|
|
maxvel_compensatory_launch(y, (gsettings.flight ~= "align" and p) or 10, math.min(4, impulse:length()))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif gsettings.flight == "brake" then
|
|
|
|
local y, p = calc_yaw_pitch(-user_meta.velocity)
|
|
|
|
ni.launch(y, p, math.min(user_meta.velocity:length(), 1))
|
|
|
|
fast_mode_reqs.flying = true
|
|
|
|
end
|
|
|
|
status_lines.flight = "Flight: " .. disp
|
|
|
|
end
|
|
|
|
|
|
|
|
local function ll_flight_control()
|
|
|
|
while true do
|
|
|
|
local ok, user_meta_temp
|
|
|
|
if ni.getMetaOwner then
|
|
|
|
ok, user_meta_temp = pcall(ni.getMetaOwner, username)
|
|
|
|
else
|
|
|
|
ok, user_meta_temp = pcall(ni.getMetaByName, username)
|
|
|
|
end
|
|
|
|
if not ok or not user_meta_temp then
|
|
|
|
speaker.playSound("entity.enderdragon.death")
|
|
|
|
user_meta = nil
|
|
|
|
for name, cfg in pairs(settings_cfg) do
|
|
|
|
if cfg.persist == false then
|
|
|
|
cfg.value = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
work_queue = {}
|
|
|
|
ni = peripheral.wrap "back"
|
|
|
|
ni.canvas().clear()
|
|
|
|
error("Failed to fetch user metadata (assuming death): " .. tostring(user_meta_temp))
|
|
|
|
end
|
|
|
|
user_meta = user_meta_temp
|
|
|
|
update_motion_vars(user_meta)
|
|
|
|
if user_meta.acceleration and gsettings.show_acceleration then
|
|
|
|
status_lines.acceleration = ("Acc: %.2f/%.2f"):format(user_meta.acceleration:length(), user_meta.acceleration.y)
|
|
|
|
end
|
|
|
|
|
|
|
|
status_lines.vel = ("Vel: %.2f/%.2f"):format(user_meta.velocity:length(), user_meta.motionY)
|
|
|
|
|
|
|
|
render_hud()
|
|
|
|
|
|
|
|
local fast_mode = false
|
|
|
|
for _, m in pairs(fast_mode_reqs) do
|
|
|
|
fast_mode = fast_mode or m
|
|
|
|
end
|
|
|
|
|
|
|
|
--status_lines.fast_mode = "Fast scan: " .. bool_to_yn(fast_mode)
|
|
|
|
|
|
|
|
schedule(run_flight, 0, "flight")
|
|
|
|
|
|
|
|
if not fast_mode then sleep(0.1) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function queue_handler()
|
|
|
|
while true do
|
|
|
|
local init = os.clock()
|
|
|
|
for index, arg in pairs(work_queue) do
|
|
|
|
if arg[1] <= os.clock() then
|
|
|
|
arg[2]()
|
|
|
|
work_queue[index] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if os.clock() == init then sleep() end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function estimate_tps()
|
|
|
|
while true do
|
|
|
|
local game_time_start = os.epoch "utc"
|
|
|
|
sleep(5)
|
|
|
|
local game_time_end = os.epoch "utc"
|
|
|
|
local utc_elapsed_seconds = (game_time_end - game_time_start) / 5000
|
|
|
|
status_lines.tps = ("TPS: %.0f"):format(20 / utc_elapsed_seconds)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function within_epsilon(a, b)
|
|
|
|
return math.abs(a - b) < 1
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: unified navigation framework
|
|
|
|
local function fly_to_target()
|
|
|
|
local last_s, last_t
|
|
|
|
while true do
|
|
|
|
while not user_meta do sleep() end
|
|
|
|
if flight_target then
|
|
|
|
local x, y, z = gps.locate()
|
|
|
|
if not y then push_notice "GPS error"
|
|
|
|
else
|
|
|
|
if y < 256 then
|
|
|
|
ni.launch(0, 270, 4)
|
|
|
|
end
|
|
|
|
local position = vector.new(x, 0, z)
|
|
|
|
local curr_t = os.clock()
|
|
|
|
local displacement = flight_target - position
|
|
|
|
status_lines.flight_target = ("%d from %d %d"):format(displacement:length(), flight_target.x, flight_target.z)
|
|
|
|
local real_displacement = displacement
|
|
|
|
if last_t then
|
|
|
|
local delta_t = curr_t - last_t
|
|
|
|
local delta_s = displacement - last_s
|
|
|
|
local deriv = delta_s * (1/delta_t)
|
|
|
|
displacement = displacement + deriv
|
|
|
|
end
|
|
|
|
local pow = math.max(math.min(4, displacement:length() / 40), 0)
|
|
|
|
local yaw, pitch = calc_yaw_pitch(displacement)
|
|
|
|
maxvel_compensatory_launch(yaw, pitch, pow)
|
|
|
|
--sleep(0)
|
|
|
|
last_t = curr_t
|
|
|
|
last_s = real_displacement
|
|
|
|
if within_epsilon(position.x, flight_target.x) and within_epsilon(position.z, flight_target.z) then flight_target = nil end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
status_lines.flight_target = nil
|
|
|
|
end
|
|
|
|
sleep(0.1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_commands()
|
|
|
|
while true do
|
|
|
|
local _, user, command, args = os.pullEvent "command"
|
|
|
|
if user == username then
|
|
|
|
if command == "lase" then
|
|
|
|
if args[1] then
|
|
|
|
targets[table.concat(args, " "):lower()] = "laser"
|
|
|
|
end
|
|
|
|
elseif command == "ctg" then
|
|
|
|
args[1] = args[1] or ".*"
|
|
|
|
local arg = table.concat(args, " ")
|
|
|
|
for k, v in pairs(targets) do
|
|
|
|
if k:lower():match(arg) then
|
|
|
|
chatbox.tell(user, k .. ": " .. v)
|
|
|
|
targets[k:lower()] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif command == "watch" then
|
|
|
|
if args[1] then
|
|
|
|
targets[table.concat(args, " "):lower()] = "watch"
|
|
|
|
end
|
|
|
|
elseif command == "select" then
|
|
|
|
if args[1] then
|
|
|
|
targets[table.concat(args, " "):lower()] = "select"
|
|
|
|
end
|
|
|
|
elseif command == "follow" then
|
|
|
|
if args[1] then
|
|
|
|
targets[table.concat(args, " "):lower()] = "follow"
|
|
|
|
end
|
|
|
|
elseif command == "notice_test" then
|
|
|
|
push_notice(table.concat(args, " "))
|
|
|
|
elseif command == "flyto" then
|
|
|
|
if args[1] == "cancel" or args[1] == nil then
|
|
|
|
flight_target = nil
|
|
|
|
else
|
|
|
|
local x, z = tonumber(args[1]), tonumber(args[2])
|
|
|
|
if type(x) ~= "number" or type(z) ~= "number" then
|
|
|
|
chatbox.tell(user, "Usage: \\flyto x z")
|
|
|
|
else
|
|
|
|
flight_target = vector.new(x, 0, z)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif command == "update" then
|
|
|
|
local h, e = http.get "https://osmarks.net/stuff/ni-ctl.lua"
|
|
|
|
assert(h, "HTTP: " .. (e or ""))
|
|
|
|
local data = h.readAll()
|
|
|
|
h.close()
|
|
|
|
local file = fs.open(shell.getRunningProgram(), "w")
|
|
|
|
file.write(data)
|
|
|
|
file.close()
|
|
|
|
chatbox.tell(user, "Update updated.")
|
|
|
|
else
|
|
|
|
for key, cfg in pairs(settings_cfg) do
|
|
|
|
if key == command or cfg.shortcode == command or (cfg.alias and cfg.alias[command]) then
|
|
|
|
if cfg.type == "bool" then
|
|
|
|
if args[1] and (args[1]:match "y" or args[1]:match "t" or args[1]:match "on") then
|
|
|
|
gsettings[key] = true
|
|
|
|
elseif args[1] and (args[1]:match "f" or args[1]:match "^n") then
|
|
|
|
gsettings[key] = false
|
|
|
|
else
|
|
|
|
gsettings[key] = not gsettings[key]
|
|
|
|
end
|
|
|
|
chatbox.tell(user, ("%s: %s"):format(key, tostring(gsettings[key])))
|
|
|
|
elseif cfg.type == "number" then
|
|
|
|
local value = tonumber(args[1])
|
|
|
|
if not value then chatbox.tell(user, "Not a number") end
|
|
|
|
if cfg.max and value > cfg.max then chatbox.tell(user, ("Max is %d"):format(cfg.max)) end
|
|
|
|
if cfg.min and value < cfg.min then chatbox.tell(user, ("Max is %d"):format(cfg.min)) end
|
|
|
|
gsettings[key] = value
|
|
|
|
else
|
|
|
|
gsettings[key] = args[1]
|
|
|
|
end
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function drill()
|
|
|
|
while true do
|
|
|
|
if gsettings.drill then
|
|
|
|
repeat sleep() until user_meta
|
|
|
|
if offload_laser then
|
|
|
|
offload_protocol("fire", user_meta.yaw, user_meta.pitch, gsettings.power)
|
|
|
|
else
|
|
|
|
schedule(function() repeat sleep() until user_meta ni.fire(user_meta.yaw, user_meta.pitch, gsettings.power) end, 0, "drill")
|
|
|
|
end
|
|
|
|
sleep(0.1)
|
|
|
|
else
|
|
|
|
os.pullEvent "settings_change"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
while true do
|
|
|
|
local cmds = {ll_flight_control, queue_handler, scan_entities, handle_commands, estimate_tps, fly_to_target, drill}
|
|
|
|
if spudnet_background then
|
|
|
|
table.insert(cmds, spudnet_background)
|
|
|
|
end
|
|
|
|
local ok, err = pcall(parallel.waitForAny, unpack(cmds))
|
|
|
|
if err == "Terminated" then break end
|
|
|
|
printError(err)
|
|
|
|
sleep(0.2)
|
|
|
|
end
|