add path blocked detection

This commit is contained in:
方而静 2023-10-31 16:32:06 +08:00
parent 0f2837029e
commit 01132299b5
Signed by: szTom
GPG Key ID: 072D999D60C6473C
4 changed files with 111 additions and 27 deletions

View File

@ -43,7 +43,7 @@ export class Account {
password: this.password,
})).data;
return new SessionCache(auth_info.accessToken, auth_info.clientToken
return new YggdrasilSession(auth_info.accessToken, auth_info.clientToken
, auth_info.selectedProfile, auth_info.availableProfiles);
}
@ -78,7 +78,7 @@ export class Credentials {
}
async authProfile(profile) {
let sc = await SessionCache.load(profile);
let sc = await YggdrasilSession.load(profile);
if (sc != null && !(await sc.validate(this.endpoint))) { sc = null; }
if (sc == null) {
@ -92,7 +92,7 @@ export class Credentials {
}
};
export class SessionCache {
export class YggdrasilSession {
constructor(accessToken, clientToken, selectedProfile, availableProfiles) {
this.accessToken = accessToken;
this.clientToken = clientToken;
@ -120,7 +120,7 @@ export class SessionCache {
static async load(profile) {
let cache_data = await readJsonFile(`.cache/${profile}.json`);
if (cache_data == null) { return null; }
return new SessionCache(cache_data.accessToken, cache_data.clientToken, cache_data.selectedProfile);
return new YggdrasilSession(cache_data.accessToken, cache_data.clientToken, cache_data.selectedProfile);
}
async validate(endpoint) {
@ -181,3 +181,25 @@ export class SessionCache {
};
}
};
export class OfflineSession {
constructor(username) {
this.username = username;
}
name() { return this.username; }
session() { return null; }
async store() {}
static async load(profile) { return new OfflineSession(profile); }
async validate() { return true; }
async selectProfile(profile) { this.username = profile; }
mineflayer() {
if (this.username == null) { throw new ProfileNotSelectedError(); }
return {
username: this.name(),
auth: 'offline',
};
}
}

View File

@ -3,7 +3,7 @@ import mineflayer from 'mineflayer';
import yargs from 'yargs';
import { parseLogin, waitEvent } from 'compass-utils';
import repl from 'node:repl';
import vm from 'node:vm';
import debug from 'debug';
async function main() {
const args = yargs((await import('yargs/helpers')).hideBin(process.argv))
@ -22,37 +22,62 @@ async function main() {
description: 'Credentials\' library file.',
type: "string",
default: "credentials.json",
}).option('offline', {
description: 'Login without credentials.',
type: 'boolean',
}).usage('Uasge: profile@host:port').help().alias('help', 'h').argv;
let login_info = args._[0];
if (login_info == null) { return; }
const [name, host, port] = parseLogin(login_info);
const credential_info = await authlib.Credentials.fromFile(args.credentialsLib);
if (credential_info == null) {
throw new Error(`Cannot load credential ${args.credentialsLib}`);
let session, endpoint = null;
if (args.offline) {
session = new authlib.OfflineSession(name);
} else {
const credential_info = await authlib.Credentials.fromFile(args.credentialsLib);
if (credential_info == null) {
throw new Error(`Cannot load credential ${args.credentialsLib}`);
}
session = await credential_info.authProfile(name);
endpoint = credential_info.endpoint;
}
const session = await credential_info.authProfile(name);
const bot = mineflayer.createBot({
host, port, version: args.protocal,
...session.mineflayer(credential_info.endpoint)
...session.mineflayer(endpoint)
});
bot.on('error', console.error);
bot.on('kicked', console.log);
bot.on('end', () => {
console.log('Disconnected. Exiting...');
process.exit(0);
});
await waitEvent(bot, 'inject_allowed');
bot.loadPlugin((await import('mineflayer-event-promise')).default);
bot.loadPlugin((await import('mineflayer-control')).default);
await bot.waitEvent('spawn');
let context = vm.createContext();
context.bot = bot;
context.Vec3 = (await import('vec3')).Vec3;
context.mineflayer = mineflayer;
context.owner = () => {
if (!args.owner) { return null; }
return bot.players[args.owner];
};
async function loadReplContextModules(context) {
context.lib = {
utils: await import('compass-utils'),
control: await import('mineflayer-control'),
};
context.bot = bot;
context.Vec3 = (await import('vec3')).Vec3;
context.mineflayer = mineflayer;
context.owner = () => {
if (!args.owner) { return null; }
return bot.players[args.owner];
};
context.sc = {};
context.sc.pos = () => bot.entity.position;
context.sc.debug_enable = (module) => debug.enable(module);
context.sc.debug_disable = (module) => debug.disable(module);
}
if (!args.noRepl) {
let r = repl.start({
prompt: 'local > ',
@ -62,7 +87,7 @@ async function main() {
terminal: true,
ignoreUndefined: true,
});
r.context = context;
loadReplContextModules(r.context);
}
}

View File

@ -1,5 +1,5 @@
import debug from 'debug';
import { Task } from 'compass-utils';
import { Queue, Task } from 'compass-utils';
import { Vec3 } from 'vec3';
const logger = debug('mineflayer-control');
@ -79,6 +79,10 @@ export class MoveInterferedError extends Error {
constructor() { super('Move task has been interfered by an external force.'); }
};
export class MovePathBlockedError extends Error {
constructor() { super('Move path is possiblely blocked.'); }
};
async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
const axis = AXIS[axis_raw];
const stable_axis = "xz"[axis % 2];
@ -98,7 +102,8 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
logger(`moveAxisTask() distance: ${remaining_dis}.`);
logger(`moveAxisTask() stable_axis: ${stable_axis}.`);
logger(`moveAxisTask() Condition: ${delta[stable_axis]} ${delta.y}.`);
if (Math.abs(delta.y) > Number.EPSILON || Math.abs(delta[stable_axis]) > Number.EPSILON) {
if (Math.abs(delta.y) > 0.5 + Number.EPSILON
|| Math.abs(delta[stable_axis]) > Number.EPSILON) {
throw new Error('Invalid Argument: target');
}
@ -120,7 +125,9 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
controls.apply(bot);
logger('moveAxisTask() started.');
let time_used = 0;
let time_used = 0, pos_queue = new Queue();
const TRACK_TICKS = 5;
pos_queue.push(pos.clone());
do {
await bot.waitForTicks(1);
task._interuptableHere();
@ -134,13 +141,33 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
logger(`moveAxisTask() pos.${stable_axis}: ${pos[stable_axis]}.`);
throw new MoveInterferedError();
}
if (Math.abs(pos.y - target.y) > 0.5 + Number.EPSILON) {
logger('moveAxisTask() y changed to much.');
logger(`moveAxisTask() target.y=${target.y} vs. pos.y=${pos.y}`);
throw new MoveInterferedError();
}
pos[stable_axis] = stable_axis_value;
pos_queue.push(pos.clone());
if (pos_queue.size() > TRACK_TICKS) { pos_queue.popFront(); }
if (pos_queue.size() == 5) {
let pos5t = pos_queue.front();
if (pos.distanceSquared(pos5t) < Number.EPSILON) {
logger('moveAxisTask() position changed too little.');
logger(`moveAxisTask() position 5 ticks ago: ${pos5t}.`);
logger(`moveAxisTask() position now: ${pos}.`);
throw new MovePathBlockedError();
}
}
delta.update(target.minus(pos));
remaining_dis = delta.dot(AXIS_UNIT[axis]);
if (Math.abs(remaining_dis) <= 0.5) {
logger('moveAxisTask() very close to target now.');
pos.update(target);
pos.x = target.x;
pos.z = target.z;
bot.entity.velocity.x = 0;
bot.entity.velocity.z = 0;
break;
@ -162,12 +189,14 @@ export default function inject(bot) {
bot.control.moveAxis = (axis, target, level = MOVE_LEVEL.SPRINT) => {
let task = new Task();
queueMicrotask(() => {
task._start();
moveAxisTask(bot, task, axis, target, level).catch(err => {
queueMicrotask(async () => {
try {
task._start();
await moveAxisTask(bot, task, axis, target, level);
} catch(err) {
bot.clearControlStates();
task._fail(err);
});
};
});
return task;
};

View File

@ -2,12 +2,15 @@ export class TaskInteruptedError extends Error {
constructor() { super('Task has been interupted.'); }
};
let task_id = 0;
export class Task {
#promise;
#resolve;
#reject;
constructor() {
this.id = ++task_id;
this.status = Task.STATUS.pending;
this.error = null;
this.result = null;
@ -80,11 +83,16 @@ export class Task {
valueOf() {
return {
id: this.id,
status: Task.STATUS_NAME[this.status],
result: this.result,
error: this.error,
};
}
toString() {
return `[Task ${this.id}: ${Task.STATUS_NAME[this.status]}]`;
}
};
Task.STATUS_NAME = {