Перейти к содержимому

Добавить команду

Меняется. Следите: #285, #579

Чтобы добавить команду, плагин регистрирует ее узлы и пишет обработчик, обычную систему Bevy. Узел может еще требовать уровень доступа.

Команды хранятся в типизированном дереве.1 Плагины регистрируют в дереве узлы, а сервер отправляет каждому игроку ту часть дерева, которую разрешает его уровень. Если вы знаете Paper, ближе всего к этому его Commands API с деревом узлов. Аналога plugin.yml и onCommand здесь нет. Примеры взяты из теста репозитория commands.rs, вне репозиторных тестов мы их не запускали.

add_command - метод приложения. Он принимает билдер, который добавляет узлы команды, и обработчик, который команду выполняет.

Сигнатура add_command
pub trait AppCommandsExt {
/// Registers a command in the tree of the set `A`: `build` adds its nodes and returns the
/// handles its handler reads, and every dimension world runs `handler` when one of its
/// branches runs. Nothing is registered when `build` returns an error.
fn add_command<A, B, C, S, M>(
&mut self,
build: impl FnOnce(&mut CommandRegistrar<'_, A, B>) -> Result<C, BuildError>,
handler: S,
) -> Result<&mut Self, RegistrationError>
where
A: CommandSet,
B: Copy + PartialEq + Send + Sync + 'static,
C: Copy + Send + Sync + 'static,
S: IntoSystem<In<Call<B, C>>, CommandResult, M> + Clone + Send + Sync + 'static,
M: 'static;

crates/mcrs_minecraft_command_engine/src/app_commands.rs:39-53

Вызывайте его после того, как в приложение добавлен CommandPlugin, и до того, как приложение завершит сборку. Для ванильного набора команд этот плагин уже добавляет MinecraftServerPlugins.

Тест ниже собирает команду echo с одним целочисленным аргументом:

Тест: узлы команды echo с одним целочисленным аргументом
#[derive(Clone, Copy)]
struct Echo {
times: ArgumentHandle<IntegerArgument>,
}
fn echo_at(
name: &'static str,
) -> impl FnOnce(&mut CommandRegistrar<'_, VanillaCommands, ()>) -> Result<Echo, BuildError> {
move |r| {
let echo = r
.literal(NodeId::ROOT, name)
.requires(PermissionCheck::Require(CommandLevel::Gamemasters))
.id()?;
let times = r
.arg(
echo,
"times",
IntegerArgument {
min: 1,
..IntegerArgument::ANY
},
)
.runs(())
.id()?;
Ok(Echo { times })
}
}

crates/mcrs_minecraft_server/tests/it/commands.rs:51-77

Билдер получает CommandRegistrar. Начиная с NodeId::ROOT, он добавляет под родительский узел литеральный узел через literal или узел аргумента через arg. Узел, на котором строка команды заканчивается, вызывает runs(...). Билдер возвращает дескрипторы аргументов, и по ним обработчик потом читает значения.

В том же файле команда регистрируется вызовом app.add_command(echo_at("echo"), run_echo).unwrap();. Вызов возвращает Result, потому что узел может конфликтовать с деревом. Тогда вы получите ошибку, и из команды не добавится ничего, как показывает тест в конце файла.

Узел запрашивает уровень через requires(PermissionCheck::Require(level)). Уровней пять, они упорядочены, и источник проходит проверку, если его уровень не ниже требуемого.

CommandLevel
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CommandLevel {
#[default]
All,
Moderators,
Gamemasters,
Admins,
Owners,
}

crates/mcrs_minecraft_command/src/permission.rs:5-13

Игрок с уровнем ниже требуемого не видит узел в дереве и не может его выполнить. Уровень игрока берется из списка операторов или из уровня сервера по умолчанию.

Строковых узлов прав вроде myplugin.use в mcrs нет. Системы прав с группами сегодня тоже нет, и план дерева команд ее не описывает.2

Обработчик команды echo выглядит так:

Тест: обработчик команды echo
fn run_echo(
In(call): In<Call<(), Echo>>,
context: VanillaContext,
mut feedback: CommandOutput,
) -> CommandResult {
let command = call.command;
let times = context.arg(&call.invocation, command.times)?;
feedback.success(
&call.invocation,
format!("echo {times}").into_text(),
Feedback::Source,
);
Ok(times)
}

crates/mcrs_minecraft_server/tests/it/commands.rs:79-92

Первый параметр обработчика - In<Call<...>>, остальные - обычные параметры системы. Обработчик возвращает CommandResult, то есть либо число, которое команда сообщает при успехе, либо ошибку. Текст он отправляет через CommandOutput, а Feedback определяет, кто этот текст услышит: только источник или источник и все операторы.

Обработчик не меняет игрока напрямую, потому что игрок может находиться в другом измерении, до которого обработчик не дотянется. Он записывает эффект через CommandOutput::effect, а мир применяет эффекты после вызова в том порядке, в каком они записаны. Тип эффекта зарегистрируйте через add_command_effect. Так teleport и gamemode меняют игроков в других измерениях. Причины описаны в docs/command-dimensions.md и на странице Измерения.

Команду можно остановить до выполнения. Перед тем как выполнить строку команды, сервер вызывает CommandEvent в мире измерения источника. Наблюдатель, то есть функция, которую Bevy вызывает при срабатывании события, может выставить cancelled, и тогда команда не выполнится. Это командная часть запланированных публичных отменяемых событий игрока.3

Дальше: Перехват чата.

  1. Работает#579 Добавляет типизированное дерево команд, ванильные команды и экран чата в клиенте. ↩

  2. Частично#285 Отслеживает дерево команд с уровнями прав и пакет команд. ↩ ↩2 ↩3

  3. Частично#283 Отслеживает публичные события игрока, которые плагин может отменить. ↩

Источники и проверка

Проверено: 26.4-snapshot-7, 3540f457b, 2026-11-10

Переведено с версии 3540f457b

Следить: #285, #579