fabric: Support 1.19 (#356)

Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
This commit is contained in:
zml 2022-06-08 14:42:44 -07:00 committed by Jason
parent 28ff5d3003
commit 63f2c9299f
21 changed files with 370 additions and 199 deletions

View file

@ -38,6 +38,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.NonNull;
import static java.util.Objects.requireNonNull;
@ -53,7 +54,7 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
public static final String COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER = "_cloud_brigadier_native_sender";
private final ArgumentType<T> nativeType;
private final Supplier<ArgumentType<T>> nativeType;
private final int expectedArgumentCount;
/**
@ -63,6 +64,16 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
* @since 1.5.0
*/
public WrappedBrigadierParser(final ArgumentType<T> nativeType) {
this(() -> nativeType, DEFAULT_ARGUMENT_COUNT);
}
/**
* Create an argument parser based on a brigadier command.
*
* @param nativeType the native command type, computed lazily
* @since 1.7.0
*/
public WrappedBrigadierParser(final Supplier<ArgumentType<T>> nativeType) {
this(nativeType, DEFAULT_ARGUMENT_COUNT);
}
@ -74,10 +85,25 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
* @since 1.5.0
*/
public WrappedBrigadierParser(
final ArgumentType<T> nativeType,
final ArgumentType<T> nativeType,
final int expectedArgumentCount
) {
this(() -> nativeType, expectedArgumentCount);
}
/**
* Create an argument parser based on a brigadier command.
*
* @param nativeType the native command type provider, calculated lazily
* @param expectedArgumentCount the number of arguments the brigadier type is expected to consume
* @since 1.7.0
*/
public WrappedBrigadierParser(
final Supplier<ArgumentType<T>> nativeType,
final int expectedArgumentCount
) {
this.nativeType = requireNonNull(nativeType, "brigadierType");
requireNonNull(nativeType, "brigadierType");
this.nativeType = nativeType;
this.expectedArgumentCount = expectedArgumentCount;
}
@ -88,7 +114,7 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
* @since 1.5.0
*/
public ArgumentType<T> getNativeArgument() {
return this.nativeType;
return this.nativeType.get();
}
@Override
@ -109,7 +135,7 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
// Then try to parse
try {
return ArgumentParseResult.success(this.nativeType.parse(reader));
return ArgumentParseResult.success(this.nativeType.get().parse(reader));
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex);
} finally {
@ -142,7 +168,7 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
false
);
final CompletableFuture<Suggestions> result = this.nativeType.listSuggestions(
final CompletableFuture<Suggestions> result = this.nativeType.get().listSuggestions(
reverseMappedContext,
new SuggestionsBuilder(input, 0)
);

View file

@ -2,7 +2,7 @@ import net.fabricmc.loom.task.AbstractRunTask
import net.ltgt.gradle.errorprone.errorprone
plugins {
id("quiet-fabric-loom") version "0.11-SNAPSHOT"
id("quiet-fabric-loom") version "0.12-SNAPSHOT"
id("cloud.base-conventions")
}
@ -44,7 +44,8 @@ dependencies {
minecraft(libs.fabricMinecraft)
mappings(loom.officialMojangMappings())
modImplementation(libs.fabricLoader)
modImplementation(fabricApi.module("fabric-command-api-v1", libs.versions.fabricApi.get()))
modImplementation(fabricApi.module("fabric-command-api-v2", libs.versions.fabricApi.get()))
modImplementation(fabricApi.module("fabric-networking-api-v1", libs.versions.fabricApi.get()))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", libs.versions.fabricApi.get()))
modImplementation(libs.fabricPermissionsApi)

View file

@ -28,8 +28,8 @@ import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.permission.PredicatePermission;
import java.util.function.Function;
import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientSuggestionProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -165,7 +165,8 @@ public final class FabricClientCommandManager<C> extends FabricCommandManager<C,
if (!Minecraft.getInstance().hasSingleplayerServer()) {
return allowOnMultiplayer;
}
return Minecraft.getInstance().getSingleplayerServer().getPlayerList().isAllowCheatsForAllPlayers();
return Minecraft.getInstance().getSingleplayerServer().getPlayerList().isAllowCheatsForAllPlayers()
|| Minecraft.getInstance().getSingleplayerServer().getWorldData().getAllowCommands();
};
}
@ -198,7 +199,8 @@ public final class FabricClientCommandManager<C> extends FabricCommandManager<C,
if (!Minecraft.getInstance().hasSingleplayerServer()) {
return allowOnMultiplayer;
}
return !Minecraft.getInstance().getSingleplayerServer().getPlayerList().isAllowCheatsForAllPlayers();
return !Minecraft.getInstance().getSingleplayerServer().getPlayerList().isAllowCheatsForAllPlayers()
&& !Minecraft.getInstance().getSingleplayerServer().getWorldData().getAllowCommands();
};
}

View file

@ -40,7 +40,6 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import cloud.commandframework.permission.PredicatePermission;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.serialization.Codec;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeToken;
@ -56,16 +55,13 @@ import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.AngleArgument;
import net.minecraft.commands.arguments.ColorArgument;
import net.minecraft.commands.arguments.CompoundTagArgument;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.commands.arguments.EntitySummonArgument;
import net.minecraft.commands.arguments.ItemEnchantmentArgument;
import net.minecraft.commands.arguments.MessageArgument;
import net.minecraft.commands.arguments.MobEffectArgument;
import net.minecraft.commands.arguments.NbtPathArgument;
import net.minecraft.commands.arguments.NbtTagArgument;
import net.minecraft.commands.arguments.ObjectiveCriteriaArgument;
@ -73,12 +69,12 @@ import net.minecraft.commands.arguments.OperationArgument;
import net.minecraft.commands.arguments.ParticleArgument;
import net.minecraft.commands.arguments.RangeArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraft.commands.arguments.ResourceOrTagLocationArgument;
import net.minecraft.commands.arguments.UuidArgument;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.coordinates.SwizzleArgument;
import net.minecraft.commands.arguments.item.ItemArgument;
import net.minecraft.commands.arguments.item.ItemInput;
import net.minecraft.commands.synchronization.SuggestionProviders;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleOptions;
@ -180,64 +176,26 @@ public abstract class FabricCommandManager<C, S extends SharedSuggestionProvider
this.registerConstantNativeParserSupplier(OperationArgument.Operation.class, OperationArgument.operation());
this.registerConstantNativeParserSupplier(ParticleOptions.class, ParticleArgument.particle());
this.registerConstantNativeParserSupplier(AngleArgument.SingleAngle.class, AngleArgument.angle());
this.registerConstantNativeParserSupplier(new TypeToken<EnumSet<Direction.Axis>>() {
}, SwizzleArgument.swizzle());
this.registerConstantNativeParserSupplier(new TypeToken<EnumSet<Direction.Axis>>() {}, SwizzleArgument.swizzle());
this.registerConstantNativeParserSupplier(ResourceLocation.class, ResourceLocationArgument.id());
this.registerConstantNativeParserSupplier(
EntityAnchorArgument.Anchor.class,
EntityAnchorArgument.anchor()
);
this.registerConstantNativeParserSupplier(EntityAnchorArgument.Anchor.class, EntityAnchorArgument.anchor());
this.registerConstantNativeParserSupplier(MinMaxBounds.Ints.class, RangeArgument.intRange());
this.registerConstantNativeParserSupplier(MinMaxBounds.Floats.class, RangeArgument.floatRange());
this.registerConstantNativeParserSupplier(ItemInput.class, ItemArgument.item());
this.registerConstantNativeParserSupplier(MinMaxBounds.Doubles.class, RangeArgument.floatRange());
this.registerContextualNativeParserSupplier(ItemInput.class, ItemArgument::item);
this.registerContextualNativeParserSupplier(BlockPredicateArgument.Result.class, BlockPredicateArgument::blockPredicate);
/* Wrapped/Constant Brigadier types, mapped value type */
this.registerConstantNativeParserSupplier(
BlockPredicateArgument.Result.class,
BlockPredicateArgument.blockPredicate()
);
this.registerConstantNativeParserSupplier(MessageArgument.Message.class, MessageArgument.message());
this.getParserRegistry().registerParserSupplier(
TypeToken.get(MinecraftTime.class),
params -> FabricArgumentParsers.time()
);
this.getParserRegistry().registerParserSupplier(TypeToken.get(MinecraftTime.class), params -> FabricArgumentParsers.time());
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void registerRegistryEntryMappings() {
this.brigadierManager.registerMapping(
new TypeToken<RegistryEntryArgument.Parser<C, ?>>() {
},
builder -> builder.to(argument -> {
/* several registries have specialized argument types, so let's use those where possible */
final ResourceKey<? extends Registry<?>> registry = argument.registryKey();
if (registry.equals(Registry.ENTITY_TYPE_REGISTRY)) {
return EntitySummonArgument.id();
} else if (registry.equals(Registry.ENCHANTMENT_REGISTRY)) {
return ItemEnchantmentArgument.enchantment();
} else if (registry.equals(Registry.MOB_EFFECT_REGISTRY)) {
return MobEffectArgument.effect();
} else if (registry.equals(Registry.DIMENSION_REGISTRY)) {
return DimensionArgument.dimension();
}
return ResourceLocationArgument.id();
}
).suggestedBy((argument, useCloud) -> {
/* A few other registries have client-side suggestion providers but no argument type */
/* Type parameters are messed up here for some reason */
final ResourceKey<? extends Registry<?>> registry = argument.registryKey();
if (registry.equals(Registry.SOUND_EVENT_REGISTRY)) {
return (SuggestionProvider<S>) SuggestionProviders.AVAILABLE_SOUNDS;
} else if (registry.equals(Registry.BIOME_REGISTRY)) {
return (SuggestionProvider<S>) SuggestionProviders.AVAILABLE_BIOMES;
} else if (registry.equals(Registry.ENTITY_TYPE_REGISTRY)
|| registry.equals(Registry.ENCHANTMENT_REGISTRY)
|| registry.equals(Registry.MOB_EFFECT_REGISTRY)
|| registry.equals(Registry.DIMENSION_REGISTRY)) {
return null; /* for types with their own argument type, use Brigadier */
}
return useCloud; /* use cloud suggestions for anything else */
})
new TypeToken<RegistryEntryArgument.Parser<C, ?>>() {},
builder -> {
builder.to(argument -> ResourceOrTagLocationArgument.<Object>resourceOrTag((ResourceKey) argument.registryKey()));
}
);
/* Find all fields of RegistryKey<? extends Registry<?>> and register those */
@ -292,6 +250,21 @@ public abstract class FabricCommandManager<C, S extends SharedSuggestionProvider
}
}
/**
* Register a parser supplier for a brigadier type that has no options and whose output can be directly used.
*
* @param type the Java type to map
* @param argument a function providing the Brigadier parser given a build context
* @param <T> value type
* @since 1.7.0
*/
final <T> void registerContextualNativeParserSupplier(
final @NonNull Class<T> type,
final @NonNull Function<CommandBuildContext, @NonNull ArgumentType<T>> argument
) {
this.getParserRegistry().registerParserSupplier(TypeToken.get(type), params -> FabricArgumentParsers.contextual(argument));
}
/**
* Register a parser supplier for a brigadier type that has no options and whose output can be directly used.
*

View file

@ -25,6 +25,7 @@ package cloud.commandframework.fabric;
import cloud.commandframework.Command;
import cloud.commandframework.arguments.StaticArgument;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.internal.CommandRegistrationHandler;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@ -33,11 +34,16 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands.CommandSelection;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -51,6 +57,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* @param <S> native sender type
*/
abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionProvider> implements CommandRegistrationHandler {
private @MonotonicNonNull FabricCommandManager<C, S> commandManager;
void initialize(final FabricCommandManager<C, S> manager) {
@ -71,7 +78,7 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
*
* @param alias the command alias
* @param destination the destination node
* @param <S> brig sender type
* @param <S> brig sender type
* @return the built node
*/
private static <S> LiteralCommandNode<S> buildRedirect(
@ -81,7 +88,7 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
// Redirects only work for nodes with children, but break the top argument-less command.
// Manually adding the root command after setting the redirect doesn't fix it.
// (See https://github.com/Mojang/brigadier/issues/46) Manually clone the node instead.
LiteralArgumentBuilder<S> builder = LiteralArgumentBuilder
final LiteralArgumentBuilder<S> builder = LiteralArgumentBuilder
.<S>literal(alias)
.requires(destination.getRequirement())
.forward(
@ -97,19 +104,65 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
}
static class Client<C> extends FabricCommandRegistrationHandler<C, FabricClientCommandSource> {
private final Set<Command<C>> registeredCommands = ConcurrentHashMap.newKeySet();
private volatile boolean registerEventFired = false;
@Override
void initialize(final FabricCommandManager<C, FabricClientCommandSource> manager) {
super.initialize(manager);
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands);
ClientPlayConnectionEvents.DISCONNECT.register(($, $$) -> this.registerEventFired = false);
}
@Override
@SuppressWarnings("unchecked")
public boolean registerCommand(final @NonNull Command<?> cmd) {
final Command<C> command = (Command<C>) cmd;
final RootCommandNode<FabricClientCommandSource> rootNode = ClientCommandManager.DISPATCHER.getRoot();
public boolean registerCommand(final @NonNull Command<?> command) {
this.registeredCommands.add((Command<C>) command);
if (this.registerEventFired) {
final ClientPacketListener connection = Minecraft.getInstance().getConnection();
if (connection == null) {
throw new IllegalStateException("Expected connection to be present but it wasn't!");
}
final CommandDispatcher<FabricClientCommandSource> dispatcher = ClientCommandManager.getActiveDispatcher();
if (dispatcher == null) {
throw new IllegalStateException("Expected an active dispatcher!");
}
FabricArgumentParsers.ContextualArgumentTypeProvider.withBuildContext(
this.commandManager(),
new CommandBuildContext(connection.registryAccess()),
false,
() -> this.registerClientCommand(dispatcher, (Command<C>) command)
);
}
return true;
}
public void registerCommands(
final CommandDispatcher<FabricClientCommandSource> dispatcher,
final CommandBuildContext commandBuildContext
) {
this.registerEventFired = true;
FabricArgumentParsers.ContextualArgumentTypeProvider.withBuildContext(
this.commandManager(),
commandBuildContext,
true,
() -> {
for (final Command<C> command : this.registeredCommands) {
this.registerClientCommand(dispatcher, command);
}
}
);
}
@SuppressWarnings("unchecked")
private void registerClientCommand(
final CommandDispatcher<FabricClientCommandSource> dispatcher,
final Command<C> command
) {
final RootCommandNode<FabricClientCommandSource> rootNode = dispatcher.getRoot();
final StaticArgument<C> first = ((StaticArgument<C>) command.getArguments().get(0));
final CommandNode<FabricClientCommandSource> baseNode = this
.commandManager()
final CommandNode<FabricClientCommandSource> baseNode = this.commandManager()
.brigadierManager()
.createLiteralCommandNode(
first.getName(),
@ -131,11 +184,11 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
for (final String alias : first.getAlternativeAliases()) {
rootNode.addChild(buildRedirect(alias, baseNode));
}
return true;
}
}
static class Server<C> extends FabricCommandRegistrationHandler<C, CommandSourceStack> {
private final Set<Command<C>> registeredCommands = ConcurrentHashMap.newKeySet();
@Override
@ -150,21 +203,32 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
return this.registeredCommands.add((Command<C>) command);
}
private void registerAllCommands(final CommandDispatcher<CommandSourceStack> dispatcher, final boolean isDedicated) {
private void registerAllCommands(
final CommandDispatcher<CommandSourceStack> dispatcher,
final CommandBuildContext access,
final Commands.CommandSelection side
) {
this.commandManager().registrationCalled();
for (final Command<C> command : this.registeredCommands) {
/* Only register commands in the declared environment */
final CommandSelection env = command.getCommandMeta().getOrDefault(
FabricServerCommandManager.META_REGISTRATION_ENVIRONMENT,
CommandSelection.ALL
);
FabricArgumentParsers.ContextualArgumentTypeProvider.withBuildContext(
this.commandManager(),
access,
true,
() -> {
for (final Command<C> command : this.registeredCommands) {
/* Only register commands in the declared environment */
final Commands.CommandSelection env = command.getCommandMeta().getOrDefault(
FabricServerCommandManager.META_REGISTRATION_ENVIRONMENT,
Commands.CommandSelection.ALL
);
if ((env == CommandSelection.INTEGRATED && isDedicated)
|| (env == CommandSelection.DEDICATED && !isDedicated)) {
continue;
if ((env == Commands.CommandSelection.INTEGRATED && !side.includeIntegrated)
|| (env == Commands.CommandSelection.DEDICATED && !side.includeDedicated)) {
continue;
}
this.registerCommand(dispatcher.getRoot(), command);
}
}
this.registerCommand(dispatcher.getRoot(), command);
}
);
}
private void registerCommand(final RootCommandNode<CommandSourceStack> dispatcher, final Command<C> command) {
@ -178,7 +242,8 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
perm
),
true,
new FabricExecutor<>(this.commandManager(), CommandSourceStack::getTextName, CommandSourceStack::sendFailure));
new FabricExecutor<>(this.commandManager(), CommandSourceStack::getTextName, CommandSourceStack::sendFailure)
);
dispatcher.addChild(baseNode);
@ -186,5 +251,7 @@ abstract class FabricCommandRegistrationHandler<C, S extends SharedSuggestionPro
dispatcher.addChild(buildRedirect(alias, baseNode));
}
}
}
}

View file

@ -32,6 +32,7 @@ import cloud.commandframework.exceptions.NoSuchCommandException;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.logging.LogUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CompletionException;
@ -44,16 +45,13 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextComponent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Command<S> {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static final Component NEWLINE = new TextComponent("\n");
private static final Component NEWLINE = Component.literal("\n");
private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command.";
private static final String MESSAGE_NO_PERMS =
"I'm sorry, but you do not have permission to perform this command. "
@ -79,6 +77,7 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
final S source = ctx.getSource();
final String input = ctx.getInput().substring(ctx.getLastChild().getNodes().get(0).getRange().getStart());
final C sender = this.manager.commandSourceMapper().apply(source);
this.manager.executeCommand(sender, input).whenComplete((result, throwable) -> {
if (throwable == null) {
return;
@ -99,8 +98,8 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
(InvalidSyntaxException) throwable,
(c, e) -> this.sendError.accept(
source,
new TextComponent("Invalid Command Syntax. Correct command syntax is: ")
.append(new TextComponent(String.format("/%s", e.getCorrectSyntax()))
Component.literal("Invalid Command Syntax. Correct command syntax is: ")
.append(Component.literal(String.format("/%s", e.getCorrectSyntax()))
.withStyle(style -> style.withColor(ChatFormatting.GRAY)))
)
);
@ -109,21 +108,21 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
sender,
InvalidCommandSenderException.class,
(InvalidCommandSenderException) throwable,
(c, e) -> this.sendError.accept(source, new TextComponent(throwable.getMessage()))
(c, e) -> this.sendError.accept(source, Component.literal(throwable.getMessage()))
);
} else if (throwable instanceof NoPermissionException) {
this.manager.handleException(
sender,
NoPermissionException.class,
(NoPermissionException) throwable,
(c, e) -> this.sendError.accept(source, new TextComponent(MESSAGE_NO_PERMS))
(c, e) -> this.sendError.accept(source, Component.literal(MESSAGE_NO_PERMS))
);
} else if (throwable instanceof NoSuchCommandException) {
this.manager.handleException(
sender,
NoSuchCommandException.class,
(NoSuchCommandException) throwable,
(c, e) -> this.sendError.accept(source, new TextComponent(MESSAGE_UNKNOWN_COMMAND))
(c, e) -> this.sendError.accept(source, Component.literal(MESSAGE_UNKNOWN_COMMAND))
);
} else if (throwable instanceof ArgumentParseException) {
this.manager.handleException(
@ -132,14 +131,14 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
(ArgumentParseException) throwable,
(c, e) -> {
if (throwable.getCause() instanceof CommandSyntaxException) {
this.sendError.accept(source, new TextComponent("Invalid Command Argument: ")
.append(new TextComponent("")
this.sendError.accept(source, Component.literal("Invalid Command Argument: ")
.append(Component.literal("")
.append(ComponentUtils
.fromMessage(((CommandSyntaxException) throwable.getCause()).getRawMessage()))
.withStyle(ChatFormatting.GRAY)));
} else {
this.sendError.accept(source, new TextComponent("Invalid Command Argument: ")
.append(new TextComponent(throwable.getCause().getMessage())
this.sendError.accept(source, Component.literal("Invalid Command Argument: ")
.append(Component.literal(throwable.getCause().getMessage())
.withStyle(ChatFormatting.GRAY)));
}
}
@ -151,7 +150,7 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
(CommandExecutionException) throwable,
(c, e) -> {
this.sendError.accept(source, this.decorateHoverStacktrace(
new TextComponent(MESSAGE_INTERNAL_ERROR),
Component.literal(MESSAGE_INTERNAL_ERROR),
throwable.getCause(),
sender
));
@ -164,7 +163,7 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
);
} else {
this.sendError.accept(source, this.decorateHoverStacktrace(
new TextComponent(MESSAGE_INTERNAL_ERROR),
Component.literal(MESSAGE_INTERNAL_ERROR),
throwable,
sender
));
@ -183,9 +182,9 @@ final class FabricExecutor<C, S extends SharedSuggestionProvider> implements Com
return input.withStyle(style -> style
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new TextComponent(stackTrace)
Component.literal(stackTrace)
.append(NEWLINE)
.append(new TextComponent(" Click to copy")
.append(Component.literal(" Click to copy")
.withStyle(s2 -> s2.withColor(ChatFormatting.GRAY).withItalic(true)))
))
.withClickEvent(new ClickEvent(

View file

@ -40,11 +40,11 @@ import cloud.commandframework.meta.CommandMeta;
import io.leangen.geantyref.TypeToken;
import java.util.function.Function;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -121,7 +121,7 @@ public final class FabricServerCommandManager<C> extends FabricCommandManager<C,
null,
4,
"",
TextComponent.EMPTY,
Component.empty(),
null,
null
)

View file

@ -28,6 +28,7 @@ import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import cloud.commandframework.fabric.FabricCommandManager;
import cloud.commandframework.fabric.data.Coordinates;
import cloud.commandframework.fabric.data.Message;
import cloud.commandframework.fabric.data.MinecraftTime;
@ -38,11 +39,17 @@ import cloud.commandframework.fabric.data.SinglePlayerSelector;
import cloud.commandframework.fabric.internal.EntitySelectorAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentMessageAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentPartAccess;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.EntityArgument;
@ -59,6 +66,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.ApiStatus;
/**
* Parsers for Vanilla command argument types.
@ -70,6 +78,17 @@ public final class FabricArgumentParsers {
private FabricArgumentParsers() {
}
/**
* A parser that wraps Brigadier argument types which need a {@link CommandBuildContext}
* @param <C> sender type
* @param <V> argument value type
* @param factory factory that creates these arguments
* @return the parser
*/
public static <C, V> @NonNull ArgumentParser<C, V> contextual(final @NonNull Function<CommandBuildContext, ArgumentType<V>> factory) {
return new WrappedBrigadierParser<>(new ContextualArgumentTypeProvider<>(factory));
}
/**
* A parser for in-game time, in ticks.
*
@ -505,4 +524,94 @@ public final class FabricArgumentParsers {
}
@ApiStatus.Internal
public static final class ContextualArgumentTypeProvider<V> implements Supplier<ArgumentType<V>> {
private static final ThreadLocal<ThreadLocalContext> CONTEXT = new ThreadLocal<>();
private static final Map<FabricCommandManager<?, ?>, Set<ContextualArgumentTypeProvider<?>>> INSTANCES =
new WeakHashMap<>();
private final Function<CommandBuildContext, ArgumentType<V>> provider;
private volatile ArgumentType<V> provided;
/**
* Temporarily expose a command build context to providers called from this thread.
*
* @param ctx the context
* @param commandManager command manager to use
* @param resetExisting whether to clear cached state from existing provider instances for this command type
* @param action an action to perform while the context is exposed
* @since 1.7.0
*/
public static void withBuildContext(
final FabricCommandManager<?, ?> commandManager,
final CommandBuildContext ctx,
final boolean resetExisting,
final Runnable action
) {
final ThreadLocalContext context = new ThreadLocalContext(commandManager, ctx);
CONTEXT.set(context);
try {
if (resetExisting) {
synchronized (INSTANCES) {
for (final ContextualArgumentTypeProvider<?> contextualArgumentTypeProvider : context.instances()) {
contextualArgumentTypeProvider.provided = null;
}
}
}
action.run();
} finally {
CONTEXT.remove();
}
}
private static final class ThreadLocalContext {
private final FabricCommandManager<?, ?> commandManager;
private final CommandBuildContext commandBuildContext;
private ThreadLocalContext(
final FabricCommandManager<?, ?> commandManager,
final CommandBuildContext commandBuildContext
) {
this.commandManager = commandManager;
this.commandBuildContext = commandBuildContext;
}
private Set<ContextualArgumentTypeProvider<?>> instances() {
return INSTANCES.computeIfAbsent(this.commandManager, $ -> Collections.newSetFromMap(new WeakHashMap<>()));
}
}
ContextualArgumentTypeProvider(final @NonNull Function<CommandBuildContext, ArgumentType<V>> provider) {
this.provider = provider;
}
@Override
public ArgumentType<V> get() {
final ThreadLocalContext ctx = CONTEXT.get();
if (ctx != null) {
synchronized (INSTANCES) {
ctx.instances().add(this);
}
}
ArgumentType<V> provided = this.provided;
if (provided == null) {
synchronized (this) {
if (this.provided == null) {
if (ctx == null) {
throw new IllegalStateException("No build context was available while trying to compute an argument type");
}
provided = this.provider.apply(ctx.commandBuildContext);
this.provided = provided;
}
}
}
return provided;
}
}
}

View file

@ -35,13 +35,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An argument parsing an unbounded {@link net.minecraft.advancements.critereon.MinMaxBounds.Floats float range}, in the form
* An argument parsing an unbounded {@link net.minecraft.advancements.critereon.MinMaxBounds.Doubles double range}, in the form
* {@code [min]..[max]}, where both lower and upper bounds are optional.
*
* @param <C> the sender type
* @since 1.5.0
*/
public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds.Floats> {
public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds.Doubles> {
FloatRangeArgument(
final boolean required,
@ -55,7 +55,7 @@ public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds
name,
new WrappedBrigadierParser<>(RangeArgument.floatRange()),
defaultValue,
MinMaxBounds.Floats.class,
MinMaxBounds.Doubles.class,
suggestionsProvider,
defaultDescription
);
@ -108,7 +108,7 @@ public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds
*/
public static <C> @NonNull FloatRangeArgument<C> optional(
final @NonNull String name,
final MinMaxBounds.@NonNull Floats defaultValue
final MinMaxBounds.@NonNull Doubles defaultValue
) {
return FloatRangeArgument.<C>builder(name).asOptionalWithDefault(defaultValue).build();
}
@ -120,10 +120,10 @@ public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, MinMaxBounds.Floats, Builder<C>> {
public static final class Builder<C> extends TypedBuilder<C, MinMaxBounds.Doubles, Builder<C>> {
Builder(final @NonNull String name) {
super(MinMaxBounds.Floats.class, name);
super(MinMaxBounds.Doubles.class, name);
}
/**
@ -151,7 +151,7 @@ public final class FloatRangeArgument<C> extends CommandArgument<C, MinMaxBounds
* @see CommandArgument.Builder#asOptionalWithDefault(String)
* @since 1.5.0
*/
public @NonNull Builder<C> asOptionalWithDefault(final MinMaxBounds.@NonNull Floats defaultValue) {
public @NonNull Builder<C> asOptionalWithDefault(final MinMaxBounds.@NonNull Doubles defaultValue) {
final StringBuilder value = new StringBuilder(6);
if (defaultValue.getMin() != null) {
value.append(defaultValue.getMin());

View file

@ -25,7 +25,6 @@ package cloud.commandframework.fabric.argument;
import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import java.util.List;
import java.util.function.BiFunction;
@ -54,7 +53,7 @@ public final class ItemInputArgument<C> extends CommandArgument<C, ItemInput> {
super(
required,
name,
new WrappedBrigadierParser<>(ItemArgument.item()),
FabricArgumentParsers.contextual(ItemArgument::item),
defaultValue,
ItemInput.class,
suggestionsProvider,

View file

@ -28,7 +28,7 @@ import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import java.util.Queue;
import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import org.checkerframework.checker.nullness.qual.NonNull;

View file

@ -36,7 +36,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction;
import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.scores.PlayerTeam;
import org.checkerframework.checker.nullness.qual.NonNull;

View file

@ -1,26 +1,3 @@
//
// 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.
//
/**
* Arguments for the Fabric environment.
*

View file

@ -25,9 +25,10 @@
"depends": {
"fabricloader": ">=0.7.4",
"fabric-command-api-v1": "*",
"fabric-command-api-v2": "*",
"fabric-networking-api-v1": "*",
"fabric-lifecycle-events-v1": "*",
"fabric-permissions-api-v0": "*",
"minecraft": ">=1.14"
"minecraft": ">1.18.2"
}
}

View file

@ -27,29 +27,32 @@ import cloud.commandframework.Command;
import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.FabricClientCommandManager;
import cloud.commandframework.fabric.argument.ItemInputArgument;
import cloud.commandframework.meta.CommandMeta;
import com.google.gson.JsonObject;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.realmsclient.RealmsMainScreen;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.GenericDirtMessageScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.synchronization.ArgumentTypes;
import net.minecraft.commands.arguments.item.ItemInput;
import net.minecraft.commands.synchronization.ArgumentUtils;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.realms.RealmsBridge;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
public final class FabricClientExample implements ClientModInitializer {
@ -68,8 +71,8 @@ public final class FabricClientExample implements ClientModInitializer {
"cloud-dump-" + Instant.now().toString().replace(':', '-') + ".json"
);
ctx.getSender().sendFeedback(
new TextComponent("Dumping command output to ")
.append(new TextComponent(target.toString())
Component.literal("Dumping command output to ")
.append(Component.literal(target.toString())
.withStyle(s -> s.withClickEvent(new ClickEvent(
ClickEvent.Action.OPEN_FILE,
target.toAbsolutePath().toString()
@ -80,11 +83,11 @@ public final class FabricClientExample implements ClientModInitializer {
final CommandDispatcher<SharedSuggestionProvider> dispatcher = Minecraft.getInstance()
.getConnection()
.getCommands();
final JsonObject object = ArgumentTypes.serializeNodeToJson(dispatcher, dispatcher.getRoot());
final JsonObject object = ArgumentUtils.serializeNodeToJson(dispatcher, dispatcher.getRoot());
json.setIndent(" ");
Streams.write(object, json);
} catch (final IOException ex) {
ctx.getSender().sendError(new TextComponent(
ctx.getSender().sendError(Component.literal(
"Unable to write file, see console for details: " + ex.getMessage()
));
}
@ -93,7 +96,7 @@ public final class FabricClientExample implements ClientModInitializer {
commandManager.command(base.literal("say")
.argument(StringArgument.greedy("message"))
.handler(ctx -> ctx.getSender().sendFeedback(
new TextComponent("Cloud client commands says: " + ctx.get("message"))
Component.literal("Cloud client commands says: " + ctx.get("message"))
)));
commandManager.command(base.literal("quit")
@ -108,21 +111,34 @@ public final class FabricClientExample implements ClientModInitializer {
commandManager.command(base.literal("requires_cheats")
.permission(FabricClientCommandManager.cheatsAllowed(false))
.handler(ctx -> ctx.getSender().sendFeedback(new TextComponent("Cheats are enabled!"))));
.handler(ctx -> ctx.getSender().sendFeedback(Component.literal("Cheats are enabled!"))));
// Test argument which requires CommandBuildContext/RegistryAccess
commandManager.command(base.literal("show_item")
.argument(ItemInputArgument.of("item"))
.handler(ctx -> {
try {
ctx.getSender().sendFeedback(
ctx.<ItemInput>get("item").createItemStack(1, false).getDisplayName()
);
} catch (final CommandSyntaxException ex) {
ctx.getSender().sendError(ComponentUtils.fromMessage(ex.getRawMessage()));
}
}));
}
private static void disconnectClient(final @NonNull Minecraft client) {
boolean singlePlayer = client.hasSingleplayerServer();
final boolean singlePlayer = client.hasSingleplayerServer();
client.level.disconnect();
if (singlePlayer) {
client.clearLevel(new GenericDirtMessageScreen(new TranslatableComponent("menu.savingLevel")));
client.clearLevel(new GenericDirtMessageScreen(Component.translatable("menu.savingLevel")));
} else {
client.clearLevel();
}
if (singlePlayer) {
client.setScreen(new TitleScreen());
} else if (client.isConnectedToRealms()) {
new RealmsBridge().switchToRealms(new TitleScreen());
client.setScreen(new RealmsMainScreen(new TitleScreen()));
} else {
client.setScreen(new JoinMultiplayerScreen(new TitleScreen()));
}

View file

@ -52,16 +52,14 @@ import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.api.metadata.Person;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.item.ItemInput;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextColor;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.phys.Vec3;
@ -86,13 +84,13 @@ public final class FabricExample implements ModInitializer {
.argument(name)
.argument(hugs)
.handler(ctx -> {
ctx.getSender().sendSuccess(new TextComponent("Hello, ")
ctx.getSender().sendSuccess(Component.literal("Hello, ")
.append(ctx.get(name))
.append(", hope you're doing well!")
.withStyle(style -> style.withColor(TextColor.fromRgb(0xAA22BB))), false);
ctx.getSender().sendSuccess(new TextComponent("Cloud would like to give you ")
.append(new TextComponent(String.valueOf(ctx.get(hugs)))
ctx.getSender().sendSuccess(Component.literal("Cloud would like to give you ")
.append(Component.literal(String.valueOf(ctx.get(hugs)))
.withStyle(style -> style.withColor(TextColor.fromRgb(0xFAB3DA))))
.append(" hug(s) <3")
.withStyle(style -> style.withBold(true)), false);
@ -109,15 +107,13 @@ public final class FabricExample implements ModInitializer {
final MultiplePlayerSelector selector = ctx.get(playerSelector);
final Collection<ServerPlayer> selected = selector.get();
selected.forEach(selectedPlayer ->
selectedPlayer.sendMessage(
new TextComponent("Wave from ")
.withStyle(style -> style.withColor(ctx.get(textColor)))
.append(ctx.getSender().getDisplayName()),
ChatType.SYSTEM,
Util.NIL_UUID
selectedPlayer.sendSystemMessage(
Component.literal("Wave from ")
.withStyle(style -> style.withColor(ctx.get(textColor)))
.append(ctx.getSender().getDisplayName())
));
ctx.getSender().sendSuccess(
new TextComponent(String.format("Waved at %d players (%s)", selected.size(),
Component.literal(String.format("Waved at %d players (%s)", selected.size(),
selector.inputString()
)),
false
@ -150,14 +146,14 @@ public final class FabricExample implements ModInitializer {
.map(ModContainer::getMetadata)
.sorted(Comparator.comparing(ModMetadata::getId))
.collect(Collectors.toList());
final TextComponent text = new TextComponent("");
text.append(new TextComponent("Loaded Mods")
final MutableComponent text = Component.literal("");
text.append(Component.literal("Loaded Mods")
.withStyle(style -> style.withColor(ChatFormatting.BLUE).applyFormat(ChatFormatting.BOLD)));
text.append(new TextComponent(String.format(" (%s)\n", modList.size()))
text.append(Component.literal(String.format(" (%s)\n", modList.size()))
.withStyle(style -> style.withColor(ChatFormatting.GRAY).applyFormat(ChatFormatting.ITALIC)));
for (final ModMetadata mod : modList) {
text.append(
new TextComponent("")
Component.literal("")
.withStyle(style -> style.withColor(ChatFormatting.WHITE)
.withClickEvent(new ClickEvent(
ClickEvent.Action.SUGGEST_COMMAND,
@ -165,17 +161,17 @@ public final class FabricExample implements ModInitializer {
))
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new TextComponent("Click for more info")
Component.literal("Click for more info")
)))
.append(new TextComponent(mod.getName()).withStyle(style -> style.withColor(ChatFormatting.GREEN)))
.append(new TextComponent(String.format(" (%s) ", mod.getId()))
.append(Component.literal(mod.getName()).withStyle(style -> style.withColor(ChatFormatting.GREEN)))
.append(Component.literal(String.format(" (%s) ", mod.getId()))
.withStyle(style -> style
.withColor(ChatFormatting.GRAY)
.applyFormat(ChatFormatting.ITALIC)))
.append(new TextComponent(String.format("v%s", mod.getVersion())))
.append(Component.literal(String.format("v%s", mod.getVersion())))
);
if (modList.indexOf(mod) != modList.size() - 1) {
text.append(new TextComponent(", ").withStyle(style -> style.withColor(ChatFormatting.GRAY)));
text.append(Component.literal(", ").withStyle(style -> style.withColor(ChatFormatting.GRAY)));
}
}
ctx.getSender().sendSuccess(text, false);
@ -204,23 +200,23 @@ public final class FabricExample implements ModInitializer {
manager.command(mods.argument(modMetadata)
.handler(ctx -> {
final ModMetadata meta = ctx.get(modMetadata);
final MutableComponent text = new TextComponent("")
.append(new TextComponent(meta.getName())
final MutableComponent text = Component.literal("")
.append(Component.literal(meta.getName())
.withStyle(style -> style.withColor(ChatFormatting.BLUE).applyFormat(ChatFormatting.BOLD)))
.append(new TextComponent("\n modid: " + meta.getId()))
.append(new TextComponent("\n version: " + meta.getVersion()))
.append(new TextComponent("\n type: " + meta.getType()));
.append(Component.literal("\n modid: " + meta.getId()))
.append(Component.literal("\n version: " + meta.getVersion()))
.append(Component.literal("\n type: " + meta.getType()));
if (!meta.getDescription().isEmpty()) {
text.append(new TextComponent("\n description: " + meta.getDescription()));
text.append(Component.literal("\n description: " + meta.getDescription()));
}
if (!meta.getAuthors().isEmpty()) {
text.append(new TextComponent("\n authors: " + meta.getAuthors().stream()
text.append(Component.literal("\n authors: " + meta.getAuthors().stream()
.map(Person::getName)
.collect(Collectors.joining(", "))));
}
if (!meta.getLicense().isEmpty()) {
text.append(new TextComponent("\n license: " + String.join(", ", meta.getLicense())));
text.append(Component.literal("\n license: " + String.join(", ", meta.getLicense())));
}
ctx.getSender().sendSuccess(
text,

View file

@ -28,8 +28,8 @@
"depends": {
"fabricloader": ">=0.7.4",
"fabric-command-api-v1": "*",
"minecraft": ">=1.14",
"fabric-command-api-v2": "*",
"minecraft": ">1.18.2",
"cloud": "*"
}
}