diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 00000000..3475e357 Binary files /dev/null and b/.idea/icon.png differ diff --git a/README.md b/README.md index 4dbcb210..ee8b3d13 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,100 @@ +
+ bits_logo -

💾 A Utility library for Orbitary development.

+# Bits + +*- Utility library for Orbitary development -* -[![License](https://img.shields.io/github/license/Orbitary/Bits)](LICENSE) -[![Java CI](https://github.com/Orbitary/Bits/actions/workflows/publish.yml/badge.svg)](https://github.com/Orbitary/Bits/actions/workflows/publish.yml) -[![Modrinth Downloads](https://img.shields.io/badge/dynamic/json?color=1bd96a&label=Modrinth&query=downloads&suffix=%20downloads&url=https%3A%2F%2Fapi.modrinth.com%2Fv2%2Fproject%2F8ZZ61TMj)](https://modrinth.com/mod/orbits) +[![](https://img.shields.io/github/license/Orbitary/Bits?style=flat-square)](LICENSE) +[![](https://img.shields.io/github/actions/workflow/status/Orbitary/Bits/publish.yml?style=flat-square)](https://github.com/Orbitary/Bits/actions/workflows/publish.yml) +[![](https://img.shields.io/badge/dynamic/json?color=1bd96a&label=modrinth&style=flat-square&query=downloads&suffix=%20downloads&url=https%3A%2F%2Fapi.modrinth.com%2Fv2%2Fproject%2F8ZZ61TMj)](https://modrinth.com/mod/orbits) -## About +
-Bits is a multi-platform utility library designed to streamline: +* * * -- Minecraft plugin development across Paper and Velocity platforms. -- Minecraft mod development using Fabric (both client and server). -- Discord Bot Development -- And more! +# ✨ Introduction -### Features: +**Bits** is a multi-platform utility library designed to streamline Minecraft plugin and mod development, as well as Discord bot development. -- Multi-platform support -- Annotation-based [Brigadier](https://github.com/Mojang/brigadier) Command API -- Sendable lifecycle builders (Actionbars, Sidebars, Waypoints, +) -- Automatic Text formatting -- Common Plugin utilities (Color, Sound, Location, Item, Math, Permission APIs) +- Built for **Paper**, **Velocity**, and **Fabric** (client & server). +- Annotation-based [Brigadier](https://github.com/Mojang/brigadier) **Command API**. +- **Sendable lifecycle builders** — Actionbars, Sidebars, Waypoints, and more. +- **Automatic text formatting** and common plugin utilities. +- Color, Sound, Location, Item, Math, and Permission APIs included. > [!NOTE] -> This project is in active development, expect regular changes and additions! +> This project is in active development — expect regular changes and additions! + +## 🛠️ Build info -## Installation +#### Modules -### Gradle (Kotlin DSL) +- **api** — Core interfaces and abstractions +- **paper** — Paper platform implementation + - **paper:sendable** — Actionbars, Sidebars, Waypoints + - **paper:command** — Brigadier command framework +- **velocity** — Velocity platform implementation + - **velocity:command** — Brigadier command framework +- **fabric** — Fabric platform implementation (client + server) + +## 💻 API + +
+Gradle (Kotlin) ```kotlin repositories { - maven { url = uri("https://repo.bitsquidd.xyz/repository/bit/") } + maven("https://repo.bitsquidd.xyz/repository/bit/") } dependencies { - // Core API - implementation("xyz.bitsquidd.bits:api:0.0.19") - - // Platform implementation - implementation("xyz.bitsquidd.bits.paper:paper:0.0.19") - implementation("xyz.bitsquidd.bits.velocity:velocity:0.0.19") - implementation("xyz.bitsquidd.bits.fabric:fabric:0.0.19") - + // Core API (required) + implementation("xyz.bitsquidd.bits:api:0.0.20") + + // Platform implementations + implementation("xyz.bitsquidd.bits.paper:paper:0.0.20") + implementation("xyz.bitsquidd.bits.velocity:velocity:0.0.20") + implementation("xyz.bitsquidd.bits.fabric:fabric:0.0.20") + // Optional: Platform-specific modules - implementation("xyz.bitsquidd.bits.paper:sendable:0.0.19") - implementation("xyz.bitsquidd.bits.paper:command:0.0.19") - implementation("xyz.bitsquidd.bits.velocity:command:0.0.19") + implementation("xyz.bitsquidd.bits.paper:sendable:0.0.20") + implementation("xyz.bitsquidd.bits.paper:command:0.0.20") + implementation("xyz.bitsquidd.bits.velocity:command:0.0.20") } ``` -### Maven +
+ +
+Gradle (Groovy) + +```groovy +repositories { + maven { url 'https://repo.bitsquidd.xyz/repository/bit/' } +} + +dependencies { + // Core API (required) + implementation 'xyz.bitsquidd.bits:api:0.0.20' + + // Platform implementations + implementation 'xyz.bitsquidd.bits.paper:paper:0.0.20' + implementation 'xyz.bitsquidd.bits.velocity:velocity:0.0.20' + implementation 'xyz.bitsquidd.bits.fabric:fabric:0.0.20' + + // Optional: Platform-specific modules + implementation 'xyz.bitsquidd.bits.paper:sendable:0.0.20' + implementation 'xyz.bitsquidd.bits.paper:command:0.0.20' + implementation 'xyz.bitsquidd.bits.velocity:command:0.0.20' +} +``` + +
+ +
+Maven ```xml @@ -67,36 +110,29 @@ dependencies { xyz.bitsquidd.bits api - 0.0.19 + 0.0.20 - + xyz.bitsquidd.bits.paper paper - 0.0.19 + 0.0.20 xyz.bitsquidd.bits.velocity velocity - 0.0.19 + 0.0.20 xyz.bitsquidd.bits.fabric fabric - 0.0.19 + 0.0.20 ``` -## Libraries - -- **API** - Core interfaces and abstractions -- **Paper** - Paper platform implementation -- **Velocity** - Velocity platform implementation -- **Fabric** - Fabric platform implementation - - **Client** - Fabric client implementation - - **Server** - Fabric server implementation +
--- Made with 🦑 by [ImBit](https://github.com/ImBit) diff --git a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java index 6b4dc87f..ecffd1e4 100644 --- a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java +++ b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java @@ -8,8 +8,12 @@ package xyz.bitsquidd.bits.util.serializer; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.guava.GuavaModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -23,27 +27,30 @@ import java.util.Set; import java.util.stream.Collectors; + public final class SerializationManager { private SerializationManager() {} public static final ObjectMapper SERIALIZER = createMapper(); private static ObjectMapper createMapper() { - JsonMapper.Builder builder = JsonMapper.builder() - .enable(MapperFeature.DEFAULT_VIEW_INCLUSION) - .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) - .disable(MapperFeature.AUTO_DETECT_IS_GETTERS) - .annotationIntrospector(new NullableAwareIntrospector()); - - getSerializers().forEach(serializer -> registerSerializer(serializer, builder)); - builder.addModule(new GuavaModule()); - builder.addModule(new JavaTimeModule()); - return builder.build(); + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(MapperFeature.DEFAULT_VIEW_INCLUSION); + mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + mapper.disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); + mapper.disable(MapperFeature.AUTO_DETECT_IS_GETTERS); + mapper.setDefaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL); + mapper.setAnnotationIntrospector(new NullableAwareIntrospector()); + + getSerializers().forEach(serializer -> registerSerializer(serializer, mapper)); + + mapper.registerModule(new GuavaModule()); + mapper.registerModule(new JavaTimeModule()); + return mapper; } - private static void registerSerializer(MultiSerializer serializer, JsonMapper.Builder builder) { + private static void registerSerializer(MultiSerializer serializer, ObjectMapper mapper) { SimpleModule module = new SimpleModule(); Class targetClass = serializer.getTargetClass(); module.addSerializer(targetClass, serializer.jacksonSerializer()); @@ -54,7 +61,7 @@ private static void registerSerializer(MultiSerializer serializer, JsonMa KeyDeserializer keyDeserializer = serializer.jacksonKeyDeserializer(); if (keyDeserializer != null) module.addKeyDeserializer(targetClass, keyDeserializer); - builder.addModule(module); + mapper.registerModule(module); } @SuppressWarnings("unchecked") diff --git a/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java b/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java index 3f8ae8b0..d4b8f210 100644 --- a/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java +++ b/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java @@ -73,6 +73,15 @@ public static > String toIdentifier(E constant) { } + /** + * Resolves an enum constant by a string identifier derived from the constant, ignoring case sensitivity. + * + * @since 0.0.20 + */ + public static > Optional getFromIdentifier(Class enumClass, String identifier) { + return getFromIdentifier(enumClass, constant -> toIdentifier(constant).equals(identifier)); + } + /** * Searches for an enum constant that matches a specific predicate. * diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8bdf4cf..17ef1b19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -adventure = "4.26.1" +adventure = "5.1.1" jackson = "2.21.4" [libraries] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118f..1a704683 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/minecraft/fabric/build.gradle.kts b/minecraft/fabric/build.gradle.kts index c5b8eb55..2f75d59a 100644 --- a/minecraft/fabric/build.gradle.kts +++ b/minecraft/fabric/build.gradle.kts @@ -34,7 +34,7 @@ repositories { dependencies { group = "xyz.bitsquidd.bits.fabric" - minecraft("com.mojang:minecraft:26.1.2") + minecraft("com.mojang:minecraft:26.2") shade(project(":minecraft"), transitive = true) shade(project(":minecraft:module:command"), transitive = true) diff --git a/minecraft/fabric/gradle/libs.versions.toml b/minecraft/fabric/gradle/libs.versions.toml index 4c7da3a6..6bd89243 100644 --- a/minecraft/fabric/gradle/libs.versions.toml +++ b/minecraft/fabric/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [libraries] -fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.19.1" } -fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.145.4+26.1.2" } +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.19.3" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.152.2+26.2" } [plugins] -fabric-loom = { id = "net.fabricmc.fabric-loom", version = "1.16-SNAPSHOT" } +fabric-loom = { id = "net.fabricmc.fabric-loom", version = "1.17-SNAPSHOT" } diff --git a/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java b/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java index 472e19bd..3acdd95c 100644 --- a/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java +++ b/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java @@ -11,19 +11,20 @@ import net.fabricmc.loader.api.FabricLoader; import xyz.bitsquidd.bits.mc.command.argument.BitsArgumentRegistry; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.provider.BitsCommandProvider; import xyz.bitsquidd.bits.wrapper.collection.AddableSet; import static xyz.bitsquidd.bits.mc.command.FabricBitsCommandManager.COMMAND_INSTANCE_ENTRYPOINT; + public class FabricClientBitsArgumentRegistry extends BitsArgumentRegistry { /** * Fabric mods must use the {@code COMMAND_INSTANCE_ENTRYPOINT} entrypoint to provide command parsers. */ @Override - protected AddableSet> initialiseParsers() { + protected AddableSet> initialiseParsers() { return super.initialiseParsers().addAll(FabricLoader.getInstance() .getEntrypoints(COMMAND_INSTANCE_ENTRYPOINT, BitsCommandProvider.class) .stream() diff --git a/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java b/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java index f922568a..4f6af7e5 100644 --- a/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java +++ b/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java @@ -8,15 +8,16 @@ package xyz.bitsquidd.bits.mc.command.provider; import xyz.bitsquidd.bits.mc.command.BitsCommand; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.collection.AddableSet; + public interface BitsCommandProvider { default AddableSet getCommands() { return AddableSet.empty(); } - default AddableSet> getArguments() { + default AddableSet> getArguments() { return AddableSet.empty(); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java index 2a3654ba..911ba7dd 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java @@ -16,7 +16,7 @@ import xyz.bitsquidd.bits.Bits; import xyz.bitsquidd.bits.log.Logger; import xyz.bitsquidd.bits.mc.command.argument.BrigadierArgumentMapping; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.debugging.TreeDebugger; import xyz.bitsquidd.bits.mc.command.exception.CommandBuildException; import xyz.bitsquidd.bits.mc.command.util.BitsCommandBuilder; @@ -221,7 +221,7 @@ private Command createCommandExecution( ArrayList<@Nullable Object> parsedArguments = new ArrayList<>(); for (CommandParameterInfo parameter : methodInfo.getAllParameters()) { - AbstractArgumentParser parser = parameter.getParser(); + ArgumentParser parser = parameter.getParser(); // Collect primitive objects for the parameter ArrayList<@Nullable Object> primitiveObjects = new ArrayList<>(); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java index 79b2bad0..549830ab 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java @@ -18,9 +18,10 @@ import org.jetbrains.annotations.Nullable; import xyz.bitsquidd.bits.log.Logger; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.impl.VoidArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.impl.generic.GenericEnumParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive.PrimitiveArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.CommandBuildException; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.util.reflection.ReflectionUtils; @@ -46,7 +47,7 @@ * Example internal usage: *
{@code
  * BitsArgumentRegistry registry = manager.getArgumentRegistry();
- * AbstractArgumentParser parser = registry.getParser(TypeSignature.of(Player.class));
+ * AbstractArgumentParser parser = registry.getParser(TypeSignature.of(Player.class));
  * }
* * @param the type of the platform's original source object @@ -54,10 +55,10 @@ * @since 0.0.10 */ public abstract class BitsArgumentRegistry { - private final Map, AbstractArgumentParser> parsers = new HashMap<>(); + private final Map, ArgumentParser> parsers = new HashMap<>(); public BitsArgumentRegistry() { - List> initialParsers = new ArrayList<>(initialiseParsers().build()); + List> initialParsers = new ArrayList<>(initialiseParsers().build()); initialParsers.forEach(parser -> parsers.put(parser.getTypeSignature(), parser)); } @@ -100,8 +101,8 @@ public BitsArgumentRegistry() { * @since 0.0.10 */ @SuppressWarnings("unchecked") - protected AddableSet> initialiseParsers() { - return AddableSet.of((Set>)(Set)ReflectionUtils.General.createClassesInDir("*", AbstractArgumentParser.class, ScannerFlags.DEFAULT)); + protected AddableSet> initialiseParsers() { + return AddableSet.of((Set>)(Set)ReflectionUtils.General.createClassesInDir("*", ArgumentParser.class, ScannerFlags.DEFAULT)); } /** @@ -114,11 +115,11 @@ protected AddableSet> initialiseParsers() { * @since 0.0.10 */ @SuppressWarnings({"unchecked", "rawtypes"}) - public AbstractArgumentParser getParser(TypeSignature typeSignature) { + public ArgumentParser getParser(TypeSignature typeSignature) { // We could consider implementing some form of search for inherited types. // This probably shouldn't be implemented as it'll cause type inconsistencies with functions. // Developers should design their command functions accordingly to use the lowest available type. - AbstractArgumentParser parser = parsers.get(typeSignature); + ArgumentParser parser = parsers.get(typeSignature); // If no parser found, we allow generic enums to be parsed. if (parser == null) { @@ -129,7 +130,7 @@ public AbstractArgumentParser getParser(TypeSignature typeSignature) { } Logger.error("No parser registered for type: " + typeSignature); - return new VoidArgumentParser(); + return VoidArgumentParser.INSTANCE; } return parser; @@ -145,18 +146,20 @@ public AbstractArgumentParser getParser(TypeSignature typeSignature) { * * @since 0.0.10 */ - public List getArgumentTypeContainer(AbstractArgumentParser parser, String baseName) { + public List getArgumentTypeContainer(ArgumentParser parser, String baseName) { List holders = new ArrayList<>(); List inputTypes = parser.getInputTypes(); // Break down the type signature into its primitives. for (int i = 0; i < inputTypes.size(); i++) { InputTypeContainer nestedTypeSigature = inputTypes.get(i); + // Get the command parser required for this input type - AbstractArgumentParser nestedParser = getParser(nestedTypeSigature.typeSignature()); + ArgumentParser nestedParser = getParser(nestedTypeSigature.typeSignature()); boolean handled = false; - // If its a primitive, we can directly add it + + // If it's a primitive, we can directly add it if (nestedParser.getInputTypes().size() == 1) { InputTypeContainer inputType = nestedParser.getInputTypes().getFirst(); ArgumentType brigadierType = toArgumentType(inputType.typeSignature()); @@ -196,30 +199,32 @@ public List getArgumentTypeContainer(AbstractArgumentP * @throws CommandSyntaxException if there's an error in parsing validation * @since 0.0.10 */ - public Object parseArguments(AbstractArgumentParser parser, List primitiveList, BitsCommandContext ctx) throws CommandSyntaxException { + public O parseArguments(ArgumentParser parser, List primitiveList, BitsCommandContext ctx) throws CommandSyntaxException { List inputTypes = parser.getInputTypes(); - - // If the input size is 1, we can directly parse it - if (inputTypes.size() == 1) return parser.parse(primitiveList, ctx); - List parsedObjects = new ArrayList<>(); for (InputTypeContainer inputType : inputTypes) { - AbstractArgumentParser nestedParser = getParser(inputType.typeSignature()); + ArgumentParser nestedParser = getParser(inputType.typeSignature()); + + if (nestedParser instanceof PrimitiveArgumentParser) { + // it's a vanilla/terminal type (String, Double, etc.) take the primitive as-is. + if (primitiveList.isEmpty()) throw new CommandBuildException("Not enough arguments for " + inputType.typeName()); + parsedObjects.add(primitiveList.removeFirst()); + continue; + } int requiredSize = nestedParser.getInputTypes().size(); if (primitiveList.size() < requiredSize) throw new CommandBuildException("Not enough arguments for " + inputType.typeName()); - ArrayList inputObjects = new ArrayList<>(primitiveList.subList(0, requiredSize)); + List inputObjects = new ArrayList<>(primitiveList.subList(0, requiredSize)); primitiveList = new ArrayList<>(primitiveList.subList(requiredSize, primitiveList.size())); - // Recursively parse the primitives with the appropriate parser Object parsedObject = parseArguments(nestedParser, inputObjects, ctx); parsedObjects.add(parsedObject); } - // Now that we have all our parsed objects, we can pass them to the main parser - return parser.parse(parsedObjects, ctx); + D data = parser.toData(parsedObjects); + return parser.parse(data, ctx); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java index f0f73c36..2f4334ee 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java @@ -11,14 +11,14 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import xyz.bitsquidd.bits.mc.command.BitsCommandManager; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; /** * A data container linking an expected return type with a built Brigadier argument type. *

- * This record is primarily used when compiling custom {@link AbstractArgumentParser} + * This record is primarily used when compiling custom {@link ArgumentParser} * instances into their base Brigadier representations for tree registration. * * @param argumentType the underlying Brigadier argument type implementation diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java deleted file mode 100644 index 579ea5c9..00000000 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This file is part of a Bit libraries package. - * Licensed under the GNU Lesser General Public License v3.0. - * - * Copyright (c) 2023-2026 ImBit - */ - -package xyz.bitsquidd.bits.mc.command.argument.parser; - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import org.jetbrains.annotations.Nullable; - -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; -import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; -import xyz.bitsquidd.bits.wrapper.type.TypeSignature; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -/** - * A parser responsible for converting a list of raw command arguments into a specific object type. - *

- * This class serves as the foundation for all custom argument parsing within the Bits command framework. - * Implementations define how string inputs or other basic types from Brigadier are structurally resolved - * into complex objects, and optionally provide tab-completion suggestions. - *

- * Example parser implementation: - *

{@code
- * public class PlayerArgumentParser extends AbstractArgumentParser {
- *     public PlayerArgumentParser() {
- *         super(TypeSignature.of(Player.class), "player_name");
- *     }
- *
- *     // Override methods to dictate parsing logic and input types.
- * }
- * }
- * - * @param The type this parser converts to. - * - * @since 0.0.10 - */ -public abstract class AbstractArgumentParser { - private final TypeSignature typeSignature; // The type signature this parser handles - private final String argumentName; // The name of the argument, used while displaying suggestions - - /** - * @param typeSignature the expected type this parser handles - * @param argumentName the label to display in command syntax suggestions - * - * @since 0.0.10 - */ - protected AbstractArgumentParser(TypeSignature typeSignature, String argumentName) { - this.typeSignature = typeSignature; - this.argumentName = argumentName; - } - - - /** - * Parses the provided primitive objects into the target complex object type {@code O}. - * - * @param inputObjects the raw inputs captured from the command context - * @param ctx the command execution context - * - * @return the successfully parsed object - * - * @throws CommandSyntaxException if the input is malformed or invalid - * @since 0.0.10 - */ - public abstract O parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException; - - /** - * Returns a list of required objects the parser expects in. - * In the case of most custom parsers this will be a String. - *

- * In the case a non-vanilla primitive is passed in, we expect a parser to be present for this.

- * For example:

    - *
  • An Integer parser would expect a single int {@code List.of(Integer.class)}
  • - *
  • A Bukkit Location parser may expect three doubles and a World {@code List.of(Double.class, Double.class, Double.class, World.class)}
  • - *
- * - * @return A list of expected required objects. - * - * @since 0.0.10 - */ - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(String.class), getArgumentName())); - } - - - //region Validation - - /** - * Helper function to validate singleton inputs for basic argument parsers. - * - * @param the type parameter of the expected output - * @param inputObjects The list of input objects to validate, expected to contain exactly one object. - * @param expectedType The expected type of the single input object. - * - * @return The validated input object cast to the expected type. - * - * @throws CommandSyntaxException if the validation fails - * @since 0.0.10 - */ - protected I singletonInputValidation(List inputObjects, Class expectedType) throws CommandSyntaxException { - List inputTypes = getInputTypes(); - if (inputTypes.size() != 1) throw ExceptionBuilder.createCommandException("Expected exactly one input type, got " + inputTypes.size() + "."); - if (inputTypes.getFirst().typeSignature().toRawType() != expectedType) { - throw ExceptionBuilder.createCommandException("Expected input type signature to be " + expectedType.getSimpleName() + ", got " + inputTypes.getFirst().typeSignature().toRawType().getSimpleName() + "."); - } - - if (inputObjects.size() != 1) throw ExceptionBuilder.createCommandException("Expected exactly one input object, got " + inputObjects.size() + "."); - Object value = inputObjects.getFirst(); - - if (!expectedType.isInstance(value)) { - throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); - } - - return expectedType.cast(value); - } - - /** - * Helper function to validate multiple arguments for complex argument parsers. - * - * @param inputObjects The list of input objects to validate, expected to match the size and types of the list returned by {@link #getInputTypes()}. - * - * @return A list of validated input objects cast to their expected types. - * - * @throws CommandSyntaxException if the input size or types are incorrect - * @since 0.0.10 - */ - protected List inputValidation(List inputObjects) throws CommandSyntaxException { - List inputTypes = getInputTypes(); - int inputSize = inputTypes.size(); - - if (inputObjects.size() != inputSize) { - throw ExceptionBuilder.createCommandException("Expected exactly " + inputSize + " input object" + (inputSize > 1 ? "s" : "") + ", got " + inputObjects.size() + "."); - } - - List returnList = new ArrayList<>(); - - for (int i = 0; i < inputSize; i++) { - InputTypeContainer expectedTypeContainer = inputTypes.get(i); - Object value = inputObjects.get(i); - Class expectedType = expectedTypeContainer.typeSignature().toRawType(); - - if (!expectedType.isInstance(value)) { - throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); - } - - returnList.add(expectedType.cast(value)); - } - - return returnList; - } - //endregion - - - /** - * Retrieves a Brigadier compatible suggestion provider for this argument parser. - *

- * This method evaluates {@link #getSuggestions()} to dynamically supply tab completion results. - * - * @param the command source type - * - * @return the suggestion provider context for Brigadier nodes - * - * @since 0.0.10 - */ - public final SuggestionProvider getSuggestionProvider() { - return (ctx, builder) -> { - Supplier> suggestionSupplier = getSuggestions(); - if (suggestionSupplier == null) return builder.buildFuture(); - - List suggestions = suggestionSupplier.get(); - String remaining = builder.getRemaining().toLowerCase(); - - for (String suggestion : suggestions) { - if (suggestion.toLowerCase().startsWith(remaining)) { - builder.suggest(suggestion); - } - } - - return builder.buildFuture(); - }; - } - - /** - * Returns a dynamically loaded list of suggestions for the argument at a given context state. - * - * @return a supplier providing available string suggestions, or null if none exist - * - * @since 0.0.10 - */ - public @Nullable Supplier> getSuggestions() { - return null; - } - - - /** - * Retrieves the core type signature handled by this parser. - * - * @return the type signature - * - * @since 0.0.10 - */ - public TypeSignature getTypeSignature() { - return typeSignature; - } - - /** - * Retrieves the argument name meant for display in syntax sequences. - * - * @return the argument label - * - * @since 0.0.10 - */ - public String getArgumentName() { - return argumentName; - } - -} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java index 53a0242d..7625ad05 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java @@ -7,17 +7,216 @@ package xyz.bitsquidd.bits.mc.command.argument.parser; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import org.jetbrains.annotations.Nullable; + +import xyz.bitsquidd.bits.mc.command.BitsCommandManager; +import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.GreedyStringArgumentParser; +import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.wrapper.GreedyString; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.lang.reflect.Constructor; +import java.lang.reflect.RecordComponent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; /** - * Annotation for marking classes as argument parsers. + * A parser responsible for converting a list of raw command arguments into a specific object type. + *

+ * This class serves as the foundation for all custom argument parsing within the Bits command framework. + * Implementations define how string inputs or other basic types from Brigadier are structurally resolved + * into complex objects, and optionally provide tab-completion suggestions. + *

+ * Example parser implementation: + *

{@code
+ * public class PlayerArgumentParser extends AbstractArgumentParser {
+ *     public PlayerArgumentParser() {
+ *         super(TypeSignature.of(Player.class), "player_name");
+ *     }
+ *
+ *     // Override methods to dictate parsing logic and input types.
+ * }
+ * }
+ * + * @param The type this parser converts to. + * @param The type of the input data (e.g., String, Integer, World) this parser expects to receive from Brigadier or parser. * - * @since 0.0.13 + * @since 0.0.10 */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ArgumentParser {} +public abstract class ArgumentParser { + private final TypeSignature typeSignature; // The type signature this parser handles + private final String argumentName; // The name of the argument, used while displaying suggestions + private final Class dataClass; // The class of the input data type + private final @Nullable Constructor recordConstructor; + + /** + * @param typeSignature the expected type this parser handles + * @param argumentName the label to display in command syntax suggestions + * @param dataClass the class of the input data type + * + * @since 0.0.10 + */ + protected ArgumentParser(TypeSignature typeSignature, String argumentName, Class dataClass) { + this.typeSignature = typeSignature; + this.argumentName = argumentName; + this.dataClass = dataClass; + this.recordConstructor = dataClass.isRecord() ? resolveCanonicalConstructor(dataClass) : null; + } + + + private static Constructor resolveCanonicalConstructor(Class recordClass) { + Class[] paramTypes = Arrays.stream(recordClass.getRecordComponents()) + .map(RecordComponent::getType) + .toArray(Class[]::new); + + try { + return recordClass.getDeclaredConstructor(paramTypes); + } catch (NoSuchMethodException e) { + // Unreachable: every record has a canonical constructor. + throw new IllegalStateException("No canonical constructor found for record " + recordClass.getSimpleName(), e); + } + } + + + /** + * Parses the provided primitive objects into the target complex object type {@code O}. + * + * @param data the raw inputs captured from the command context + * @param ctx the command execution context + * + * @return the successfully parsed object + * + * @throws CommandSyntaxException if the input is malformed or invalid + * @since 0.0.10 + */ + public abstract O parse(D data, BitsCommandContext ctx) throws CommandSyntaxException; + + /** + * Returns a list of required objects the parser expects in. + * In the case of most custom parsers this will be a String. + *

+ * In the case a non-vanilla primitive is passed in, we expect a parser to be present for this.

+ * For example:

    + *
  • An Integer parser would expect a single int {@code List.of(Integer.class)}
  • + *
  • A Bukkit Location parser may expect three doubles and a World {@code List.of(Double.class, Double.class, Double.class, World.class)}
  • + *
+ * + * @return A list of expected required objects. + * + * @since 0.0.20 + */ + public final List getInputTypes() { + // Special case for GreedyString: it captures all remaining input as a single string. + if (this instanceof GreedyStringArgumentParser) return List.of(new InputTypeContainer(TypeSignature.of(GreedyString.class), getArgumentName())); + + // Special case for non-record types: treat the entire data class the input. + if (!dataClass.isRecord()) return List.of(new InputTypeContainer(TypeSignature.of(dataClass), argumentName)); + + RecordComponent[] components = dataClass.getRecordComponents(); + List inputTypes = new ArrayList<>(components.length); + + for (RecordComponent component : components) { + inputTypes.add(new InputTypeContainer(TypeSignature.of(component.getType()), component.getName())); + } + + return inputTypes; + } + + + public final D toData(List inputObjects) throws CommandSyntaxException { + List inputTypes = getInputTypes(); + + if (inputObjects.size() != inputTypes.size()) throw ExceptionBuilder.createCommandException("Expected exactly " + inputTypes.size() + " input object" + (inputTypes.size() > 1 ? "s" : "") + ", got " + inputObjects.size() + "."); + Object[] validated = new Object[inputTypes.size()]; + + for (int i = 0; i < inputTypes.size(); i++) { + Class expectedType = inputTypes.get(i).typeSignature().toRawType(); + Object value = inputObjects.get(i); + + if (!expectedType.isInstance(value)) throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); + validated[i] = value; + } + + // Single-value case: D itself is the scalar type (e.g. D = Double) + if (!dataClass.isRecord()) return dataClass.cast(validated[0]); + + try { + return Objects.requireNonNull(this.recordConstructor).newInstance(validated); + } catch (ReflectiveOperationException e) { + throw ExceptionBuilder.createCommandException("Failed to construct " + dataClass.getSimpleName() + ": " + e.getMessage()); + } + } + + + /** + * Retrieves a Brigadier compatible suggestion provider for this argument parser. + *

+ * This method evaluates {@link #getSuggestions} to dynamically supply tab completion results. + * + * @param the command source type + * + * @return the suggestion provider context for Brigadier nodes + * + * @since 0.0.10 + */ + public final SuggestionProvider createSuggestionProvider() { + SuggestionSupplier suggestionSupplier = getSuggestions(); + if (suggestionSupplier == null) return (ctx, builder) -> builder.buildFuture(); // No suggestions available + BitsCommandManager manager = BitsCommandManager.get(); + + return (ctx, builder) -> { + List suggestions = suggestionSupplier.getSuggestions(manager.createContext(ctx)); + String remaining = builder.getRemaining(); + + for (String suggestion : suggestions) { + if (suggestion.toLowerCase().startsWith(remaining.toLowerCase())) { + builder.suggest(suggestion); + } + } + + return builder.buildFuture(); + }; + } + + /** + * Returns a dynamically loaded list of suggestions for the argument at a given context state. + * + * @return a supplier providing available string suggestions, or null if none exist + * + * @since 0.0.10 + */ + public @Nullable SuggestionSupplier getSuggestions() { + return null; + } + + + /** + * Retrieves the core type signature handled by this parser. + * + * @return the type signature + * + * @since 0.0.10 + */ + public TypeSignature getTypeSignature() { + return typeSignature; + } + + /** + * Retrieves the argument name meant for display in syntax sequences. + * + * @return the argument label + * + * @since 0.0.10 + */ + public String getArgumentName() { + return argumentName; + } + +} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java new file mode 100644 index 00000000..4be1e3e1 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java @@ -0,0 +1,23 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser; + +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + + +/** + * Argument parser designed for types that can be directly parsed from a single string input without any additional validation or transformation. + * + * @param + */ +public abstract class BasicArgumentParser extends ArgumentParser { + protected BasicArgumentParser(TypeSignature typeSignature, String argumentName) { + super(typeSignature, argumentName, String.class); + } + +} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java new file mode 100644 index 00000000..411eeb82 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java @@ -0,0 +1,25 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser; + +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; + +import java.util.List; + + +@FunctionalInterface +public interface SuggestionSupplier { + + List getSuggestions(BitsCommandContext ctx); + + + static SuggestionSupplier empty() { + return ctx -> List.of(); + } + +} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java index e1aedc2a..7546c63d 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java @@ -8,14 +8,11 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; import xyz.bitsquidd.bits.mc.command.BrigadierTreeGenerator; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.GreedyString; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * Argument parser for {@link GreedyString} values. @@ -24,20 +21,15 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class GreedyStringArgumentParser extends AbstractArgumentParser { - public GreedyStringArgumentParser() { - super(TypeSignature.of(GreedyString.class), "String..."); - } - @Override - public GreedyString parse(List inputObjects, BitsCommandContext ctx) { - return GreedyString.of((String)inputObjects.getFirst()); // Note: can't use singletonInputValidation as GreedyString is special... +public final class GreedyStringArgumentParser extends ArgumentParser { + public GreedyStringArgumentParser() { + super(TypeSignature.of(GreedyString.class), "String...", String.class); } @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(GreedyString.class), getArgumentName())); + public GreedyString parse(String data, BitsCommandContext ctx) { + return GreedyString.of(data); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java new file mode 100644 index 00000000..0664f6b0 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java @@ -0,0 +1,51 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser.impl; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; + +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.util.Collections; +import java.util.List; + + +/** + * Argument parser for handling {@link Key Keys}. + * + * @since 0.0.20 + */ + +public final class KeyArgumentParser extends BasicArgumentParser { + public KeyArgumentParser() { + super(TypeSignature.of(Key.class), "Key"); + } + + @Override + public Key parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Key.key(data); + } + + @Override + public SuggestionSupplier getSuggestions() { + return ctx -> { + String lastInput = ctx.getLastInput(); + if (lastInput.isEmpty()) return Collections.emptyList(); + if (lastInput.contains(Key.DEFAULT_SEPARATOR + "")) return Collections.emptyList(); + return List.of( + lastInput + Key.DEFAULT_SEPARATOR, + Key.MINECRAFT_NAMESPACE + Key.DEFAULT_SEPARATOR + lastInput + ); + }; + } + +} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java index e15fe7f0..60671b53 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java @@ -9,15 +9,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.Link; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Stream; //TODO add better validation for this, enum for a type? WWW. HTTPS:// etc.? @@ -29,21 +27,20 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class LinkArgumentParser extends AbstractArgumentParser { + +public final class LinkArgumentParser extends BasicArgumentParser { public LinkArgumentParser() { super(TypeSignature.of(Link.class), "URL"); } @Override - public Link parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - return Link.of(inputString); + public Link parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Link.of(data); } @Override - public Supplier> getSuggestions() { - return () -> Stream.of("https://", "http://", "www.").toList(); + public SuggestionSupplier getSuggestions() { + return ctx -> List.of("https://", "http://", "www."); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java index 710b65b8..327b6786 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java @@ -9,12 +9,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -32,32 +33,53 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class UUIDArgumentParser extends AbstractArgumentParser { + +public final class UUIDArgumentParser extends BasicArgumentParser { + private static final List DASH_POSITIONS = List.of(8, 12, 16, 20); + private static final char DASH_CHAR = '-'; + private static final int HEX_CHAR_COUNT = 32; + private static final int CANONICAL_LENGTH = HEX_CHAR_COUNT + DASH_POSITIONS.size(); public UUIDArgumentParser() { super(TypeSignature.of(UUID.class), "UUID"); } @Override - public UUID parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - String hexOnly = inputString.replaceAll("[^a-fA-F0-9]", ""); - - if (hexOnly.length() != 32) throw ExceptionBuilder.createCommandException("Invalid UUID format: " + inputString); + public UUID parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + String hexOnly = data.replaceAll("[^a-fA-F0-9]", ""); + if (hexOnly.length() != HEX_CHAR_COUNT) throw ExceptionBuilder.createCommandException("Invalid UUID format: " + data); - String canonical = hexOnly.substring(0, 8) + "-" - + hexOnly.substring(8, 12) + "-" - + hexOnly.substring(12, 16) + "-" - + hexOnly.substring(16, 20) + "-" - + hexOnly.substring(20); + StringBuilder canonical = new StringBuilder(CANONICAL_LENGTH); + for (int i = 0; i < hexOnly.length(); i++) { + if (DASH_POSITIONS.contains(i)) canonical.append(DASH_CHAR); + canonical.append(hexOnly.charAt(i)); + } try { - return UUID.fromString(canonical); + return UUID.fromString(canonical.toString()); } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException("Invalid UUID format: " + inputString); + throw ExceptionBuilder.createCommandException("Invalid UUID format: " + data); } } + @Override + public SuggestionSupplier getSuggestions() { + return ctx -> { + String lastInput = ctx.getLastInput(); + if (lastInput.isEmpty()) return Collections.emptyList(); + if (lastInput.length() >= CANONICAL_LENGTH) return Collections.emptyList(); + + String hexOnly = lastInput.replaceAll("[^a-fA-F0-9]", ""); + if (hexOnly.length() > HEX_CHAR_COUNT) return Collections.emptyList(); + + StringBuilder suggestion = new StringBuilder(lastInput); + for (int pos : DASH_POSITIONS) { + if (suggestion.length() > pos && suggestion.charAt(pos) != DASH_CHAR) { + suggestion.insert(pos, DASH_CHAR); + } + } + return List.of(suggestion.toString()); + }; + } + } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java index 85451c0f..cb7afb28 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java @@ -7,12 +7,10 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * A fallback argument parser that explicitly consumes and returns {@code null} for {@link Void} types. @@ -21,15 +19,16 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class VoidArgumentParser extends AbstractArgumentParser { - public VoidArgumentParser() { +public final class VoidArgumentParser extends BasicArgumentParser { + public static final VoidArgumentParser INSTANCE = new VoidArgumentParser(); + + private VoidArgumentParser() { super(TypeSignature.of(Void.class), "Void"); } @Override - public Void parse(List inputObjects, BitsCommandContext ctx) { + public Void parse(String inputObjects, BitsCommandContext ctx) { return null; } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java similarity index 64% rename from minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java rename to minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java index 3242f6e7..560cee41 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java @@ -10,15 +10,16 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.jetbrains.annotations.Nullable; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; + /** * Base abstract parser providing common parsing and suggestion logic for enum types. *

@@ -34,12 +35,12 @@ * } * } * - * @param the specific enum type + * @param the specific enum type * * @since 0.0.10 */ -public abstract class AbstractEnumArgumentParser> extends AbstractArgumentParser { - private final Class enumClass; +public abstract class EnumArgumentParser> extends BasicArgumentParser { + private final Class enumClass; /** * @param enumClass the class object representing the enum to parse @@ -47,29 +48,27 @@ public abstract class AbstractEnumArgumentParser> extends Abst * @throws IllegalArgumentException if the provided class is not an enum * @since 0.0.10 */ - public AbstractEnumArgumentParser(Class enumClass) { + public EnumArgumentParser(Class enumClass) { super(TypeSignature.of(Enum.class), enumClass.getName()); this.enumClass = enumClass; if (!enumClass.isEnum()) throw new IllegalArgumentException("Provided class " + enumClass.getName() + " is not an enum!"); } @Override - public T parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - T enumValue; + public E parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + E enumValue; try { - enumValue = Enum.valueOf(enumClass, inputString); + enumValue = Enum.valueOf(enumClass, data); } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException(inputString + " is not a valid " + enumClass.getSimpleName() + "."); + throw ExceptionBuilder.createCommandException(data + " is not a valid " + enumClass.getSimpleName() + "."); } return enumValue; } @Override - public @Nullable Supplier> getSuggestions() { - return () -> enumClass.isEnum() ? Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList() : List.of(); + public @Nullable SuggestionSupplier getSuggestions() { + return ctx -> enumClass.isEnum() ? Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList() : List.of(); } } \ No newline at end of file diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java index 0f522bcf..afddfa5e 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java @@ -9,21 +9,21 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.impl.abs.AbstractEnumArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.abs.EnumArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.util.wrapper.Enums; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; + /** * A fallback, generically instantiated parser that handles unmapped enum types automatically. *

- * Developer Note: Custom enum parsers should override {@link AbstractEnumArgumentParser} + * Developer Note: Custom enum parsers should override {@link EnumArgumentParser} * if you want more fine-grained control over enum arguments and tab completion. *

* This implementation serves as a basic parser that is fallen back upon when no specific enum parser is available. @@ -32,9 +32,8 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class GenericEnumParser> extends AbstractArgumentParser { +public final class GenericEnumParser> extends BasicArgumentParser { private final Class enumClass; public GenericEnumParser(Class enumClass) { @@ -43,19 +42,14 @@ public GenericEnumParser(Class enumClass) { } @Override - public E parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - for (E constant : enumClass.getEnumConstants()) { - if (constant.name().equalsIgnoreCase(inputString)) return constant; - } - - throw ExceptionBuilder.createCommandException("Enum constant not found: " + inputString + " for enum " + enumClass.getSimpleName() + "."); + public E parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Enums.getFromIdentifier(enumClass, data) + .orElseThrow(() -> ExceptionBuilder.createCommandException("Enum constant not found: " + data + " for enum " + enumClass.getSimpleName() + ".")); } @Override - public Supplier> getSuggestions() { - return () -> Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList(); + public SuggestionSupplier getSuggestions() { + return ctx -> Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList(); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java index 444646e2..8ed2b8c1 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java @@ -7,17 +7,17 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import java.util.List; -import java.util.function.Supplier; + /** * Argument parser for Boolean values. * * @since 0.0.10 */ -@ArgumentParser + public final class BooleanArgumentParser extends PrimitiveArgumentParser { public BooleanArgumentParser() { @@ -25,8 +25,8 @@ public BooleanArgumentParser() { } @Override - public Supplier> getSuggestions() { - return () -> List.of("true", "false"); + public SuggestionSupplier getSuggestions() { + return ctx -> List.of("true", "false"); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java index 987fa30e..44dd84c1 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Double values. * * @since 0.0.10 */ -@ArgumentParser + public final class DoubleArgumentParser extends PrimitiveArgumentParser { public DoubleArgumentParser() { super(Double.class, "Double"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java index b0bc401d..22162ca9 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Float values. * * @since 0.0.10 */ -@ArgumentParser + public final class FloatArgumentParser extends PrimitiveArgumentParser { public FloatArgumentParser() { super(Float.class, "Float"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java index 79be0c9a..c04430a7 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Integer values. * * @since 0.0.10 */ -@ArgumentParser + public final class IntegerArgumentParser extends PrimitiveArgumentParser { public IntegerArgumentParser() { diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java index d59ce089..8e4e88df 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Long values. * * @since 0.0.10 */ -@ArgumentParser + public final class LongArgumentParser extends PrimitiveArgumentParser { public LongArgumentParser() { diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java index 12fb813c..ab6d26bc 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java @@ -9,12 +9,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * A foundation parser designed specifically for types that correspond to underlying Brigadier primitives. @@ -26,21 +24,15 @@ * * @since 0.0.10 */ -public abstract class PrimitiveArgumentParser extends AbstractArgumentParser { +public abstract class PrimitiveArgumentParser extends ArgumentParser { protected PrimitiveArgumentParser(Class outputClass, String argumentName) { - super(TypeSignature.of(outputClass), argumentName); + super(TypeSignature.of(outputClass), argumentName, outputClass); } @Override - @SuppressWarnings("unchecked") - public final O parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - return (O)singletonInputValidation(inputObjects, getTypeSignature().toRawType()); - } - - @Override - public final List getInputTypes() { - return List.of(new InputTypeContainer(getTypeSignature(), getArgumentName())); + public final O parse(O data, BitsCommandContext ctx) throws CommandSyntaxException { + return data; } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java index c8373738..fd241bea 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for String values. * * @since 0.0.10 */ -@ArgumentParser + public final class StringArgumentParser extends PrimitiveArgumentParser { public StringArgumentParser() { super(String.class, "String"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java index 145cdc0c..346be50f 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java @@ -12,7 +12,7 @@ import xyz.bitsquidd.bits.mc.command.BitsCommandManager; import xyz.bitsquidd.bits.mc.command.argument.BrigadierArgumentMapping; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; import java.lang.reflect.Parameter; @@ -39,7 +39,7 @@ public class CommandParameterInfo { private final TypeSignature typeSignature; // TypeSignature of the parameter - private final AbstractArgumentParser parser; + private final ArgumentParser parser; private final List heldArguments = new ArrayList<>(); // Direct mapping from the custom args to brigadier ArgumentTypes @@ -81,13 +81,13 @@ public CommandParameterInfo(Parameter parameter) { if (useArgSuggestions) { TypeSignature inputType = parser.getInputTypes().get(i).typeSignature(); - AbstractArgumentParser inputParser = BitsCommandManager.get().getArgumentRegistry().getParser(inputType); + ArgumentParser inputParser = BitsCommandManager.get().getArgumentRegistry().getParser(inputType); // Use the input-specific parser's suggestions - if (hasSuggestions(inputParser)) argumentBuilder.suggests(inputParser.getSuggestionProvider()); + if (hasSuggestions(inputParser)) argumentBuilder.suggests(inputParser.createSuggestionProvider()); } else { // Use the main parser's suggestions - if (hasSuggestions(parser)) argumentBuilder.suggests(parser.getSuggestionProvider()); + if (hasSuggestions(parser)) argumentBuilder.suggests(parser.createSuggestionProvider()); } brigadierArguments.add(argumentBuilder); @@ -95,7 +95,7 @@ public CommandParameterInfo(Parameter parameter) { return brigadierArguments; } - private boolean hasSuggestions(AbstractArgumentParser parser) { + private boolean hasSuggestions(ArgumentParser parser) { return parser.getSuggestions() != null; } @@ -106,7 +106,7 @@ private boolean hasSuggestions(AbstractArgumentParser parser) { * * @since 0.0.10 */ - public AbstractArgumentParser getParser() { + public ArgumentParser getParser() { return parser; } diff --git a/minecraft/paper/gradle/libs.versions.toml b/minecraft/paper/gradle/libs.versions.toml index b9bcfb98..cc649d63 100644 --- a/minecraft/paper/gradle/libs.versions.toml +++ b/minecraft/paper/gradle/libs.versions.toml @@ -1,8 +1,7 @@ [versions] -paper-api = "26.1.2.build.+" +paper-api = "26.2-rc-2.build.+" [libraries] -paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper-api" } bstats = { group = "org.bstats", name = "bstats-bukkit", version = "3.2.1" } [plugins] diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java index c885f865..02f8107f 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java @@ -7,38 +7,28 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import com.mojang.brigadier.exceptions.CommandSyntaxException; - -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.paper.location.wrapper.BlockPos; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -public final class BlockPosArgumentParser extends AbstractArgumentParser { +public final class BlockPosArgumentParser extends ArgumentParser { + + public record BlockPosData( + double x, + double y, + double z + ) {} public BlockPosArgumentParser() { - super(TypeSignature.of(BlockPos.class), "BlockPos"); + super(TypeSignature.of(BlockPos.class), "BlockPos", BlockPosData.class); } - @Override - public BlockPos parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - List inputs = inputValidation(inputObjects); - double x = (double)inputs.get(0); - double y = (double)inputs.get(1); - double z = (double)inputs.get(2); - return BlockPos.of(x, y, z); - } @Override - public List getInputTypes() { - return List.of( - new InputTypeContainer(TypeSignature.of(Double.class), "x"), - new InputTypeContainer(TypeSignature.of(Double.class), "y"), - new InputTypeContainer(TypeSignature.of(Double.class), "z") - ); + public BlockPos parse(BlockPosData data, BitsCommandContext ctx) { + return BlockPos.of(data.x(), data.y(), data.z()); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java index eb438a13..0da13020 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java @@ -7,42 +7,31 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.bukkit.Location; import org.bukkit.World; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -public final class LocationArgumentParser extends AbstractArgumentParser { +public final class LocationArgumentParser extends ArgumentParser { + + public record LocationData( + double x, + double y, + double z, + World world + ) {} public LocationArgumentParser() { - super(TypeSignature.of(Location.class), "Location"); + super(TypeSignature.of(Location.class), "Location", LocationData.class); } - @Override - public Location parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - List inputs = inputValidation(inputObjects); - double x = (double)inputs.get(0); - double y = (double)inputs.get(1); - double z = (double)inputs.get(2); - World world = (World)inputs.get(3); - - return new Location(world, x, y, z); - } @Override - public List getInputTypes() { - return List.of( - new InputTypeContainer(TypeSignature.of(Double.class), "x"), - new InputTypeContainer(TypeSignature.of(Double.class), "y"), - new InputTypeContainer(TypeSignature.of(Double.class), "z"), - new InputTypeContainer(TypeSignature.of(World.class), "world") - ); + public Location parse(LocationData data, BitsCommandContext ctx) { + return new Location(data.world(), data.x(), data.y(), data.z()); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java index 0606bd0c..eda97994 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java @@ -15,8 +15,8 @@ import org.jetbrains.annotations.Nullable; import xyz.bitsquidd.bits.mc.command.PaperBitsCommandContext; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -25,9 +25,9 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; -import java.util.function.Supplier; -public final class PlayerCollectionArgumentParser extends AbstractArgumentParser> { + +public final class PlayerCollectionArgumentParser extends ArgumentParser, EntitySelector> { // TODO: Just pull from vanilla EntitySelector. Also pull the completion. private enum SelectorType { @@ -58,14 +58,12 @@ public Collection get(BitsCommandContext ctx) { } public PlayerCollectionArgumentParser() { - super(TypeSignature.of(Collection.class, Player.class), "Players"); + super(TypeSignature.of(Collection.class, Player.class), "Players", EntitySelector.class); } @Override - public Collection parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - EntitySelector entitySelctor = singletonInputValidation(inputObjects, EntitySelector.class); - - return entitySelctor.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) + public Collection parse(EntitySelector data, BitsCommandContext ctx) throws CommandSyntaxException { + return data.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) .stream() .map(playerEntity -> playerEntity.getBukkitEntity().getPlayer()) .map(Objects::requireNonNull) @@ -73,18 +71,13 @@ public Collection parse(List inputObjects, BitsCommandContext } @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(EntitySelector.class), getArgumentName())); - } - - @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { List suggestions = new ArrayList<>(); suggestions.add(SelectorType.ALL.selector); suggestions.add(SelectorType.SELF.selector); suggestions.addAll(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList()); - return () -> suggestions; + return _ -> suggestions; } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java index fc16ca69..0bfd347e 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java @@ -13,42 +13,34 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; import java.util.Objects; -import java.util.function.Supplier; -public final class PlayerSingleArgumentParser extends AbstractArgumentParser { + +public final class PlayerSingleArgumentParser extends ArgumentParser { public PlayerSingleArgumentParser() { - super(TypeSignature.of(Player.class), "Player"); + super(TypeSignature.of(Player.class), "Player", EntitySelector.class); } @Override - public Player parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - EntitySelector entitySelctor = singletonInputValidation(inputObjects, EntitySelector.class); - - return entitySelctor.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) + public Player parse(EntitySelector data, BitsCommandContext ctx) throws CommandSyntaxException { + return data.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) .stream() .map(playerEntity -> playerEntity.getBukkitEntity().getPlayer()) .map(Objects::requireNonNull) .findFirst() - .orElseThrow(() -> ExceptionBuilder.createCommandException("Player not found: " + entitySelctor)); - } - - @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(EntitySelector.class), getArgumentName())); + .orElseThrow(() -> ExceptionBuilder.createCommandException("Player not found: " + data)); } @Override - public Supplier> getSuggestions() { - return () -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); + public SuggestionSupplier getSuggestions() { + return _ -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java index 9603dcbb..16852fd9 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java @@ -8,35 +8,33 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; import org.bukkit.Bukkit; import org.bukkit.World; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -import java.util.function.Supplier; -public final class WorldArgumentParser extends AbstractArgumentParser { +public final class WorldArgumentParser extends ArgumentParser { public WorldArgumentParser() { - super(TypeSignature.of(World.class), "World"); + super(TypeSignature.of(World.class), "World", Key.class); } @Override - public World parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - World world = Bukkit.getWorld(inputString); - if (world == null) throw ExceptionBuilder.createCommandException("World not found: " + inputString + "."); + public World parse(Key data, BitsCommandContext ctx) throws CommandSyntaxException { + World world = Bukkit.getWorld(data); + if (world == null) throw ExceptionBuilder.createCommandException("World not found: " + data + "."); return world; } @Override - public Supplier> getSuggestions() { - return () -> Bukkit.getWorlds().stream().map(World::getName).toList(); + public SuggestionSupplier getSuggestions() { + return _ -> Bukkit.getWorlds().stream().map(w -> "\"" + w.key() + "\"").toList(); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java new file mode 100644 index 00000000..81e647c6 --- /dev/null +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java @@ -0,0 +1,67 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser.impl; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; +import org.bukkit.Bukkit; + +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.paper.wrapper.WorldDefinition; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static xyz.bitsquidd.bits.paper.util.Keys.NSK; + + +public final class WorldDefinitionArgumentParser extends ArgumentParser { + private static final Path DIMENSIONS_PATH = Bukkit.getWorldContainer().toPath().resolve("world/dimensions"); + + public WorldDefinitionArgumentParser() { + super(TypeSignature.of(WorldDefinition.class), "WorldDefinition", Key.class); + } + + @Override + public WorldDefinition parse(Key data, BitsCommandContext ctx) throws CommandSyntaxException { + return WorldDefinition.of(NSK(data)); + } + + @Override + public SuggestionSupplier getSuggestions() { + Set worldKeys = new HashSet<>(); + try (Stream namespaces = Files.list(DIMENSIONS_PATH)) { + namespaces.forEach(namespacePath -> { + String namespace = namespacePath.getFileName().toString(); + try (Stream dimensions = Files.list(namespacePath)) { + dimensions.forEach(p -> { + File dimensionFile = p.toFile(); + if (!dimensionFile.isDirectory()) return; + worldKeys.add(Key.key(namespace, dimensionFile.getName())); + }); + } catch (IOException e) { + // If we can't read the dimensions directory, we just skip it. + } + }); + } catch (IOException e) { + // If we can't read the dimensions directory, we just return an empty list of suggestions. + return SuggestionSupplier.empty(); + } + + return _ -> worldKeys.stream().map(k -> "\"" + k + "\"").toList(); + } + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java index ab9cde47..aa840a33 100644 --- a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java @@ -7,12 +7,15 @@ package xyz.bitsquidd.bits.paper.location.wrapper; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import java.util.Objects; + /** * Immutable record representing the coordinates of a chunk in a specific world. * @@ -40,11 +43,27 @@ public static ChunkCoordinate fromChunk(Chunk chunk) { ); } + public static ChunkCoordinate fromChunk(ChunkAccess chunkAccess) { + if (!(chunkAccess instanceof LevelChunk levelChunk)) throw new IllegalArgumentException("ChunkAccess must be an instance of LevelChunk"); + + return new ChunkCoordinate( + levelChunk.getPos().x(), + levelChunk.getPos().z(), + levelChunk.getLevel().getWorld() + ); + } + + public int manhattanDistance(ChunkCoordinate other) { if (!Objects.equals(this.world, other.world)) throw new IllegalArgumentException("Cannot calculate distance between chunks in different worlds"); return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); } + public int euclideanDistance(ChunkCoordinate other) { + if (!Objects.equals(this.world, other.world)) throw new IllegalArgumentException("Cannot calculate distance between chunks in different worlds"); + return (int)Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.z - other.z, 2)); + } + @Override public int hashCode() { diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java new file mode 100644 index 00000000..9bc576b9 --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java @@ -0,0 +1,29 @@ +package xyz.bitsquidd.bits.paper.util; + +import net.kyori.adventure.key.Key; +import org.bukkit.NamespacedKey; + +import java.util.Locale; + + +public final class Keys { + private Keys() {} + + public static NamespacedKey NSK(final Key key) { + return new NamespacedKey(key.namespace(), key.value()); + } + + public static String sanitize(String key) { + // Normalize to lower\-case and strip surrounding whitespace + String sanitized = key.trim().toLowerCase(Locale.ROOT); + + // Replace any sequence of invalid characters with a single underscore + sanitized = sanitized.replaceAll("[^a-z0-9_]+", "_"); + + // Remove leading/trailing underscores + sanitized = sanitized.replaceAll("^_+|_+$", ""); + + return sanitized; + } + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java new file mode 100644 index 00000000..0a00452f --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java @@ -0,0 +1,49 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.paper.wrapper; + +import net.kyori.adventure.key.Key; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.WorldCreator; + +import java.util.Objects; + + +/** + * A representation of a Bukkit world definition. May be currently loaded or unloaded. + * + * @since 0.0.20 + */ +public final class WorldDefinition { + public final NamespacedKey key; + + private WorldDefinition(NamespacedKey key) { + this.key = key; + } + + public static WorldDefinition of(Key key) { + return new WorldDefinition(new NamespacedKey(key.namespace(), key.value())); + } + + + public World load() { + World loaded = Bukkit.getWorld(key); + if (loaded != null) return loaded; + + return Objects.requireNonNull(Bukkit.createWorld(WorldCreator.ofKey(key)), "Failed to load world with key: " + key); + } + + public void unload(final boolean save) { + World loaded = Bukkit.getWorld(key); + if (loaded != null) Bukkit.unloadWorld(loaded, save); + } + + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java new file mode 100644 index 00000000..1f9ea5e4 --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package xyz.bitsquidd.bits.paper.wrapper; + +import org.jetbrains.annotations.NotNullByDefault; \ No newline at end of file diff --git a/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java b/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java index 391198c2..abc7feaf 100644 --- a/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java +++ b/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java @@ -59,8 +59,7 @@ public AnimationPose evaluate(int ticks) { float localProgress = Math.min((progress - kf.progress()) / intervalLength, 1f); float easedProgress = kf.easing().progress(localProgress); - AnimationPose delta = AnimationPose.lerp(AnimationPose.identity(), kf.pose(), easedProgress); - result = AnimationPose.add(result, delta); + result = AnimationPose.lerp(kf.pose(), next.pose(), easedProgress); } return result; }