-- This is paid software, do not distribute, Rule 6.5 -- Settings, change them for different effects local ownerUserName = "jasper_185" -- If set uses getMetaByName instead of getMetaOwner when getMetaOwner isn't available local maxSpeed = 1 -- The speed of the last slot, note: it is impossible to go over speed 4, and trying to go over that limit will cause your movement direction to not sync up with your actual velocity "pushing" you towards the diagonals local launchMult = 1 -- Higher values = stronger launch, may cause overshoot if too high local baseSlotSpeed = 1 -- The base speed you got at, if set to 0 your slot 0 will not let you move at all, should be 0 if not using a keyboard module (WHY ARE YOU NOT USING IT? ITS AWESOME AND CHEAP) local speedPower = 0 -- The power the speed is raised to (0 = constant speed, 1 = linear speed increase from each slot, 2 = exponential speed increase) local sprintSpeedIncrease = 4 -- Whenever we are sprinting, how much faster do we go? local gpsHover = true -- Do we use the GPS to stay still in the air while we are not moving? local gpsStrength = 0.025 -- How strong is the GPS correction? (really doesn't need to be strong, as base hover is already really good and instead just makes it less stable) local pitchChangesForward = false-- If you are looking up and pressing forward do you go up or do you go horizontal? local doubleTapUpToToggle = 0.3 --t Whats the duration of time to toggle flight by spamming the up key? set to nil or to a negative number to disable and use a dedicated toggle key instead local lagCompensation = true -- Do we try calculate the amount of lag we have, and try to compensate for that? local smoothing = 0.65 -- How smooth is the flight? 0 = no smoothing, 1 = so smooth you will never start moving, 0.9 = pretty smooth local autoDisengageOnGroundTouch = true -- Do we disable flight once we touch the ground? -- Still settings, but more technical local gravityCancelation = 0.08 -- How strong gravity is, too low and you will fall down, too high and you will shoot up (The constant I for the Y PID) local drag = 0.98 -- What is the drag local waterGravityCancelation = 0.01 -- Water version of the above local PStrength = 1 -- Strenght of P in PD settings local DStrength = 1 -- Strenght of D in PD settings local rateLimitAtPower = 1.5 -- Going above this power rate limits us local yMult = 0.5 -- Launching in the Y dir is less strong local elytraMult = 0.4 -- While elytra flying all dirs are less strong local maxXZRatio = 0.8 -- How much X and Z axes can be reduced for more power in the Y axis before stopping, lowering this too much will give bad controll when going at high speeds, but having it too high will make hoving slightly less stable -- Keybinds local forwardKey = keys.w; local backKey = keys.s; local leftKey = keys.a; local rightKey = keys.d; local upKey = keys.space; local downKey = keys.leftShift; local SprintKey = keys.r; -- Lets you hold this key to enable sprint mode, so it also lets you go at sprint speed backwards local toggleFlightKey = keys.g; -- Only used when doubleTapSpaceToToggle is set to false -- Internal code, don't change unless you know what you are doing! local modules = peripheral.wrap("back") local hasKeyboard = modules.hasModule("plethora:keyboard") local pressedKeys = {} -- Global for isActive local flightActive = false local lastGroundTouch = os.clock() local function isFlightActiveRaw(meta) if hasKeyboard then if autoDisengageOnGroundTouch then if lastGroundTouch + 0.5 < os.clock() and not meta.isAirborne then flightActive = false end if not meta.isAirborne then lastGroundTouch = os.clock() end end return flightActive -- Toggled by keyboard module end -- Enable flight when sneaking and having a decent Y vel so it doesn't activate when just holding shift on a block if meta.isSneaking then if not flightActive then flightActive = math.abs(meta.motionY) > 0.2 end else flightActive = false end return flightActive end -- Calculates your desired speed local isSprinting = false local function getSpeedRaw(meta) local selectedSlot = meta.heldItemSlot local speed = math.pow((selectedSlot + baseSlotSpeed) / (9 + baseSlotSpeed), speedPower) * maxSpeed; if meta.isSprinting or pressedKeys[SprintKey] then isSprinting = true elseif not pressedKeys[forwardKey] and not pressedKeys[backKey] and not pressedKeys[leftKey] and not pressedKeys[rightKey] and not pressedKeys[upKey] and not pressedKeys[downKey] then -- Not great, but oh well isSprinting = false end if isSprinting then speed = speed * sprintSpeedIncrease end return speed end -- Calculates your desired movement direction local gpsX, gpsY, gpsZ local function getDesiredDirRaw(meta) -- If you want a goto function, you can calculate the direction to the next waypoint and return that here for example if not hasKeyboard then local pitch = -math.rad(meta.pitch) local yaw = math.rad(meta.yaw) local dx = -math.sin(yaw) * math.cos(pitch) local dy = math.sin(pitch) local dz = math.cos(yaw) * math.cos(pitch) return dx, dy, dz else local pitch = -math.rad(meta.pitch) if not pitchChangesForward then pitch = 0 end local yaw = math.rad(meta.yaw) local fx = -math.sin(yaw) * math.cos(pitch) local fy = math.sin(pitch) local fz = math.cos(yaw) * math.cos(pitch) local sx = math.cos(yaw) local sz = math.sin(yaw) local forward = (pressedKeys[forwardKey] and 1 or 0) + (pressedKeys[backKey] and -1 or 0) local left = (pressedKeys[leftKey] and 1 or 0) + (pressedKeys[rightKey] and -1 or 0) local up = (pressedKeys[upKey] and 1 or 0) + (pressedKeys[downKey] and -1 or 0) return fx * forward + sx * left, fy * forward + up, fz * forward + sz * left end end -- Below is the actual math / logic, i don't recommend changing this -- Overrides local defaultIsFlightActiveOverride = function(defaultFlightActive, meta) return defaultFlightActive end local defaultGetSpeedOverride = function(defaultSpeed, meta) return defaultSpeed end local defaultGetDesiredDirOverride = function(defaultDirX, defaultDirY, defaultDirZ, meta) return defaultDirX, defaultDirY, defaultDirZ end local defaultMotionOverride = function(defaultMotionX, defaultMotionY, defaultMotionZ, meta) return defaultMotionX, defaultMotionY, defaultMotionZ end local isFlightActiveOverride = defaultIsFlightActiveOverride local getSpeedOverride = defaultGetSpeedOverride local getDesiredDirOverride = defaultGetDesiredDirOverride local motionOverride = defaultMotionOverride local function isFlightActive(meta) return isFlightActiveOverride(isFlightActiveRaw(meta), meta) end local function getSpeed(meta) return getSpeedOverride(getSpeedRaw(meta), meta) end local function getDesiredDir(meta) local x, y, z = getDesiredDirRaw(meta) return getDesiredDirOverride(x, y, z, meta) end -- utility function to convert the launch(yaw, pitch, speed) into a launch(x, y, z) local lastLaunchX = 0 local lastLaunchY = 0 local lastLaunchZ = 0 local function launch(x, y, z, isElytraFlying) lastLaunchX = x lastLaunchY = y lastLaunchZ = z y = y / yMult if isElytraFlying then x = x / elytraMult y = y / elytraMult z = z / elytraMult end local speed = math.sqrt(x * x + y * y + z * z) if speed <= 0 then return end local launchSpeed = speed if launchSpeed > 4 then launchSpeed = 4 end if launchSpeed > rateLimitAtPower then launchSpeed = rateLimitAtPower end if launchSpeed ~= speed then -- reduce X and Z, but keep Y the same -- This way hover is more stable when moving around quickly -- ((x*x+z*z)*M + y * y)^0.5 = launchSpeed if math.abs(y) < launchSpeed then local ratio = (launchSpeed * launchSpeed - y * y) / (x * x + z * z) if ratio > maxXZRatio then lastLaunchX = lastLaunchX * ratio lastLaunchZ = lastLaunchZ * ratio speed = launchSpeed end elseif maxXZRatio <= 0 then -- Okay, just using the Y is requiring more speed than we have -- Set x and z to 0, and just maximize Y for what we can. not ideal. x = 0 z = 0 speed = math.abs(y) end end local yaw = math.deg(math.atan2(-x, z)) local pitch = math.deg(math.asin(-y / speed)) if yaw ~= yaw or pitch ~= pitch then return end -- Check for NaN's if launchSpeed ~= speed then local ratio = launchSpeed / speed lastLaunchX = lastLaunchX * ratio lastLaunchY = lastLaunchY * ratio lastLaunchZ = lastLaunchZ * ratio end coroutine.resume(coroutine.create(function() modules.launch(yaw, pitch, launchSpeed) end)) end -- Gps locking globals local lockY -- Do you want to lock your coord? local desiredGpsY -- What are your locked coords? -- For smoothing local lastDesiredX, lastDesiredY, lastDesiredZ = 0, 0, 0 local function flyLoop() local lastIngame = os.epoch("ingame") local lastUtc = os.epoch("utc") local expectedYVel = 0 while true do local meta if ownerUserName and not modules.getMetaOwner then meta = modules.getMetaByName(ownerUserName) else meta = modules.getMetaOwner() end -- Fix inconsistency with meta input (it can be from before the launch, or after the launch, so add the expected launch speed if we are behind) if math.abs(meta.motionY - expectedYVel) < math.abs(meta.motionY - expectedYVel - lastLaunchY) then meta.motionX = meta.motionX + lastLaunchX meta.motionY = meta.motionY + lastLaunchY meta.motionZ = meta.motionZ + lastLaunchZ end local gravity if meta.isInWater then gravity = waterGravityCancelation else gravity = gravityCancelation end expectedYVel = (meta.motionY - gravity) * drag local nowIngame = os.epoch("ingame") local nowUtc = os.epoch("utc") local estimatedServerTicks = (nowIngame - lastIngame) / 3600 local estimatedClientTicks = math.floor((nowUtc - lastUtc) / 50 + 0.5) lastIngame = nowIngame lastUtc = lastUtc + estimatedClientTicks * 50 estimatedServerTicks = 1 -- Its better this way if not lagCompensation then estimatedClientTicks = 1 end if not meta then modules = peripheral.wrap("back") -- Fix it in case of death -- But also don't sleep as FOR SOME REASON that can happen when you fly into unloaded chunks -- And also don't restart for the same reason.. UGH! -- There is just no good way to handle this end if meta and isFlightActive(meta) then local speed = getSpeed(meta) -- Calculate D local cx = -meta.motionX * drag * DStrength local cy = -(meta.motionY - gravity) * drag * DStrength local cz = -meta.motionZ * drag * DStrength local dx, dy, dz = getDesiredDir(meta) -- Apply speed dx = dx * speed dy = dy * speed dz = dz * speed dx, dy, dz = motionOverride(dx, dy, dz, meta) -- Apply smoothing local canLockY = dy == 0 -- We need this before smoothing for gps hover dx = dx + (lastDesiredX - dx) * smoothing dy = dy + (lastDesiredY - dy) * smoothing dz = dz + (lastDesiredZ - dz) * smoothing lastDesiredX = dx lastDesiredY = dy lastDesiredZ = dz -- Calculate P dx = dx * PStrength dy = dy * PStrength dz = dz * PStrength if gpsHover then -- Only start to lock the coords once we have slowed down to prevent a "jerk back" effect due to the GPS coords we have being slightly behind lockY = canLockY and math.abs(dy) <= 0.001 and (lockY or math.abs(meta.motionY) < (0.1 * estimatedServerTicks)) -- Clear if it we do not want to lock the axis if not lockY then desiredGpsY = nil end -- Set our desired movement direction towards our locked position if lockY and desiredGpsY and gpsY then dy = (desiredGpsY - gpsY) * gpsStrength end end launch( (cx + dx) * launchMult, (cy + dy) * launchMult + gravity * (estimatedServerTicks + estimatedClientTicks - 1), (cz + dz) * launchMult, meta.isElytraFlying ) else lockY = false desiredGpsY = nil end end end local lastUpKeyPressed = 0 local function keyboardInputLoop() while true do local data = {os.pullEvent()} if data[1] == "key_up" then pressedKeys[data[2]] = false -- Toggle flight logic if doubleTapUpToToggle and doubleTapUpToToggle > 0 then if data[2] == upKey then lastUpKeyPressed = os.epoch("utc") end end elseif data[1] == "key" then -- Toggle flight logic if doubleTapUpToToggle and doubleTapUpToToggle > 0 then if data[2] == upKey then local now = os.epoch("utc") if now - doubleTapUpToToggle * 1000 < lastUpKeyPressed then flightActive = not flightActive lastUpKeyPressed = 0 end end elseif data[2] == toggleFlightKey and not pressedKeys[data[2]] then flightActive = not flightActive end pressedKeys[data[2]] = true end end end local overridesRequireGPS = false local function updateGpsLoop() while true do if lockY or overridesRequireGPS then overridesRequireGPS = false gpsX, gpsY, gpsZ = gps.locate() if not gpsX or gpsX ~= gpsX then -- NaN(i)? gpsX = nil gpsY = nil gpsZ = nil elseif not desiredGpsY then desiredGpsY = gpsY end end sleep(0.2) -- We don't need to spam the GPS so hard end end ---Gets the current estimated coords, can return nil when calling for the first couple time or gps failed ---@return number | nil x ---@return number | nil y ---@return number | nil z local function getCoords() overridesRequireGPS = true return gpsX, gpsY, gpsZ end local function doFlyloop() parallel.waitForAny(flyLoop, keyboardInputLoop, updateGpsLoop) end local args = {...} if #args > 0 then print("running as flight library (args detected), call doFlightLoop to start flying!") ---@param overrideFunc (fun(defaultIsFlightActive: boolean, playerMeta: table): boolean) | nil local function setIsFlightActiveOverride(overrideFunc) isFlightActiveOverride = overrideFunc or defaultIsFlightActiveOverride end ---@param overrideFunc (fun(defaultSpeed: number, playerMeta: table): number) | nil local function setGetSpeedOverride(overrideFunc) getSpeedOverride = overrideFunc or defaultGetSpeedOverride end ---@param overrideFunc (fun(defaultDesiredX: number, defaultDesiredY: number, defaultDesiredZ: number, playerMeta: table): number, number, number) | nil local function setGetDesiredDirOverride(overrideFunc) getDesiredDirOverride = overrideFunc or defaultGetDesiredDirOverride end ---@param overrideFunc (fun(defaultMotionX: number, defaultMotionY: number, defaultMotionZ: number, playerMeta: table): number, number, number) | nil local function setMotionOverride(overrideFunc) motionOverride = overrideFunc or defaultMotionOverride end ---@param newFlightActive boolean local function setBaseFlightActive(newFlightActive) flightActive = newFlightActive end return { doFlyloop = doFlyloop, getCoords = getCoords, pressedKeys = pressedKeys, setBaseFlightActive = setBaseFlightActive, setIsFlightActiveOverride = setIsFlightActiveOverride, setGetSpeedOverride = setGetSpeedOverride, setGetDesiredDirOverride = setGetDesiredDirOverride, setMotionOverride = setMotionOverride, } else doFlyloop() end -- Created by jasper_185