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, password: this.password,
})).data; })).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); , auth_info.selectedProfile, auth_info.availableProfiles);
} }
@ -78,7 +78,7 @@ export class Credentials {
} }
async authProfile(profile) { 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 && !(await sc.validate(this.endpoint))) { sc = null; }
if (sc == null) { if (sc == null) {
@ -92,7 +92,7 @@ export class Credentials {
} }
}; };
export class SessionCache { export class YggdrasilSession {
constructor(accessToken, clientToken, selectedProfile, availableProfiles) { constructor(accessToken, clientToken, selectedProfile, availableProfiles) {
this.accessToken = accessToken; this.accessToken = accessToken;
this.clientToken = clientToken; this.clientToken = clientToken;
@ -120,7 +120,7 @@ export class SessionCache {
static async load(profile) { static async load(profile) {
let cache_data = await readJsonFile(`.cache/${profile}.json`); let cache_data = await readJsonFile(`.cache/${profile}.json`);
if (cache_data == null) { return null; } 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) { 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 yargs from 'yargs';
import { parseLogin, waitEvent } from 'compass-utils'; import { parseLogin, waitEvent } from 'compass-utils';
import repl from 'node:repl'; import repl from 'node:repl';
import vm from 'node:vm'; import debug from 'debug';
async function main() { async function main() {
const args = yargs((await import('yargs/helpers')).hideBin(process.argv)) const args = yargs((await import('yargs/helpers')).hideBin(process.argv))
@ -22,30 +22,48 @@ async function main() {
description: 'Credentials\' library file.', description: 'Credentials\' library file.',
type: "string", type: "string",
default: "credentials.json", default: "credentials.json",
}).option('offline', {
description: 'Login without credentials.',
type: 'boolean',
}).usage('Uasge: profile@host:port').help().alias('help', 'h').argv; }).usage('Uasge: profile@host:port').help().alias('help', 'h').argv;
let login_info = args._[0]; let login_info = args._[0];
if (login_info == null) { return; } if (login_info == null) { return; }
const [name, host, port] = parseLogin(login_info); const [name, host, port] = parseLogin(login_info);
let session, endpoint = null;
if (args.offline) {
session = new authlib.OfflineSession(name);
} else {
const credential_info = await authlib.Credentials.fromFile(args.credentialsLib); const credential_info = await authlib.Credentials.fromFile(args.credentialsLib);
if (credential_info == null) { if (credential_info == null) {
throw new Error(`Cannot load credential ${args.credentialsLib}`); 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({ const bot = mineflayer.createBot({
host, port, version: args.protocal, host, port, version: args.protocal,
...session.mineflayer(credential_info.endpoint) ...session.mineflayer(endpoint)
}); });
bot.on('error', console.error); bot.on('error', console.error);
bot.on('kicked', console.log); bot.on('kicked', console.log);
bot.on('end', () => {
console.log('Disconnected. Exiting...');
process.exit(0);
});
await waitEvent(bot, 'inject_allowed'); await waitEvent(bot, 'inject_allowed');
bot.loadPlugin((await import('mineflayer-event-promise')).default); bot.loadPlugin((await import('mineflayer-event-promise')).default);
bot.loadPlugin((await import('mineflayer-control')).default); bot.loadPlugin((await import('mineflayer-control')).default);
await bot.waitEvent('spawn'); await bot.waitEvent('spawn');
let context = vm.createContext(); async function loadReplContextModules(context) {
context.lib = {
utils: await import('compass-utils'),
control: await import('mineflayer-control'),
};
context.bot = bot; context.bot = bot;
context.Vec3 = (await import('vec3')).Vec3; context.Vec3 = (await import('vec3')).Vec3;
context.mineflayer = mineflayer; context.mineflayer = mineflayer;
@ -53,6 +71,13 @@ async function main() {
if (!args.owner) { return null; } if (!args.owner) { return null; }
return bot.players[args.owner]; 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) { if (!args.noRepl) {
let r = repl.start({ let r = repl.start({
prompt: 'local > ', prompt: 'local > ',
@ -62,7 +87,7 @@ async function main() {
terminal: true, terminal: true,
ignoreUndefined: true, ignoreUndefined: true,
}); });
r.context = context; loadReplContextModules(r.context);
} }
} }

View File

@ -1,5 +1,5 @@
import debug from 'debug'; import debug from 'debug';
import { Task } from 'compass-utils'; import { Queue, Task } from 'compass-utils';
import { Vec3 } from 'vec3'; import { Vec3 } from 'vec3';
const logger = debug('mineflayer-control'); 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.'); } 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) { async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
const axis = AXIS[axis_raw]; const axis = AXIS[axis_raw];
const stable_axis = "xz"[axis % 2]; 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() distance: ${remaining_dis}.`);
logger(`moveAxisTask() stable_axis: ${stable_axis}.`); logger(`moveAxisTask() stable_axis: ${stable_axis}.`);
logger(`moveAxisTask() Condition: ${delta[stable_axis]} ${delta.y}.`); 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'); throw new Error('Invalid Argument: target');
} }
@ -120,7 +125,9 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
controls.apply(bot); controls.apply(bot);
logger('moveAxisTask() started.'); 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 { do {
await bot.waitForTicks(1); await bot.waitForTicks(1);
task._interuptableHere(); task._interuptableHere();
@ -134,13 +141,33 @@ async function moveAxisTask(bot, task, axis_raw, target_raw, level) {
logger(`moveAxisTask() pos.${stable_axis}: ${pos[stable_axis]}.`); logger(`moveAxisTask() pos.${stable_axis}: ${pos[stable_axis]}.`);
throw new MoveInterferedError(); 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[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)); delta.update(target.minus(pos));
remaining_dis = delta.dot(AXIS_UNIT[axis]); remaining_dis = delta.dot(AXIS_UNIT[axis]);
if (Math.abs(remaining_dis) <= 0.5) { if (Math.abs(remaining_dis) <= 0.5) {
logger('moveAxisTask() very close to target now.'); 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.x = 0;
bot.entity.velocity.z = 0; bot.entity.velocity.z = 0;
break; break;
@ -162,12 +189,14 @@ export default function inject(bot) {
bot.control.moveAxis = (axis, target, level = MOVE_LEVEL.SPRINT) => { bot.control.moveAxis = (axis, target, level = MOVE_LEVEL.SPRINT) => {
let task = new Task(); let task = new Task();
queueMicrotask(() => { queueMicrotask(async () => {
try {
task._start(); task._start();
moveAxisTask(bot, task, axis, target, level).catch(err => { await moveAxisTask(bot, task, axis, target, level);
} catch(err) {
bot.clearControlStates(); bot.clearControlStates();
task._fail(err); task._fail(err);
}); };
}); });
return task; return task;
}; };

View File

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