Creare comandi può permettere a uno sviluppatore di mod di aggiungere funzionalità che possono essere usate attraverso un comando. Questo tutorial ti insegnerà come registrare comandi e qual è la struttura generale dei comandi di Brigadier.
INFO
Brigadier è un parser e un dispatcher di comandi scritto da Mojang per Minecraft. È una libreria comandi basata su una gerarchia dove costruisci un albero di comandi e parametri.
Brigadier è open source: https://github.com/Mojang/brigadier
L'interface Command
com.mojang.brigadier.Command è un'interfaccia funzionale, che esegue del codice specifico, e lancia una CommandSyntaxException in determinati casi. Ha un tipo generico S, che definisce il tipo della sorgente del comando. La sorgente del comando fornisce del contesto in cui un comando è stato eseguito. In Minecraft, la sorgente del comando è tipicamente una CommandSourceStack che potrebbe rappresentare un server, un blocco comandi, una connessione remota (RCON), un giocatore o un'entità.
L'unico metodo di Command, run(CommandContext<S>) accetta un CommandContext<S> come unico parametro e restituisce un intero. Il contesto del comando contiene la tua sorgente del comando di S e ti permette di ottenere parametri, osservare i nodi di un comando di cui è stato effettuato il parsing e vedere l'input usato in questo comando.
Come altre interfacce funzionali, viene solitamente usata come una lambda o come un riferimento a un metodo:
java
Command<CommandSourceStack> command = context -> {
return 0;
};1
2
3
2
3
L'intero può essere considerato il risultato del comando. Di solito valori minori o uguali a zero indicano che un comando è fallito e non farà nulla. Valori positivi indicano che il comando ha avuto successo e ha fatto qualcosa. Brigadier fornisce una costante per indicare il successo; Command#SINGLE_SUCCESS.
Cosa Può Fare la CommandSourceStack?
Una CommandSourceStack fornisce del contesto aggiuntivo dipendente dall'implementazione quando un comando viene eseguito. Questo include la possibilità di ottenere l'entità che ha eseguito il comando, il mondo in cui esso è stato eseguito o il server su cui è stato eseguito.
Puoi accedere alla sorgente del comando dal contesto del comando chiamando getSource() sull'istanza di CommandContext.
java
Command<CommandSourceStack> command = context -> {
CommandSourceStack source = context.getSource();
return 0;
};1
2
3
4
2
3
4
Registrare un Comando Basilare
I comandi sono registrati all'interno del CommandRegistrationCallback fornito dall'API di Fabric.
INFO
Per informazioni su come registrare i callback, vedi per favore la guida Eventi.
L'evento dovrebbe essere registrato nell'initializer della tua mod.
Il callback ha tre parametri:
CommandDispatcher<CommandSourceStack> dispatcher- Usato per registrare, analizzare, ed eseguire comandi.Sè il tipo di fonte di comando che il dispatcher supporta.CommandBuildContext registryAccess- Fornisce un'astrazione alle registry che potrebbero essere passate ad alcuni argomenti di metodi di comandiCommands.CommandSelection environment- Identifica il tipo di server su cui i comandi vengono registrati.
Nell'initializer della mod, registriamo un semplice comando:
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("test_command").executes(context -> {
context.getSource().sendSuccess(() -> Component.literal("Called /test_command."), false);
return 1;
}));
});1
2
3
4
5
6
2
3
4
5
6
Nel metodo sendSuccess() il primo parametro è il testo che viene mandato, che è un Supplier<Component> per evitare d'istanziare oggetti Component quando non è necessario.
Il secondo parametro determina se trasmettere il feedback agli altri moderatori. In generale, se il comando deve ottenere informazioni senza effettivamente modificare il mondo, come il tempo corrente o una statistica di un giocatore, dovrebbe essere false. Se il comando fa qualcosa, come cambiare il tempo o modificare il punteggio di qualcuno, dovrebbe essere true.
Se il comando fallisce, anziché chiamare sendSuccess(), puoi direttamente lanciare qualsiasi eccezione e il server o il client la gestiranno in modo appropriato.
CommandSyntaxException generalmente viene lanciata per indicare errori di sintassi nel comando o negli argomenti. Puoi anche implementare la tua eccezione personalizzata.
Per eseguire questo comando, devi scrivere /test_command, tutto minuscolo.
INFO
Da ora in poi, estrarremo la logica scritta all'interno della lambda passata nei costruttori .executes() in metodi individuali. Potremo quindi passare a .executes() un riferimento al metodo. Faremo questo per chiarezza.
Ambiente di Registrazione
Se vuoi, puoi anche assicurarti che un comando venga registrato solo sotto circostanze specifiche, per esempio, solo nell'ambiente dedicato:
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
if (environment.includeDedicated) {
dispatcher.register(Commands.literal("dedicated_command")
.executes(ExampleModCommands::executeDedicatedCommand));
}
});1
2
3
4
5
6
2
3
4
5
6
java
private static int executeDedicatedCommand(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /dedicated_command."), false);
return 1;
}1
2
3
4
5
2
3
4
5
Requisiti dei Comandi
Immagina di avere un comando e vuoi che solo i moderatori lo possano eseguire. Questo è dove il metodo requires() entra in gioco. Il metodo requires() ha un solo argomento Predicate<S> che fornirà una CommandSourceStack con cui testare e determinare se la CommandSource può eseguire il comando.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("required_command")
.requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_MODERATOR))
.executes(ExampleModCommands::executeRequiredCommand));
});1
2
3
4
5
2
3
4
5
java
private static int executeRequiredCommand(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /required_command."), false);
return 1;
}1
2
3
4
5
2
3
4
5
Questo comando verrà eseguito solo se la fonte del comando è almeno un moderatore, inclusi i blocchi comandi. Altrimenti, il comando non è registrato.
Questo ha l'effetto collaterale di non mostrare il comando se si completa con Tab a nessuno eccetto moderatori. Inoltre è il motivo per cui non puoi completare molti dei comandi con Tab a meno di abilitare i comandi.
Sotto Comandi
Per aggiungere un sotto comando, devi registrare il primo nodo letterale del comando normalmente. Per avere un sotto comando, devi aggiungere il nodo letterale successivo al nodo esistente.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("command_one")
.then(Commands.literal("sub_command_one").executes(ExampleModCommands::executeSubCommandOne)));
});1
2
3
4
2
3
4
java
private static int executeSubCommandOne(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /command sub_command_one."), false);
return 1;
}1
2
3
4
5
2
3
4
5
Similarmente agli argomenti, i nodi dei sotto comandi possono anch'essi essere opzionali. Nel caso seguente, sia /command_two che /command_two sub_command_two saranno validi.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("command_two")
.executes(ExampleModCommands::executeCommandTwo)
.then(Commands.literal("sub_command_two").executes(ExampleModCommands::executeSubCommandTwo)));
});1
2
3
4
5
2
3
4
5
java
private static int executeCommandTwo(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /command_two."), false);
return 1;
}
private static int executeSubCommandTwo(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /sub_command_two."), false);
return 1;
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Comandi Lato Client
L'API di Fabric ha un ClientCommandManager nel package net.fabricmc.fabric.api.client.command.v2 che può essere usato per registrare comandi lato client. Il codice dovrebbe esistere solo nel codice lato client.
java
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(ClientCommandManager.literal("clienttater").executes(context -> {
context.getSource().sendFeedback(Component.literal("Called /clienttater with no arguments."));
return 1;
}));
});1
2
3
4
5
6
2
3
4
5
6
Reindirizzare Comandi
I comandi reindirizzati - anche noti come alias - sono un modo di reindirizzare la funzionalità di un comando a un altro. Questo è utile quando vuoi cambiare il nome di un comando, ma vuoi comunque supportare il vecchio nome.
WARNING
Brigadier reinderizzerà soltanto i nodi di comandi contenenti parametri. Se volessi reinderizzare il nodo di un comando senza parametri, fornisci un costruttore .executes() con un riferimento alla stessa logica presentata nell'esempio.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
var redirectedBy = dispatcher.register(Commands.literal("redirected_by").executes(ExampleModCommands::executeRedirectedBy));
dispatcher.register(Commands.literal("to_redirect").executes(ExampleModCommands::executeRedirectedBy).redirect(redirectedBy));
});1
2
3
4
2
3
4
java
private static int executeRedirectedBy(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /redirected_by."), false);
return 1;
}1
2
3
4
5
2
3
4
5
Domande Frequenti (FAQ)
Perché il Mio Codice Non Viene Compilato?
Catturare o lanciare una
CommandSyntaxException-CommandSyntaxExceptionnon è unaRuntimeException. Se la lanci, dovresti farlo in metodi che lanciano unaCommandSyntaxExceptionnelle firme dei metodi, oppure dovresti catturarla. Brigadier gestirà le eccezioni controllate e ti inoltrerà il messaggio d'errore effettivo nel gioco.Problemi con i generic - Potresti avere un problema con i generic una volta ogni tanto. Se stai registrando comandi sul server (ovvero nella maggior parte dei casi), assicurati di star usando
Commands.literaloCommands.argumentanzichéLiteralArgumentBuilder.literaloRequiredArgumentBuilder.argument.Controlla il metodo
sendSuccess()- Potresti aver dimenticato di fornire un valore booleano come secondo argomento. Ricordati anche che, da Minecraft 1.20, il primo parametro èSupplier<Component>anzichéComponent.Un Command dovrebbe restituire un intero - Quando registri comandi, il metodo
executes()accetta un oggettoCommand, che è solitamente una lambda. La lambda dovrebbe restituire un intero, anziché altri tipi.
Posso Registrare Comandi al Runtime?
WARNING
Questo si può fare, ma non è consigliato. Bisognerebbe ottenere l'oggetto Commands dal server e aggiungere i comandi che vuoi al suo CommandDispatcher.
Dopo averlo fatto, devi nuovamente inviare l'albero di comandi a ogni giocatore usando Commands.sendCommands(ServerPlayer).
Questo è necessario perché il client mantiene una cache locale dell'albero dei comandi che riceve durante il login (o quando vengono mandati pacchetti per moderatori) per suggerimenti locali e messaggi di errore ricchi.
Posso De-Registrare Comandi al Runtime?
WARNING
Anche questo si può fare, tuttavia è ancora meno stabile della registrazione di comandi al runtime e potrebbe causare effetti collaterali non desiderati.
Per tenere le cose semplici, devi usare la reflection su Brigadier e rimuovere nodi. Dopodiché, devi mandare nuovamente l'albero di comandi a ogni giocatore usando sendCommands(ServerPlayer).
Se non mandi l'albero di comandi aggiornato, il client potrebbe credere che il comando esista ancora, anche se fallirà l'esecuzione sul server.













