From 6df6dbcaf673e5432364e2a3edb6c9754fbc0a7e Mon Sep 17 00:00:00 2001 From: szdytom Date: Thu, 2 Nov 2023 08:30:59 +0800 Subject: [PATCH] [plugin/flyctl] add `.ascend()` --- index.mjs | 5 ++ package.json | 3 +- plugin/fly-control/index.mjs | 146 ++++++++++++++++++++++++++++++++ plugin/fly-control/package.json | 11 +++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 plugin/fly-control/index.mjs create mode 100644 plugin/fly-control/package.json diff --git a/index.mjs b/index.mjs index f0dcc24..ddf4cff 100644 --- a/index.mjs +++ b/index.mjs @@ -57,6 +57,7 @@ async function main() { await waitEvent(bot, 'inject_allowed'); bot.loadPlugin((await import('mineflayer-event-promise')).default); bot.loadPlugin((await import('mineflayer-control')).default); + bot.loadPlugin((await import('mineflayer-fly-control')).default); await bot.waitEvent('spawn'); async function loadReplContextModules(context) { @@ -76,8 +77,10 @@ async function main() { context.sc = {}; context.sc.pos = () => bot.entity.position; context.sc.debug_mfc = () => debug.enable('mineflayer-control'); + context.sc.debug_mff = () => debug.enable('mineflayer-fly-control'); context.sc.q = () => bot.quit(); context.sc.sleep = asyncSleep; + context.sc.tossHeld = () => bot.tossStack(bot.heldItem); } if (!args.noRepl) { @@ -89,6 +92,8 @@ async function main() { terminal: true, ignoreUndefined: true, }); + + r.on('exit', () => bot.quit()); loadReplContextModules(r.context); } } diff --git a/package.json b/package.json index 45f3fc2..d38be23 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "mineflayer-event-promise": "file:plugin/event-promise", "compass-utils": "file:utils", "mineflayer-control": "file:plugin/control", - "mineflayer-pathfinder": "^2.4.5", - "prismarine-viewer": "^1.25.0", + "mineflayer-fly-control": "file:plugin/fly-control", "yargs": "^17.7.2" }, "engines": { diff --git a/plugin/fly-control/index.mjs b/plugin/fly-control/index.mjs new file mode 100644 index 0000000..9ce591d --- /dev/null +++ b/plugin/fly-control/index.mjs @@ -0,0 +1,146 @@ +import assert from 'node:assert/strict'; +import debug from 'debug'; +import { Task } from 'compass-utils'; +const logger = debug('mineflayer-fly-control'); + +export class ElytraNotEquippedError extends Error { + constructor() { super('Elytra is not equipped!'); } +} + +export class InsufficientRocketError extends Error { + constructor(flight_requirement, flight_actual) { + super(`Expected ${flight_requirement} flight in total, got ${flight_actual}`); + this.flight_requirement = flight_requirement; + this.flight_actual = flight_actual; + } +}; + +export class AlreadyElytraFlyingError extends Error { + constructor() { super('Already elytra flying!'); } +} + +export function fireworkFlight(firework_item) { + return firework_item?.nbt?.value?.Fireworks?.value?.Flight?.value ?? 1; +} + +async function takeOffTask(bot, task) { + if (bot.entity.elytraFlying) { throw new AlreadyElytraFlyingError(); } + if (bot.entity.onGround) { + await task._waitDependent(bot.control.jumpToHighest()); + task._interuptableHere(); + } + + await bot.elytraFly(); + task._interuptableHere(); +} + +async function ascendTask(bot, task, flight, gracefulMode) { + assert.ok(typeof flight == 'number'); + bot.control.adjustXZ(); + await bot.look(0, Math.PI / 2, true); + task._interuptableHere(); + + if (!bot.entity.elytraFlying) { await takeOffTask(bot, task); } + await bot.waitForTicks(1); + task._interuptableHere(); + + function gracefulModePredicate() { + return bot.entity.velocity.y >= 0.01; + } + + function fastModePredicate() { + return bot.fireworkRocketDuration > 0; + } + + const predicate = gracefulMode ? gracefulModePredicate : fastModePredicate; + + let flight_pre_rocket = fireworkFlight(bot.heldItem); + for (let i = 0; i < flight; i += flight_pre_rocket) { + bot.activateItem(); + do { + await bot.waitForTicks(Math.max(1, bot.fireworkRocketDuration)); + task._interuptableHere(); + } while (predicate()); + } +} + +export default function inject(bot) { + const firework_id = bot.registry.itemsByName.firework_rocket.id; + const elytra_id = bot.registry.itemsByName.elytra.id; + const elytra_slot = bot.getEquipmentDestSlot('torso'); + + bot.flyctl = {}; + bot.flyctl.skipValidation = false; + function beforeFlyValidation(flight_requirement = 0) { + assert.ok(typeof flight_requirement == 'number'); + assert.ok(flight_requirement >= 0); + + if (bot.flyctl.skipValidation) { + logger('beforeFlyValidation() skipped.'); + return; + } + + logger(`beforeFlyValidation() elytra slot: ${elytra_slot}`); + let elytra_slot_item = bot.inventory.slots[elytra_slot]?.type; + if (elytra_slot_item != elytra_id) { + logger(`beforeFlyValidation() failed: elytra slot found ${elytra_slot_item}.`); + logger(`beforeFlyValidation() expected ${elytra_id}.`); + throw new ElytraNotEquippedError(); + } + + if (flight_requirement > 0) { + let rocket_item = bot.heldItem; + if (rocket_item?.type != firework_id) { + logger('beforeFlyValidation() failed: holding is not rocket.'); + logger(`beforeFlyValidation() found ${rocket_item?.type} expected ${firework_id} .`); + throw new InsufficientRocketError(flight_requirement, 0); + } + + let flight_sum = rocket_item.count * fireworkFlight(rocket_item); + if (flight_sum < flight_requirement) { + throw new InsufficientRocketError(flight_requirement, flight_sum); + } + } + + logger('beforeFlyValidation() passed.'); + } + + bot.flyctl.prepare = async () => { + await bot.equip(elytra_id, 'torso'); + await bot.equip(firework_id); + }; + + bot.flyctl.ascend = async (flight = 1, gracefulMode = true) => { + let task = new Task(); + queueMicrotask(async () => { + try { + beforeFlyValidation(flight); + task._ready(await ascendTask(bot, task, flight, gracefulMode)); + } catch(err) { + task._fail(err); + } + }); + return task; + }; + + bot.flyctl.gracefulAscend = (flight = 1) => { + return bot.flyctl.ascend(flight, true); + }; + + bot.flyctl.fastAscend = (flight = 1) => { + return bot.flyctl.ascend(flight, false); + }; + + bot.flyctl.takeOff = () => { + let task = new Task(); + queueMicrotask(async () => { + try { + beforeFlyValidation(flight); + task._ready(await takeOffTask(bot, task)); + } catch(err) { + task._fail(err); + } + }); + return task; + }; +} diff --git a/plugin/fly-control/package.json b/plugin/fly-control/package.json new file mode 100644 index 0000000..b10bfc2 --- /dev/null +++ b/plugin/fly-control/package.json @@ -0,0 +1,11 @@ +{ + "name": "mineflayer-fly-control", + "description": "High-level & handy API for mineflayer bot elytra flying.", + "type": "module", + "main": "index.mjs", + "dependencies": { + "debug": "^4.3.4", + "vec3": "^0.1.8", + "compass-utils": "file:../../utils" + } +}