From 2a2aa6a793a779a0cffffc841eee31f6894d8ac1 Mon Sep 17 00:00:00 2001 From: sophie Date: Wed, 9 Jul 2025 12:56:25 +0000 Subject: [PATCH] Add more --- fly2.lua | 98 ++++++ minit.lua | 189 ++++++++++++ nictl | 721 +++++++++++++++++++++++++++++++++++++++++++ pcm-norma-client.lua | 110 +++++++ shrekFly.lua | 282 +++++++++++++++++ 5 files changed, 1400 insertions(+) create mode 100644 fly2.lua create mode 100644 minit.lua create mode 100644 nictl create mode 100644 pcm-norma-client.lua create mode 100644 shrekFly.lua diff --git a/fly2.lua b/fly2.lua new file mode 100644 index 0000000..f5f75bb --- /dev/null +++ b/fly2.lua @@ -0,0 +1,98 @@ +--- This script allows the player to fly, as if they were in creative mode. Be warned, this isn't perfect, and lag may +--- result in your death. + +--- Firstly we want to ensure that we have a neural interface and wrap it. +local modules = peripheral.find("neuralInterface") +if not modules then + error("Must have a neural interface", 0) +end + +--- - We require a sensor and introspection module in order to gather information about the player +--- - The sensor is used to determine where the ground is relative to the player, meaning we can slow the player +--- before they hit the floor. +--- - The kinetic augment is (obviously) used to launch the player. +if not modules.hasModule("plethora:sensor") then error("Must have a sensor", 0) end +if not modules.hasModule("plethora:scanner") then error("Must have a scanner", 0) end +if not modules.hasModule("plethora:introspection") then error("Must have an introspection module", 0) end +if not modules.hasModule("plethora:kinetic", 0) then error("Must have a kinetic agument", 0) end + +--- We run several loop at once, to ensure that various components do not delay each other. +local meta = {} +local hover = false +parallel.waitForAny( + --- This loop just pulls user input. It handles a couple of function keys, as well as + --- setting the "hover" field to true/false. + --- + --- We recommend running [with the keyboard in your neural interface](../items/keyboard.html#using-with-the-neural-interface), + --- as this allows you to navigate without having the interface open. + function() + while true do + local event, key = os.pullEvent() + if event == "key" and key == keys.o then + -- The O key launches you high into the air. + modules.launch(0, -90, 3) + elseif event == "key" and key == keys.p then + -- The P key launches you a little into the air. + modules.launch(0, -90, 1) + elseif event == "key" and key == keys.l then + -- The l key launches you in whatever direction you are looking. + modules.launch(meta.yaw, meta.pitch, 3) + elseif event == "key" and key == keys.k then + -- Holding the K key enables "hover" mode. We disable it when it is released. + if not hover then + hover = true + os.queueEvent("hover") + end + elseif event == "key_up" and key == keys.k then + hover = false + end + end + end, + --- Continuously update the metadata. We do this in a separate loop to ensure this doesn't delay + --- other functions + function() + while true do + meta = modules.getMetaOwner() + end + end, + --- If we are hovering then attempt to catapult us back into air, with sufficient velocity to + --- just counteract gravity. + function() + while true do + if hover then + -- We calculate the required motion we need to take + local mY = meta.motionY + mY = (mY - 0.138) / 0.8 + + -- If it is sufficiently large then we fire ourselves in that direction. + if mY > 0.5 or mY < 0 then + local sign = 1 + if mY < 0 then sign = -1 end + modules.launch(0, 90 * sign, math.min(4, math.abs(mY))) + else + sleep(0) + end + else + os.pullEvent("hover") + end + end + end, + --- If we can detect a block below us, and we're falling sufficiently fast, then attempt to slow our fall. This + ---needs to react as fast as possible, so we can't call many peripheral methods here. + function() + while true do + local blocks = modules.scan() + for y = 0, -8, -1 do + -- Scan from the current block downwards + local block = blocks[1 + (8 + (8 + y)*17 + 8*17^2)] + if block.name ~= "minecraft:air" then + if meta.motionY < -0.3 then + -- If we're moving slowly, then launch ourselves up + modules.launch(0, -90, math.min(4, meta.motionY / -0.5)) + end + break + end + end + end + end +) \ No newline at end of file diff --git a/minit.lua b/minit.lua new file mode 100644 index 0000000..2886b91 --- /dev/null +++ b/minit.lua @@ -0,0 +1,189 @@ +-- Minit Beta 1.0.0 +-- Copyright (C) 2023 AlexDevs +-- This software is licensed under the MIT license. + +settings.define("minit.cycleSleep", { + description = "Sleep time between cycles", + type = "number", + default = 0.1, +}) + +settings.define("minit.cycleTimeout", { + description = "Cycles timeout", + type = "number", + default = 1, +}) + +settings.define("minit.modulesPath", { + description = "Path to modules", + type = "string", + default = "modules", +}) + +local modulesPath = settings.get("minit.modulesPath") +local neuralInterface + +local expect = require("cc.expect").expect + +local logPrefix = "%s %s:" +local function getTime() + return os.date("%H:%M:%S") +end +local function log(label, ...) + local time = getTime() + print(string.format(logPrefix, time, label), ...) +end + +local function logError(label, ...) + local time = getTime() + printError(string.format(logPrefix, time, label), ...) +end + +local modules = {} +local function loadModules() + log("Minit", "Loading modules from /" .. modulesPath) + local files = fs.list(modulesPath) + for i = 1, #files do + local ok, par = pcall(require, fs.combine(modulesPath, files[i]) + :gsub("/", ".") + :gsub("%.lua$", "")) + if ok then + par.name = par.name or files[i]:gsub("%.lua$", "") + table.insert(modules, par) + log("Minit", "Loaded module " .. par.name) + else + logError("Minit", "Could not load module " .. files[i] .. ": " .. par) + end + end +end + +local function getCallbacks(name, ...) + local args = table.pack(...) + local callbacks = {} + for i = 1, #modules do + local module = modules[i] + if type(module[name]) == "function" then + table.insert(callbacks, function() + module[name](table.unpack(args)) + end) + end + end + return table.unpack(callbacks) +end + +local tasks = {} +local function addTask(task) + expect(1, task, "function") + table.insert(tasks, coroutine.create(task)) +end + +local function initModules() + for i = 1, #modules do + local module = modules[i] + if type(module.init) == "function" then + module.init({ + addTask = addTask, + log = function(...) + log(module.name, ...) + end, + logError = function(...) + logError(module.name, ...) + end, + }) + end + end +end + +local function setupNeuralInterface() + neuralInterface = peripheral.wrap("back") + + if not neuralInterface then + error("No neural interface found") + end + + -- Wait for owner to be alive + local function getOwner() + local meta + parallel.waitForAny(function() + meta = neuralInterface.getMetaOwner() + end, function() + sleep(1) + end) + + return meta + end + + local meta = getOwner() + if not meta or not meta.isAlive then + log("Minit", "Waiting for respawn...") + end + while not meta or not meta.isAlive do + sleep(0.2) + meta = getOwner() + end + + parallel.waitForAll(getCallbacks("setup", neuralInterface)) +end + +local function cycleUpdate() + local metaOwner = neuralInterface.getMetaOwner() + + parallel.waitForAll(getCallbacks("update", metaOwner)) + + sleep(settings.get("minit.cycleSleep")) +end + +local function tasksHandler() + local ev = { n = 0 } + local filters = {} + + while true do + for i, thread in pairs(tasks) do + if coroutine.status(thread) == "dead" then + tasks[i] = nil + filters[i] = nil + else + if filters[i] == nil or filters[i] == ev[1] or ev[1] == "terminate" then + local ok, param = coroutine.resume(thread, table.unpack(ev, 1, ev.n)) + if not ok then + logError("Task", param) + else + filters[i] = param + end + end + end + end + ev = table.pack(coroutine.yield()) + end +end + +local function main() + setupNeuralInterface() + while true do + parallel.waitForAny( + cycleUpdate, + function() + sleep(settings.get("minit.cycleTimeout")) + end + ) + end +end + +loadModules() +initModules() + +parallel.waitForAny( + function() + while true do + local ok, err = pcall(main) + if not ok then + if err == "Terminated" then + break + end + logError("Minit", err) + sleep(1) + end + end + end, + tasksHandler +) diff --git a/nictl b/nictl new file mode 100644 index 0000000..7e23904 --- /dev/null +++ b/nictl @@ -0,0 +1,721 @@ +local module = { + name = "ni-ctl", +} + +local username = settings.get "username" or "gollark" +local ni +local speaker = peripheral.find "speaker" +local modem = peripheral.find "modem" +local offload_laser = settings.get "offload_laser" +local w, h + +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 + + +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 function scan_entities() + local last_velocity + local last_time + + local function update_motion_vars(new_meta) + local time = os.epoch "utc" + 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.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) / 1000 + user_meta.acceleration = (user_meta.velocity - last_velocity) / timestep + end + last_velocity = user_meta.velocity + end + last_time = time + end + + while true do + fast_mode_reqs.laser = false + fast_mode_reqs.acting = false + local entities = ni.sense() + for _, entity in pairs(entities) do + if entity.displayName == username then + update_motion_vars(entity) + end + end + local maybe_players = {} + local things = {} + local lasers = {} + 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) + + fast_mode_reqs.lasers = false + + 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) + if entity.displayName ~= username then + things[entity.displayName] = (things[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.lasers = 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 + + 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 + group_3d.clear() + group_3d.recenter() + 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(things) 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 + + 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 things[entity.displayName] and things[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() else sleep(0.2) 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 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() + while true do + while not user_meta do sleep() end + if flight_shortcodes[gsettings.flight] then gsettings.flight = flight_shortcodes[gsettings.flight] end + local disp = gsettings.flight + local sleep_time = 0.1 + if ((user_meta.acceleration and ( + (user_meta.motionY + math.max(user_meta.acceleration.y, 0) < -0.3) + or (user_meta.acceleration.y < -5))) + or (not user_meta.acceleration and user_meta.motionY < -0.3)) + and gsettings.brake then + --[[if user_meta.motionY < 0 then + local stop_commands = {} + local remaining = -user_meta.motionY + 0.1 + while remaining > 0 do + local pow = math.min(4, remaining) + table.insert(stop_commands, function() ni.launch(0, 270, pow) end) + remaining = remaining - pow + end + local go_commands = {} + local remaining = -user_meta.motionY + while remaining > 0 do + local pow = math.min(4, remaining) + table.insert(go_commands, function() ni.launch(0, 90, pow) end) + remaining = remaining - pow + end + parallel.waitForAll(table.unpack(stop_commands)) + parallel.waitForAll(table.unpack(go_commands)) + else]] + ni.launch(0, 270, math.max(0.4, math.min(4, -user_meta.motionY / 3))) + sleep_time = nil + --ni.launch(0, 270, 0.4) + --end + fast_mode_reqs.flying = true + disp = disp .. " F" + end + fast_mode_reqs.flying = false + 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 + 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)) + sleep_time = nil + else + maxvel_compensatory_launch(yaw, (gsettings.flight ~= "align" and pitch) or 10, power) + sleep_time = nil + 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 + if gsettings.flight == "hover" then + fast_mode_reqs.flying = true + local mY = user_meta.motionY + sleep_time = 0 + mY = (mY - 0.138) / 0.8 + + -- If it is sufficiently large then we fire ourselves in that direction. + if mY > 0.5 or mY < 0 then + local sign = 1 + if mY < 0 then sign = -1 end + ni.launch(0, 90 * sign, math.min(4, math.abs(mY))) + sleep_time = nil + end + end + status_lines.flight = "Flight: " .. disp + if sleep_time ~= nil then sleep(sleep_time) end + 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 + +function module.init(init) + init.log("This module is WIP!") + init.addTask(function() + while true do + local cmds = { queue_handler, scan_entities, run_flight, 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 + end) +end + +function module.setup(neural) + ni = neural + w, h = ni.canvas().getSize() + + initialize_group_thing() +end + +return module diff --git a/pcm-norma-client.lua b/pcm-norma-client.lua new file mode 100644 index 0000000..8deeab4 --- /dev/null +++ b/pcm-norma-client.lua @@ -0,0 +1,110 @@ +local connectionURL = "wss://0a71-2a03-ec00-b14a-55a-39f7-fdb9-1340-2e18.ngrok-free.app" + +local function splitByChunk(text, chunkSize) + local s = {} + for i=1, #text, chunkSize do + s[#s+1] = text:sub(i,i+chunkSize - 1) + end + return s +end + +function string.starts(String,Start) + return string.sub(String,1,string.len(Start))==Start +end + +local dfpwm = require("cc.audio.dfpwm") +local ws, err = http.websocket(connectionURL) +local speakers = {peripheral.find("speaker")} +local monitor = peripheral.find("monitor") + +if monitor then + monitor.clear() + monitor.setCursorPos(1, 1) + monitor.write("yourfriend's music") + monitor.setCursorPos(1, 3) + monitor.write("currently playing:") + monitor.setCursorPos(1, 4) + + monitor.write("nothing") +end + +function restart() + print("connection with ", connectionURL, " closed!") + print("restarting in 1s") + os.sleep(1) + ws, err = http.websocket(connectionURL) + if not ws then + printError(err) + restart() + end +end + +if not ws then + restart() + printError(err) +end + + +parallel.waitForAll( + function() + while true do + local _, url = os.pullEvent("websocket_closed") + if url == connectionURL then + restart() + end + end + end, + function() + while true do + local _, url, resp, isBinary = os.pullEvent("websocket_message") + if url == connectionURL then + if not string.starts(resp, "{") then + local bytes = {("b"):rep(#resp):unpack(resp)} + bytes[#bytes]=nil + + local functions = {} + + for k,v in pairs(speakers) do + table.insert(functions, function () + while not v.playAudio(bytes, 3) do + os.pullEvent("speaker_audio_empty") + end + end) + end + + parallel.waitForAll(table.unpack(functions)) + else + local json = textutils.unserialiseJSON(resp) + if json.type == "update" then + local file = io.open(shell.getRunningProgram(), "w") + if not file then return end + file:write(json.file) + end + if json.type == "playing" then + if monitor then + local width, height = monitor.getSize() + for k=4,height do + monitor.setCursorPos(1, k) + monitor.write(string.rep(" ", width)) + end + + for k, v in pairs(splitByChunk(json.data, width)) do + monitor.setCursorPos(1, k+3) + monitor.write(v) + end + else + print("Playing: ") + print(json.data) + end + end + if json.type == "reboot" then + os.reboot() + end + if json.type == "shutdown" then + os.shutdown() + end + end + end + end + end +) diff --git a/shrekFly.lua b/shrekFly.lua new file mode 100644 index 0000000..fa3b0ab --- /dev/null +++ b/shrekFly.lua @@ -0,0 +1,282 @@ +-- shrekflight +-- Releasd under MIT by ShreksHellraiser + +-- This is a script that provides basic creative-like flight and automatic flight to coordinates using plethora. +-- Requires a keyboard, entity sensor, introspection module, and kinetic module + +-- To fly, simply double tap space, hold space to fly up, hold shift to fly down. +-- You can modify your horizontal and vertical speeds in this with the commands +-- ^vspeed and ^hspeed + +-- This also includes a ^goto command, this accepts a coordinate and will automatically fly you to that location. +-- This is not *100%* reliable, but as long as you *first* set your position ~200 blocks above the ground and then +-- set your target to where you want you should be fine. +-- supports ~ style relative coordinates for your current gps location, and . for your set goto position. +-- ^toggle will disable/reenable goto, entering fly mode by double tapping will too. + +local modules = peripheral.find("neuralInterface") +if not modules then + error("Must have a neural interface", 0) +end + +if not modules.hasModule("plethora:sensor") then error("Must have a sensor", 0) end +if not modules.hasModule("plethora:introspection") then error("Must have an introspection module", 0) end +if not modules.hasModule("plethora:kinetic", 0) then error("Must have a kinetic agument", 0) end +-- if not modules.hasModule("plethora:glasses") then error("Must have overlay glasses", 0) end + +local function start_pid(k_p, k_i, k_d) + local e, de, ie = 0, 0, 0 + ---Process a frame of the PID + ---@param dt number frametime + ---@param sp number desired setpoint + ---@param pv number process variable + return function(dt, sp, pv) + local ne = sp - pv + de = (ne - e) / dt -- instantaneous derivative + ie = ie + (ne * dt) -- summed integral + e = ne -- error + return (k_p * e) + (k_i * ie) + (k_d * de) + end +end + +local target_coords = { 0, 200, 0 } +local goto_enable = false +local hover_enable = false + +local function control() + -- assert(chatbox.isConnected(), "Chatbox isn't connected!") + while true do + local event, user, command, args, data = os.pullEvent("command") + if data.ownerOnly then + if command == "goto" then + local x, y, z = gps.locate() + for i = 1, 3 do + if args[i] then + if args[i]:sub(1, 1) == "~" and x and y and z then + -- current coords + local offset = tonumber(args[i]:sub(2, -1)) or 0 + print(offset) + target_coords[i] = ((i == 1 and x) or (i == 2 and y) or z) + offset + elseif args[i]:sub(1, 1) == "." then + -- change it + local offset = tonumber(args[i]:sub(2, -1)) or 0 + target_coords[i] = target_coords[i] + offset + elseif tonumber(args[i]) then + print(i, args[i]) + target_coords[i] = tonumber(args[i]) + end + end + end + print(("target %u %u %u"):format(target_coords[1], target_coords[2], target_coords[3])) + goto_enable = true + hover_enable = false + elseif command == "toggle" then + hover_enable = hover_enable and goto_enable + goto_enable = not goto_enable + end + end + end +end + +local function calc_xz_angle(x, z) + local xz_angle = math.atan(math.abs(x / z)) * 180 / math.pi + if z == 0 then + xz_angle = 90 + end + + if x > 0 then + xz_angle = -xz_angle -- -90 @ +x + if z < 0 then + -- -180 @ -z + xz_angle = -180 - xz_angle + end + elseif z < 0 then + -- +180 @ -z + xz_angle = -180 - xz_angle + end + return xz_angle +end + +local function calc_yxz_angle(y, xz) + local yxz_angle = -math.atan(math.abs(y / xz)) * 180 / math.pi + if xz == 0 then + yxz_angle = -90 + end + + -- hard coded boost to emphasize upward vertical movement + -- if y > 2 and yxz_angle < 10 then + -- yxz_angle = 20 + -- end + if y < 0 then + yxz_angle = -yxz_angle + end + return yxz_angle +end + +local function p_pid() + local y_pid = start_pid(0.05, 0.005, 0.06) + local x_pid = start_pid(0.1, 0, 0) + local z_pid = start_pid(0.1, 0, 0) + local t0 = os.epoch("utc") + sleep() + while true do + sleep(0.2) + local x, y, z = gps.locate() + if goto_enable and x and y and z and x == x and y == y and z == z then + local t = os.epoch("utc") + local dt = (t - t0) / 1000 + local y_impulse = y_pid(dt, target_coords[2], y) + local x_impulse = x_pid(dt, target_coords[1], x) + local z_impulse = z_pid(dt, target_coords[3], z) + local x_vec = vector.new(x_impulse, 0, 0) + local y_vec = vector.new(0, y_impulse, 0) + local z_vec = vector.new(0, 0, z_impulse) + local result = ((x_vec + y_vec + z_vec) / 3) + + local xz_hyp = math.sqrt(result.x ^ 2 + result.z ^ 2) + local xz_angle = calc_xz_angle(result.x, result.z) + local yxz_angle = calc_yxz_angle(result.y, xz_hyp) + + modules.launch(xz_angle, yxz_angle, math.min(4, math.abs(result:length()))) + t0 = t + end + end +end + +local target_vel = { 0, 0, 0 } +local y_impulse_scale = 0.5 +local k_p = 4 +local owner = modules.getMetaOwner() +local function v_pid() + local x_pid = start_pid(0.1, 0, 0) + local z_pid = start_pid(0.1, 0, 0) + local t0 = os.epoch("utc") + sleep() + while true do + local t = os.epoch("utc") + while hover_enable and not goto_enable do + t = os.epoch("utc") + sleep(0) + owner = modules.getMetaOwner() + if not owner.isAirborne then + hover_enable = false + end + local dt = (t - t0) / 1000 + local ticks = dt / 0.05 + local x, y, z = owner.motionX, owner.motionY, owner.motionZ + + local y_vel_err = (0.08 * ticks) + target_vel[2] - y + -- IMPULSE STRENGTH LINEARLY CORROLATES TO VELOCITY + local y_impulse = k_p * y_vel_err / y_impulse_scale + + local x_impulse = x_pid(dt, target_vel[1], x) + local z_impulse = z_pid(dt, target_vel[3], z) + + local x_vec = vector.new(x_impulse, 0, 0) + local y_vec = vector.new(0, y_impulse, 0) + local z_vec = vector.new(0, 0, z_impulse) + local result = ((x_vec + y_vec + z_vec) / 3) + local xz_hyp = math.sqrt(result.x ^ 2 + result.z ^ 2) + local xz_angle = calc_xz_angle(result.x, result.z) + local yxz_angle = calc_yxz_angle(result.y, xz_hyp) + local power = math.min(4, math.abs(result:length())) + + modules.launch(xz_angle, yxz_angle, power) + t0 = t + end + os.pullEvent("velocity_control") + end +end + +local function wrap_angle(t) + if t > 180 then + t = -360 + t + elseif t < -180 then + t = 360 + t + end + return t +end + +local function rad(deg) + return deg / 180 * math.pi +end + +local function deg(rad) + return rad * 180 / math.pi +end + +local v_speed = 0.5 +local sprinting_speed = 6 + +local held_keys = {} +local function get_v_vectors() + local theta = owner.yaw + local result = vector.new(0, 0, 0) + local forward_power = ((held_keys[keys.w] and 1) or (held_keys[keys.s] and -1) or 0) + local sideways_power = ((held_keys[keys.d] and -1) or (held_keys[keys.a] and 1) or 0) + local h_power = 3 + if owner.isSprinting then + h_power = sprinting_speed + end + result.x = result.x + h_power * math.cos(rad(theta)) * sideways_power + result.z = result.z + h_power * math.sin(rad(theta)) * sideways_power + result.x = result.x + h_power * math.cos(rad(wrap_angle(theta + 90))) * forward_power + result.z = result.z + h_power * math.sin(rad(wrap_angle(theta + 90))) * forward_power + return result.x, result.z +end + +local function v_control() + local space_presses = 0 + local space_press_timer + local t0 = os.epoch("utc") + while true do + local event, key, command, args, data = os.pullEvent() + local t = os.epoch("utc") + local dt = (t - t0) / 1000 + if event == "key" then + if key == keys.space or key == keys.h then + if not held_keys[keys.space] then + space_presses = space_presses + 1 + end + if space_presses > 1 or key == keys.h then + -- double tapped + hover_enable = not hover_enable + goto_enable = false + print("double tap", hover_enable) + os.queueEvent("velocity_control") + space_presses = 0 + else + target_vel[2] = v_speed + end + else + space_presses = 0 + end + if key == keys.leftShift then + target_vel[2] = -v_speed + end + held_keys[key] = true + elseif event == "key_up" then + held_keys[key] = false + if key == keys.space or key == keys.leftShift then + if space_press_timer then + os.cancelTimer(space_press_timer) + end + space_press_timer = os.startTimer(0.5) + target_vel[2] = 0 + end + elseif event == "timer" and key == space_press_timer then + space_presses = 0 + elseif event == "command" and data.ownerOnly then + local speed = tonumber(args[1]) + if command == "hspeed" and speed then + sprinting_speed = speed --[[@as number]] + elseif command == "vspeed" and speed then + v_speed = speed + end + end + t0 = t + target_vel[1], target_vel[3] = get_v_vectors() + end +end + +parallel.waitForAny(v_pid, v_control, p_pid, control)