diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/MinestomCommandManager.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/MinestomCommandManager.java index ba968eff..61decdab 100644 --- a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/MinestomCommandManager.java +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/MinestomCommandManager.java @@ -40,6 +40,7 @@ import org.incendo.cloud.minestom.parser.EntityTypeParser; import org.incendo.cloud.minestom.parser.GameModeParser; import org.incendo.cloud.minestom.parser.InstanceParser; +import org.incendo.cloud.minestom.parser.ItemStackParser; import org.incendo.cloud.minestom.parser.PlayerParser; import org.incendo.cloud.minestom.parser.location.PosParser; import org.incendo.cloud.minestom.parser.location.VecParser; @@ -82,7 +83,8 @@ public MinestomCommandManager( .registerParser(GameModeParser.gameModeParser()) .registerParser(DimensionTypeParser.dimensionTypeParser()) .registerParser(PosParser.posParser()) - .registerParser(VecParser.vecParser()); + .registerParser(VecParser.vecParser()) + .registerParser(ItemStackParser.itemStackParser()); this.captionRegistry().registerProvider(new MinestomDefaultCaptionsProvider<>()); diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomCaptionKeys.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomCaptionKeys.java index 48558016..4a8f706f 100644 --- a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomCaptionKeys.java +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomCaptionKeys.java @@ -72,6 +72,11 @@ public final class MinestomCaptionKeys { */ public static final Caption ARGUMENT_PARSE_FAILURE_POS = of("argument.parse.failure.pos"); + /** + * Variables: {@code } + */ + public static final Caption ARGUMENT_PARSE_FAILURE_ITEM_STACK = of("argument.parse.failure.item_stack"); + private MinestomCaptionKeys() { } diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomDefaultCaptionsProvider.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomDefaultCaptionsProvider.java index 973b8b2d..bc7f8993 100644 --- a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomDefaultCaptionsProvider.java +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/caption/MinestomDefaultCaptionsProvider.java @@ -69,6 +69,11 @@ public final class MinestomDefaultCaptionsProvider extends DelegatingCaptionP */ public static final String ARGUMENT_PARSE_FAILURE_POS = "'' is not a valid pos (expected: x y z, optionally with yaw and pitch)"; + /** + * Default caption for {@link MinestomCaptionKeys#ARGUMENT_PARSE_FAILURE_ITEM_STACK} + */ + public static final String ARGUMENT_PARSE_FAILURE_ITEM_STACK = "'' is not a valid item"; + private static final CaptionProvider PROVIDER = CaptionProvider.constantProvider() .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_PLAYER, ARGUMENT_PARSE_FAILURE_PLAYER) .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_ENTITY_TYPE, ARGUMENT_PARSE_FAILURE_ENTITY_TYPE) @@ -77,6 +82,7 @@ public final class MinestomDefaultCaptionsProvider extends DelegatingCaptionP .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_DIMENSION_TYPE, ARGUMENT_PARSE_FAILURE_DIMENSION_TYPE) .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_VEC, ARGUMENT_PARSE_FAILURE_VEC) .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_POS, ARGUMENT_PARSE_FAILURE_POS) + .putCaption(MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_ITEM_STACK, ARGUMENT_PARSE_FAILURE_ITEM_STACK) .build(); @SuppressWarnings("unchecked") diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/ProtoItemStack.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/ProtoItemStack.java new file mode 100644 index 00000000..266f5475 --- /dev/null +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/ProtoItemStack.java @@ -0,0 +1,67 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.cloud.minestom.data; + +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.apiguardian.api.API; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Intermediary result for an argument which parses a {@link net.minestom.server.item.Material} and optional NBT data. + * + * @since 2.1.0 + */ +@API(status = API.Status.STABLE, since = "2.0.0") +public final class ProtoItemStack { + + private final Material material; + + /** + * Creates a new proto item stack. + * + * @param material the material + */ + public ProtoItemStack(final @NonNull Material material) { + this.material = material; + } + + /** + * Creates an {@link ItemStack} from this proto item stack with the given amount. + * + * @param amount the stack size + * @return the created item stack + * @throws IllegalArgumentException if the amount exceeds the material's max stack size + */ + public @NonNull ItemStack createItemStack(final int amount) { + if (amount > this.material.maxStackSize()) { + throw new IllegalArgumentException(String.format( + "The maximum stack size for %s is %d", + this.material.name(), + this.material.maxStackSize() + )); + } + return ItemStack.of(this.material, amount); + } +} diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/package-info.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/package-info.java new file mode 100644 index 00000000..6c1624f0 --- /dev/null +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/data/package-info.java @@ -0,0 +1,4 @@ +/** + * cloud-minestom data holders + */ +package org.incendo.cloud.minestom.data; diff --git a/cloud-minestom/src/main/java/org/incendo/cloud/minestom/parser/ItemStackParser.java b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/parser/ItemStackParser.java new file mode 100644 index 00000000..4c1846ed --- /dev/null +++ b/cloud-minestom/src/main/java/org/incendo/cloud/minestom/parser/ItemStackParser.java @@ -0,0 +1,139 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.cloud.minestom.parser; + +import java.util.stream.Collectors; +import net.kyori.adventure.key.Key; +import net.minestom.server.item.Material; +import org.apiguardian.api.API; +import org.incendo.cloud.caption.CaptionVariable; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.exception.parsing.ParserException; +import org.incendo.cloud.minestom.caption.MinestomCaptionKeys; +import org.incendo.cloud.minestom.data.ProtoItemStack; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; +import org.jspecify.annotations.NonNull; + +/** + * Parser for {@link ProtoItemStack}. + * + * @param command sender type + */ +public final class ItemStackParser implements ArgumentParser, BlockingSuggestionProvider.Strings { + + /** + * Creates a new item stack parser. + * + * @param command sender type + * @return the created parser + * @since 2.0.0 + */ + @API(status = API.Status.STABLE, since = "2.0.0") + public static @NonNull ParserDescriptor itemStackParser() { + return ParserDescriptor.of(new ItemStackParser<>(), ProtoItemStack.class); + } + + /** + * Returns a {@link CommandComponent.Builder} using {@link #itemStackParser()} as the parser. + * + * @param the command sender type + * @return the component builder + * @since 2.0.0 + */ + @API(status = API.Status.STABLE, since = "2.0.0") + public static CommandComponent.@NonNull Builder itemStackComponent() { + return CommandComponent.builder().parser(itemStackParser()); + } + + @Override + public @NonNull ArgumentParseResult<@NonNull ProtoItemStack> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull CommandInput commandInput + ) { + final String input = commandInput.readString(); + final Key key; + try { + key = Key.key(input); + } catch (final Exception e) { + return ArgumentParseResult.failure(new ItemStackParseException(input, commandContext)); + } + + final Material material = Material.fromKey(key); + if (material == null) { + return ArgumentParseResult.failure(new ItemStackParseException(input, commandContext)); + } + return ArgumentParseResult.success(new ProtoItemStack(material)); + } + + @Override + public @NonNull Iterable<@NonNull String> stringSuggestions( + final @NonNull CommandContext commandContext, + final @NonNull CommandInput input + ) { + return Material.values().stream() + .map(m -> m.key().value()) + .collect(Collectors.toList()); + } + + /** + * Exception thrown when an item stack cannot be parsed from the input provided. + */ + public static final class ItemStackParseException extends ParserException { + + private final String input; + + /** + * Create a new item stack parse exception. + * + * @param input string input + * @param context command context + */ + public ItemStackParseException( + final @NonNull String input, + final @NonNull CommandContext context + ) { + super( + ItemStackParser.class, + context, + MinestomCaptionKeys.ARGUMENT_PARSE_FAILURE_ITEM_STACK, + CaptionVariable.of("input", input) + ); + this.input = input; + } + + /** + * Returns the supplied input. + * + * @return input value + */ + public @NonNull String input() { + return this.input; + } + } +}