chore: initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type CursorArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<CursorArgs> {
|
||||
readonly name = 'attack_cursor_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '攻击光标实体', description: '攻击光标指向且在指定距离内的实体,不自动移动'};
|
||||
readonly args = entity_arguments(false, false, true);
|
||||
async run(bot: Bot, args: CursorArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, false, 'attack');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type NearestArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<NearestArgs> {
|
||||
readonly name = 'attack_nearest_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '攻击最近实体', description: '攻击指定距离内名称匹配的最近实体,可选瞄准,不自动移动'};
|
||||
readonly args = entity_arguments(true, false, true);
|
||||
async run(bot: Bot, args: NearestArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, true, 'attack', this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {base_define} from "../../../types/bot/plugins/plugin.ts";
|
||||
|
||||
export const define: base_define = {
|
||||
path: "entity",
|
||||
i18n: [
|
||||
{
|
||||
lang: "zh-CN",
|
||||
name: "实体操作",
|
||||
description: "Bot 实体操作, 提供实体相关功能"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type CursorItemArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<CursorItemArgs> {
|
||||
readonly name = 'feed_cursor_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '喂食光标实体', description: '对光标实体使用指定手的物品,可选瞄准,由服务器判断是否可以喂食'};
|
||||
readonly args = entity_arguments(false, true);
|
||||
async run(bot: Bot, args: CursorItemArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, false, 'item');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type NearestItemArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<NearestItemArgs> {
|
||||
readonly name = 'feed_nearest_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '喂食最近实体', description: '对指定距离内名称匹配的最近实体使用指定手的物品,由服务器判断是否可以喂食'};
|
||||
readonly args = entity_arguments(true, true);
|
||||
async run(bot: Bot, args: NearestItemArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, true, 'item', this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type CursorArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<CursorArgs> {
|
||||
readonly name = 'null_main_hand_right_cursor_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '空主手右键光标实体', description: '主手为空时右键交互光标实体,可选瞄准,主手有物品则失败'};
|
||||
readonly args = entity_arguments(false, false);
|
||||
|
||||
async run(bot: Bot, args: CursorArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, false, 'empty_hand');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type NearestArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<NearestArgs> {
|
||||
readonly name = 'null_main_hand_right_nearest_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '空主手右键最近实体', description: '主手为空时右键交互指定距离内名称匹配的最近实体,可选瞄准,主手有物品则失败'};
|
||||
readonly args = entity_arguments(true, false);
|
||||
|
||||
async run(bot: Bot, args: NearestArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, true, 'empty_hand', this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type CursorArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<CursorArgs> {
|
||||
readonly name = 'open_cursor_entity_container';
|
||||
readonly i18n = {lang: 'zh-CN', name: '打开光标实体容器', description: '激活光标实体并等待新容器窗口,已有窗口时失败,最多等待五秒'};
|
||||
readonly args = entity_arguments(false, false);
|
||||
async run(bot: Bot, args: CursorArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, false, 'open');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type NearestArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<NearestArgs> {
|
||||
readonly name = 'open_nearest_entity_container';
|
||||
readonly i18n = {lang: 'zh-CN', name: '打开最近实体容器', description: '激活指定距离内名称匹配的最近实体并等待新窗口,已有窗口时失败,最多等待五秒'};
|
||||
readonly args = entity_arguments(true, false);
|
||||
async run(bot: Bot, args: NearestArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, true, 'open', this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {FunctionArgument} from '../../../types/bot/function/function.ts';
|
||||
import {on_function_cancel} from '../cancellation.ts';
|
||||
|
||||
type Entity = NonNullable<ReturnType<Bot['entityAtCursor']>>;
|
||||
export type EntityOperation = 'attack' | 'item' | 'open' | 'empty_hand';
|
||||
export type CursorArgs = [reach: number, ignore_blocks: boolean, need_look?: boolean];
|
||||
export type NearestArgs = [name: string, reach: number, lock_target: boolean, need_look?: boolean];
|
||||
export type CursorItemArgs = [reach: number, ignore_blocks: boolean, off_hand: boolean, need_look?: boolean];
|
||||
export type NearestItemArgs = [name: string, reach: number, off_hand: boolean, lock_target: boolean, need_look?: boolean];
|
||||
|
||||
const target_locks = new WeakMap<Bot, {
|
||||
self: Bot['entity']; client: Bot['_client']; targets: Map<string, Entity>;
|
||||
}>();
|
||||
|
||||
export function entity_arguments(nearest: boolean, item: boolean, attack = false): FunctionArgument[] {
|
||||
const arg = (name: string, type: FunctionArgument['type'], label: string, description: string): FunctionArgument =>
|
||||
({name, type, i18n: {lang: 'zh-CN', name: label, description}});
|
||||
const reach = arg(nearest && !attack ? 'max_distance' : 'reach', 'number', '最大距离', '眼睛到实体碰撞箱的最大距离,必须为有限正数');
|
||||
const args = nearest
|
||||
? [arg('name', 'string', '实体类型 ID 或用户名', '不区分大小写的子串匹配,如 zom 或 minecraft:zom;也支持玩家用户名'), reach]
|
||||
: [reach, arg('ignore_blocks', 'boolean', '忽略方块遮挡', '是否忽略光标射线上的方块遮挡')];
|
||||
if (item) args.push(arg('off_hand', 'boolean', '使用副手', 'false 使用主手,true 使用副手,所选手必须持有物品'));
|
||||
if (nearest) args.push(arg('lock_target', 'boolean', '持续锁定实体', '相同函数和参数持续操作首次选中的实体,直到实体消失;超距时保留锁定但不操作'));
|
||||
args.push({...arg('need_look', 'boolean', '瞄准实体', '是否先瞄准实体中心,默认 false'), default: false});
|
||||
return args;
|
||||
}
|
||||
|
||||
function valid_entity(bot: Bot, target: Entity): boolean {
|
||||
return !!target && target !== bot.entity && target.id !== bot.entity?.id
|
||||
&& !(bot.username && target.username === bot.username)
|
||||
&& target.isValid !== false && bot.entities?.[target.id] === target
|
||||
&& Number.isFinite(target.id) && !!target.position
|
||||
&& [target.position.x, target.position.y, target.position.z, target.width, target.height].every(Number.isFinite)
|
||||
&& target.width > 0 && target.height > 0;
|
||||
}
|
||||
|
||||
function distance(bot: Bot, target: Entity): number {
|
||||
const self = bot.entity;
|
||||
const eye_height = (self as Entity & {eyeHeight?: number})?.eyeHeight ?? self?.height;
|
||||
if (!self?.position || ![self.position.x, self.position.y, self.position.z, eye_height].every(Number.isFinite)) return Infinity;
|
||||
const half = target.width / 2;
|
||||
const dx = Math.max(Math.abs(self.position.x - target.position.x) - half, 0);
|
||||
const dy = Math.max(target.position.y - (self.position.y + eye_height), self.position.y + eye_height - (target.position.y + target.height), 0);
|
||||
const dz = Math.max(Math.abs(self.position.z - target.position.z) - half, 0);
|
||||
return Math.hypot(dx, dy, dz);
|
||||
}
|
||||
|
||||
function has_item(bot: Bot, off_hand: boolean): boolean {
|
||||
return off_hand ? !bot.supportFeature('doesntHaveOffHandSlot') && !!bot.inventory?.slots[45] : !!bot.heldItem;
|
||||
}
|
||||
|
||||
function open_container(bot: Bot, target: Entity, active: () => boolean): Promise<boolean> {
|
||||
if (bot.currentWindow || !active()) return Promise.resolve(false);
|
||||
return new Promise(resolve => {
|
||||
let settled = false;
|
||||
const finish = (success: boolean) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
bot.removeListener('windowOpen', on_open);
|
||||
bot.removeListener('end', on_end);
|
||||
unwatch();
|
||||
resolve(success);
|
||||
};
|
||||
const on_open = (window: Bot['currentWindow']) => {
|
||||
finish(!!window && active() && bot.currentWindow === window);
|
||||
};
|
||||
const on_end = () => finish(false);
|
||||
const timer = setTimeout(() => finish(false), 5000);
|
||||
const unwatch = on_function_cancel(bot, on_end);
|
||||
bot.on('windowOpen', on_open);
|
||||
bot.on('end', on_end);
|
||||
try {
|
||||
Promise.resolve(bot.interactEntity(target, {hand: 0})).catch(() => finish(false));
|
||||
} catch {
|
||||
finish(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function run_entity_operation(
|
||||
bot: Bot, args: unknown[], nearest: boolean, operation: EntityOperation, function_name: string = operation,
|
||||
): Promise<boolean> {
|
||||
const item = operation === 'item';
|
||||
const required_count = (item ? 3 : 2) + (nearest ? 1 : 0);
|
||||
if (!Array.isArray(args) || args.length !== required_count && args.length !== required_count + 1) return false;
|
||||
if (args.length > required_count && typeof args[required_count] !== 'boolean') return false;
|
||||
const need_look = args[required_count] === true;
|
||||
args = args.slice(0, required_count);
|
||||
const reach = args[nearest ? 1 : 0];
|
||||
if (typeof reach !== 'number' || !Number.isFinite(reach) || reach <= 0) return false;
|
||||
if (nearest ? typeof args[0] !== 'string' || !args[0].trim() : typeof args[1] !== 'boolean') return false;
|
||||
if (item && typeof args[2] !== 'boolean') return false;
|
||||
if (nearest && typeof args.at(-1) !== 'boolean') return false;
|
||||
const self = bot.entity;
|
||||
const client = bot._client;
|
||||
let ended = false;
|
||||
let cancel!: () => void;
|
||||
const cancelled = new Promise<false>(resolve => { cancel = () => resolve(false); });
|
||||
const on_end = () => { ended = true; cancel(); };
|
||||
const unwatch = on_function_cancel(bot, on_end);
|
||||
const active = () => !ended && !!self && bot.entity === self && bot._client === client && self.isValid !== false;
|
||||
try {
|
||||
bot.on('end', on_end);
|
||||
if (operation === 'empty_hand' && bot.heldItem) return false;
|
||||
if (!active() || operation === 'open' && bot.currentWindow || item && !has_item(bot, args[2] as boolean)) return false;
|
||||
let target: Entity | undefined;
|
||||
if (nearest) {
|
||||
let locks = target_locks.get(bot);
|
||||
if (!locks || locks.self !== self || locks.client !== client) {
|
||||
locks = {self, client, targets: new Map()};
|
||||
target_locks.set(bot, locks);
|
||||
}
|
||||
for (const [key, entity] of locks.targets) {
|
||||
if (!valid_entity(bot, entity)) locks.targets.delete(key);
|
||||
}
|
||||
const lock_target = args.at(-1) === true;
|
||||
const key = JSON.stringify([function_name, [...args, need_look]]);
|
||||
target = lock_target ? locks.targets.get(key) : undefined;
|
||||
if (!target) {
|
||||
const name = (args[0] as string).trim().toLowerCase();
|
||||
const entity_name = name.replace(/^minecraft:/, '');
|
||||
target = Object.values(bot.entities).filter(entity => valid_entity(bot, entity)
|
||||
&& (entity_name.length > 0 && typeof entity.name === 'string'
|
||||
&& entity.name.toLowerCase().replace(/^minecraft:/, '').includes(entity_name)
|
||||
|| typeof entity.username === 'string' && entity.username.toLowerCase().includes(name)))
|
||||
.map(entity => ({entity, distance: distance(bot, entity)}))
|
||||
.filter(entry => Number.isFinite(entry.distance) && entry.distance <= reach)
|
||||
.sort((a, b) => a.distance - b.distance)[0]?.entity;
|
||||
if (lock_target && target) locks.targets.set(key, target);
|
||||
}
|
||||
} else {
|
||||
target = bot.entityAtCursor(reach, true, args[1] as boolean) ?? undefined;
|
||||
}
|
||||
const in_reach = () => active() && valid_entity(bot, target!) && distance(bot, target!) <= reach;
|
||||
if (!target || !in_reach()) return false;
|
||||
if (need_look) {
|
||||
if (!await Promise.race([
|
||||
bot.lookAt(target.position.offset(0, target.height / 2, 0)).then(() => true, () => false), cancelled,
|
||||
])) return false;
|
||||
if (!in_reach()) return false;
|
||||
}
|
||||
if (operation === 'attack') {
|
||||
bot.attack(target);
|
||||
return true;
|
||||
}
|
||||
if (operation === 'empty_hand') {
|
||||
if (bot.heldItem) return false;
|
||||
bot.interactEntity(target, {hand: 0});
|
||||
return true;
|
||||
}
|
||||
if (item) {
|
||||
if (!has_item(bot, args[2] as boolean)) return false;
|
||||
bot.interactEntity(target, {hand: args[2] ? 1 : 0, position: target.position.offset(0, target.height / 2, 0)});
|
||||
return true;
|
||||
}
|
||||
return await open_container(bot, target, active);
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
unwatch();
|
||||
bot.removeListener('end', on_end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type CursorItemArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<CursorItemArgs> {
|
||||
readonly name = 'use_item_on_cursor_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '对光标实体使用物品', description: '对光标实体使用指定手的物品,可选瞄准,不自动移动'};
|
||||
readonly args = entity_arguments(false, true);
|
||||
async run(bot: Bot, args: CursorItemArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, false, 'item');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type {Bot} from '@wp2508/mineflayer';
|
||||
import type {BaseFunction} from '../../../types/bot/function/function.ts';
|
||||
import {entity_arguments, run_entity_operation, type NearestItemArgs} from './shared.ts';
|
||||
|
||||
export class init implements BaseFunction<NearestItemArgs> {
|
||||
readonly name = 'use_item_on_nearest_entity';
|
||||
readonly i18n = {lang: 'zh-CN', name: '对最近实体使用物品', description: '对指定距离内名称匹配的最近实体使用指定手的物品,可选瞄准'};
|
||||
readonly args = entity_arguments(true, true);
|
||||
async run(bot: Bot, args: NearestItemArgs): Promise<boolean> {
|
||||
return run_entity_operation(bot, args, true, 'item', this.name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user