Добавить команду
Чтобы добавить команду, плагин регистрирует ее узлы и пишет обработчик, обычную систему Bevy. Узел может еще требовать уровень доступа.
Команды хранятся в типизированном дереве.1 Плагины регистрируют в дереве узлы, а сервер отправляет каждому игроку ту часть дерева, которую разрешает его уровень. Если вы знаете Paper, ближе всего к этому его Commands API с деревом узлов. Аналога plugin.yml и onCommand здесь нет. Примеры взяты из теста репозитория commands.rs, вне репозиторных тестов мы их не запускали.
Команду регистрирует add_command
Заголовок раздела «Команду регистрирует 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 с одним целочисленным аргументом:
#[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)). Уровней пять, они упорядочены, и источник проходит проверку, если его уровень не ниже требуемого.
#[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 выглядит так:
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
Что пока не работает
Заголовок раздела «Что пока не работает»Дальше: Перехват чата.
Footnotes
Заголовок раздела «Footnotes»Источники и проверка
Проверено: 26.4-snapshot-7, 3540f457b, 2026-11-10
Переведено с версии 3540f457b