fabric: implement location argument types

This commit is contained in:
jmp 2021-03-12 20:19:06 -08:00 committed by Jason
parent c95d0c70cd
commit 427842cade
11 changed files with 1285 additions and 77 deletions

View file

@ -25,9 +25,13 @@
package cloud.commandframework.fabric;
import cloud.commandframework.CommandTree;
import cloud.commandframework.arguments.parser.ParserParameters;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.annotations.specifier.Center;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.argument.FabricArgumentParsers.FabricParserParameters;
import cloud.commandframework.fabric.data.Coordinates;
import cloud.commandframework.fabric.data.Message;
import cloud.commandframework.fabric.data.MultipleEntitySelector;
import cloud.commandframework.fabric.data.MultiplePlayerSelector;
@ -131,6 +135,34 @@ public final class FabricServerCommandManager<C> extends FabricCommandManager<C,
private void registerParsers() {
this.getParserRegistry().registerParserSupplier(TypeToken.get(Message.class), params -> FabricArgumentParsers.message());
// Location arguments
this.getParserRegistry().registerAnnotationMapper(
Center.class,
(annotation, type) -> ParserParameters.single(FabricParserParameters.CENTER_INTEGERS, true)
);
this.getParserRegistry().registerParserSupplier(
TypeToken.get(Coordinates.class),
params -> FabricArgumentParsers.vec3(params.get(
FabricParserParameters.CENTER_INTEGERS,
false
))
);
this.getParserRegistry().registerParserSupplier(
TypeToken.get(Coordinates.CoordinatesXZ.class),
params -> FabricArgumentParsers.vec2(params.get(
FabricParserParameters.CENTER_INTEGERS,
false
))
);
this.getParserRegistry().registerParserSupplier(
TypeToken.get(Coordinates.BlockCoordinates.class),
params -> FabricArgumentParsers.blockPos()
);
this.getParserRegistry().registerParserSupplier(
TypeToken.get(Coordinates.ColumnCoordinates.class),
params -> FabricArgumentParsers.columnPos()
);
// Entity selectors
this.getParserRegistry().registerParserSupplier(
TypeToken.get(SinglePlayerSelector.class),

View file

@ -0,0 +1,30 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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.
//
/**
* Annotations for cloud-fabric.
*
* @since 1.5.0
*/
package cloud.commandframework.fabric.annotations;

View file

@ -0,0 +1,42 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.annotations.specifier;
import cloud.commandframework.fabric.argument.server.Vec3Argument;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used to enable coordinate centering for {@link Vec3Argument}.
*
* @since 1.5.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Center {
}

View file

@ -0,0 +1,30 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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.
//
/**
* Specifier annotations for cloud-fabric.
*
* @since 1.5.0
*/
package cloud.commandframework.fabric.annotations.specifier;

View file

@ -26,8 +26,11 @@ package cloud.commandframework.fabric.argument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserParameter;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import cloud.commandframework.fabric.data.Coordinates;
import cloud.commandframework.fabric.data.Message;
import cloud.commandframework.fabric.data.MinecraftTime;
import cloud.commandframework.fabric.data.MultipleEntitySelector;
@ -38,19 +41,28 @@ import cloud.commandframework.fabric.internal.EntitySelectorAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentTypeMessageFormatAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentTypeMessageSelectorAccess;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.leangen.geantyref.TypeToken;
import net.minecraft.command.CommandSource;
import net.minecraft.command.EntitySelector;
import net.minecraft.command.argument.BlockPosArgumentType;
import net.minecraft.command.argument.ColumnPosArgumentType;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.MessageArgumentType;
import net.minecraft.command.argument.PosArgument;
import net.minecraft.command.argument.TimeArgumentType;
import net.minecraft.command.argument.Vec2ArgumentType;
import net.minecraft.command.argument.Vec3ArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.function.Function;
/**
* Parsers for Vanilla command argument types.
@ -74,6 +86,67 @@ public final class FabricArgumentParsers {
.map((ctx, val) -> ArgumentParseResult.success(MinecraftTime.of(val)));
}
/**
* A parser for block coordinates.
*
* @param <C> sender type
* @return a parser instance
*/
public static <C> @NonNull ArgumentParser<C, Coordinates.BlockCoordinates> blockPos() {
return new WrappedBrigadierParser<C, PosArgument>(BlockPosArgumentType.blockPos())
.map(FabricArgumentParsers::mapToCoordinates);
}
/**
* A parser for column coordinates.
*
* @param <C> sender type
* @return a parser instance
*/
public static <C> @NonNull ArgumentParser<C, Coordinates.ColumnCoordinates> columnPos() {
return new WrappedBrigadierParser<C, PosArgument>(ColumnPosArgumentType.columnPos())
.map(FabricArgumentParsers::mapToCoordinates);
}
/**
* A parser for coordinates, relative or absolute, from 2 doubles for x and z,
* with y always defaulting to 0.
*
* @param centerIntegers whether to center integers at x.5
* @param <C> sender type
* @return a parser instance
*/
public static <C> @NonNull ArgumentParser<C, Coordinates.CoordinatesXZ> vec2(final boolean centerIntegers) {
return new WrappedBrigadierParser<C, PosArgument>(new Vec2ArgumentType(centerIntegers))
.map(FabricArgumentParsers::mapToCoordinates);
}
/**
* A parser for coordinates, relative or absolute, from 3 doubles.
*
* @param centerIntegers whether to center integers at x.5
* @param <C> sender type
* @return a parser instance
*/
public static <C> @NonNull ArgumentParser<C, Coordinates> vec3(final boolean centerIntegers) {
return new WrappedBrigadierParser<C, PosArgument>(Vec3ArgumentType.vec3(centerIntegers))
.map(FabricArgumentParsers::mapToCoordinates);
}
@SuppressWarnings("unchecked")
private static <C, O extends Coordinates> @NonNull ArgumentParseResult<@NonNull O> mapToCoordinates(
final @NonNull CommandContext<C> ctx,
final @NonNull PosArgument posArgument
) {
return requireServerCommandSource(
ctx,
serverCommandSource -> ArgumentParseResult.success((O) new CoordinatesImpl(
serverCommandSource,
posArgument
))
);
}
/**
* A parser for {@link SinglePlayerSelector}.
*
@ -83,21 +156,16 @@ public final class FabricArgumentParsers {
*/
public static <C> @NonNull ArgumentParser<C, SinglePlayerSelector> singlePlayerSelector() {
return new WrappedBrigadierParser<C, EntitySelector>(EntityArgumentType.player())
.map((ctx, entitySelector) -> {
final CommandSource either = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(either instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
try {
return ArgumentParseResult.success(new SinglePlayerSelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getPlayer((ServerCommandSource) either)
));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
});
.map((ctx, entitySelector) -> requireServerCommandSource(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new SinglePlayerSelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getPlayer(serverCommandSource)
))
)
));
}
/**
@ -109,21 +177,16 @@ public final class FabricArgumentParsers {
*/
public static <C> @NonNull ArgumentParser<C, MultiplePlayerSelector> multiplePlayerSelector() {
return new WrappedBrigadierParser<C, EntitySelector>(EntityArgumentType.players())
.map((ctx, entitySelector) -> {
final CommandSource either = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(either instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
try {
return ArgumentParseResult.success(new MultiplePlayerSelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getPlayers((ServerCommandSource) either)
));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
});
.map((ctx, entitySelector) -> requireServerCommandSource(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new MultiplePlayerSelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getPlayers(serverCommandSource)
))
)
));
}
/**
@ -135,21 +198,16 @@ public final class FabricArgumentParsers {
*/
public static <C> @NonNull ArgumentParser<C, SingleEntitySelector> singleEntitySelector() {
return new WrappedBrigadierParser<C, EntitySelector>(EntityArgumentType.entity())
.map((ctx, entitySelector) -> {
final CommandSource either = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(either instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
try {
return ArgumentParseResult.success(new SingleEntitySelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getEntity((ServerCommandSource) either)
));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
});
.map((ctx, entitySelector) -> requireServerCommandSource(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new SingleEntitySelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.getEntity(serverCommandSource)
))
)
));
}
/**
@ -161,21 +219,16 @@ public final class FabricArgumentParsers {
*/
public static <C> @NonNull ArgumentParser<C, MultipleEntitySelector> multipleEntitySelector() {
return new WrappedBrigadierParser<C, EntitySelector>(EntityArgumentType.entities())
.map((ctx, entitySelector) -> {
final CommandSource either = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(either instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
try {
return ArgumentParseResult.success(new MultipleEntitySelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
Collections.unmodifiableCollection(entitySelector.getEntities((ServerCommandSource) either))
));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
});
.map((ctx, entitySelector) -> requireServerCommandSource(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new MultipleEntitySelector(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
Collections.unmodifiableCollection(entitySelector.getEntities(serverCommandSource))
))
)
));
}
/**
@ -187,28 +240,75 @@ public final class FabricArgumentParsers {
*/
public static <C> @NonNull ArgumentParser<C, Message> message() {
return new WrappedBrigadierParser<C, MessageArgumentType.MessageFormat>(MessageArgumentType.message())
.map((ctx, format) -> {
final CommandSource either = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(either instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
try {
return ArgumentParseResult.success(MessageImpl.from(
(ServerCommandSource) either,
format,
true
));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
});
.map((ctx, format) -> requireServerCommandSource(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(MessageImpl.from(
serverCommandSource,
format,
true
))
)
));
}
@FunctionalInterface
private interface CommandSyntaxExceptionThrowingParseResultSupplier<O> {
@NonNull ArgumentParseResult<O> result() throws CommandSyntaxException;
}
private static <O> @NonNull ArgumentParseResult<O> handleCommandSyntaxExceptionAsFailure(
final @NonNull CommandSyntaxExceptionThrowingParseResultSupplier<O> resultSupplier
) {
try {
return resultSupplier.result();
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
}
}
private static @NonNull IllegalStateException serverOnly() {
return new IllegalStateException("This command argument type is server-only.");
}
private static <C, O> @NonNull ArgumentParseResult<O> requireServerCommandSource(
final @NonNull CommandContext<C> context,
final @NonNull Function<ServerCommandSource, ArgumentParseResult<O>> resultFunction
) {
final CommandSource nativeSource = context.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
if (!(nativeSource instanceof ServerCommandSource)) {
return ArgumentParseResult.failure(serverOnly());
}
return resultFunction.apply((ServerCommandSource) nativeSource);
}
/**
* {@link ParserParameter} keys for cloud-fabric.
*
* @since 1.5.0
*/
public static final class FabricParserParameters {
/**
* Indicates that positions should be centered on the middle of blocks, i.e. x.5.
*
* @since 1.5.0
*/
public static final ParserParameter<Boolean> CENTER_INTEGERS = create("center_integers", TypeToken.get(Boolean.class));
private static <T> @NonNull ParserParameter<T> create(
final @NonNull String key,
final @NonNull TypeToken<T> expectedType
) {
return new ParserParameter<>(key, expectedType);
}
}
static final class MessageImpl implements Message {
private final Collection<Entity> mentionedEntities;
private final Text contents;
@ -217,7 +317,7 @@ public final class FabricArgumentParsers {
final MessageArgumentType.@NonNull MessageFormat message,
final boolean useSelectors
) throws CommandSyntaxException {
final Text contents = message.format(source, useSelectors);
final Text contents = message.format(source, useSelectors);
final MessageArgumentType.MessageSelector[] selectors =
((MessageArgumentTypeMessageFormatAccess) message).accessor$selectors();
final Collection<Entity> entities;
@ -226,7 +326,9 @@ public final class FabricArgumentParsers {
} else {
entities = new HashSet<>();
for (final MessageArgumentType.MessageSelector selector : selectors) {
entities.addAll(((MessageArgumentTypeMessageSelectorAccess) selector).accessor$selector().getEntities(source));
entities.addAll(((MessageArgumentTypeMessageSelectorAccess) selector)
.accessor$selector()
.getEntities(source));
}
}
@ -250,4 +352,49 @@ public final class FabricArgumentParsers {
}
static final class CoordinatesImpl implements Coordinates,
Coordinates.CoordinatesXZ,
Coordinates.BlockCoordinates,
Coordinates.ColumnCoordinates {
private final ServerCommandSource source;
private final PosArgument posArgument;
private CoordinatesImpl(final @NonNull ServerCommandSource source, final @NonNull PosArgument posArgument) {
this.source = source;
this.posArgument = posArgument;
}
@Override
public @NonNull Vec3d position() {
return this.posArgument.toAbsolutePos(this.source);
}
@Override
public @NonNull BlockPos blockPos() {
return new BlockPos(this.position());
}
@Override
public boolean isXRelative() {
return this.posArgument.isXRelative();
}
@Override
public boolean isYRelative() {
return this.posArgument.isYRelative();
}
@Override
public boolean isZRelative() {
return this.posArgument.isZRelative();
}
@Override
public @NonNull PosArgument getWrappedCoordinates() {
return this.posArgument;
}
}
}

View file

@ -0,0 +1,151 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.argument.server;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.data.Coordinates.BlockCoordinates;
import net.minecraft.util.math.BlockPos;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.function.BiFunction;
/**
* An argument for resolving block coordinates.
*
* @param <C> the sender type
* @since 1.5.0
*/
public final class BlockPosArgument<C> extends CommandArgument<C, BlockCoordinates> {
BlockPosArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
super(
required,
name,
FabricArgumentParsers.blockPos(),
defaultValue,
BlockCoordinates.class,
suggestionsProvider
);
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> BlockPosArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new BlockPosArgument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull BlockPosArgument<C> of(final @NonNull String name) {
return BlockPosArgument.<C>newBuilder(name).asRequired().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull BlockPosArgument<C> optional(final @NonNull String name) {
return BlockPosArgument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull BlockPosArgument<C> optional(final @NonNull String name, final @NonNull BlockPos defaultValue) {
return BlockPosArgument.<C>newBuilder(name)
.asOptionalWithDefault(serializeBlockPos(defaultValue))
.build();
}
private static @NonNull String serializeBlockPos(final @NonNull BlockPos pos) {
return String.format(
"%s %s %s",
pos.getX(),
pos.getY(),
pos.getZ()
);
}
/**
* Builder for {@link BlockPosArgument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, BlockCoordinates, Builder<C>> {
Builder(final @NonNull String name) {
super(BlockCoordinates.class, name);
}
/**
* Build a block position argument.
*
* @return Constructed argument
* @since 1.5.0
*/
@Override
public @NonNull BlockPosArgument<C> build() {
return new BlockPosArgument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider()
);
}
}
}

View file

@ -0,0 +1,150 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.argument.server;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.data.Coordinates.ColumnCoordinates;
import net.minecraft.util.math.BlockPos;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.function.BiFunction;
/**
* An argument for resolving column (xz) coordinates.
*
* @param <C> the sender type
* @since 1.5.0
*/
public final class ColumnPosArgument<C> extends CommandArgument<C, ColumnCoordinates> {
ColumnPosArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
super(
required,
name,
FabricArgumentParsers.columnPos(),
defaultValue,
ColumnCoordinates.class,
suggestionsProvider
);
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> ColumnPosArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new ColumnPosArgument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull ColumnPosArgument<C> of(final @NonNull String name) {
return ColumnPosArgument.<C>newBuilder(name).asRequired().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull ColumnPosArgument<C> optional(final @NonNull String name) {
return ColumnPosArgument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value, y coordinate will be ignored as it is always 0
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull ColumnPosArgument<C> optional(final @NonNull String name, final @NonNull BlockPos defaultValue) {
return ColumnPosArgument.<C>newBuilder(name)
.asOptionalWithDefault(serializeColumnPos(defaultValue))
.build();
}
private static @NonNull String serializeColumnPos(final @NonNull BlockPos pos) {
return String.format(
"%s %s",
pos.getX(),
pos.getZ()
);
}
/**
* Builder for {@link ColumnPosArgument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, ColumnCoordinates, Builder<C>> {
Builder(final @NonNull String name) {
super(ColumnCoordinates.class, name);
}
/**
* Build a column position argument.
*
* @return Constructed argument
* @since 1.5.0
*/
@Override
public @NonNull ColumnPosArgument<C> build() {
return new ColumnPosArgument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider()
);
}
}
}

View file

@ -0,0 +1,236 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.argument.server;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.data.Coordinates;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.function.BiFunction;
/**
* An argument for resolving x and z coordinates from 2 doubles.
*
* @param <C> the sender type
* @since 1.5.0
*/
public final class Vec2Argument<C> extends CommandArgument<C, Coordinates.CoordinatesXZ> {
private final boolean centerIntegers;
Vec2Argument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider,
final boolean centerIntegers
) {
super(
required,
name,
FabricArgumentParsers.vec2(centerIntegers),
defaultValue,
Coordinates.CoordinatesXZ.class,
suggestionsProvider
);
this.centerIntegers = centerIntegers;
}
/**
* Get whether integers will be centered to x.5. Defaults to false.
*
* @return whether integers will be centered
* @since 1.5.0
*/
public boolean centerIntegers() {
return this.centerIntegers;
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> Vec2Argument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new Vec2Argument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> of(final @NonNull String name) {
return Vec2Argument.<C>newBuilder(name).asRequired().build();
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> of(final @NonNull String name, final boolean centerIntegers) {
return Vec2Argument.<C>newBuilder(name).centerIntegers(centerIntegers).asRequired().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> optional(final @NonNull String name) {
return Vec2Argument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> optional(final @NonNull String name, final boolean centerIntegers) {
return Vec2Argument.<C>newBuilder(name).centerIntegers(centerIntegers).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value, y will be ignored as it is always 0
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> optional(final @NonNull String name, final @NonNull Vec3d defaultValue) {
return Vec2Argument.<C>newBuilder(name)
.asOptionalWithDefault(serializeXandZ(defaultValue))
.build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value, y will be ignored as it is always 0
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec2Argument<C> optional(
final @NonNull String name,
final boolean centerIntegers,
final @NonNull Vec3d defaultValue
) {
return Vec2Argument.<C>newBuilder(name)
.centerIntegers(centerIntegers)
.asOptionalWithDefault(serializeXandZ(defaultValue))
.build();
}
private static @NonNull String serializeXandZ(final @NonNull Vec3d vec3d) {
return String.format(
"%.10f %.10f",
vec3d.x,
vec3d.z
);
}
/**
* Builder for {@link Vec2Argument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, Coordinates.CoordinatesXZ, Builder<C>> {
private boolean centerIntegers = false;
Builder(final @NonNull String name) {
super(Coordinates.CoordinatesXZ.class, name);
}
/**
* Set whether integers will be centered to x.5. Will be false by default if unset.
*
* @param centerIntegers whether integers will be centered
* @return this builder
* @since 1.5.0
*/
public @NonNull Builder<C> centerIntegers(final boolean centerIntegers) {
this.centerIntegers = centerIntegers;
return this;
}
/**
* Get whether integers will be centered to x.5. Defaults to false.
*
* @return whether integers will be centered
* @since 1.5.0
*/
public boolean centerIntegers() {
return this.centerIntegers;
}
/**
* Build a vec2 argument.
*
* @return Constructed argument
* @since 1.5.0
*/
@Override
public @NonNull Vec2Argument<C> build() {
return new Vec2Argument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.centerIntegers()
);
}
}
}

View file

@ -0,0 +1,237 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.argument.server;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.data.Coordinates;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.function.BiFunction;
/**
* An argument for resolving coordinates from 3 doubles.
*
* @param <C> the sender type
* @since 1.5.0
*/
public final class Vec3Argument<C> extends CommandArgument<C, Coordinates> {
private final boolean centerIntegers;
Vec3Argument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider,
final boolean centerIntegers
) {
super(
required,
name,
FabricArgumentParsers.vec3(centerIntegers),
defaultValue,
Coordinates.class,
suggestionsProvider
);
this.centerIntegers = centerIntegers;
}
/**
* Get whether integers will be centered to x.5. Defaults to false.
*
* @return whether integers will be centered
* @since 1.5.0
*/
public boolean centerIntegers() {
return this.centerIntegers;
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> Vec3Argument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new Vec3Argument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> of(final @NonNull String name) {
return Vec3Argument.<C>newBuilder(name).asRequired().build();
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> of(final @NonNull String name, final boolean centerIntegers) {
return Vec3Argument.<C>newBuilder(name).centerIntegers(centerIntegers).asRequired().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> optional(final @NonNull String name) {
return Vec3Argument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> optional(final @NonNull String name, final boolean centerIntegers) {
return Vec3Argument.<C>newBuilder(name).centerIntegers(centerIntegers).asOptional().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> optional(final @NonNull String name, final @NonNull Vec3d defaultValue) {
return Vec3Argument.<C>newBuilder(name)
.asOptionalWithDefault(serializeVec3(defaultValue))
.build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param defaultValue default value
* @param centerIntegers whether to center integers to x.5.
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull Vec3Argument<C> optional(
final @NonNull String name,
final boolean centerIntegers,
final @NonNull Vec3d defaultValue
) {
return Vec3Argument.<C>newBuilder(name)
.centerIntegers(centerIntegers)
.asOptionalWithDefault(serializeVec3(defaultValue))
.build();
}
private static @NonNull String serializeVec3(final @NonNull Vec3d vec3d) {
return String.format(
"%.10f %.10f %.10f",
vec3d.x,
vec3d.y,
vec3d.z
);
}
/**
* Builder for {@link Vec3Argument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, Coordinates, Builder<C>> {
private boolean centerIntegers = false;
Builder(final @NonNull String name) {
super(Coordinates.class, name);
}
/**
* Set whether integers will be centered to x.5. Will be false by default if unset.
*
* @param centerIntegers whether integers will be centered
* @return this builder
* @since 1.5.0
*/
public @NonNull Builder<C> centerIntegers(final boolean centerIntegers) {
this.centerIntegers = centerIntegers;
return this;
}
/**
* Get whether integers will be centered to x.5. Defaults to false.
*
* @return whether integers will be centered
* @since 1.5.0
*/
public boolean centerIntegers() {
return this.centerIntegers;
}
/**
* Build a vec3 argument.
*
* @return Constructed argument
* @since 1.5.0
*/
@Override
public @NonNull Vec3Argument<C> build() {
return new Vec3Argument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.centerIntegers()
);
}
}
}

View file

@ -0,0 +1,117 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.fabric.data;
import net.minecraft.command.argument.BlockPosArgumentType;
import net.minecraft.command.argument.ColumnPosArgumentType;
import net.minecraft.command.argument.PosArgument;
import net.minecraft.command.argument.Vec2ArgumentType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A {@link PosArgument} wrapper for easier use with cloud commands.
*
* @since 1.5.0
*/
public interface Coordinates {
/**
* Resolve a position from the parsed coordinates.
*
* @return position
* @since 1.5.0
*/
@NonNull Vec3d position();
/**
* Resolve a block position from the parsed coordinates.
*
* @return block position
* @since 1.5.0
*/
@NonNull BlockPos blockPos();
/**
* Get whether the x coordinate is relative.
*
* @return whether the x coordinate is relative
* @since 1.5.0
*/
boolean isXRelative();
/**
* Get whether the y coordinate is relative.
*
* @return whether the y coordinate is relative
* @since 1.5.0
*/
boolean isYRelative();
/**
* Get whether the z coordinate is relative.
*
* @return whether the z coordinate is relative
* @since 1.5.0
*/
boolean isZRelative();
/**
* Get the coordinates wrapped by this {@link Coordinates}.
*
* @return the base coordinates
* @since 1.5.0
*/
@NonNull PosArgument getWrappedCoordinates();
/**
* A specialized version of {@link Coordinates} for representing the result of the vanilla {@link Vec2ArgumentType},
* which accepts two doubles for the x and z coordinate, always defaulting to 0 for the y coordinate.
*
* @since 1.5.0
*/
interface CoordinatesXZ extends Coordinates {
}
/**
* A specialized version of {@link Coordinates} for representing the result of the vanilla {@link BlockPosArgumentType}.
*
* @since 1.5.0
*/
interface BlockCoordinates extends Coordinates {
}
/**
* A specialized version of {@link Coordinates} for representing the result of the vanilla {@link ColumnPosArgumentType}.
*
* @since 1.5.0
*/
interface ColumnCoordinates extends Coordinates {
}
}

View file

@ -33,9 +33,16 @@ import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.FabricServerCommandManager;
import cloud.commandframework.fabric.argument.ColorArgument;
import cloud.commandframework.fabric.argument.ItemDataArgument;
import cloud.commandframework.fabric.argument.server.ColumnPosArgument;
import cloud.commandframework.fabric.argument.server.MultipleEntitySelectorArgument;
import cloud.commandframework.fabric.argument.server.MultiplePlayerSelectorArgument;
import cloud.commandframework.fabric.argument.server.Vec3Argument;
import cloud.commandframework.fabric.data.Coordinates;
import cloud.commandframework.fabric.data.Coordinates.ColumnCoordinates;
import cloud.commandframework.fabric.data.MultipleEntitySelector;
import cloud.commandframework.fabric.data.MultiplePlayerSelector;
import cloud.commandframework.fabric.testmod.mixin.GiveCommandAccess;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
@ -52,6 +59,8 @@ import net.minecraft.text.MutableText;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Vec3d;
import java.util.Collection;
import java.util.Comparator;
@ -217,6 +226,33 @@ public final class FabricExample implements ModInitializer {
false
);
}));
manager.command(base.literal("teleport")
.permission("cloud.teleport")
.argument(MultipleEntitySelectorArgument.of("targets"))
.argument(Vec3Argument.of("location"))
.handler(ctx -> {
final MultipleEntitySelector selector = ctx.get("targets");
final Vec3d location = ctx.<Coordinates>get("location").position();
selector.get().forEach(target ->
target.requestTeleport(location.getX(), location.getY(), location.getZ()));
}));
manager.command(base.literal("gotochunk")
.permission("cloud.gotochunk")
.argument(ColumnPosArgument.of("chunk_position"))
.handler(ctx -> {
final ServerPlayerEntity player;
try {
player = ctx.getSender().getPlayer();
} catch (final CommandSyntaxException e) {
ctx.getSender().sendFeedback(new LiteralText("Must be a player to use this command"), false);
return;
}
final Vec3d vec = ctx.<ColumnCoordinates>get("chunk_position").position();
final ChunkPos pos = new ChunkPos((int) vec.getX(), (int) vec.getZ());
player.requestTeleport(pos.getStartX(), 128, pos.getStartZ());
}));
}
}