diff --git a/build-logic/src/main/java/qsl/internal/Versions.java b/build-logic/src/main/java/qsl/internal/Versions.java index 40d88c5b43..b35a06ead1 100644 --- a/build-logic/src/main/java/qsl/internal/Versions.java +++ b/build-logic/src/main/java/qsl/internal/Versions.java @@ -24,7 +24,7 @@ public final class Versions { /** * The target Minecraft version. */ - public static final String MINECRAFT_VERSION = "1.18.1"; + public static final String MINECRAFT_VERSION = "22w03a"; /** * The target Quilt Mappings build. diff --git a/build-logic/src/main/java/qsl/internal/extension/QslModuleExtension.java b/build-logic/src/main/java/qsl/internal/extension/QslModuleExtension.java index 8b197e9e9b..61ad33ca85 100644 --- a/build-logic/src/main/java/qsl/internal/extension/QslModuleExtension.java +++ b/build-logic/src/main/java/qsl/internal/extension/QslModuleExtension.java @@ -1,13 +1,5 @@ package qsl.internal.extension; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import javax.inject.Inject; - import groovy.util.Node; import org.gradle.api.Action; import org.gradle.api.Project; @@ -25,6 +17,9 @@ import qsl.internal.task.ApplyLicenseTask; import qsl.internal.task.CheckLicenseTask; +import javax.inject.Inject; +import java.util.*; + public class QslModuleExtension extends QslExtension { private final Property library; private final Property moduleName; @@ -92,17 +87,31 @@ public void coreTestmodDependencies(Iterable dependencies) { } } - public void interLibraryDependencies(Iterable dependencies) { + public void intraLibraryDependencies(Iterable dependencies) { + this.addIntraLibraryDependencies(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, dependencies); + } + + public void intraLibraryTestmodDependencies(Iterable dependencies) { + this.addIntraLibraryDependencies("testmodImplementation", dependencies); + } + + private void addIntraLibraryDependencies(String configuration, Iterable dependencies) { String library = this.getLibrary().get(); + } + + public void interLibraryTestmodDependencies(String library, Iterable dependencies) { + this.addInterLibraryDependencies("testmodImplementation", library, dependencies); + } + private void addInterLibraryDependencies(String configuration, String library, Iterable dependencies) { for (String dependency : dependencies) { - Map map = new LinkedHashMap<>(2); + var map = new LinkedHashMap(2); map.put("path", ":" + library + ":" + dependency); map.put("configuration", "dev"); Dependency project = this.project.getDependencies().project(map); this.moduleDependencies.add(project); - this.project.getDependencies().add(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, project); + this.project.getDependencies().add(configuration, project); } } diff --git a/build-logic/src/main/java/qsl/internal/license/LicenseHeader.java b/build-logic/src/main/java/qsl/internal/license/LicenseHeader.java index 730710ee3f..df65599d8d 100644 --- a/build-logic/src/main/java/qsl/internal/license/LicenseHeader.java +++ b/build-logic/src/main/java/qsl/internal/license/LicenseHeader.java @@ -80,7 +80,7 @@ private static String getLineSeparator(String text) { private static Pattern getValidator(String headerFormat) { String pattern = escapeRegexControl(headerFormat) - .replace(YEAR_KEY, "(\\d{4}(, \\d{4})*)"); + .replace(YEAR_KEY, "(\\d{4}(-\\d{4})?)"); var lineSeparator = getLineSeparator(headerFormat); String[] lines = getHeaderLines(pattern.split(lineSeparator)); var patternBuilder = new StringBuilder("\\/\\*" + lineSeparator); @@ -283,8 +283,8 @@ private String getYearString(Project project, String source) { var matcher = this.validator.matcher(source); - if (matcher.matches()) { - String[] serializedYears = matcher.group(1).split("\n"); + if (matcher.find()) { + String[] serializedYears = matcher.group(1).split("-"); int min = -1; for (var serializedYear : serializedYears) { @@ -301,7 +301,7 @@ private String getYearString(Project project, String source) { if (min == -1) { return String.valueOf(lastModifiedYear); - } else { + } else if (min != lastModifiedYear) { return min + "-" + lastModifiedYear; } } diff --git a/library/block/block_extensions/src/main/resources/fabric.mod.json b/library/block/block_extensions/src/main/resources/fabric.mod.json index f7735c5b69..eabb5ef801 100644 --- a/library/block/block_extensions/src/main/resources/fabric.mod.json +++ b/library/block/block_extensions/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ ], "depends": { "fabricloader": ">=0.12", - "minecraft": ">=1.18.1", + "minecraft": ">=1.18.2-alpha.22.3.a", "quilt_base": "*" }, "description": "Extensions for creating and working with blocks.", diff --git a/library/command/build.gradle b/library/command/build.gradle new file mode 100644 index 0000000000..c5cff56935 --- /dev/null +++ b/library/command/build.gradle @@ -0,0 +1,8 @@ +plugins { + id("qsl.library") +} + +qslLibrary { + libraryName = "command" + version = "1.0.0" +} diff --git a/library/command/client_command/build.gradle b/library/command/client_command/build.gradle new file mode 100644 index 0000000000..f93831e15a --- /dev/null +++ b/library/command/client_command/build.gradle @@ -0,0 +1,12 @@ +plugins { + id("qsl.module") +} + +qslModule { + moduleName = "client_command" + version = "1.0.0" + library = "command" + coreDependencies([ + "qsl_base" + ]) +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandManager.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandManager.java new file mode 100644 index 0000000000..fcc8fbc2af --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandManager.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.api.client; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +/** + * Manages client-sided commands and provides some related helper methods, analogous to + * {@link net.minecraft.server.command.CommandManager CommandManager}. + *

+ * Client-sided commands are executed wholly on the client, + * so players can use them in both singleplayer and multiplayer. + *

+ * Command registrations should be done with {@link ClientCommandRegistrationCallback}. + *

+ * The commands are run on the client game thread by default. + * Avoid doing any heavy calculations here as that can freeze the game's rendering. + * To mitigate this, you can move heavy code to another thread. + *

+ * This class also has alternatives to the server-side helper methods in + * {@link net.minecraft.server.command.CommandManager CommandManager}: {@link #literal(String)} and + * {@link #argument(String, ArgumentType)}. + *

+ * Server-sided commands have precedence over client-sided commands. Client-sided commands which are overridden by + * server-sided ones can be run anyway with {@code /qcc run}. + * + *

Example command

+ *
{@code
+ * ClientCommandRegistrationCallback.EVENT.register(dispatcher ->
+ *   dispatcher.register(
+ *     ClientCommandManager.literal("hello").executes(context -> {
+ *       context.getSource().sendFeedback(new LiteralText("Hello, world!"));
+ *       return 0;
+ *     })
+ *   )
+ * );
+ * }
+ */ +@Environment(EnvType.CLIENT) +public final class ClientCommandManager { + /** + * The command dispatcher that handles client command registration and execution. + */ + public static final CommandDispatcher DISPATCHER = new CommandDispatcher<>(); + + private ClientCommandManager() { + } + + /** + * Creates a literal argument builder. + * + * @param name the literal name + * @return the created argument builder + * + * @see LiteralArgumentBuilder#literal(String) + * @see net.minecraft.server.command.CommandManager#literal(String) + */ + public static LiteralArgumentBuilder literal(String name) { + return LiteralArgumentBuilder.literal(name); + } + + /** + * Creates a required argument builder. + * + * @param name the name of the argument + * @param type the type of the argument + * @param the type of the parsed argument value + * @return the created argument builder + * + * @see RequiredArgumentBuilder#argument(String, ArgumentType) + * @see net.minecraft.server.command.CommandManager#argument(String, ArgumentType) + */ + public static RequiredArgumentBuilder argument(String name, ArgumentType type) { + return RequiredArgumentBuilder.argument(name, type); + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandRegistrationCallback.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandRegistrationCallback.java new file mode 100644 index 0000000000..8cc215223d --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/ClientCommandRegistrationCallback.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.api.client; + +import com.mojang.brigadier.CommandDispatcher; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; + +/** + * Callback for registering client-side commands. + * + * @see ClientCommandManager + */ +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface ClientCommandRegistrationCallback extends ClientEventAwareListener { + /** + * Event invoked when client-sided commands are registered. + */ + Event EVENT = Event.create(ClientCommandRegistrationCallback.class, callbacks -> dispatcher -> { + for (var callback : callbacks) { + callback.registerCommands(dispatcher); + } + }); + + /** + * Called when client-side commands are registered. + * + * @param dispatcher the command dispatcher + */ + void registerCommands(CommandDispatcher dispatcher); +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/QuiltClientCommandSource.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/QuiltClientCommandSource.java new file mode 100644 index 0000000000..1f84a17fb3 --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/api/client/QuiltClientCommandSource.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.api.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.Entity; +import net.minecraft.text.Text; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +/** + * Extensions to {@link CommandSource}, implemented on {@link net.minecraft.client.network.ClientCommandSource + * ClientCommandSource} for + * client commands - most of these methods are equivalents to methods on + * {@link net.minecraft.server.command.ServerCommandSource ServerCommandSource}, to provide a more familiar API. + */ +@Environment(EnvType.CLIENT) +public interface QuiltClientCommandSource extends CommandSource { + /** + * Sends a feedback message to the player. + * + * @param message the feedback message + */ + void sendFeedback(Text message); + + /** + * Sends an error message to the player. + * + * @param message the error message + */ + void sendError(Text message); + + /** + * Gets the client instance used to run the command. + * + * @return the client + */ + MinecraftClient getClient(); + + /** + * Gets the player that used the command. + * + * @return the player + */ + ClientPlayerEntity getPlayer(); + + /** + * Gets the entity that used the command. + * + * @return the entity + */ + default Entity getEntity() { + return getPlayer(); + } + + /** + * Gets the position from where the command has been executed. + * + * @return the position + */ + default Vec3d getPosition() { + return getPlayer().getPos(); + } + + /** + * Gets the rotation of the entity that used the command. + * + * @return the rotation + */ + default Vec2f getRotation() { + return getPlayer().getRotationClient(); + } + + /** + * Gets the world where the player used the command. + * + * @return the world + */ + ClientWorld getWorld(); + + /** + * Gets the meta property under {@code key} that was assigned to this source. + *

+ * This method should return the same result for every call with the same {@code key}. + * + * @param key the meta key + * + * @return the meta + */ + Object getMeta(String key); + + /** + * Sets the meta property under key {@code key} with the value {@code value}. + * + * @param key the meta key + * @param value the meta value + */ + void setMeta(String key, Object value); +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/impl/client/ClientCommandInternals.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/impl/client/ClientCommandInternals.java new file mode 100644 index 0000000000..82be11ceef --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/impl/client/ClientCommandInternals.java @@ -0,0 +1,344 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.impl.client; + +import static org.quiltmc.qsl.command.api.client.ClientCommandManager.DISPATCHER; +import static org.quiltmc.qsl.command.api.client.ClientCommandManager.argument; +import static org.quiltmc.qsl.command.api.client.ClientCommandManager.literal; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.google.common.collect.Iterables; +import com.mojang.brigadier.AmbiguityConsumer; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.exceptions.BuiltInExceptionProvider; +import com.mojang.brigadier.exceptions.CommandExceptionType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.CommandNode; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.command.CommandException; +import net.minecraft.command.CommandSource; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.Texts; +import net.minecraft.text.TranslatableText; + +import org.quiltmc.qsl.command.api.client.ClientCommandManager; +import org.quiltmc.qsl.command.api.client.ClientCommandRegistrationCallback; +import org.quiltmc.qsl.command.api.client.QuiltClientCommandSource; +import org.quiltmc.qsl.command.mixin.HelpCommandAccessor; + +@Environment(EnvType.CLIENT) +@ApiStatus.Internal +public final class ClientCommandInternals { + private static final Logger LOGGER = LoggerFactory.getLogger(ClientCommandInternals.class); + private static final char PREFIX = '/'; + private static final String API_COMMAND_NAME = "quilt_commands:client_commands"; + private static final String SHORT_API_COMMAND_NAME = "qcc"; + private static final Command DUMMY_COMMAND = ctx -> 0; + + /** + * Executes a client-sided command from a message. + * + * @param message the command message + * + * @return {@code true} if the message was executed as a command client-side (and therefore should not be sent to the + * server), {@code false} otherwise + */ + public static boolean executeCommand(String message) { + if (message.isEmpty()) { + return false; // Nothing to process + } + + if (message.charAt(0) != PREFIX) { + return false; // Incorrect prefix, won't execute anything. + } + + MinecraftClient client = MinecraftClient.getInstance(); + + // The interface is implemented on ClientCommandSource with a mixin. + // noinspection ConstantConditions + QuiltClientCommandSource commandSource = (QuiltClientCommandSource) client.getNetworkHandler().getCommandSource(); + + client.getProfiler().push(message); + + try { + // Only run client commands if there are no matching server-side commands. + String command = message.substring(1); + CommandDispatcher serverDispatcher = client.getNetworkHandler().getCommandDispatcher(); + ParseResults serverResults = serverDispatcher.parse(command, client.getNetworkHandler().getCommandSource()); + + if (serverResults.getReader().canRead() || isCommandInvalidOrDummy(serverResults)) { + DISPATCHER.execute(command, commandSource); + return true; + } else { + return false; + } + } catch (CommandSyntaxException e) { + boolean ignored = shouldIgnore(e.getType()); + + if (ignored) { + LOGGER.debug("Syntax exception for client-side command '{}'", message, e); + } else { + LOGGER.warn("Syntax exception for client-side command '{}'", message, e); + } + + if (ignored) { + return false; + } + + commandSource.sendError(getErrorMessage(e)); + return true; + } catch (CommandException e) { + LOGGER.warn("Error while executing client-side command '{}'", message, e); + commandSource.sendError(e.getTextMessage()); + return true; + } catch (RuntimeException e) { + LOGGER.warn("Error while executing client-side command '{}'", message, e); + commandSource.sendError(Text.of(e.getMessage())); + return true; + } finally { + client.getProfiler().pop(); + } + } + + /** + * Tests whether a parse result is invalid or the command it resolves to is a dummy command. + * + * Used to work out whether a command in the main dispatcher is a dummy command added by {@link ClientCommandInternals#addDummyCommands(CommandDispatcher, QuiltClientCommandSource)}. + * + * @param parse the parse results to test + * @param the command source type + * + * @return {@code true} if the parse result is invalid or the command is a dummy, {@code false} otherwise + */ + public static boolean isCommandInvalidOrDummy(final ParseResults parse) { + if (parse.getReader().canRead()) { + return true; + } + + final String command = parse.getReader().getString(); + final CommandContext context = parse.getContext().build(command); + + return context.getCommand() == null || context.getCommand() == DUMMY_COMMAND; + } + + /** + * Tests whether a command syntax exception with the type + * should be ignored and the message sent to the server. + * + * @param type the exception type + * @return {@code true} if ignored, {@code false} otherwise + */ + private static boolean shouldIgnore(CommandExceptionType type) { + BuiltInExceptionProvider builtins = CommandSyntaxException.BUILT_IN_EXCEPTIONS; + + // Only ignore unknown commands and node parse exceptions. + // The argument-related dispatcher exceptions are not ignored because + // they will only happen if the user enters a correct command. + return type == builtins.dispatcherUnknownCommand() || type == builtins.dispatcherParseException(); + } + + /** + * Analogous to {@code CommandSuggestor#formatException}, but returns a {@link Text} rather than an + * {@link net.minecraft.text.OrderedText OrderedText}. + * + * @param e the exception to get the error message from + * + * @return the error message as a {@link Text} + */ + private static Text getErrorMessage(CommandSyntaxException e) { + Text message = Texts.toText(e.getRawMessage()); + String context = e.getContext(); + + return context != null ? new TranslatableText("command.context.parse_error", message, context) : message; + } + + /** + * Registers client-sided commands, then runs final initialization tasks such as + * {@link CommandDispatcher#findAmbiguities(AmbiguityConsumer)} on the command dispatcher. Also registers + * a {@code /qcc help} command if there are other commands present. + */ + public static void initialize() { + ClientCommandRegistrationCallback.EVENT.invoker().registerCommands(DISPATCHER); + + if (!DISPATCHER.getRoot().getChildren().isEmpty()) { + // Register the qcc command only if there are other commands; + // it is not needed if there are no client commands. + CommandNode mainNode = DISPATCHER.register( + literal(API_COMMAND_NAME) + .then(createHelpCommand()) + .then(createRunCommand()) + ); + DISPATCHER.register(literal(SHORT_API_COMMAND_NAME).redirect(mainNode)); + } + + DISPATCHER.findAmbiguities((parent, child, sibling, inputs) -> + LOGGER.warn("Ambiguity between arguments {} and {} with inputs: {}", DISPATCHER.getPath(child), DISPATCHER.getPath(sibling), inputs) + ); + } + + /** + * @return the {@code run} subcommand for {@code /qcc} + */ + private static LiteralArgumentBuilder createRunCommand() { + LiteralArgumentBuilder runCommand = literal("run"); + for (CommandNode node : ClientCommandManager.DISPATCHER.getRoot().getChildren()) { + runCommand.then(node); + } + return runCommand; + } + + /** + * @return the {@code help} subcommand for {@code /qcc} + */ + private static LiteralArgumentBuilder createHelpCommand() { + return literal("help").then( + argument("command", StringArgumentType.greedyString()) + .executes(ClientCommandInternals::executeSpecificHelp)) + .executes(ClientCommandInternals::executeRootHelp); + } + + /** + * Runs {@link #executeHelp(CommandNode, CommandContext)} on the root node of the client-side command dispatcher. + * + * @param context the command context + * + * @return the number of commands + */ + private static int executeRootHelp(CommandContext context) { + return executeHelp(DISPATCHER.getRoot(), context); + } + + /** + * Runs {@link #executeHelp(CommandNode, CommandContext)} on a specific client-side command specified as a command + * argument. + * + * @param context the command context + * + * @return the number of subcommands of the command specified in the command argument + * + * @throws CommandSyntaxException if no such command as given in the command argument is given + */ + private static int executeSpecificHelp(CommandContext context) throws CommandSyntaxException { + ParseResults parseResults = DISPATCHER.parse(StringArgumentType.getString(context, "command"), context.getSource()); + List> nodes = parseResults.getContext().getNodes(); + + if (nodes.isEmpty()) { + throw HelpCommandAccessor.getFailedException().create(); + } + + // we know the call to Iterables#getLast won't be null as nodes is guaranteed to be nonempty + return executeHelp(Objects.requireNonNull(Iterables.getLast(nodes)).getNode(), context); + } + + /** + * Shows usage hints for a command node. + * + * @param startNode the node to get the usages of + * @param context the command context + * + * @return the amount of usage hints (i.e. the number of subcommands of startNode) + */ + private static int executeHelp(CommandNode startNode, CommandContext context) { + Map, String> commands = DISPATCHER.getSmartUsage(startNode, context.getSource()); + + for (var command : commands.values()) { + context.getSource().sendFeedback(new LiteralText(PREFIX + command)); + } + + return commands.size(); + } + + /** + * Adds dummy versions of the client commands to a given command dispatcher. Used to add the commands to + * {@link ClientPlayNetworkHandler}'s command dispatcher for autocompletion. + * + * @param target the target command dispatcher + * @param source the command source - commands which the source cannot use are filtered out + */ + public static void addDummyCommands(CommandDispatcher target, QuiltClientCommandSource source) { + var originalToCopy = new Object2ObjectOpenHashMap, CommandNode>(); + originalToCopy.put(DISPATCHER.getRoot(), target.getRoot()); + copyChildren(DISPATCHER.getRoot(), target.getRoot(), source, originalToCopy); + } + + /** + * Copies the child commands from origin to target, filtered by {@code child.canUse(source)}. + * Mimics vanilla's {@code CommandManager#makeTreeForSource}. Runs recursively. + * + * @param origin the source command node + * @param target the target command node + * @param source the command source + * @param originalToCopy a mutable map from original command nodes to their copies, used for redirects; + * should contain a mapping from origin to target + */ + private static void copyChildren( + CommandNode origin, + CommandNode target, + QuiltClientCommandSource source, + Map, CommandNode> originalToCopy + ) { + for (CommandNode child : origin.getChildren()) { + if (!child.canUse(source)) continue; + + if (target.getChild(child.getName()) != null) { + continue; + } + + ArgumentBuilder builder = child.createBuilder(); + + // Reset the unnecessary non-completion stuff from the builder + builder.requires(s -> true); // This is checked with the if check above. + + if (builder.getCommand() != null) { + builder.executes(DUMMY_COMMAND); + } + + // Set up redirects + if (builder.getRedirect() != null) { + builder.redirect(originalToCopy.get(builder.getRedirect())); + } + + CommandNode result = builder.build(); + originalToCopy.put(child, result); + target.addChild(result); + + if (!child.getChildren().isEmpty()) { + copyChildren(child, result, source, originalToCopy); + } + } + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/HelpCommandAccessor.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/HelpCommandAccessor.java new file mode 100644 index 0000000000..65da1547ff --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/HelpCommandAccessor.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin; + +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.server.command.HelpCommand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(HelpCommand.class) +public interface HelpCommandAccessor { + @Accessor("FAILED_EXCEPTION") + static SimpleCommandExceptionType getFailedException() { + throw new AssertionError("Accessor injection failed."); + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientCommandSourceMixin.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientCommandSourceMixin.java new file mode 100644 index 0000000000..ea2a91dcbd --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientCommandSourceMixin.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin.client; + +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientCommandSource; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.MessageType; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Util; + +import org.quiltmc.qsl.command.api.client.QuiltClientCommandSource; + +@Mixin(ClientCommandSource.class) +abstract class ClientCommandSourceMixin implements QuiltClientCommandSource { + @Shadow + @Final + private MinecraftClient client; + + @Unique private final Map meta = new Object2ObjectOpenHashMap<>(); + + @Override + public void sendFeedback(Text message) { + this.client.inGameHud.addChatMessage(MessageType.SYSTEM, message, Util.NIL_UUID); + } + + @Override + public void sendError(Text message) { + this.client.inGameHud.addChatMessage(MessageType.SYSTEM, new LiteralText("").append(message).formatted(Formatting.RED), Util.NIL_UUID); + } + + @Override + public MinecraftClient getClient() { + return this.client; + } + + @Override + public ClientPlayerEntity getPlayer() { + return this.client.player; + } + + @Override + public ClientWorld getWorld() { + return this.client.world; + } + + @Override + public Object getMeta(String key) { + return this.meta.get(key); + } + + @Override + public void setMeta(String key, Object value) { + this.meta.put(key, value); + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayNetworkHandlerMixin.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000000..ddba6d15ef --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin.client; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.client.network.ClientCommandSource; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.command.CommandSource; +import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket; +import org.quiltmc.qsl.command.api.client.QuiltClientCommandSource; +import org.quiltmc.qsl.command.impl.client.ClientCommandInternals; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +abstract class ClientPlayNetworkHandlerMixin { + @Shadow + private CommandDispatcher commandDispatcher; + + @Shadow + @Final + private ClientCommandSource commandSource; + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Inject(method = "onCommandTree", at = @At("RETURN")) + private void onOnCommandTree(CommandTreeS2CPacket packet, CallbackInfo info) { + // Add the commands to the vanilla dispatcher for completion. + // It's done here because both the server and the client commands have + // to be in the same dispatcher and completion results. + ClientCommandInternals.addDummyCommands((CommandDispatcher) commandDispatcher, (QuiltClientCommandSource) commandSource); + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayerEntityMixin.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayerEntityMixin.java new file mode 100644 index 0000000000..11703efbc3 --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/ClientPlayerEntityMixin.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin.client; + +import net.minecraft.client.network.ClientPlayerEntity; +import org.quiltmc.qsl.command.impl.client.ClientCommandInternals; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayerEntity.class) +abstract class ClientPlayerEntityMixin { + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + private void onSendChatMessage(String message, CallbackInfo info) { + if (ClientCommandInternals.executeCommand(message)) { + info.cancel(); + } + } +} diff --git a/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/MinecraftClientMixin.java b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000000..10d20ba5f7 --- /dev/null +++ b/library/command/client_command/src/main/java/org/quiltmc/qsl/command/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin.client; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.RunArgs; +import org.quiltmc.qsl.command.impl.client.ClientCommandInternals; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftClient.class) +abstract class MinecraftClientMixin { + @Inject(method = "", at = @At("RETURN")) + private void onConstruct(RunArgs args, CallbackInfo info) { + ClientCommandInternals.initialize(); + } +} diff --git a/library/command/client_command/src/main/resources/assets/quilt_client_command/icon.png b/library/command/client_command/src/main/resources/assets/quilt_client_command/icon.png new file mode 100644 index 0000000000..8d54ad0021 Binary files /dev/null and b/library/command/client_command/src/main/resources/assets/quilt_client_command/icon.png differ diff --git a/library/command/client_command/src/main/resources/fabric.mod.json b/library/command/client_command/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..d388e3abc9 --- /dev/null +++ b/library/command/client_command/src/main/resources/fabric.mod.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": 1, + "id": "quilt_client_command", + "name": "Quilt Client Command API", + "version": "${version}", + "environment": "client", + "license": "Apache-2.0", + "icon": "assets/quilt_client_command/icon.png", + "contact": { + "homepage": "https://quiltmc.org", + "issues": "https://github.com/QuiltMC/quilt-standard-libraries/issues", + "sources": "https://github.com/QuiltMC/quilt-standard-libraries" + }, + "authors": [ + "QuiltMC" + ], + "depends": { + "fabricloader": ">=0.12", + "minecraft": ">=1.18.2-alpha.22.3.a", + "quilt_base": "*" + }, + "description": "Quilt APIs relating to client commands.", + "mixins": [ + "quilt_client_command.mixins.json" + ], + "custom": { + "modmenu": { + "badges": [ + "library" + ], + "parent": { + "id": "qsl", + "name": "Quilt Standard Libraries", + "description": "A set of libraries to assist in making Quilt mods.", + "icon": "assets/quilt_client_command/icon.png", + "badges": [ + "library" + ] + } + } + } +} diff --git a/library/command/client_command/src/main/resources/quilt_client_command.mixins.json b/library/command/client_command/src/main/resources/quilt_client_command.mixins.json new file mode 100644 index 0000000000..4f703d7966 --- /dev/null +++ b/library/command/client_command/src/main/resources/quilt_client_command.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "org.quiltmc.qsl.command.mixin", + "compatibilityLevel": "JAVA_17", + "client": [ + "HelpCommandAccessor", + "client.ClientCommandSourceMixin", + "client.ClientPlayerEntityMixin", + "client.ClientPlayNetworkHandlerMixin", + "client.MinecraftClientMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/library/command/client_command/src/testmod/java/org/quiltmc/qsl/command/client/test/ClientCommandApiTest.java b/library/command/client_command/src/testmod/java/org/quiltmc/qsl/command/client/test/ClientCommandApiTest.java new file mode 100644 index 0000000000..247284d786 --- /dev/null +++ b/library/command/client_command/src/testmod/java/org/quiltmc/qsl/command/client/test/ClientCommandApiTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.client.test; + +import net.fabricmc.api.ClientModInitializer; +import net.minecraft.text.LiteralText; +import org.quiltmc.qsl.command.api.client.ClientCommandManager; +import org.quiltmc.qsl.command.api.client.ClientCommandRegistrationCallback; + +public class ClientCommandApiTest implements ClientModInitializer { + @Override + public void onInitializeClient() { + ClientCommandRegistrationCallback.EVENT.register(dispatcher -> { + dispatcher.register( + ClientCommandManager.literal("test_client_command") + .executes(ctx -> { + ctx.getSource().sendFeedback(new LiteralText("It works!")); + return 0; + }) + ); + + dispatcher.register( + ClientCommandManager.literal("seed") + .executes(ctx -> { + ctx.getSource().sendFeedback(new LiteralText("This is a client-only command which conflicts with a server command!")); + return 0; + }) + ); + }); + } +} diff --git a/library/command/client_command/src/testmod/resources/fabric.mod.json b/library/command/client_command/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..63547b0c60 --- /dev/null +++ b/library/command/client_command/src/testmod/resources/fabric.mod.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 1, + "id": "quilt_client_command_testmod", + "name": "Quilt Client Command API Test Mod", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/quilt_base/icon.png", + "contact": { + "homepage": "https://quiltmc.org", + "issues": "https://github.com/QuiltMC/quilt-standard-libraries/issues", + "sources": "https://github.com/QuiltMC/quilt-standard-libraries" + }, + "authors": [ + "QuiltMC" + ], + "depends": { + "fabricloader": ">=0.12" + }, + "entrypoints": { + "client": [ + "org.quiltmc.qsl.command.client.test.ClientCommandApiTest" + ] + }, + "description": "Testmod for Quilt APIs relating to client commands.", + "mixins": [] +} diff --git a/library/command/command/build.gradle b/library/command/command/build.gradle new file mode 100644 index 0000000000..5e528eb797 --- /dev/null +++ b/library/command/command/build.gradle @@ -0,0 +1,12 @@ +plugins { + id("qsl.module") +} + +qslModule { + moduleName = "command" + version = "1.0.0" + library = "command" + coreDependencies([ + "qsl_base" + ]) +} diff --git a/library/command/command/src/main/java/org/quiltmc/qsl/command/api/CommandRegistrationCallback.java b/library/command/command/src/main/java/org/quiltmc/qsl/command/api/CommandRegistrationCallback.java new file mode 100644 index 0000000000..13632ed3d7 --- /dev/null +++ b/library/command/command/src/main/java/org/quiltmc/qsl/command/api/CommandRegistrationCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.api; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.command.ServerCommandSource; +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; + +/** + * Callback for registering server-side commands. + */ +@FunctionalInterface +public interface CommandRegistrationCallback extends EventAwareListener { + /** + * Event invoked when server-side commands are registered. + */ + Event EVENT = Event.create(CommandRegistrationCallback.class, callbacks -> (dispatcher, integrated, dedicated) -> { + for (var callback : callbacks) { + callback.registerCommands(dispatcher, integrated, dedicated); + } + }); + + /** + * Called when server-side commands are registered. + * + * @param dispatcher the command dispatcher + * @param integrated whether commands intended for only the integrated server (i.e. singleplayer) should be registered + * @param dedicated whether commands intended for only the dedicated server should be registered + */ + void registerCommands(CommandDispatcher dispatcher, boolean integrated, boolean dedicated); +} diff --git a/library/command/command/src/main/java/org/quiltmc/qsl/command/mixin/CommandManagerMixin.java b/library/command/command/src/main/java/org/quiltmc/qsl/command/mixin/CommandManagerMixin.java new file mode 100644 index 0000000000..29e1d812a3 --- /dev/null +++ b/library/command/command/src/main/java/org/quiltmc/qsl/command/mixin/CommandManagerMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.mixin; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import org.quiltmc.qsl.command.api.CommandRegistrationCallback; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(CommandManager.class) +public abstract class CommandManagerMixin { + @Shadow + @Final + private CommandDispatcher dispatcher; + + @Inject(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/CommandDispatcher;findAmbiguities(Lcom/mojang/brigadier/AmbiguityConsumer;)V")) + void addQuiltCommands(CommandManager.RegistrationEnvironment environment, CallbackInfo ci) { + CommandRegistrationCallback.EVENT.invoker().registerCommands(this.dispatcher, environmentMatches(environment, CommandManager.RegistrationEnvironment.INTEGRATED), environmentMatches(environment, CommandManager.RegistrationEnvironment.DEDICATED)); + } + + @Unique + private static boolean environmentMatches(CommandManager.RegistrationEnvironment a, CommandManager.RegistrationEnvironment b) { + return a == b || b == CommandManager.RegistrationEnvironment.ALL; + } +} diff --git a/library/command/command/src/main/resources/assets/quilt_command/icon.png b/library/command/command/src/main/resources/assets/quilt_command/icon.png new file mode 100644 index 0000000000..8d54ad0021 Binary files /dev/null and b/library/command/command/src/main/resources/assets/quilt_command/icon.png differ diff --git a/library/command/command/src/main/resources/fabric.mod.json b/library/command/command/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..02d221fcc6 --- /dev/null +++ b/library/command/command/src/main/resources/fabric.mod.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": 1, + "id": "quilt_command", + "name": "Quilt Command API", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/quilt_command/icon.png", + "contact": { + "homepage": "https://quiltmc.org", + "issues": "https://github.com/QuiltMC/quilt-standard-libraries/issues", + "sources": "https://github.com/QuiltMC/quilt-standard-libraries" + }, + "authors": [ + "QuiltMC" + ], + "depends": { + "fabricloader": ">=0.12", + "minecraft": ">=1.18.2-alpha.22.3.a", + "quilt_base": "*" + }, + "description": "Quilt APIs relating to commands.", + "mixins": [ + "quilt_command.mixins.json" + ], + "custom": { + "modmenu": { + "badges": [ + "library" + ], + "parent": { + "id": "qsl", + "name": "Quilt Standard Libraries", + "description": "A set of libraries to assist in making Quilt mods.", + "icon": "assets/quilt_command/icon.png", + "badges": [ + "library" + ] + } + } + } +} diff --git a/library/command/command/src/main/resources/quilt_command.mixins.json b/library/command/command/src/main/resources/quilt_command.mixins.json new file mode 100644 index 0000000000..fd8ef5e03d --- /dev/null +++ b/library/command/command/src/main/resources/quilt_command.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "org.quiltmc.qsl.command.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "CommandManagerMixin" + ], + "client": [], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/library/command/command/src/testmod/java/org/quiltmc/qsl/command/test/CommandApiTest.java b/library/command/command/src/testmod/java/org/quiltmc/qsl/command/test/CommandApiTest.java new file mode 100644 index 0000000000..04925d1bbf --- /dev/null +++ b/library/command/command/src/testmod/java/org/quiltmc/qsl/command/test/CommandApiTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.command.test; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.text.LiteralText; +import org.quiltmc.qsl.command.api.CommandRegistrationCallback; + +import static net.minecraft.server.command.CommandManager.literal; + +public class CommandApiTest implements ModInitializer { + @Override + public void onInitialize() { + CommandRegistrationCallback.EVENT.register((dispatcher, integrated, dedicated) -> { + if (dedicated) { + dispatcher.register(literal("ping") + .executes(ctx -> { + ctx.getSource().sendFeedback(new LiteralText("pong!"), false); + return 0; + }) + ); + } else if (integrated) { + dispatcher.register(literal("singleplayer_only") + .executes(ctx -> { + ctx.getSource().sendFeedback(new LiteralText("This command should only exist in singleplayer"), false); + return 0; + }) + ); + } + + dispatcher.register(literal("quilt") + .executes(ctx -> { + //noinspection OptionalGetWithoutIsPresent + ctx.getSource().sendFeedback(new LiteralText("Quilt Version: "+FabricLoader.getInstance().getModContainer("quilt_base").get().getMetadata().getVersion().getFriendlyString()), false); + return 0; + }) + ); + }); + } +} diff --git a/library/command/command/src/testmod/resources/fabric.mod.json b/library/command/command/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..d3fcec05ba --- /dev/null +++ b/library/command/command/src/testmod/resources/fabric.mod.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 1, + "id": "quilt_command_testmod", + "name": "Quilt Command API Test Mod", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/quilt_base/icon.png", + "contact": { + "homepage": "https://quiltmc.org", + "issues": "https://github.com/QuiltMC/quilt-standard-libraries/issues", + "sources": "https://github.com/QuiltMC/quilt-standard-libraries" + }, + "authors": [ + "QuiltMC" + ], + "depends": { + "fabricloader": ">=0.12" + }, + "entrypoints": { + "main": [ + "org.quiltmc.qsl.command.test.CommandApiTest" + ] + }, + "description": "Testmod for Quilt APIs relating to commands.", + "mixins": [] +} diff --git a/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/api/CrashReportEvents.java b/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/api/CrashReportEvents.java index 95aaa36041..2b4ad884ff 100644 --- a/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/api/CrashReportEvents.java +++ b/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/api/CrashReportEvents.java @@ -30,6 +30,7 @@ import net.minecraft.world.World; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; /** * Events which allow the manipulation of crash reports. @@ -111,7 +112,7 @@ public final class CrashReportEvents { * @see #SYSTEM_DETAILS */ @FunctionalInterface - public interface SystemDetails { + public interface SystemDetails extends EventAwareListener { void addDetails(net.minecraft.util.SystemDetails details); } @@ -121,7 +122,7 @@ public interface SystemDetails { * @see #WORLD_DETAILS */ @FunctionalInterface - public interface WorldDetails { + public interface WorldDetails extends EventAwareListener { void addDetails(World world, CrashReportSection section); } @@ -131,7 +132,7 @@ public interface WorldDetails { * @see #BLOCK_DETAILS */ @FunctionalInterface - public interface BlockDetails { + public interface BlockDetails extends EventAwareListener { void addDetails(HeightLimitView world, BlockPos pos, @Nullable BlockState state, CrashReportSection section); } @@ -141,7 +142,7 @@ public interface BlockDetails { * @see #ENTITY_DETAILS */ @FunctionalInterface - public interface EntityDetails { + public interface EntityDetails extends EventAwareListener { void addDetails(Entity entity, CrashReportSection section); } @@ -151,7 +152,7 @@ public interface EntityDetails { * @see #BLOCKENTITY_DETAILS */ @FunctionalInterface - public interface BlockEntityDetails { + public interface BlockEntityDetails extends EventAwareListener { void addDetails(BlockEntity entity, CrashReportSection section); } @@ -161,7 +162,7 @@ public interface BlockEntityDetails { * @see #CRASH_REPORT_CREATION */ @FunctionalInterface - public interface CrashReportCreation { + public interface CrashReportCreation extends EventAwareListener { void onCreate(CrashReport report); } } diff --git a/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/impl/CrashInfoImpl.java b/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/impl/CrashInfoImpl.java index 66b62115c6..92676a0499 100644 --- a/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/impl/CrashInfoImpl.java +++ b/library/core/crash_info/src/main/java/org/quiltmc/qsl/crash/impl/CrashInfoImpl.java @@ -16,18 +16,19 @@ package org.quiltmc.qsl.crash.impl; -import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import org.jetbrains.annotations.ApiStatus; +import net.minecraft.util.SystemDetails; + import org.quiltmc.qsl.crash.api.CrashReportEvents; @ApiStatus.Internal -public final class CrashInfoImpl implements ModInitializer { +public final class CrashInfoImpl implements CrashReportEvents.SystemDetails { @Override - public void onInitialize() { - CrashReportEvents.SYSTEM_DETAILS.register(details -> details.addSection("Quilt Mods", () -> { + public void addDetails(SystemDetails details) { + details.addSection("Quilt Mods", () -> { var builder = new StringBuilder(); for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { @@ -41,6 +42,6 @@ public void onInitialize() { } return builder.toString(); - })); + }); } } diff --git a/library/core/crash_info/src/main/resources/fabric.mod.json b/library/core/crash_info/src/main/resources/fabric.mod.json index b2dd463d3b..f4513eb87d 100644 --- a/library/core/crash_info/src/main/resources/fabric.mod.json +++ b/library/core/crash_info/src/main/resources/fabric.mod.json @@ -15,13 +15,14 @@ "QuiltMC" ], "entrypoints": { - "main": [ + "events": [ "org.quiltmc.qsl.crash.impl.CrashInfoImpl" ] }, "depends": { "fabricloader": ">=0.12", - "minecraft": ">=1.18-rc.4" + "minecraft": ">=1.18.2-alpha.22.3.a", + "quilt_base": "*" }, "description": "Adds information about the Quilt environment in crash reports, and provides an API for other mods to add their own information.", "mixins": [ diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientLifecycleEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientLifecycleEvents.java index 20971dec6b..a3853341ee 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientLifecycleEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientLifecycleEvents.java @@ -22,6 +22,7 @@ import net.minecraft.client.MinecraftClient; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; /** * Events indicating the lifecycle of a Minecraft client. @@ -87,7 +88,7 @@ private ClientLifecycleEvents() { */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface Ready { + public interface Ready extends ClientEventAwareListener { /** * Called when a majority of client facilities have been initialized. * @@ -105,7 +106,7 @@ public interface Ready { */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface Stopping { + public interface Stopping extends ClientEventAwareListener { /** * Called when a Minecraft client has finished its last tick and is shutting down. * @@ -121,7 +122,7 @@ public interface Stopping { */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface Stopped { + public interface Stopped extends ClientEventAwareListener { /** * Called when a Minecraft client has finished shutdown and the client will be exited. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientTickEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientTickEvents.java index 52c4bbad1a..61594d561b 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientTickEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientTickEvents.java @@ -22,6 +22,7 @@ import net.minecraft.client.MinecraftClient; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; /** * Events indicating progress through the tick loop of a Minecraft client. @@ -64,7 +65,7 @@ private ClientTickEvents() { */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface Start { + public interface Start extends ClientEventAwareListener { /** * Called before the client has started an iteration of the tick loop. * @@ -80,7 +81,7 @@ public interface Start { */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface End { + public interface End extends ClientEventAwareListener { /** * Called at the end of an iteration of the client's tick loop. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientWorldTickEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientWorldTickEvents.java index a24db80a36..02a557f7f1 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientWorldTickEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/client/event/ClientWorldTickEvents.java @@ -16,19 +16,20 @@ package org.quiltmc.qsl.lifecycle.api.client.event; -import org.quiltmc.qsl.base.api.event.Event; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.world.ClientWorld; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; /** * Events related to a ticking Minecraft client's world. * *

A note of warning

- * + *

* Callbacks registered to any of these events should ensure as little time as possible is spent executing, since the tick * loop is a very hot code path. */ @@ -52,36 +53,39 @@ public final class ClientWorldTickEvents { } }); - private ClientWorldTickEvents() {} + private ClientWorldTickEvents() { + } /** * Functional interface to be implemented on callbacks for {@link #START}. + * * @see #START */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface Start { + public interface Start extends ClientEventAwareListener { /** * Called before a world is ticked. * * @param client the client - * @param world the world being ticked + * @param world the world being ticked */ void startWorldTick(MinecraftClient client, ClientWorld world); } /** * Functional interface to be implemented on callbacks for {@link #END}. + * * @see #END */ @FunctionalInterface @Environment(EnvType.CLIENT) - public interface End { + public interface End extends ClientEventAwareListener { /** * Called after a world is ticked. * * @param client the client - * @param world the world being ticked + * @param world the world being ticked */ void endWorldTick(MinecraftClient client, ClientWorld world); } diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerLifecycleEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerLifecycleEvents.java index 32e9acb83a..3b894f488e 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerLifecycleEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerLifecycleEvents.java @@ -19,6 +19,7 @@ import net.minecraft.server.MinecraftServer; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; /** * Events indicating the lifecycle of a Minecraft server. @@ -116,7 +117,7 @@ private ServerLifecycleEvents() { * @see #STARTING */ @FunctionalInterface - public interface Starting { + public interface Starting extends EventAwareListener { /** * Called when a Minecraft server is starting. * @@ -131,7 +132,7 @@ public interface Starting { * @see #READY */ @FunctionalInterface - public interface Ready { + public interface Ready extends EventAwareListener { /** * Called when a Minecraft server is ready to tick and accept players. * @@ -146,7 +147,7 @@ public interface Ready { * @see #STOPPING */ @FunctionalInterface - public interface Stopping { + public interface Stopping extends EventAwareListener { /** * Called when a Minecraft server has finished its last tick and is shutting down. * @@ -161,7 +162,7 @@ public interface Stopping { * @see #STOPPED */ @FunctionalInterface - public interface Stopped { + public interface Stopped extends EventAwareListener { /** * Called when a Minecraft server has finished shutdown and the server will be exited. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerTickEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerTickEvents.java index 1121beef0c..46e7850e75 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerTickEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerTickEvents.java @@ -19,6 +19,7 @@ import net.minecraft.server.MinecraftServer; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; /** * Events indicating progress through the tick loop of a Minecraft server. @@ -62,7 +63,7 @@ private ServerTickEvents() { * @see #START */ @FunctionalInterface - public interface Start { + public interface Start extends EventAwareListener { /** * Called before the server has started an iteration of the tick loop. * @@ -77,7 +78,7 @@ public interface Start { * @see #END */ @FunctionalInterface - public interface End { + public interface End extends EventAwareListener { /** * Called at the end of an iteration of the server's tick loop. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldLoadEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldLoadEvents.java index d6c73456f0..4cb667af6f 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldLoadEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldLoadEvents.java @@ -20,6 +20,7 @@ import net.minecraft.server.world.ServerWorld; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; /** * Events indicating whether a world has been loaded or unloaded from a server. @@ -61,7 +62,7 @@ private ServerWorldLoadEvents() { * @see #LOAD */ @FunctionalInterface - public interface Load { + public interface Load extends EventAwareListener { /** * Called when a world is loaded onto a Minecraft server. * @@ -79,7 +80,7 @@ public interface Load { * @see #UNLOAD */ @FunctionalInterface - public interface Unload { + public interface Unload extends EventAwareListener { /** * Called when a world is unloaded from a Minecraft server. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldTickEvents.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldTickEvents.java index ebc5a4058b..e5726724a2 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldTickEvents.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/api/event/ServerWorldTickEvents.java @@ -20,6 +20,7 @@ import net.minecraft.server.world.ServerWorld; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; /** * Events related to a ticking Minecraft server's worlds. @@ -57,7 +58,7 @@ private ServerWorldTickEvents() { * @see #START */ @FunctionalInterface - public interface Start { + public interface Start extends EventAwareListener { /** * Called before a world is ticked. * @@ -73,7 +74,7 @@ public interface Start { * @see #END */ @FunctionalInterface - public interface End { + public interface End extends EventAwareListener { /** * Called after a world is ticked. * diff --git a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/mixin/client/MinecraftClientMixin.java b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/mixin/client/MinecraftClientMixin.java index 8d1c3690b8..5686561d0a 100644 --- a/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/mixin/client/MinecraftClientMixin.java +++ b/library/core/lifecycle_events/src/main/java/org/quiltmc/qsl/lifecycle/mixin/client/MinecraftClientMixin.java @@ -46,7 +46,7 @@ private void clientReady(CallbackInfo info) { @Inject( method = "stop", - at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false) + at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false) ) private void clientStopping(CallbackInfo info) { ClientLifecycleEvents.STOPPING.invoker().stoppingClient((MinecraftClient) (Object) this); diff --git a/library/core/lifecycle_events/src/main/resources/fabric.mod.json b/library/core/lifecycle_events/src/main/resources/fabric.mod.json index 3818f7c2d2..834c73e80a 100644 --- a/library/core/lifecycle_events/src/main/resources/fabric.mod.json +++ b/library/core/lifecycle_events/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ ], "depends": { "fabricloader": ">=0.12", - "minecraft": ">=1.18.1", + "minecraft": ">=1.18.2-alpha.22.3.a", "quilt_base": "*" }, "description": "Events to track the lifecycle of Minecraft.", diff --git a/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/ServerLifecycleTests.java b/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/ServerLifecycleTests.java index 98ff75a533..92f0d277c2 100644 --- a/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/ServerLifecycleTests.java +++ b/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/ServerLifecycleTests.java @@ -17,8 +17,10 @@ package org.quiltmc.qsl.lifecycle.test.event; import net.fabricmc.api.ModInitializer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.server.MinecraftServer; import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; import org.quiltmc.qsl.lifecycle.api.event.ServerWorldLoadEvents; @@ -26,23 +28,14 @@ /** * Tests related to the lifecycle of a server. */ -public final class ServerLifecycleTests implements ModInitializer { - public static final Logger LOGGER = LogManager.getLogger("LifecycleEventsTest"); +public final class ServerLifecycleTests implements ModInitializer, + ServerLifecycleEvents.Starting, ServerLifecycleEvents.Ready, + ServerLifecycleEvents.Stopped, ServerLifecycleEvents.Stopping { + public static final ServerLifecycleTests TESTS = new ServerLifecycleTests(); + public static final Logger LOGGER = LoggerFactory.getLogger("LifecycleEventsTest"); @Override public void onInitialize() { - ServerLifecycleEvents.READY.register(server -> { - LOGGER.info("Started Server!"); - }); - - ServerLifecycleEvents.STOPPING.register(server -> { - LOGGER.info("Stopping Server!"); - }); - - ServerLifecycleEvents.STOPPED.register(server -> { - LOGGER.info("Stopped Server!"); - }); - ServerWorldLoadEvents.LOAD.register((server, world) -> { LOGGER.info("Loaded world " + world.getRegistryKey().getValue().toString()); }); @@ -51,4 +44,24 @@ public void onInitialize() { LOGGER.info("Unloaded world " + world.getRegistryKey().getValue().toString()); }); } + + @Override + public void startingServer(MinecraftServer server) { + LOGGER.info("Starting Server!"); + } + + @Override + public void readyServer(MinecraftServer server) { + LOGGER.info("Started Server!"); + } + + @Override + public void stoppingServer(MinecraftServer server) { + LOGGER.info("Stopping Server!"); + } + + @Override + public void exitServer(MinecraftServer server) { + LOGGER.info("Stopped Server!"); + } } diff --git a/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/client/ClientLifecycleTests.java b/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/client/ClientLifecycleTests.java index 6173dd6ba3..437b9efa79 100644 --- a/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/client/ClientLifecycleTests.java +++ b/library/core/lifecycle_events/src/testmod/java/org/quiltmc/qsl/lifecycle/test/event/client/ClientLifecycleTests.java @@ -16,38 +16,38 @@ package org.quiltmc.qsl.lifecycle.test.event.client; -import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; + import org.quiltmc.qsl.lifecycle.api.client.event.ClientLifecycleEvents; import org.quiltmc.qsl.lifecycle.test.event.ServerLifecycleTests; @Environment(EnvType.CLIENT) -public final class ClientLifecycleTests implements ClientModInitializer { +public final class ClientLifecycleTests implements ClientLifecycleEvents.Ready, ClientLifecycleEvents.Stopping { private boolean startCalled; private boolean stopCalled; @Override - public void onInitializeClient() { - ClientLifecycleEvents.READY.register(client -> { - if (this.startCalled) { - throw new IllegalStateException("Start was already called!"); - } - - this.startCalled = true; - client.submitAndJoin(() -> { // This should fail if the client thread was not bound yet. - ServerLifecycleTests.LOGGER.info("Started the client"); - }); + public void readyClient(MinecraftClient client) { + if (this.startCalled) { + throw new IllegalStateException("Start was already called!"); + } + + this.startCalled = true; + client.submitAndJoin(() -> { // This should fail if the client thread was not bound yet. + ServerLifecycleTests.LOGGER.info("Started the client"); }); + } - ClientLifecycleEvents.STOPPING.register(client -> { - if (this.stopCalled) { - throw new IllegalStateException("Stop was already called!"); - } + @Override + public void stoppingClient(MinecraftClient client) { + if (this.stopCalled) { + throw new IllegalStateException("Stop was already called!"); + } - this.stopCalled = true; - ServerLifecycleTests.LOGGER.info("Client has started stopping!"); - }); + this.stopCalled = true; + ServerLifecycleTests.LOGGER.info("Client has started stopping!"); } } diff --git a/library/core/lifecycle_events/src/testmod/resources/fabric.mod.json b/library/core/lifecycle_events/src/testmod/resources/fabric.mod.json index 1392d8cb1a..0074f25442 100644 --- a/library/core/lifecycle_events/src/testmod/resources/fabric.mod.json +++ b/library/core/lifecycle_events/src/testmod/resources/fabric.mod.json @@ -10,12 +10,17 @@ }, "entrypoints": { "main": [ - "org.quiltmc.qsl.lifecycle.test.event.ServerLifecycleTests", + "org.quiltmc.qsl.lifecycle.test.event.ServerLifecycleTests::TESTS", "org.quiltmc.qsl.lifecycle.test.event.ServerTickTests" ], "client": [ - "org.quiltmc.qsl.lifecycle.test.event.client.ClientLifecycleTests", "org.quiltmc.qsl.lifecycle.test.event.client.ClientTickTests" + ], + "events": [ + "org.quiltmc.qsl.lifecycle.test.event.ServerLifecycleTests::TESTS" + ], + "client_events": [ + "org.quiltmc.qsl.lifecycle.test.event.client.ClientLifecycleTests" ] } } diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/Event.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/Event.java index 523c720d37..39b651cd8a 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/Event.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/Event.java @@ -1,6 +1,6 @@ /* * Copyright 2016, 2017, 2018, 2019 FabricMC - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.quiltmc.qsl.base.api.util.QuiltAssertions; import org.quiltmc.qsl.base.impl.QuiltBaseImpl; import org.quiltmc.qsl.base.impl.event.EventPhaseData; +import org.quiltmc.qsl.base.impl.event.EventRegistry; import org.quiltmc.qsl.base.impl.event.PhaseSorting; /** @@ -186,9 +187,31 @@ public static Event createWithPhases(Class type, Function + * The registration of the listener will be refused if one of the listed event involves generics in its callback type, + * as checking for valid registration is just too expensive, please use the regular {@link #register(Object)} method + * for those as the Java compiler will be able to do the checks itself. + * + * @param listener the listener of events + * @param events the events to listen + * @throws IllegalArgumentException if the listener doesn't listen one of the events to listen + * @see #register(Object) + * @see #register(Identifier, Object) + */ + public static void listenAll(Object listener, Event... events) { + if (events.length == 0) { + throw new IllegalArgumentException("Tried to register a listener for an empty event list."); + } + + EventRegistry.listenAll(listener, events); + } + /** * The function used to generate the implementation of the invoker to execute events. */ + private final Class type; private final Function implementation; private final Lock lock = new ReentrantLock(); /** @@ -213,15 +236,26 @@ private Event(Class type, Function implementation) { Objects.requireNonNull(type, "Class specifying the type of T in the event cannot be null"); Objects.requireNonNull(implementation, "Function to generate invoker implementation for T cannot be null"); + this.type = type; this.implementation = implementation; this.callbacks = (T[]) Array.newInstance(type, 0); this.update(); + + EventRegistry.register(this); + } + + /** + * {@return the class of the type of the invoker used to execute an event and the class of the type of the callback} + */ + public Class getType() { + return this.type; } /** * Register a callback to the event. * * @param callback the callback + * @see #register(Identifier, Object) */ public void register(T callback) { this.register(DEFAULT_PHASE, callback); @@ -331,4 +365,14 @@ private void update() { // backing array this.invoker = this.implementation.apply(Arrays.copyOf(this.callbacks, this.callbacks.length)); } + + @Override + public String toString() { + return "Event{" + + "type=" + this.type + + ", implementation=" + this.implementation + + ", phases=" + this.phases + + ", sortedPhases=" + this.sortedPhases + + '}'; + } } diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/EventAwareListener.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/EventAwareListener.java new file mode 100644 index 0000000000..122b61b48d --- /dev/null +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/EventAwareListener.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.api.event; + +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; +import org.quiltmc.qsl.base.api.event.server.DedicatedServerEventAwareListener; + +/** + * Represents an event callback aware of its uniquely associated event, may be used as an entrypoint. + *

+ * In {@code fabric.mod.json}, the entrypoint is defined with {@code events} key. + * + * @see ClientEventAwareListener + * @see DedicatedServerEventAwareListener + */ +public interface EventAwareListener { +} diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ListenerPhase.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ListenerPhase.java new file mode 100644 index 0000000000..52f9108bd9 --- /dev/null +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ListenerPhase.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.api.event; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.minecraft.util.Identifier; + +/** + * Annotates a specific callback in a listener a specific phase to listen. + * + * @see Event#addPhaseOrdering(Identifier, Identifier) + * @see Event#register(Identifier, Object) + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ListenerPhase { + /** + * {@return the targeted callback interface} + */ + Class callbackTarget(); + + /** + * {@return the namespace of the phase to listen} + */ + String namespace(); + + /** + * {@return the path of the phase to listen} + */ + String path(); +} diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ParameterInvokingEvent.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ParameterInvokingEvent.java index 06b1c64a9e..a5806a542b 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ParameterInvokingEvent.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/ParameterInvokingEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ * second parameter is the entity being spooked. The entity being spooked is the invoking parameter. * *

Why make an event parameter invoking?

- * + *

* A good reason to make an event parameter invoking is when the event directly relates to an action performed on a * specific object. For example, an entity being unloaded from a world. With a traditional event, you would register a * callback and check for your specific entity in the callback, typically via an {@code instanceof} check for modded @@ -67,7 +67,7 @@ * } * *

How to use this annotation

- * + *

* An event which may invoke a parameter should first place the annotation on the field of the event instance or the * method which returns an event instance. * @@ -90,6 +90,6 @@ */ @Documented // Documentation @Retention(RetentionPolicy.CLASS) // For static analysis -@Target({ ElementType.FIELD, ElementType.METHOD }) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface ParameterInvokingEvent { } diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/client/ClientEventAwareListener.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/client/ClientEventAwareListener.java new file mode 100644 index 0000000000..19ca15e8bb --- /dev/null +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/client/ClientEventAwareListener.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.api.event.client; + +import org.quiltmc.qsl.base.api.event.EventAwareListener; +import org.quiltmc.qsl.base.api.event.server.DedicatedServerEventAwareListener; + +/** + * Represents a client-sided event callback aware of its uniquely associated event, may be used as an entrypoint. + *

+ * In {@code fabric.mod.json}, the entrypoint is defined with {@code client_events} key. + * + * @see EventAwareListener + * @see DedicatedServerEventAwareListener + */ +public interface ClientEventAwareListener { +} diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/package-info.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/package-info.java index fb181d8451..1c174415d7 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/package-info.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/package-info.java @@ -1,5 +1,5 @@ /** - * Quilt standard library event apis. + * Quilt standard library event APIs. * * @see org.quiltmc.qsl.base.api.event.Event */ diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/server/DedicatedServerEventAwareListener.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/server/DedicatedServerEventAwareListener.java new file mode 100644 index 0000000000..f38a35f01f --- /dev/null +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/event/server/DedicatedServerEventAwareListener.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.api.event.server; + +import org.quiltmc.qsl.base.api.event.EventAwareListener; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; + +/** + * Represents a dedicated-server-sided event callback aware of its uniquely associated event, may be used as an entrypoint. + *

+ * In {@code fabric.mod.json}, the entrypoint is defined with {@code server_events} key. + *

+ * Any event callback interface extending this interface can be listened using this entrypoint. + * + * @see EventAwareListener + * @see ClientEventAwareListener + */ +public interface DedicatedServerEventAwareListener { +} diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/util/QuiltAssertions.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/util/QuiltAssertions.java index d6b97b23e7..3f5836c3fd 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/util/QuiltAssertions.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/api/util/QuiltAssertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/QuiltBaseImpl.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/QuiltBaseImpl.java index d1d18d9862..f77f04e137 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/QuiltBaseImpl.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/QuiltBaseImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package org.quiltmc.qsl.base.impl; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.util.Identifier; @@ -26,7 +26,7 @@ @ApiStatus.Internal public final class QuiltBaseImpl { - public static final Logger LOGGER = LogManager.getLogger("quilt_base"); + public static final Logger LOGGER = LoggerFactory.getLogger("quilt_base"); /** * Represents the number of ticks before an auto test server audits mixins and shutdowns. *

diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventPhaseData.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventPhaseData.java index 1abbbadafd..5920dad36f 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventPhaseData.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventPhaseData.java @@ -1,6 +1,6 @@ /* * Copyright 2016, 2017, 2018, 2019 FabricMC - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventRegistry.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventRegistry.java new file mode 100644 index 0000000000..e5985416ca --- /dev/null +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/EventRegistry.java @@ -0,0 +1,122 @@ +/* + * Copyright 2021-2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.impl.event; + +import java.util.List; +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.loader.api.FabricLoader; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.util.Identifier; + +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; +import org.quiltmc.qsl.base.api.event.ListenerPhase; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; +import org.quiltmc.qsl.base.api.event.server.DedicatedServerEventAwareListener; + +@ApiStatus.Internal +public final class EventRegistry { + private EventRegistry() { + throw new UnsupportedOperationException("EventRegistry only contains static definitions."); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void listenAll(Object listener, Event... events) { + var listenedPhases = getListenedPhases(listener.getClass()); + + // Check whether we actually can register stuff. We only commit the registration if all events can. + for (var event : events) { + if (!event.getType().isAssignableFrom(listener.getClass())) { + throw new IllegalArgumentException("Given object " + listener + " is not a listener of event " + event); + } + + if (event.getType().getTypeParameters().length > 0) { + throw new IllegalArgumentException("Cannot register a listener for the event " + event + " which is using generic parameters with listenAll."); + } + + listenedPhases.putIfAbsent(event.getType(), Event.DEFAULT_PHASE); + } + + // We can register, so we do! + for (var event : events) { + ((Event) event).register(listenedPhases.get(event.getType()), listener); + } + } + + private static Map, Identifier> getListenedPhases(Class listenerClass) { + var map = new Object2ObjectOpenHashMap, Identifier>(); + + for (var annotation : listenerClass.getAnnotations()) { + if (annotation instanceof ListenerPhase phase) { + map.put(phase.callbackTarget(), new Identifier(phase.namespace(), phase.path())); + } + } + + return map; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void register(Event event) { + for (var target : EventSideTarget.VALUES) { + // Search if the callback qualifies is unique to this event. + if (target.listenerClass().isAssignableFrom(event.getType())) { + List entrypoints = FabricLoader.getInstance().getEntrypoints(target.entrypointKey(), target.listenerClass()); + + // Search for matching entrypoint. + for (Object entrypoint : entrypoints) { + var listenedPhases = getListenedPhases(entrypoint.getClass()); + + // Searching if the given entrypoint is a listener of the event being registered. + if (event.getType().isAssignableFrom(entrypoint.getClass())) { + // It is, then register the listener. + var phase = listenedPhases.getOrDefault(event.getType(), Event.DEFAULT_PHASE); + ((Event) event).register(phase, entrypoint); + } + } + + break; + } + } + } + + enum EventSideTarget { + CLIENT("client_events", ClientEventAwareListener.class), + COMMON("events", EventAwareListener.class), + DEDICATED_SERVER("server_events", DedicatedServerEventAwareListener.class); + + public static final List VALUES = List.of(values()); + + private final String entrypointKey; + private final Class listenerClass; + + EventSideTarget(String entrypointKey, Class listenerClass) { + this.entrypointKey = entrypointKey; + this.listenerClass = listenerClass; + } + + public String entrypointKey() { + return this.entrypointKey; + } + + public Class listenerClass() { + return this.listenerClass; + } + } +} diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/PhaseSorting.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/PhaseSorting.java index ad2fd263d7..0327f29cb8 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/PhaseSorting.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/impl/event/PhaseSorting.java @@ -1,6 +1,6 @@ /* * Copyright 2016, 2017, 2018, 2019 FabricMC - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/mixin/MinecraftServerMixin.java b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/mixin/MinecraftServerMixin.java index 208fedd40d..6ee2a8d811 100644 --- a/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/mixin/MinecraftServerMixin.java +++ b/library/core/qsl_base/src/main/java/org/quiltmc/qsl/base/mixin/MinecraftServerMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/library/core/qsl_base/src/main/resources/fabric.mod.json b/library/core/qsl_base/src/main/resources/fabric.mod.json index 726f53dc07..1a75065f2a 100644 --- a/library/core/qsl_base/src/main/resources/fabric.mod.json +++ b/library/core/qsl_base/src/main/resources/fabric.mod.json @@ -15,7 +15,8 @@ "QuiltMC" ], "depends": { - "fabricloader": ">=0.12" + "fabricloader": ">=0.12", + "minecraft": ">=1.18.2-alpha.22.3.a" }, "description": "Base level API for Quilt mods.", "mixins": [ diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/QuiltBaseTestMod.java b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/QuiltBaseTestMod.java index 632fbc0f55..ee5dd461aa 100644 --- a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/QuiltBaseTestMod.java +++ b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/QuiltBaseTestMod.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,24 @@ package org.quiltmc.qsl.base.test; +import java.util.List; + import net.fabricmc.api.ModInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.quiltmc.qsl.base.test.event.EventListenAllTests; +import org.quiltmc.qsl.base.test.event.EventTests; public final class QuiltBaseTestMod implements ModInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger(QuiltBaseTestMod.class); + @Override public void onInitialize() { - EventTests.run(); + List.of(new EventTests(), new EventListenAllTests()) + .forEach(test -> { + LOGGER.info("Testing " + test.getClass().getSimpleName() + "..."); + test.run(); + }); } } diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventListenAllTests.java b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventListenAllTests.java new file mode 100644 index 0000000000..dde5298a53 --- /dev/null +++ b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventListenAllTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.test.event; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.Items; + +import org.quiltmc.qsl.base.api.event.Event; + +public final class EventListenAllTests implements Runnable, TestCallback, GenericTestCallback { + private static final Logger LOGGER = LoggerFactory.getLogger(EventListenAllTests.class); + + @Override + public void run() { + var testEvent = Event.create(TestCallback.class, listeners -> () -> { + for (var test : listeners) { + test.onTest(); + } + }); + var testGenericEventBlock = Event.>create(GenericTestCallback.class, listeners -> msg -> { + for (var test : listeners) { + test.onGenericTest(msg); + } + }); + var testGenericEventItem = Event.>create(GenericTestCallback.class, listeners -> msg -> { + for (var test : listeners) { + test.onGenericTest(msg); + } + }); + + try { + Event.listenAll(this, testEvent, testGenericEventBlock); + + throw new IllegalStateException("Event#listenAll failed to refuse registration of listener for event testGenericEventBlock"); + } catch (IllegalArgumentException e) { + // Expected behavior + } + + testEvent.invoker().onTest(); + try { + testGenericEventBlock.invoker().onGenericTest(Blocks.SPRUCE_LOG); + LOGGER.info("Event#listenAll successfully refused registration of listener for event testGenericEventBlock"); + } catch (ClassCastException e) { + LOGGER.error("Event#listenAll failed to refuse registration of listener for event testGenericEventBlock"); + throw e; + } + + Event.listenAll(this, testEvent); + testGenericEventItem.register(this); + + testEvent.invoker().onTest(); + testGenericEventItem.invoker().onGenericTest(Items.ROSE_BUSH); + } + + @Override + public void onTest() { + LOGGER.info("Tested test event."); + } + + @Override + public void onGenericTest(Item obj) { + LOGGER.info("Hello object {}.", obj); + } +} diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/EventTests.java b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventTests.java similarity index 91% rename from library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/EventTests.java rename to library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventTests.java index 13b77510e9..74303054fb 100644 --- a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/EventTests.java +++ b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/EventTests.java @@ -1,6 +1,6 @@ /* * Copyright 2016, 2017, 2018, 2019 FabricMC - * Copyright 2021 QuiltMC + * Copyright 2021-2022 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.quiltmc.qsl.base.test; +package org.quiltmc.qsl.base.test.event; import java.util.ArrayList; import java.util.List; @@ -23,8 +23,7 @@ import java.util.function.Consumer; import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; import net.minecraft.util.Identifier; @@ -32,10 +31,11 @@ import org.quiltmc.qsl.base.impl.QuiltBaseImpl; import org.quiltmc.qsl.base.impl.event.PhaseSorting; -final class EventTests { +public final class EventTests implements Runnable { private static final Logger LOGGER = QuiltBaseImpl.LOGGER; - public static void run() { + @Override + public void run() { long time1 = System.currentTimeMillis(); testDefaultPhaseOnly(); @@ -51,19 +51,19 @@ public static void run() { LOGGER.info("Event unit tests succeeded in {} milliseconds.", time2 - time1); } - private static final Function INVOKER_FACTORY = listeners -> () -> { - for (Test test : listeners) { + private static final Function INVOKER_FACTORY = listeners -> () -> { + for (var test : listeners) { test.onTest(); } }; private static int currentListener = 0; - private static Event createEvent() { - return Event.create(Test.class, INVOKER_FACTORY); + private static Event createEvent() { + return Event.create(TestCallback.class, INVOKER_FACTORY); } - private static Test ensureOrder(int order) { + private static TestCallback ensureOrder(int order) { return () -> { assertEquals(order, currentListener); ++currentListener; @@ -85,7 +85,7 @@ private static void testDefaultPhaseOnly() { private static void testMultipleDefaultPhases() { var first = new Identifier("quilt", "first"); var second = new Identifier("quilt", "second"); - var event = Event.createWithPhases(Test.class, INVOKER_FACTORY, first, second, Event.DEFAULT_PHASE); + var event = Event.createWithPhases(TestCallback.class, INVOKER_FACTORY, first, second, Event.DEFAULT_PHASE); event.register(second, ensureOrder(1)); event.register(ensureOrder(2)); @@ -193,7 +193,7 @@ private static void testDeterministicOrdering() { var y = new Identifier("quilt", "y"); var z = new Identifier("quilt", "z"); - List>> dependencies = List.of( + List>> dependencies = List.of( ev -> ev.addPhaseOrdering(a, z), ev -> ev.addPhaseOrdering(d, e), ev -> ev.addPhaseOrdering(e, z), @@ -236,7 +236,7 @@ private static void testTwoCycles() { Identifier d = new Identifier("quilt", "d"); Identifier e = new Identifier("quilt", "e"); - List>> dependencies = List.of( + List>> dependencies = List.of( ev -> ev.addPhaseOrdering(e, a), ev -> ev.addPhaseOrdering(a, b), ev -> ev.addPhaseOrdering(b, a), @@ -279,11 +279,6 @@ private static void testAllPermutations(List selected, List toSelect, } } - @FunctionalInterface - interface Test { - void onTest(); - } - private static void assertEquals(Object expected, Object actual) { if (!Objects.equals(expected, actual)) { throw new AssertionError(String.format("assertEquals failed%nexpected: %s%n but was: %s", expected, actual)); diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/GenericTestCallback.java b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/GenericTestCallback.java new file mode 100644 index 0000000000..fe3892a812 --- /dev/null +++ b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/GenericTestCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.test.event; + +@FunctionalInterface +interface GenericTestCallback { + void onGenericTest(T obj); +} diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/TestCallback.java b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/TestCallback.java new file mode 100644 index 0000000000..d8a25019c7 --- /dev/null +++ b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/TestCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.base.test.event; + +@FunctionalInterface +public interface TestCallback { + void onTest(); +} diff --git a/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/graph.png b/library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/graph.png similarity index 100% rename from library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/graph.png rename to library/core/qsl_base/src/testmod/java/org/quiltmc/qsl/base/test/event/graph.png diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoaderEvents.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoaderEvents.java index 135484fb48..e953fbac8d 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoaderEvents.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoaderEvents.java @@ -22,6 +22,7 @@ import net.minecraft.server.MinecraftServer; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; import org.quiltmc.qsl.resource.loader.api.reloader.IdentifiableResourceReloader; /** @@ -62,7 +63,7 @@ private ResourceLoaderEvents() { * @see #START_DATA_PACK_RELOAD */ @FunctionalInterface - public interface StartDataPackReload { + public interface StartDataPackReload extends EventAwareListener { /** * Called before data packs on a Minecraft server have been reloaded. * @@ -78,7 +79,7 @@ public interface StartDataPackReload { * @see #END_DATA_PACK_RELOAD */ @FunctionalInterface - public interface EndDataPackReload { + public interface EndDataPackReload extends EventAwareListener { /** * Called after data packs on a Minecraft server have been reloaded. *

diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/client/ClientResourceLoaderEvents.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/client/ClientResourceLoaderEvents.java index e468815a73..5a45d70bac 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/client/ClientResourceLoaderEvents.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/client/ClientResourceLoaderEvents.java @@ -24,6 +24,7 @@ import net.minecraft.resource.ResourceManager; import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; import org.quiltmc.qsl.resource.loader.api.ResourceLoader; import org.quiltmc.qsl.resource.loader.api.reloader.IdentifiableResourceReloader; @@ -66,7 +67,7 @@ private ClientResourceLoaderEvents() { * @see #START_RESOURCE_PACK_RELOAD */ @FunctionalInterface - public interface StartResourcePackReload { + public interface StartResourcePackReload extends ClientEventAwareListener { /** * Called before resource packs on the Minecraft client have been reloaded. * @@ -83,7 +84,7 @@ public interface StartResourcePackReload { * @see #END_RESOURCE_PACK_RELOAD */ @FunctionalInterface - public interface EndResourcePackReload { + public interface EndResourcePackReload extends ClientEventAwareListener { /** * Called after resource packs on the Minecraft client have been reloaded. *

diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ModNioResourcePack.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ModNioResourcePack.java index 6d4de12b41..0185b41e6b 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ModNioResourcePack.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ModNioResourcePack.java @@ -26,16 +26,19 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.metadata.ModMetadata; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.resource.AbstractFileResourcePack; import net.minecraft.resource.ResourceType; @@ -49,8 +52,9 @@ */ @ApiStatus.Internal public class ModNioResourcePack extends AbstractFileResourcePack { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(ModNioResourcePack.class); private static final Pattern RESOURCE_PACK_PATH = Pattern.compile("[a-z0-9-_]+"); + private final String name; private final ModMetadata modInfo; private final Path basePath; private final ResourceType type; @@ -58,11 +62,13 @@ public class ModNioResourcePack extends AbstractFileResourcePack { private final AutoCloseable closer; private final String separator; private final ResourcePackActivationType activationType; - private Set namespaceCache; + private final Map> namespaces = new EnumMap<>(ResourceType.class); - public ModNioResourcePack(ModMetadata modInfo, Path path, ResourceType type, AutoCloseable closer, - ResourcePackActivationType activationType) { + public ModNioResourcePack(@Nullable String name, ModMetadata modInfo, Path path, + ResourceType type, AutoCloseable closer, + ResourcePackActivationType activationType) { super(null); + this.name = name == null ? ModResourcePackUtil.getName(modInfo) : name; this.modInfo = modInfo; this.basePath = path.toAbsolutePath().normalize(); this.type = type; @@ -116,7 +122,7 @@ protected boolean containsFile(String filename) { @Override public Collection findResources(ResourceType type, String namespace, String path, int depth, - Predicate pathFilter) { + Predicate pathFilter) { var ids = new ArrayList(); String nioPath = path.replace("/", separator); @@ -159,8 +165,12 @@ protected void warnInvalidNamespace(String s) { @Override public Set getNamespaces(ResourceType type) { - if (this.namespaceCache != null) { - return this.namespaceCache; + if (this.cacheable) { + var namespaces = this.namespaces.get(type); + + if (namespaces != null) { + return namespaces; + } } try { @@ -186,8 +196,8 @@ public Set getNamespaces(ResourceType type) { } } - if (cacheable) { - this.namespaceCache = namespaces; + if (this.cacheable) { + this.namespaces.put(type, namespaces); } return namespaces; @@ -214,6 +224,6 @@ public ResourcePackActivationType getActivationType() { @Override public String getName() { - return ModResourcePackUtil.getName(this.modInfo); + return this.name; } } diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java index db00c21009..fbe32890b1 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java @@ -37,10 +37,10 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.resource.AbstractFileResourcePack; import net.minecraft.resource.DefaultResourcePack; @@ -66,7 +66,7 @@ public final class ResourceLoaderImpl implements ResourceLoader { private static final Map IMPL_MAP = new EnumMap<>(ResourceType.class); private static final Map CLIENT_BUILTIN_RESOURCE_PACKS = new Object2ObjectOpenHashMap<>(); private static final Map SERVER_BUILTIN_RESOURCE_PACKS = new Object2ObjectOpenHashMap<>(); - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger("ResourceLoader"); private final Set addedListenerIds = new HashSet<>(); private final Set addedReloaders = new LinkedHashSet<>(); @@ -167,6 +167,7 @@ private static Path locateDefaultResourcePack(ResourceType type) { public static ModNioResourcePack locateAndLoadDefaultResourcePack(ResourceType type) { return new ModNioResourcePack( + "Default", FabricLoader.getInstance().getModContainer("minecraft").map(ModContainer::getMetadata).orElseThrow(), locateDefaultResourcePack(type), type, @@ -203,7 +204,8 @@ public static void appendModResourcePacks(List packs, ResourceType path = childPath; } - var pack = new ModNioResourcePack(container.getMetadata(), path, type, null, ResourcePackActivationType.ALWAYS_ENABLED); + var pack = new ModNioResourcePack(null, container.getMetadata(), path, type, null, + ResourcePackActivationType.ALWAYS_ENABLED); if (!pack.getNamespaces(type).isEmpty()) { packs.add(pack); @@ -231,7 +233,7 @@ public static GroupResourcePack.Wrapped buildProgrammerArtResourcePack(AbstractF } public static void appendResourcesFromGroup(NamespaceResourceManagerAccessor manager, Identifier id, - GroupResourcePack groupResourcePack, List resources) + GroupResourcePack groupResourcePack, List resources) throws IOException { var packs = groupResourcePack.getPacks(id.getNamespace()); @@ -264,7 +266,7 @@ public static void appendResourcesFromGroup(NamespaceResourceManagerAccessor man * @see ResourceLoader#registerBuiltinResourcePack(Identifier, ModContainer, ResourcePackActivationType) */ public static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, - ResourcePackActivationType activationType) { + ResourcePackActivationType activationType) { String separator = container.getRootPath().getFileSystem().getSeparator(); subPath = subPath.replace("/", separator); @@ -301,17 +303,12 @@ private static boolean registerBuiltinResourcePack(ResourceType type, ModNioReso } private static ModNioResourcePack newBuiltinResourcePack(ModContainer container, String name, Path resourcePackPath, - ResourceType type, ResourcePackActivationType activationType) { - return new ModNioResourcePack(container.getMetadata(), resourcePackPath, type, null, activationType) { - @Override - public String getName() { - return name; // Built-in resource pack provided by a mod, the name is overriden. - } - }; + ResourceType type, ResourcePackActivationType activationType) { + return new ModNioResourcePack(name, container.getMetadata(), resourcePackPath, type, null, activationType); } public static void registerBuiltinResourcePacks(ResourceType type, Consumer profileAdder, - ResourcePackProfile.Factory factory) { + ResourcePackProfile.Factory factory) { var builtinPacks = type == ResourceType.CLIENT_RESOURCES ? CLIENT_BUILTIN_RESOURCE_PACKS : SERVER_BUILTIN_RESOURCE_PACKS; diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/ReloadableResourceManagerImplMixin.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/ReloadableResourceManagerImplMixin.java index 4fef93d98a..9df59acc80 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/ReloadableResourceManagerImplMixin.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/ReloadableResourceManagerImplMixin.java @@ -52,7 +52,7 @@ public class ReloadableResourceManagerImplMixin { @Inject( method = "reload", - at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z", remap = false) + at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;isDebugEnabled()Z", remap = false) ) private void reload(Executor prepareExecutor, Executor applyExecutor, CompletableFuture initialStage, List packs, diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/CreateWorldScreenMixin.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/CreateWorldScreenMixin.java index d9b7cd2dd2..b95bc4153d 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/CreateWorldScreenMixin.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/CreateWorldScreenMixin.java @@ -84,6 +84,7 @@ private void onStartDataPackLoading(ResourcePackManager dataPackManager, Callbac // Lambda method in CreateWorldScreen#applyDataPacks, at CompletableFuture#thenAcceptAsync. // Take a ServerResourceManager parameter. + @SuppressWarnings("target") @Inject( method = "m_oezpkwme(Lnet/minecraft/resource/DataPackSettings;Lnet/minecraft/resource/ServerResourceManager;)V", at = @At( @@ -97,11 +98,13 @@ private void onEndDataPackLoading(DataPackSettings dataPackSettings, ServerResou // Lambda method in CreateWorldScreen#applyDataPacks, at CompletableFuture#handle. // Take Void and Throwable parameters. + @SuppressWarnings("target") @Inject( method = "m_paskjwcu(Ljava/lang/Void;Ljava/lang/Throwable;)Ljava/lang/Object;", at = @At( value = "INVOKE", - target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;Ljava/lang/Throwable;)V" + target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Throwable;)V", + remap = false ) ) private void onFailDataPackLoading(Void unused, Throwable throwable, CallbackInfoReturnable cir) { diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/MinecraftClientMixin.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/MinecraftClientMixin.java index 0d457f08db..6da15e42c4 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/MinecraftClientMixin.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/client/MinecraftClientMixin.java @@ -69,6 +69,7 @@ private void onFirstReloadResources(RunArgs runArgs, CallbackInfo ci) { // Lambda method in MinecraftClient#, at MinecraftClient#setOverlay. // Take an Optional parameter. + @SuppressWarnings("target") @Inject(method = "m_aaltpyph(Ljava/util/Optional;)V", at = @At("HEAD")) private void onFirstEndReloadResources(Optional error, CallbackInfo ci) { ClientResourceLoaderEvents.END_RESOURCE_PACK_RELOAD.invoker().onEndResourcePackReload( @@ -91,6 +92,7 @@ private void onStartReloadResources(boolean force, CallbackInfoReturnable parameter. + @SuppressWarnings("target") @Inject(method = "m_pxfxqhcl(Ljava/util/concurrent/CompletableFuture;Ljava/util/Optional;)V", at = @At(value = "HEAD")) private void onEndReloadResources(CompletableFuture completableFuture, Optional error, CallbackInfo ci) { ClientResourceLoaderEvents.END_RESOURCE_PACK_RELOAD.invoker().onEndResourcePackReload( diff --git a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/server/MainMixin.java b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/server/MainMixin.java index 1206509b0b..cff0cbc919 100644 --- a/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/server/MainMixin.java +++ b/library/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/server/MainMixin.java @@ -53,7 +53,7 @@ private static ServerResourceManager onSuccessfulReloadResources(ServerResourceM method = "main", at = @At( value = "INVOKE", - target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;Ljava/lang/Throwable;)V" + target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Throwable;)V" ), index = 1, remap = false diff --git a/library/core/resource_loader/src/main/resources/fabric.mod.json b/library/core/resource_loader/src/main/resources/fabric.mod.json index e29751f9be..7b75d49803 100644 --- a/library/core/resource_loader/src/main/resources/fabric.mod.json +++ b/library/core/resource_loader/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ ], "depends": { "fabricloader": ">=0.12", - "minecraft": ">=1.18.1" + "minecraft": ">=1.18.2-alpha.22.3.a" }, "description": "Asset and data resource loading.", "accessWidener": "quilt_resource_loader.accesswidener", diff --git a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceLoaderEventsTestMod.java b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceLoaderEventsTestMod.java index 95c1216cb9..fe99528201 100644 --- a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceLoaderEventsTestMod.java +++ b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceLoaderEventsTestMod.java @@ -16,25 +16,30 @@ package org.quiltmc.qsl.resource.loader.test; -import net.fabricmc.api.ModInitializer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.MinecraftServer; import org.quiltmc.qsl.resource.loader.api.ResourceLoaderEvents; -public class ResourceLoaderEventsTestMod implements ModInitializer { - private static final Logger LOGGER = LogManager.getLogger(); +public class ResourceLoaderEventsTestMod implements ResourceLoaderEvents.StartDataPackReload, + ResourceLoaderEvents.EndDataPackReload { + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceLoaderEventsTestMod.class); + + @Override + public void onStartDataPackReload(@Nullable MinecraftServer server, @Nullable ServerResourceManager oldResourceManager) { + LOGGER.info("Preparing for data pack reload, old resource manager: {}", oldResourceManager); + } @Override - public void onInitialize() { - ResourceLoaderEvents.START_DATA_PACK_RELOAD.register((server, oldResourceManager) -> - LOGGER.info("Preparing for data pack reload, old resource manager: {}", oldResourceManager)); - ResourceLoaderEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, error) -> { - if (error == null) { - LOGGER.info("Finished data pack reloading successfully on {}.", Thread.currentThread()); - } else { - LOGGER.error("Failed to reload on {} because {}.", Thread.currentThread(), error); - } - }); + public void onEndDataPackReload(@Nullable MinecraftServer server, ServerResourceManager resourceManager, @Nullable Throwable error) { + if (error == null) { + LOGGER.info("Finished data pack reloading successfully on {}.", Thread.currentThread()); + } else { + LOGGER.error("Failed to reload on {} because {}.", Thread.currentThread(), error); + } } } diff --git a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceReloaderTestMod.java b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceReloaderTestMod.java index 3ca6d17dbe..e61e34fd74 100644 --- a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceReloaderTestMod.java +++ b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/ResourceReloaderTestMod.java @@ -23,8 +23,8 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceType; @@ -35,7 +35,7 @@ import org.quiltmc.qsl.resource.loader.api.reloader.SimpleSynchronousResourceReloader; public class ResourceReloaderTestMod implements ModInitializer { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceReloaderTestMod.class); private static boolean clientResources = false; private static boolean serverResources = false; diff --git a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/client/ClientResourceLoaderEventsTestMod.java b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/client/ClientResourceLoaderEventsTestMod.java index 83e814a504..1e03cb9a5b 100644 --- a/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/client/ClientResourceLoaderEventsTestMod.java +++ b/library/core/resource_loader/src/testmod/java/org/quiltmc/qsl/resource/loader/test/client/ClientResourceLoaderEventsTestMod.java @@ -16,28 +16,32 @@ package org.quiltmc.qsl.resource.loader.test.client; -import net.fabricmc.api.ClientModInitializer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.resource.ResourceManager; import org.quiltmc.qsl.resource.loader.api.client.ClientResourceLoaderEvents; -public class ClientResourceLoaderEventsTestMod implements ClientModInitializer { - private static final Logger LOGGER = LogManager.getLogger(); +public class ClientResourceLoaderEventsTestMod implements ClientResourceLoaderEvents.StartResourcePackReload, + ClientResourceLoaderEvents.EndResourcePackReload { + private static final Logger LOGGER = LoggerFactory.getLogger(ClientResourceLoaderEventsTestMod.class); + + @Override + public void onStartResourcePackReload(MinecraftClient client, ResourceManager resourceManager, boolean first) { + LOGGER.info("Preparing for resource pack reload, resource manager: {}. Is it the first time?: {}", + resourceManager, first); + } @Override - public void onInitializeClient() { - ClientResourceLoaderEvents.START_RESOURCE_PACK_RELOAD.register((client, resourceManager, first) -> - LOGGER.info("Preparing for resource pack reload, resource manager: {}. Is it the first time?: {}", - resourceManager, first) - ); - ClientResourceLoaderEvents.END_RESOURCE_PACK_RELOAD.register((server, resourceManager, first, error) -> { - if (error == null) { - LOGGER.info("Finished {}resource pack reloading successfully on {}.", - (first ? "first " : ""), Thread.currentThread()); - } else { - LOGGER.error("Failed to reload resource packs on {} because {}.", Thread.currentThread(), error); - } - }); + public void onEndResourcePackReload(MinecraftClient client, ResourceManager resourceManager, boolean first, @Nullable Throwable error) { + if (error == null) { + LOGGER.info("Finished {}resource pack reloading successfully on {}.", + (first ? "first " : ""), Thread.currentThread()); + } else { + LOGGER.error("Failed to reload resource packs on {} because {}.", Thread.currentThread(), error); + } } } diff --git a/library/core/resource_loader/src/testmod/resources/fabric.mod.json b/library/core/resource_loader/src/testmod/resources/fabric.mod.json index 40d526b976..8f5d313df0 100644 --- a/library/core/resource_loader/src/testmod/resources/fabric.mod.json +++ b/library/core/resource_loader/src/testmod/resources/fabric.mod.json @@ -11,11 +11,13 @@ "entrypoints": { "main": [ "org.quiltmc.qsl.resource.loader.test.BuiltinResourcePackTestMod", - "org.quiltmc.qsl.resource.loader.test.ResourceLoaderEventsTestMod", "org.quiltmc.qsl.resource.loader.test.ResourceReloaderTestMod" ], - "client": [ + "client_events": [ "org.quiltmc.qsl.resource.loader.test.client.ClientResourceLoaderEventsTestMod" + ], + "events": [ + "org.quiltmc.qsl.resource.loader.test.ResourceLoaderEventsTestMod" ] } } diff --git a/library/data/tags/build.gradle b/library/data/tags/build.gradle index ea5a5dd53d..e99ff759a2 100644 --- a/library/data/tags/build.gradle +++ b/library/data/tags/build.gradle @@ -13,6 +13,9 @@ qslModule { "qsl_base", "lifecycle_events" ]) + interLibraryTestmodDependencies("command", [ + "command" + ]) } loom { diff --git a/library/data/tags/src/main/java/org/quiltmc/qsl/tag/impl/TagRegistryImpl.java b/library/data/tags/src/main/java/org/quiltmc/qsl/tag/impl/TagRegistryImpl.java index cfc50f7c93..83d775df3d 100644 --- a/library/data/tags/src/main/java/org/quiltmc/qsl/tag/impl/TagRegistryImpl.java +++ b/library/data/tags/src/main/java/org/quiltmc/qsl/tag/impl/TagRegistryImpl.java @@ -23,9 +23,9 @@ import com.google.common.base.Stopwatch; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ServerResourceManager; @@ -51,7 +51,7 @@ @SuppressWarnings("ClassCanBeRecord") @ApiStatus.Internal public final class TagRegistryImpl implements TagRegistry { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger("TagRegistry"); private static final ThreadLocal MISSING_TAGS_CLIENT_FETCH = new ThreadLocal<>(); private final RequiredTagList tagList; diff --git a/library/data/tags/src/main/resources/fabric.mod.json b/library/data/tags/src/main/resources/fabric.mod.json index 977d7aa760..6128d9fdee 100644 --- a/library/data/tags/src/main/resources/fabric.mod.json +++ b/library/data/tags/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ ], "depends": { "fabricloader": ">=0.12", - "minecraft": ">=1.18.1", + "minecraft": ">=1.18.2-alpha.22.3.a", "quilt_resource_loader": "*" }, "description": "Tag loading and management.", diff --git a/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/TagsTestMod.java b/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/TagsTestMod.java index 7b50813dcb..5418d515e5 100644 --- a/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/TagsTestMod.java +++ b/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/TagsTestMod.java @@ -16,27 +16,38 @@ package org.quiltmc.qsl.tag.test; -import java.util.stream.Collectors; - +import com.mojang.brigadier.CommandDispatcher; import net.fabricmc.api.ModInitializer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import net.minecraft.block.Block; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; import net.minecraft.tag.Tag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.registry.Registry; - +import net.minecraft.world.biome.Biome; +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.command.api.CommandRegistrationCallback; import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; import org.quiltmc.qsl.tag.api.TagRegistry; import org.quiltmc.qsl.tag.api.TagType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Consumer; +import java.util.stream.Collectors; -public final class TagsTestMod implements ModInitializer { +import static net.minecraft.server.command.CommandManager.literal; + + +public final class TagsTestMod implements ModInitializer, ServerLifecycleEvents.Ready, CommandRegistrationCallback { public static final String NAMESPACE = "quilt_tags_testmod"; - public static final Logger LOGGER = LogManager.getLogger(); + public static final Logger LOGGER = LoggerFactory.getLogger(TagsTestMod.class); /** - * This tag is only registered on the client: + * This tag means: * - worlds opened by the client must have this tag to work, only included in the quilt_tags_testmod:required_test_pack * data-pack so the testing can be exactly sure. * - can connect to servers that don't have the tag (since it's not TagType.CLIENT_SERVER_REQUIRED) @@ -44,6 +55,9 @@ public final class TagsTestMod implements ModInitializer { public static final Tag.Identified TEST_REQUIRED_BLOCK_TAG = TagRegistry.BLOCK.create( id("required_block_tag"), TagType.SERVER_REQUIRED ); + public static final Tag.Identified TEST_BIOME_TAG = TagRegistry.BIOME.create( + id("registry_test") + ); public static Identifier id(String path) { return new Identifier(NAMESPACE, path); @@ -51,15 +65,57 @@ public static Identifier id(String path) { @Override public void onInitialize() { - ServerLifecycleEvents.READY.register(server -> { - // Asserts the existence of the tag. - Tag tag = server.getTagManager().getTag(Registry.BLOCK_KEY, TEST_REQUIRED_BLOCK_TAG.getId(), - identifier -> new IllegalStateException("Could not find tag " + identifier)); - LOGGER.info("Tag content: {}", tag.values().stream() - .map(Registry.BLOCK::getId) - .map(Identifier::toString) - .collect(Collectors.joining(", ")) - ); - }); + Event.listenAll(this, CommandRegistrationCallback.EVENT, ServerLifecycleEvents.READY); + } + + @Override + public void registerCommands(CommandDispatcher dispatcher, boolean integrated, boolean dedicated) { + dispatcher.register(literal("biome_tag_test") + .then(literal("registry").executes(context -> { + TEST_BIOME_TAG.values().forEach(biome -> { + Identifier id = context.getSource().getRegistryManager().get(Registry.BIOME_KEY).getId(biome); + context.getSource().sendFeedback(new LiteralText(id.toString()), false); + }); + + return 1; + })) + .then(literal("list_all").executes(context -> { + context.getSource().getServer().getTagManager().getOrCreateTagGroup(Registry.BIOME_KEY).getTags() + .forEach((tagId, tag) -> { + displayTag( + tagId, tag, context.getSource().getRegistryManager().get(Registry.BIOME_KEY), + msg -> context.getSource().sendFeedback(msg, false) + ); + }); + + return 1; + })) + ); + } + + @Override + public void readyServer(MinecraftServer server) { + // Asserts the existence of the tag. + Tag tag = server.getTagManager().getTag(Registry.BLOCK_KEY, TEST_REQUIRED_BLOCK_TAG.getId(), + identifier -> new IllegalStateException("Could not find tag " + identifier)); + LOGGER.info("Tag content: {}", tag.values().stream() + .map(Registry.BLOCK::getId) + .map(Identifier::toString) + .collect(Collectors.joining(", ")) + ); + } + + public static void displayTag(Tag.Identified tag, Registry registry, Consumer feedbackConsumer) { + displayTag(tag.getId(), tag, registry, feedbackConsumer); + } + + public static void displayTag(Identifier tagId, Tag tag, Registry registry, Consumer feedbackConsumer) { + feedbackConsumer.accept(new LiteralText(tagId + ":").formatted(Formatting.GREEN)); + + for (var value : tag.values()) { + Identifier id = registry.getId(value); + feedbackConsumer.accept(new LiteralText(" - ") + .append(new LiteralText(id.toString()).formatted(Formatting.GOLD))); + } } } diff --git a/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/client/ClientTagsTestMod.java b/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/client/ClientTagsTestMod.java index 74faf50117..1f44668a44 100644 --- a/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/client/ClientTagsTestMod.java +++ b/library/data/tags/src/testmod/java/org/quiltmc/qsl/tag/test/client/ClientTagsTestMod.java @@ -17,18 +17,14 @@ package org.quiltmc.qsl.tag.test.client; import net.fabricmc.api.ClientModInitializer; - import net.minecraft.block.Block; import net.minecraft.client.MinecraftClient; import net.minecraft.item.Item; import net.minecraft.tag.Tag; -import net.minecraft.text.LiteralText; -import net.minecraft.util.Formatting; -import net.minecraft.util.Identifier; +import net.minecraft.text.Text; import net.minecraft.util.registry.Registry; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; - import org.quiltmc.qsl.lifecycle.api.client.event.ClientWorldTickEvents; import org.quiltmc.qsl.resource.loader.api.ResourceLoader; import org.quiltmc.qsl.resource.loader.api.ResourcePackActivationType; @@ -36,6 +32,8 @@ import org.quiltmc.qsl.tag.api.TagType; import org.quiltmc.qsl.tag.test.TagsTestMod; +import java.util.function.Consumer; + public final class ClientTagsTestMod implements ClientModInitializer { public static final Tag.Identified TEST_CLIENT_BLOCK_TAG = TagRegistry.BLOCK.create( TagsTestMod.id("client_block_tag"), TagType.CLIENT_ONLY @@ -47,6 +45,7 @@ public final class ClientTagsTestMod implements ClientModInitializer { TagsTestMod.id("default_item_tag"), TagType.CLIENT_FALLBACK ); + private static final Consumer FEEDBACK_CONSUMER = msg -> MinecraftClient.getInstance().player.sendMessage(msg, false); private World lastWorld; @Override @@ -56,23 +55,13 @@ public void onInitializeClient() { ClientWorldTickEvents.START.register((client, world) -> { if (this.lastWorld != world) { - displayTag(client, TEST_CLIENT_BLOCK_TAG, Registry.BLOCK); - displayTag(client, TEST_CLIENT_BIOME_TAG, client.world.getRegistryManager().get(Registry.BIOME_KEY)); - displayTag(client, TEST_DEFAULT_ITEM_TAG, Registry.ITEM); + TagsTestMod.displayTag(TEST_CLIENT_BLOCK_TAG, Registry.BLOCK, FEEDBACK_CONSUMER); + TagsTestMod.displayTag(TEST_CLIENT_BIOME_TAG, client.world.getRegistryManager().get(Registry.BIOME_KEY), + FEEDBACK_CONSUMER); + TagsTestMod.displayTag(TEST_DEFAULT_ITEM_TAG, Registry.ITEM, FEEDBACK_CONSUMER); this.lastWorld = world; } }); } - - private static void displayTag(MinecraftClient client, Tag.Identified tag, Registry registry) { - client.player.sendMessage(new LiteralText(tag.getId() + ":").formatted(Formatting.GREEN), - false); - for (var value : tag.values()) { - Identifier id = registry.getId(value); - client.player.sendMessage(new LiteralText(" - ") - .append(new LiteralText(id.toString()).formatted(Formatting.GOLD)), - false); - } - } } diff --git a/library/item/item_group/build.gradle b/library/item/item_group/build.gradle new file mode 100644 index 0000000000..98d52123aa --- /dev/null +++ b/library/item/item_group/build.gradle @@ -0,0 +1,13 @@ +plugins { + id("qsl.module") +} + +qslModule { + moduleName = "item_group" + version = "1.0.0" + library = "item" + coreDependencies([ + "qsl_base", + "resource_loader" + ]) +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/QuiltItemGroup.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/QuiltItemGroup.java new file mode 100644 index 0000000000..4e7e9fe9f6 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/QuiltItemGroup.java @@ -0,0 +1,250 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.api; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.quiltmc.qsl.itemgroup.impl.ItemGroupExtensions; + +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.item.Item; +import net.minecraft.item.ItemConvertible; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; + +/** + * Extensions for the {@link ItemGroup} class. Currently, the only extension is setting the icon with either an {@link ItemConvertible} or {@link ItemStack} after the item has been created ({@link QuiltItemGroup#setIcon(ItemConvertible)}, {@link QuiltItemGroup#setIcon(ItemStack)}).
+ * + * A {@link QuiltItemGroup} can be directly created with {@link QuiltItemGroup#create(Identifier)} or {@link QuiltItemGroup#createWithIcon(Identifier, Supplier)}.
+ * A {@link Builder}, which is used to add specific {@link ItemStack}s, especially with {@link net.minecraft.nbt.NbtElement}s, can be obtained with {@link QuiltItemGroup#builder(Identifier)}. + */ +public final class QuiltItemGroup extends ItemGroup { + private final Supplier iconSupplier; + private @Nullable ItemStack icon; + private final @Nullable Consumer> stacksForDisplay; + private final @Nullable Text displayText; + + private QuiltItemGroup(int index, String id, Supplier iconSupplier, @Nullable Consumer> stacksForDisplay, @Nullable Text displayText) { + super(index, id); + this.iconSupplier = iconSupplier; + this.stacksForDisplay = stacksForDisplay; + this.displayText = displayText; + } + + /** + * Sets the {@link Item} to use as the icon for the {@link ItemGroup} + * + * @param icon the {@link Item} for the icon + */ + public void setIcon(ItemConvertible icon) { + this.icon = icon.asItem().getDefaultStack(); + } + + /** + * Sets the {@link ItemStack} to use as the icon for the {@link ItemGroup}, allowing for NBT to be used + * + * @param icon the {@link ItemStack} for the icon + */ + public void setIcon(ItemStack icon) { + this.icon = icon; + } + + @Override + public ItemStack createIcon() { + ItemStack supplierIcon = this.iconSupplier.get(); + if (!supplierIcon.isEmpty()) { + return supplierIcon; + } else if (icon == null) { + return ItemStack.EMPTY; + } + + return icon; + } + + @Override + public void appendStacks(DefaultedList stacks) { + super.appendStacks(stacks); + + if (this.stacksForDisplay != null) { + this.stacksForDisplay.accept(stacks); + } + } + + @Override + public Text getTranslationKey() { + return this.displayText == null ? super.getTranslationKey() : this.displayText; + } + + /** + * Create a new {@link Builder}. + * Using the constructor allows for the use of the {@link Builder#icon(Supplier)} and {@link Builder#appendItems(Consumer)} methods. + * Manually setting the icon with {@link QuiltItemGroup#setIcon(ItemConvertible)} is possible after calling {@link Builder#build()} + * + * @param identifier the {@link Identifier} will become the name of the {@link ItemGroup} and will be used for the translation key + */ + public static Builder builder(Identifier identifier) { + return new Builder(identifier); + } + + /** + * This is a single method that creates an {@link ItemGroup} one call. + * This method should only be used when setting the icon with {@link QuiltItemGroup#setIcon(ItemConvertible)}. + * + * @param identifier the identifier will become the name of the {@link ItemGroup} and will be used for the translation key + * @return an instance of the created {@link ItemGroup} + */ + public static QuiltItemGroup create(Identifier identifier) { + return new Builder(identifier).build(); + } + + /** + * This is a single method that makes creating an {@link ItemGroup} with an icon one call. + * + * @param identifier the identifier will become the name of the {@link ItemGroup} and will be used for the translation key + * @param iconSupplier the supplier should return the {@link ItemStack} that you wish to show on the tab + * @return an instance of the created {@link QuiltItemGroup} + */ + public static QuiltItemGroup createWithIcon(Identifier identifier, @NotNull Supplier iconSupplier) { + return new Builder(identifier).icon(iconSupplier).build(); + } + + /** + * A builder class that helps with creating and adding {@link ItemGroup}s to the {@link CreativeInventoryScreen}. + *

+ * Potential uses: + *

+ * Creating an {@link ItemGroup} and then supplying the item icon: + *

{@code
+	 * public class MyMod implements ModInitializer {
+	 *   public static final QuiltItemGroup MY_ITEM_GROUP =
+	 *   	QuiltItemGroup.builder(
+	 *   		new Identifier("my_mod:item_group"))
+	 *   	.build();
+	 *
+	 *   public static Item MY_ITEM;
+	 *
+	 *   @Override
+	 *   public void onInitialize() {
+	 *     MY_ITEM = new Item(new QuiltItemSettings().group(MY_ITEM_GROUP));
+	 *     MY_ITEM_GROUP.icon(MY_ITEM);
+	 *   }
+	 * }
+	 * }
+ *

+ * Creating an {@link ItemGroup} with the icon as a supplier: + *

{@code
+	 * public class MyMod implements ModInitializer {
+	 *   public static final QuiltItemGroup MY_ITEM_GROUP =
+	 *   	QuiltItemGroup.builder(
+	 *   		new Identifier("my_mod:item_group"))
+	 *   	.icon(() -> new ItemStack(MyMod.MY_ITEM))
+	 *   	.build();
+	 *
+	 *   public static Item MY_ITEM;
+	 *
+	 *   @Override
+	 *   public void onInitialize() {
+	 *     MY_ITEM = new Item(new QuiltItemSettings().group(MY_ITEM_GROUP));
+	 *   }
+	 * }
+	 * }
+ *

+ * Creating an {@link ItemGroup} with the icon as a supplier and custom {@link ItemStack}s: + *

{@code
+	 * public class MyMod implements ModInitializer {
+	 *   public static final QuiltItemGroup MY_ITEM_GROUP =
+	 *   	QuiltItemGroup.builder(
+	 *   		new Identifier("my_mod:item_group"))
+	 *   	.icon(() -> new ItemStack(MyMod.MY_ITEM))
+	 *   	.appendItems(stacks -> {
+	 *   	    stacks.add(new ItemStack(MyMod.MY_ITEM));
+	 *   	    stacks.add(new ItemStack(Items.STONE));
+	 *   	})
+	 *   	.build();
+	 *
+	 *   public static Item MY_ITEM;
+	 *
+	 *   @Override
+	 *   public void onInitialize() {
+	 *     MY_ITEM = new Item(new QuiltItemSettings());
+	 *   }
+	 * }
+	 * }
+ */ + public static final class Builder { + private final Identifier identifier; + private Supplier iconSupplier = () -> ItemStack.EMPTY; + private Consumer> stacksForDisplay; + private Text text; + + private Builder(Identifier identifier) { + this.identifier = identifier; + } + + /** + * This is used to add an icon to the {@link ItemGroup}. + * + * @param iconSupplier the supplier should return the {@link ItemStack} that you wish to show on the tab + * @return {@code this} + */ + public Builder icon(@NotNull Supplier iconSupplier) { + this.iconSupplier = iconSupplier; + return this; + } + + /** + * Adds a custom list of items to be displayed in a tab, such as items with enchantments, potions, or other NBT values. + * + * @param stacksForDisplay add {@link ItemStack}s to this list to show in the {@link ItemGroup} + * @return {@code this} + */ + public Builder appendItems(@Nullable Consumer> stacksForDisplay) { + this.stacksForDisplay = stacksForDisplay; + return this; + } + + /** + * Set the {@link Text} used as the name when rendering the {@link ItemGroup} + * @param text The {@link Text} to render as the name + * @return {@code this} + */ + public Builder displayText(@NotNull Text text) { + this.text = text; + return this; + } + + /** + * @return a new {@link QuiltItemGroup} + */ + public QuiltItemGroup build() { + ((ItemGroupExtensions) GROUPS[0]).quilt$expandArray(); + return new QuiltItemGroup( + GROUPS.length - 1, + String.format("%s.%s", this.identifier.getNamespace(), this.identifier.getPath()), + this.iconSupplier, this.stacksForDisplay, this.text + ); + } + } +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/package-info.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/package-info.java new file mode 100644 index 0000000000..48831be1ab --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/api/package-info.java @@ -0,0 +1,4 @@ +/** + * {@link net.minecraft.item.ItemGroup} and {@link org.quiltmc.qsl.itemgroup.api.QuiltItemGroup} APIs + */ +package org.quiltmc.qsl.itemgroup.api; diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/CreativeGuiExtensions.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/CreativeGuiExtensions.java new file mode 100644 index 0000000000..a04a18fc86 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/CreativeGuiExtensions.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.impl; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface CreativeGuiExtensions { + void quilt$nextPage(); + + void quilt$previousPage(); + + int quilt$currentPage(); + + boolean quilt$isButtonVisible(QuiltCreativePlayerInventoryScreenWidgets.Type type); + + boolean quilt$isButtonEnabled(QuiltCreativePlayerInventoryScreenWidgets.Type type); +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/ItemGroupExtensions.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/ItemGroupExtensions.java new file mode 100644 index 0000000000..a41c9ec779 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/ItemGroupExtensions.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.impl; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface ItemGroupExtensions { + void quilt$expandArray(); +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/QuiltCreativePlayerInventoryScreenWidgets.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/QuiltCreativePlayerInventoryScreenWidgets.java new file mode 100644 index 0000000000..3ecf569b23 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/QuiltCreativePlayerInventoryScreenWidgets.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.impl; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +import com.mojang.blaze3d.systems.RenderSystem; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemGroup; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; + +@ApiStatus.Internal +public final class QuiltCreativePlayerInventoryScreenWidgets { + private static final Identifier BUTTON_TEXTURE = new Identifier("quilt_item_group", "textures/gui/creative_buttons.png"); + public static final Set ALWAYS_SHOWN_GROUPS = new HashSet<>(); + + static { + ALWAYS_SHOWN_GROUPS.add(ItemGroup.SEARCH); + ALWAYS_SHOWN_GROUPS.add(ItemGroup.INVENTORY); + ALWAYS_SHOWN_GROUPS.add(ItemGroup.HOTBAR); + } + + public static class ItemGroupButtonWidget extends ButtonWidget { + public static final String TRANSLATION_KEY = "quilt_item_group.gui.creative_tab_page"; + private final CreativeGuiExtensions extensions; + private final CreativeInventoryScreen gui; + private final Type type; + + public ItemGroupButtonWidget(int x, int y, Type type, CreativeGuiExtensions extensions) { + super(x, y, 11, 10, type.text, (bw) -> type.clickConsumer.accept(extensions)); + this.extensions = extensions; + this.type = type; + this.gui = (CreativeInventoryScreen) extensions; + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + this.visible = this.extensions.quilt$isButtonVisible(this.type); + this.active = this.extensions.quilt$isButtonEnabled(this.type); + + if (this.visible) { + int u = this.active && this.hovered ? 22 : 0; + int v = this.active ? 0 : 10; + + RenderSystem.setShaderTexture(0, BUTTON_TEXTURE); + RenderSystem.setShaderColor(1F, 1F, 1F, 1F); + this.drawTexture(matrices, this.x, this.y, u + (this.type == Type.NEXT ? 11 : 0), v, 11, 10); + + if (this.hovered) { + gui.renderTooltip(matrices, new TranslatableText(TRANSLATION_KEY, extensions.quilt$currentPage() + 1, ((ItemGroup.GROUPS.length - 12) / 9) + 2), mouseX, mouseY); + } + } + } + } + + public enum Type { + NEXT(new LiteralText(">"), CreativeGuiExtensions::quilt$nextPage), + PREVIOUS(new LiteralText("<"), CreativeGuiExtensions::quilt$previousPage); + + final Text text; + final Consumer clickConsumer; + + Type(Text text, Consumer clickConsumer) { + this.text = text; + this.clickConsumer = clickConsumer; + } + } +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/package-info.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/package-info.java new file mode 100644 index 0000000000..2e30fd7377 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/impl/package-info.java @@ -0,0 +1,7 @@ +/** + * Internal code handling the rendering of {@link net.minecraft.item.ItemGroup}s and adding new {@link net.minecraft.item.ItemGroup}s + */ +@ApiStatus.Internal +package org.quiltmc.qsl.itemgroup.impl; +import org.jetbrains.annotations.ApiStatus; + diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/ItemGroupMixin.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/ItemGroupMixin.java new file mode 100644 index 0000000000..b3ab9cdd03 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/ItemGroupMixin.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.item.ItemGroup; + +import org.quiltmc.qsl.itemgroup.impl.ItemGroupExtensions; + +@Mixin(ItemGroup.class) +public abstract class ItemGroupMixin implements ItemGroupExtensions { + @Shadow + @Final + @Mutable + public static ItemGroup[] GROUPS; + + @Override + public void quilt$expandArray() { + ItemGroup[] tempGroups = GROUPS; + GROUPS = new ItemGroup[GROUPS.length + 1]; + + System.arraycopy(tempGroups, 0, GROUPS, 0, tempGroups.length); + } +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/CreativeInventoryScreenMixin.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/CreativeInventoryScreenMixin.java new file mode 100644 index 0000000000..03832fd0ac --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/CreativeInventoryScreenMixin.java @@ -0,0 +1,183 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.mixin.client; + +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.gui.screen.ingame.AbstractInventoryScreen; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemGroup; +import net.minecraft.text.Text; + +import org.quiltmc.qsl.itemgroup.impl.CreativeGuiExtensions; +import org.quiltmc.qsl.itemgroup.impl.QuiltCreativePlayerInventoryScreenWidgets; + +@Mixin(CreativeInventoryScreen.class) +public abstract class CreativeInventoryScreenMixin extends AbstractInventoryScreen implements CreativeGuiExtensions { + private final Map PAGE_TO_SELECTED_INDEX = new Object2ObjectOpenHashMap<>(); + + private CreativeInventoryScreenMixin(CreativeInventoryScreen.CreativeScreenHandler screenHandler, PlayerInventory playerInventory, Text title) { + super(screenHandler, playerInventory, title); + } + + @Shadow + protected abstract void setSelectedTab(ItemGroup itemGroup); + + @Shadow + public abstract int getSelectedTab(); + + /** + * In order to match the behavior of Vanilla where closing and opening the creative inventory brings you to the previously open item group, we also replicate this behavior with the current page + */ + @Unique + private static int quilt$currentPage = 0; + + @Unique + private int getPageOffset(int page) { + if (page == 0) { + return 0; + } + return 12 + ((12 - QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.size()) * (page - 1)); + } + + @Unique + private int getOffsetPage(int offset) { + if (offset < 12) { + return 0; + } else { + return 1 + ((offset - 12) / (12 - QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.size())); + } + } + + @Override + public void quilt$nextPage() { + if (this.getPageOffset(quilt$currentPage + 1) >= ItemGroup.GROUPS.length) { + return; + } + + PAGE_TO_SELECTED_INDEX.compute(quilt$currentPage, (page, pos) -> getSelectedTab()); + + quilt$currentPage++; + this.quilt$updateSelection(); + } + + @Override + public void quilt$previousPage() { + if (quilt$currentPage == 0) { + return; + } + + PAGE_TO_SELECTED_INDEX.compute(quilt$currentPage, (page, pos) -> this.getSelectedTab()); + + quilt$currentPage--; + this.quilt$updateSelection(); + } + + @Override + public boolean quilt$isButtonVisible(QuiltCreativePlayerInventoryScreenWidgets.Type type) { + return ItemGroup.GROUPS.length > 12; + } + + @Override + public boolean quilt$isButtonEnabled(QuiltCreativePlayerInventoryScreenWidgets.Type type) { + if (type == QuiltCreativePlayerInventoryScreenWidgets.Type.NEXT) { + return !(this.getPageOffset(quilt$currentPage + 1) >= ItemGroup.GROUPS.length); + } + + if (type == QuiltCreativePlayerInventoryScreenWidgets.Type.PREVIOUS) { + return quilt$currentPage != 0; + } + + return false; + } + + @Unique + private void quilt$updateSelection() { + int pageMaxIndex = this.getPageOffset(quilt$currentPage); + int pageMinIndex = this.getPageOffset(quilt$currentPage + 1) - 1; + int selectedTab = this.getSelectedTab(); + + if (selectedTab < pageMaxIndex || selectedTab > pageMinIndex) { + this.setSelectedTab(ItemGroup.GROUPS[PAGE_TO_SELECTED_INDEX.getOrDefault(quilt$currentPage, this.getPageOffset(quilt$currentPage))]); + } + } + + @Inject(method = "init", at = @At("RETURN")) + private void init(CallbackInfo info) { + this.quilt$updateSelection(); + + int xpos = x + 116; + int ypos = y - 10; + + this.addDrawableChild(new QuiltCreativePlayerInventoryScreenWidgets.ItemGroupButtonWidget(xpos + 11, ypos, QuiltCreativePlayerInventoryScreenWidgets.Type.NEXT, this)); + this.addDrawableChild(new QuiltCreativePlayerInventoryScreenWidgets.ItemGroupButtonWidget(xpos, ypos, QuiltCreativePlayerInventoryScreenWidgets.Type.PREVIOUS, this)); + } + + @Inject(method = "setSelectedTab", at = @At("HEAD"), cancellable = true) + private void setSelectedTab(ItemGroup itemGroup, CallbackInfo info) { + if (this.quilt$isGroupNotVisible(itemGroup)) { + info.cancel(); + } + } + + @Inject(method = "renderTabTooltipIfHovered", at = @At("HEAD"), cancellable = true) + private void renderTabTooltipIfHovered(MatrixStack matrixStack, ItemGroup itemGroup, int mx, int my, CallbackInfoReturnable info) { + if (this.quilt$isGroupNotVisible(itemGroup)) { + info.setReturnValue(false); + } + } + + @Inject(method = "isClickInTab", at = @At("HEAD"), cancellable = true) + private void isClickInTab(ItemGroup itemGroup, double mx, double my, CallbackInfoReturnable info) { + if (this.quilt$isGroupNotVisible(itemGroup)) { + info.setReturnValue(false); + } + } + + @Inject(method = "renderTabIcon", at = @At("HEAD"), cancellable = true) + private void renderTabIcon(MatrixStack matrixStack, ItemGroup itemGroup, CallbackInfo info) { + if (this.quilt$isGroupNotVisible(itemGroup)) { + info.cancel(); + } + } + + @Unique + private boolean quilt$isGroupNotVisible(ItemGroup itemGroup) { + if (QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.contains(itemGroup)) { + return false; + } + + return quilt$currentPage != this.getOffsetPage(itemGroup.getIndex()); + } + + @Override + public int quilt$currentPage() { + return quilt$currentPage; + } +} diff --git a/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/ItemGroupMixin.java b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/ItemGroupMixin.java new file mode 100644 index 0000000000..6a47f4e059 --- /dev/null +++ b/library/item/item_group/src/main/java/org/quiltmc/qsl/itemgroup/mixin/client/ItemGroupMixin.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.item.ItemGroup; + +import org.quiltmc.qsl.itemgroup.impl.QuiltCreativePlayerInventoryScreenWidgets; + +@Mixin(ItemGroup.class) +public abstract class ItemGroupMixin { + @Shadow + public abstract int getIndex(); + + @Shadow + public abstract boolean isTopRow(); + + @Inject(method = "isTopRow", cancellable = true, at = @At("HEAD")) + private void isTopRow(CallbackInfoReturnable info) { + if (this.getIndex() > 11) { + info.setReturnValue((this.getIndex() - 12) % (12 - QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.size()) < 4); + } + } + + @Inject(method = "getColumn", cancellable = true, at = @At("HEAD")) + private void getColumn(CallbackInfoReturnable info) { + if (this.getIndex() > 11) { + if (this.isTopRow()) { + info.setReturnValue((this.getIndex() - 12) % (12 - QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.size())); + } else { + info.setReturnValue((this.getIndex() - 12) % (12 - QuiltCreativePlayerInventoryScreenWidgets.ALWAYS_SHOWN_GROUPS.size()) - 4); + } + } + } +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/icon.png b/library/item/item_group/src/main/resources/assets/quilt_item_group/icon.png new file mode 100644 index 0000000000..8d54ad0021 Binary files /dev/null and b/library/item/item_group/src/main/resources/assets/quilt_item_group/icon.png differ diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/bg_bg.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/bg_bg.json new file mode 100644 index 0000000000..52971dbf99 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/bg_bg.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Страница %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/de_de.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/de_de.json new file mode 100644 index 0000000000..b00b65cd74 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/de_de.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Seite %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/el_gr.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/el_gr.json new file mode 100644 index 0000000000..fca1a83d99 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/el_gr.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Σελίδα %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/en_us.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/en_us.json new file mode 100644 index 0000000000..acfad7716c --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Page %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/es_mx.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/es_mx.json new file mode 100644 index 0000000000..a59cea7246 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/es_mx.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Página %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/et_ee.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/et_ee.json new file mode 100644 index 0000000000..1a6a874497 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/et_ee.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Leht %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fa_ir.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fa_ir.json new file mode 100644 index 0000000000..7c4fb92886 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fa_ir.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "صفحۀ %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fi_fi.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fi_fi.json new file mode 100644 index 0000000000..31f557aeba --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/fi_fi.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Sivu %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/pl_pl.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/pl_pl.json new file mode 100644 index 0000000000..77c2211623 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/pl_pl.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Strona %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/sv_se.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/sv_se.json new file mode 100644 index 0000000000..9f873863e3 --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/sv_se.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Sida %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/tr_tr.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/tr_tr.json new file mode 100644 index 0000000000..59ac7864cf --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/tr_tr.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "Sayfa: %d/%d" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_cn.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_cn.json new file mode 100644 index 0000000000..fc143cc13f --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_cn.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "第 %d/%d 页" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_tw.json b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_tw.json new file mode 100644 index 0000000000..468fa35b2a --- /dev/null +++ b/library/item/item_group/src/main/resources/assets/quilt_item_group/lang/zh_tw.json @@ -0,0 +1,3 @@ +{ + "quilt_item_group.gui.creative_tab_page": "第 %d/%d 頁" +} diff --git a/library/item/item_group/src/main/resources/assets/quilt_item_group/textures/gui/creative_buttons.png b/library/item/item_group/src/main/resources/assets/quilt_item_group/textures/gui/creative_buttons.png new file mode 100644 index 0000000000..27b9a1499e Binary files /dev/null and b/library/item/item_group/src/main/resources/assets/quilt_item_group/textures/gui/creative_buttons.png differ diff --git a/library/item/item_group/src/main/resources/fabric.mod.json b/library/item/item_group/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..7126e55b44 --- /dev/null +++ b/library/item/item_group/src/main/resources/fabric.mod.json @@ -0,0 +1,43 @@ +{ + "schemaVersion": 1, + "id": "quilt_item_group", + "name": "Quilt Item Group API", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/quilt_item_group/icon.png", + "contact": { + "homepage": "https://quiltmc.org", + "issues": "https://github.com/QuiltMC/quilt-standard-libraries/issues", + "sources": "https://github.com/QuiltMC/quilt-standard-libraries" + }, + "authors": [ + "QuiltMC" + ], + "depends": { + "minecraft": ">=1.18.2-alpha.22.3.a", + "quilt_base": "*", + "quilt_resource_loader": "*", + "fabricloader": ">=0.12" + }, + "description": "An API for adding Item Groups to Minecraft, allowing for custom icon display stacks, delayed icon registration, and adding custom stacks to the groups contents. This API also adds the GUI elements that allow for the multiple pages of item groups that can occur", + "mixins": [ + "quilt_item_group.mixins.json" + ], + "custom": { + "modmenu": { + "badges": [ + "library" + ], + "parent": { + "id": "qsl", + "name": "Quilt Standard Libraries", + "description": "A set of libraries to assist in making Quilt mods.", + "icon": "assets/quilt_item_group/icon.png", + "badges": [ + "library" + ] + } + } + } +} diff --git a/library/item/item_group/src/main/resources/quilt_item_group.mixins.json b/library/item/item_group/src/main/resources/quilt_item_group.mixins.json new file mode 100644 index 0000000000..8a6f1f2335 --- /dev/null +++ b/library/item/item_group/src/main/resources/quilt_item_group.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "org.quiltmc.qsl.itemgroup.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ItemGroupMixin" + ], + "client": [ + "client.ItemGroupMixin", + "client.CreativeInventoryScreenMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/library/item/item_group/src/testmod/java/org/quiltmc/qsl/itemgroup/test/ItemGroupTest.java b/library/item/item_group/src/testmod/java/org/quiltmc/qsl/itemgroup/test/ItemGroupTest.java new file mode 100644 index 0000000000..82c360e28c --- /dev/null +++ b/library/item/item_group/src/testmod/java/org/quiltmc/qsl/itemgroup/test/ItemGroupTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.itemgroup.test; + +import java.util.stream.IntStream; + +import org.quiltmc.qsl.itemgroup.api.QuiltItemGroup; + +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +import net.fabricmc.api.ModInitializer; + +public class ItemGroupTest implements ModInitializer { + public static final String NAMESPACE = "quilt_item_group_testmod"; + // Adds an item group with all items in it + private static final ItemGroup SUPPLIER_ITEM_GROUP = QuiltItemGroup.builder(new Identifier(NAMESPACE, "test_supplied_group")) + .icon(() -> new ItemStack(Items.STONE)) + .appendItems(stacks -> + Registry.ITEM.stream() + .map(ItemStack::new) + .filter(itemStack -> itemStack.toString().contains("stone")) + .forEach(stacks::add) + ).build(); + + private static final QuiltItemGroup DELAYED_ITEM_GROUP = QuiltItemGroup.builder(new Identifier(NAMESPACE, "test_delayed_group")) + .appendItems(stacks -> + Registry.ITEM.stream() + .filter(item -> item != Items.AIR) + .map(ItemStack::new) + .forEach(stacks::add) + ).build(); + + private static final QuiltItemGroup[] MANY_GROUPS = IntStream.range(0, 20).mapToObj(i -> QuiltItemGroup.builder(new Identifier(NAMESPACE, "many_group_" + i)).build()).toArray(QuiltItemGroup[]::new); + + @Override + public void onInitialize() { + DELAYED_ITEM_GROUP.setIcon(Items.EMERALD); + } +} diff --git a/library/item/item_group/src/testmod/resources/fabric.mod.json b/library/item/item_group/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..e46ecd7e0a --- /dev/null +++ b/library/item/item_group/src/testmod/resources/fabric.mod.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "id": "quilt_item_group_testmod", + "name": "Quilt Item Group API Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "quilt_item_group": "*" + }, + "entrypoints": { + "main": [ + "org.quiltmc.qsl.itemgroup.test.ItemGroupTest" + ] + } +} diff --git a/settings.gradle b/settings.gradle index 29b33dddf1..efe4c5d966 100644 --- a/settings.gradle +++ b/settings.gradle @@ -32,6 +32,7 @@ library("block") library("data") library("entity") library("item") +library("command") def library(String library) { include(library)