All Downloads are FREE. Search and download functionalities are using the official Maven repository.

personthecat.catlib.command.LibCommandBuilder Maven / Gradle / Ivy

Go to download

Utilities for serialization, commands, noise generation, IO, and some new data types.

The newest version!
package personthecat.catlib.command;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.minecraft.class_2168;
import net.minecraft.class_2170;
import org.jetbrains.annotations.Nullable;
import personthecat.catlib.command.function.CommandFunction;
import personthecat.catlib.data.ModDescriptor;
import personthecat.catlib.util.McUtils;
import personthecat.catlib.linting.SyntaxLinter;
import personthecat.fresult.Result;

import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * A manual builder type used for generating argument nodes with automatically-generated
 * custom help entries. In addition to providing a convenient, platform-agnostic way to
 * control the type of command and accepted server side on which to register the command,
 * this wrapper will take care of handling errors and can provide additional utilities to
 * the command definitions via {@link CommandContextWrapper} which are not already
 * by the regular {@link CommandContext}.
 * 

* To begin using this wrapper, start by creating a {@link Template} as follows: *

 *     LibCommandBuilder.named("myCommand")
 * 

* The template returned by this method provides a series of options which can be used * the configure the generated help command, supply mod information, and more. *

{@code
 *     LibCommandBuilder.named("myCommand")
 *
 *         // Append text to the generated help message.
 *         .append("Runs a demo command")
 *
 *         // An optional linter used for syntax highlighting in the chat.
 *         .linter(new MyLinter())
 *
 *         // Only run this on a dedicated server
 *         .side(CommandSide.DEDICATED)
 *
 *         // Optionally wrap a throwing consumer of type {@link CommandContextWrapper}.
 *         // This converts the wrapper to an int function of type {@link CommandContext}.
 *         .wrap("", wrapper -> wrapper.sendMessage("Hello, world!"))
 *         ...
 * }

* Finally, once the command's metadata have been configured, you can return a complete * command builder by calling {@link Template#generate}. *

{@code
 *     final LibCommandBuilder cmd = LibCommandBuilder.named("myCommand")
 *         ...
 *         // This method exposes the raw Brigadier argument tree.
 *         .generate((builder, wrappers) -> builder.execute(wrappers.get("")));
 * }

* Generated builder objects can then be passed into an active {@link CommandRegistrationContext} * or annotated with {@link personthecat.catlib.command.annotations.CommandBuilder} and passed in through the parent class. *

*/ @Log4j2 @AllArgsConstructor @ParametersAreNonnullByDefault public final class LibCommandBuilder { private final LiteralArgumentBuilder command; private final HelpCommandInfo info; private final CommandType type; private final CommandSide side; /** * Constructs a new builder template using the only piece of required * information for creating a command: it's name! * * @param name The literal argument required to run the command. * @return A new template used to customize the command. */ public static Template named(final String name) { return new Template(name); } public LiteralArgumentBuilder getCommand() { return this.command; } public HelpCommandInfo getHelpInfo() { return this.info; } public CommandType getType() { return this.type; } public CommandSide getSide() { return this.side; } public static class Template { private final CommandMapBuilder wrappers; private final String name; private String arguments; private String description; private CommandType type; private CommandSide side; @Nullable private SyntaxLinter linter; @Nullable private ModDescriptor mod; private Template(final String name) { this.wrappers = new CommandMapBuilder(); this.name = name; this.arguments = ""; this.description = ""; this.type = CommandType.MOD; this.side = CommandSide.EITHER; } /** * Sets the optional subtext to be displayed after the command name on the * current mod's generated help page. * * @param arguments The subtext of the command, usually its arguments. * @return this, for method chaining. */ public Template arguments(final String arguments) { this.arguments = arguments; return this; } /** * Enables this command's generated help entry and Sets the entire description * to be displayed on the mod's help page. * * @param description The automatically formatted help text. * @return this, for method chaining. */ public Template description(final String description) { this.description = description; return this; } /** * Appends additional text to this command's help entry, if needed. *

* Note that this does not affect how the entry will be formatted, as that * can be configured automatically via {@link CommandRegistrationContext}. *

* * @param description The additional description text to append. * @return this, for method chaining. */ public Template append(final String description) { this.description += " " + description; return this; } /** * The type of command to generate. Either a sub command of the mod's main * command, or a global command attached to the root command node. * * @param type The type of command being generated. * @return this, for method chaining. */ public Template type(final CommandType type) { this.type = type; return this; } /** * The type of server side required for this command to be registered. * * @param side Whichever side the command should run on. * @return this, for method chaining. */ public Template side(final CommandSide side) { this.side = side; return this; } /** * An optional linter which can be used to highlight text in the chat. * This will be provided to the exit nodes via {@link CommandContextWrapper}. * * @param linter The syntax highlighter used by the context wrapper. * @return this, for method chaining. */ public Template linter(final SyntaxLinter linter) { this.linter = linter; return this; } /** * Configures this command's mod descriptor which will be used by the * registration context and certain argument types to provide a customized * experience to the command function. *

* Note that this step is unnecessary if a {@link CommandRegistrationContext} * has already been created and has not yet been disposed of. *

* * @param mod The current mod's descriptor. * @return this, for method chaining. */ public Template mod(final ModDescriptor mod) { this.mod = mod; return this; } /** * Converts a consumer of type {@link CommandContextWrapper} into a standard * {@link Command} function. * * @param key The key used to retrieve this command when calling {@link #generate}. * @param endpoint The actual method being executed by the command. * @return this, for method chaining. */ public Template wrap(final String key, final CommandFunction endpoint) { this.wrappers.put(key, endpoint); return this; } /** * Generates everything needed for the accepting {@link CommandRegistrationContext} * to build and register this command. * * @param generator A bi-consumer providing access to the underlying argument node * and wrapped command functions. * @return A fully-constructed command builder. */ public LibCommandBuilder generate(final CommandGenerator generator) { if (this.mod == null) this.mod = CommandRegistrationContext.getActiveModOrThrow(); if (this.linter == null) this.linter = this.mod.getDefaultLinter(); final LiteralArgumentBuilder builder = class_2170.method_9247(this.name); final BuilderUtil util = new BuilderUtil(this.wrappers, this.linter, this.mod); final HelpCommandInfo helpInfo = new HelpCommandInfo(this.name, this.arguments, this.description, this.type); if (this.side.canRegister(McUtils.isDedicatedServer())) { return new LibCommandBuilder(generator.apply(builder, util), helpInfo, this.type, this.side); } return new LibCommandBuilder(builder, helpInfo, this.type, this.side); } } private static class CommandMapBuilder extends HashMap {} @AllArgsConstructor public static class BuilderUtil { private final Map map; private final SyntaxLinter linter; private final ModDescriptor mod; public Command get(final String key) { final CommandFunction fn = this.map.get(key); Objects.requireNonNull(fn, "No command function for key: " + key); return ctx -> this.wrapCommand(new CommandContextWrapper(ctx, this.linter, this.mod), fn); } public Command wrap(final CommandFunction fn) { return ctx -> this.wrapCommand(new CommandContextWrapper(ctx, this.linter, this.mod), fn); } private int wrapCommand(final CommandContextWrapper wrapper, final CommandFunction fn) { return Result.suppress(() -> fn.execute(wrapper)) .ifErr(e -> this.handleException(wrapper, e)) .fold(v -> 1, e -> -1); } private void handleException(final CommandContextWrapper wrapper, final Throwable e) { // There will always be an ITE if the method was invoked reflectively. if (e instanceof InvocationTargetException && e.getCause() != null) { this.handleException(wrapper, e.getCause()); return; } log.warn("Error running command: {}\nStacktrace:\n{}", e.getMessage(), e.getStackTrace()); wrapper.sendError("{}: {}", e.getClass().getSimpleName(), e.getMessage()); Throwable cause = e; while ((cause = cause.getCause()) != null) { log.warn("Caused by: {}\nStacktrace:\n{}", cause.getMessage(), cause.getStackTrace()); wrapper.sendError(cause.getMessage()); } } } @FunctionalInterface public interface CommandGenerator { LiteralArgumentBuilder apply(final LiteralArgumentBuilder builder, final BuilderUtil wrappers); } }