feature: use cloud v2 for commands

This commit is contained in:
Alexander Söderberg 2023-12-31 20:55:49 +01:00
parent e4613cfc62
commit 6088dd8409
No known key found for this signature in database
19 changed files with 794 additions and 1 deletions

View File

@ -53,6 +53,9 @@ dependencies {
// Adventure
implementation(libs.adventureBukkit)
// Cloud
implementation(libs.cloudPaper)
}
tasks.processResources {
@ -77,6 +80,7 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("com.google.inject", "com.plotsquared.google")
relocate("org.aopalliance", "com.plotsquared.core.aopalliance")
relocate("cloud.commandframework.services", "com.plotsquared.core.services")
relocate("cloud.commandframework", "com.plotsquared.commands")
relocate("io.leangen.geantyref", "com.plotsquared.core.geantyref")
relocate("com.intellectualsites.arkitektonika", "com.plotsquared.core.arkitektonika")
relocate("com.intellectualsites.http", "com.plotsquared.core.http")

View File

@ -28,6 +28,7 @@ import com.google.inject.TypeLiteral;
import com.plotsquared.bukkit.generator.BukkitPlotGenerator;
import com.plotsquared.bukkit.inject.BackupModule;
import com.plotsquared.bukkit.inject.BukkitModule;
import com.plotsquared.bukkit.inject.CloudModule;
import com.plotsquared.bukkit.inject.PermissionModule;
import com.plotsquared.bukkit.inject.WorldManagerModule;
import com.plotsquared.bukkit.listener.BlockEventListener;
@ -64,6 +65,7 @@ import com.plotsquared.bukkit.uuid.SquirrelIdUUIDService;
import com.plotsquared.core.PlotPlatform;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.backup.BackupManager;
import com.plotsquared.core.commands.PlotSquaredCommandManager;
import com.plotsquared.core.components.ComponentPresetManager;
import com.plotsquared.core.configuration.ConfigurationNode;
import com.plotsquared.core.configuration.ConfigurationSection;
@ -83,6 +85,7 @@ import com.plotsquared.core.inject.annotations.DefaultGenerator;
import com.plotsquared.core.inject.annotations.ImpromptuPipeline;
import com.plotsquared.core.inject.annotations.WorldConfig;
import com.plotsquared.core.inject.annotations.WorldFile;
import com.plotsquared.core.inject.modules.CommandModule;
import com.plotsquared.core.inject.modules.PlotSquaredModule;
import com.plotsquared.core.listener.PlotListener;
import com.plotsquared.core.listener.WESubscriber;
@ -293,6 +296,8 @@ public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPl
new PermissionModule(),
new WorldManagerModule(),
new PlotSquaredModule(),
new CommandModule(),
new CloudModule(this),
new BukkitModule(this),
new BackupModule()
);
@ -388,6 +393,8 @@ public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPl
// Commands
if (Settings.Enabled_Components.COMMANDS) {
this.registerCommands();
// Register the commands.
this.injector().getInstance(PlotSquaredCommandManager.class).registerDefaultCommands();
}
// Permissions

View File

@ -0,0 +1,86 @@
package com.plotsquared.bukkit.inject;
import cloud.commandframework.CloudCapability;
import cloud.commandframework.CommandManager;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.minecraft.extras.AudienceProvider;
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
import cloud.commandframework.paper.PaperCommandManager;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.plotsquared.bukkit.BukkitPlatform;
import com.plotsquared.bukkit.util.BukkitUtil;
import com.plotsquared.core.commands.CommonCommandRequirement;
import com.plotsquared.core.commands.PlotSquaredCaptionProvider;
import com.plotsquared.core.commands.processing.CommandRequirementPostprocessor;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.ConsolePlayer;
import com.plotsquared.core.player.PlotPlayer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
public class CloudModule extends AbstractModule {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + CloudModule.class.getSimpleName());
private static @NonNull CommandSender convert(final @NonNull PlotPlayer<?> player) {
if (player instanceof ConsolePlayer) {
return Bukkit.getConsoleSender();
}
return (Player) player.getPlatformPlayer();
}
private static @NonNull PlotPlayer<?> convert (final @NonNull CommandSender sender) {
if (sender instanceof Player player) {
return BukkitUtil.adapt(player);
}
return ConsolePlayer.getConsole();
}
private final BukkitPlatform bukkitPlatform;
public CloudModule(final @NonNull BukkitPlatform bukkitPlatform) {
this.bukkitPlatform = bukkitPlatform;
}
@Override
protected void configure() {
try {
final PaperCommandManager<PlotPlayer<?>> commandManager = new PaperCommandManager<PlotPlayer<?>>(
this.bukkitPlatform,
AsynchronousCommandExecutionCoordinator.<PlotPlayer<?>>builder().withAsynchronousParsing().build(),
CloudModule::convert,
CloudModule::convert
);
commandManager.captionRegistry().registerProvider(new PlotSquaredCaptionProvider());
if (commandManager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
commandManager.registerAsynchronousCompletions();
}
if (commandManager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
commandManager.registerBrigadier();
}
final CommandRequirementPostprocessor requirementPostprocessor = new CommandRequirementPostprocessor();
requirementPostprocessor.registerRequirements(CommonCommandRequirement.values());
commandManager.registerCommandPostProcessor(requirementPostprocessor);
// TODO(City): Override parsing errors using MM parsing.
MinecraftExceptionHandler.<PlotPlayer<?>>create(PlotPlayer::getAudience)
.defaultHandlers()
.decorator((ctx, component) -> TranslatableCaption.of("core.prefix").
toComponent(ctx.context().sender())
.append(component))
.registerTo(commandManager);
bind(Key.get(new TypeLiteral<CommandManager<PlotPlayer<?>>>() {})).toInstance(commandManager);
} catch (final Exception e) {
LOGGER.error("Failed to configure command manager", e);
}
}
}

View File

@ -15,6 +15,10 @@ dependencies {
api(libs.adventureApi)
api(libs.adventureMiniMessage)
// Cloud
api(libs.cloud)
api(libs.cloudMinecraftExtras)
// Guice
api(libs.guice) {
exclude(group = "com.google.guava")

View File

@ -0,0 +1,28 @@
package com.plotsquared.core.commands;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.keys.CloudKeyHolder;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Something that is required for a command to be executed.
*/
public interface CommandRequirement extends CloudKeyHolder<Boolean> {
/**
* Returns the caption sent when the requirement is not met.
*
* @return the caption
*/
@NonNull TranslatableCaption failureCaption();
/**
* Evaluates whether the requirement is met.
*
* @param context command context to evaluate
* @return {@code true} if the requirement is met, else {@code false}
*/
boolean evaluate(final @NonNull CommandContext<PlotPlayer<?>> context);
}

View File

@ -0,0 +1,50 @@
package com.plotsquared.core.commands;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.keys.CloudKey;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* Common {@link CommandRequirement command requirements}.
*/
public enum CommonCommandRequirement implements CommandRequirement {
/**
* Requires that the command sender is currently in a plot.
*/
REQUIRES_PLOT(TranslatableCaption.of("errors.not_in_plot"), ctx -> ctx.sender().getCurrentPlot() != null),
/**
* Requires that the command sender is in a claimed plot.
*/
REQUIRES_OWNER(TranslatableCaption.of("working.plot_not_claimed"),
ctx -> ctx.sender().getCurrentPlot() != null && ctx.sender().getCurrentPlot().hasOwner()
);
private final TranslatableCaption failureCaption;
private final Predicate<CommandContext<PlotPlayer<?>>> predicate;
CommonCommandRequirement(
final @NonNull TranslatableCaption failureCaption,
final @NonNull Predicate<CommandContext<PlotPlayer<?>>> predicate
) {
this.failureCaption = failureCaption;
this.predicate = predicate;
}
public @NonNull TranslatableCaption failureCaption() {
return this.failureCaption;
}
@Override
public boolean evaluate(final @NonNull CommandContext<PlotPlayer<?>> context) {
return this.predicate.test(context);
}
@Override
public @NonNull CloudKey<Boolean> key() {
return CloudKey.of(String.format("requirement_%s", this.name()), Boolean.class);
}
}

View File

@ -0,0 +1,32 @@
package com.plotsquared.core.commands;
import cloud.commandframework.captions.Caption;
import cloud.commandframework.captions.CaptionProvider;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.configuration.caption.CaptionMap;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* {@link CaptionProvider} that retrieves caption values from the {@link CaptionMap caption map}.
*/
public final class PlotSquaredCaptionProvider implements CaptionProvider<PlotPlayer<?>> {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotSquaredCaptionProvider.class.getSimpleName());
@Override
public @Nullable String provide(final @NonNull Caption caption, final @NonNull PlotPlayer<?> recipient) {
try {
return PlotSquared.get()
.getCaptionMap(TranslatableCaption.DEFAULT_NAMESPACE)
.getMessage(TranslatableCaption.of(caption.key()), recipient);
} catch (final CaptionMap.NoSuchCaptionException ignored) {
LOGGER.warn("Missing caption '{}', will attempt to fall back on Cloud defaults", caption.key());
return null;
}
}
}

View File

@ -0,0 +1,47 @@
package com.plotsquared.core.commands;
import cloud.commandframework.Command;
import cloud.commandframework.CommandBean;
import cloud.commandframework.CommandProperties;
import com.plotsquared.core.command.CommandCategory;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
public abstract class PlotSquaredCommandBean extends CommandBean<PlotPlayer<?>> {
/**
* Returns the category of the command.
*
* @return the category
*/
public abstract @NonNull CommandCategory category();
/**
* Returns the requirements for the command to be executable.
*
* @return the requirements
*/
public abstract @NonNull Set<@NonNull CommandRequirement> requirements();
@Override
protected final @NonNull CommandProperties properties() {
return CommandProperties.of("platsquared", "plat");
}
@Override
protected final Command.@NonNull Builder<PlotPlayer<?>> configure(final Command.@NonNull Builder<PlotPlayer<?>> builder) {
Command.@NonNull Builder<PlotPlayer<?>> intermediaryBuilder =
this.configurePlotCommand(builder.meta(PlotSquaredCommandMeta.META_CATEGORY,
this.category()));
for (final CommandRequirement requirement : this.requirements()) {
intermediaryBuilder = intermediaryBuilder.meta(requirement.key(), true);
}
return intermediaryBuilder;
}
protected abstract Command.@NonNull Builder<PlotPlayer<?>> configurePlotCommand(
Command.@NonNull Builder<PlotPlayer<?>> builder
);
}

View File

@ -0,0 +1,56 @@
package com.plotsquared.core.commands;
import cloud.commandframework.CommandManager;
import cloud.commandframework.annotations.injection.GuiceInjectionService;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.plotsquared.core.commands.injection.PlotInjector;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
@Singleton
public final class PlotSquaredCommandManager {
private final CommandManager<PlotPlayer<?>> commandManager;
private final Injector injector;
@Inject
public PlotSquaredCommandManager(
final @NonNull CommandManager<PlotPlayer<?>> commandManager,
final @NonNull Injector injector
) {
this.commandManager = commandManager;
this.injector = injector;
this.registerInjectors();
}
/**
* Registers the commands that are shipped with PlotSquared.
*/
public void registerDefaultCommands() {
final Set<PlotSquaredCommandBean> commands =
this.injector.getInstance(Key.get(new TypeLiteral<Set<PlotSquaredCommandBean>>() {}));
commands.forEach(command -> this.commandManager().command(command));
}
/**
* Returns the command manager.
*
* @return the command manager
*/
public @NonNull CommandManager<PlotPlayer<?>> commandManager() {
return this.commandManager;
}
private void registerInjectors() {
this.commandManager.parameterInjectorRegistry().registerInjector(Plot.class,
this.injector.getInstance(PlotInjector.class));
this.commandManager.parameterInjectorRegistry().registerInjectionService(GuiceInjectionService.create(this.injector));
}
}

View File

@ -0,0 +1,18 @@
package com.plotsquared.core.commands;
import cloud.commandframework.keys.CloudKey;
import com.plotsquared.core.command.CommandCategory;
/**
* Shared {@link cloud.commandframework.meta.CommandMeta command meta} keys.
*/
public final class PlotSquaredCommandMeta {
/**
* Key that determines what {@link CommandCategory category} a command belongs to.
*/
public static final CloudKey<CommandCategory> META_CATEGORY = CloudKey.of("category", CommandCategory.class);
private PlotSquaredCommandMeta() {
}
}

View File

@ -0,0 +1,196 @@
package com.plotsquared.core.commands.command.setting.flag;
import cloud.commandframework.Command;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.keys.CloudKey;
import com.google.inject.Inject;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.command.CommandCategory;
import com.plotsquared.core.commands.CommandRequirement;
import com.plotsquared.core.commands.CommonCommandRequirement;
import com.plotsquared.core.commands.PlotSquaredCommandBean;
import com.plotsquared.core.commands.suggestions.FlagValueSuggestionProvider;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.caption.CaptionUtility;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.events.PlotFlagAddEvent;
import com.plotsquared.core.events.Result;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.flag.FlagParseException;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.IntegerFlag;
import com.plotsquared.core.plot.flag.types.ListFlag;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.MathMan;
import io.leangen.geantyref.TypeToken;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Set;
import static cloud.commandframework.arguments.standard.StringParser.greedyStringParser;
import static com.plotsquared.core.commands.parser.PlotFlagParser.plotFlagParser;
public final class FlagSetCommand extends PlotSquaredCommandBean {
private static final CloudKey<PlotFlag<?, ?>> COMPONENT_FLAG = CloudKey.of("flag", new TypeToken<PlotFlag<?, ?>>() {});
private static final CloudKey<String> COMPONENT_VALUE = CloudKey.of("value", String.class);
private static boolean checkPermValue(
final @NonNull PlotPlayer<?> player,
final @NonNull PlotFlag<?, ?> flag, @NonNull String key, @NonNull String value
) {
key = key.toLowerCase();
value = value.toLowerCase();
String perm = Permission.PERMISSION_SET_FLAG_KEY_VALUE.format(key.toLowerCase(), value.toLowerCase());
if (flag instanceof IntegerFlag && MathMan.isInteger(value)) {
try {
int numeric = Integer.parseInt(value);
// Getting full permission without ".<amount>" at the end
perm = perm.substring(0, perm.length() - value.length() - 1);
boolean result = false;
if (numeric >= 0) {
int checkRange = PlotSquared.get().getPlatform().equalsIgnoreCase("bukkit") ?
numeric :
Settings.Limit.MAX_PLOTS;
result = player.hasPermissionRange(perm, checkRange) >= numeric;
}
if (!result) {
player.sendMessage(
TranslatableCaption.of("permission.no_permission"),
TagResolver.resolver(
"node",
Tag.inserting(Component.text(perm + "." + numeric))
)
);
}
return result;
} catch (NumberFormatException ignore) {
}
} else if (flag instanceof final ListFlag<?, ?> listFlag) {
try {
PlotFlag<? extends List<?>, ?> parsedFlag = listFlag.parse(value);
for (final Object entry : parsedFlag.getValue()) {
final String permission = Permission.PERMISSION_SET_FLAG_KEY_VALUE.format(
key.toLowerCase(),
entry.toString().toLowerCase()
);
final boolean result = player.hasPermission(permission);
if (!result) {
player.sendMessage(
TranslatableCaption.of("permission.no_permission"),
TagResolver.resolver("node", Tag.inserting(Component.text(permission)))
);
return false;
}
}
} catch (final FlagParseException e) {
player.sendMessage(
TranslatableCaption.of("flag.flag_parse_error"),
TagResolver.builder()
.tag("flag_name", Tag.inserting(Component.text(flag.getName())))
.tag("flag_value", Tag.inserting(Component.text(e.getValue())))
.tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
.build()
);
return false;
} catch (final Exception e) {
return false;
}
return true;
}
boolean result;
String basePerm = Permission.PERMISSION_SET_FLAG_KEY.format(key.toLowerCase());
if (flag.isValuedPermission()) {
result = player.hasKeyedPermission(basePerm, value);
} else {
result = player.hasPermission(basePerm);
perm = basePerm;
}
if (!result) {
player.sendMessage(
TranslatableCaption.of("permission.no_permission"),
TagResolver.resolver("node", Tag.inserting(Component.text(perm)))
);
}
return result;
}
private final EventDispatcher eventDispatcher;
@Inject
public FlagSetCommand(final @NonNull EventDispatcher eventDispatcher) {
this.eventDispatcher = eventDispatcher;
}
@Override
public @NonNull CommandCategory category() {
return CommandCategory.SETTINGS;
}
@Override
public @NonNull Set<@NonNull CommandRequirement> requirements() {
// TODO: Figure out how to handle the override permission check :)
return Set.of(CommonCommandRequirement.REQUIRES_PLOT, CommonCommandRequirement.REQUIRES_OWNER);
}
@Override
protected Command.@NonNull Builder<PlotPlayer<?>> configurePlotCommand(
final Command.@NonNull Builder<PlotPlayer<?>> builder
) {
return builder.literal("flag")
.literal("set")
.required(COMPONENT_FLAG, plotFlagParser())
.required(COMPONENT_VALUE, greedyStringParser(), new FlagValueSuggestionProvider(COMPONENT_FLAG));
}
@Override
public void execute(final @NonNull CommandContext<PlotPlayer<?>> commandContext) {
final PlotPlayer<?> player = commandContext.sender();
final Plot plot = commandContext.inject(Plot.class).orElseThrow();
final PlotFlag<?, ?> flag = commandContext.get(COMPONENT_FLAG);
final String flagValue = commandContext.get(COMPONENT_VALUE);
final PlotFlagAddEvent event = this.eventDispatcher.callFlagAdd(flag, plot);
if (event.getEventResult() == Result.DENY) {
player.sendMessage(
TranslatableCaption.of("events.event_denied"),
TagResolver.resolver("value", Tag.inserting(Component.text("Flag set")))
);
return;
}
if (event.getEventResult() != Result.FORCE && !checkPermValue(player, flag, flag.getName(), flagValue)) {
return;
}
final String sanitizedValue = CaptionUtility.stripClickEvents(flag, flagValue);
final PlotFlag<?, ?> parsedFlag;
try {
parsedFlag = flag.parse(flagValue);
} catch (final FlagParseException e) {
player.sendMessage(
TranslatableCaption.of("flag.flag_parse_error"),
TagResolver.builder()
.tag("flag_name", Tag.inserting(Component.text(flag.getName())))
.tag("flag_value", Tag.inserting(Component.text(e.getValue())))
.tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
.build()
);
return;
}
plot.setFlag(parsedFlag);
player.sendMessage(
TranslatableCaption.of("flag.flag_added"),
TagResolver.builder()
.tag("flag", Tag.inserting(Component.text(flag.getName())))
.tag("value", Tag.inserting(Component.text(parsedFlag.toString())))
.build()
);
}
}

View File

@ -0,0 +1,24 @@
package com.plotsquared.core.commands.injection;
import cloud.commandframework.annotations.AnnotationAccessor;
import cloud.commandframework.annotations.injection.ParameterInjector;
import cloud.commandframework.context.CommandContext;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* {@link ParameterInjector} that returns the current plot of the player.
*/
public final class PlotInjector implements ParameterInjector<PlotPlayer<?>, Plot> {
@Override
public @Nullable Plot create(
final @NonNull CommandContext<PlotPlayer<?>> context,
final @NonNull AnnotationAccessor annotationAccessor
) {
// TODO: Allow for overriding for console.
return context.sender().getCurrentPlot();
}
}

View File

@ -0,0 +1,81 @@
package com.plotsquared.core.commands.parser;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserDescriptor;
import cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider;
import cloud.commandframework.arguments.suggestion.Suggestion;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandInput;
import cloud.commandframework.exceptions.parsing.ParserException;
import com.plotsquared.core.configuration.caption.LocaleHolder;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.flag.GlobalFlagContainer;
import com.plotsquared.core.plot.flag.InternalFlag;
import com.plotsquared.core.plot.flag.PlotFlag;
import io.leangen.geantyref.TypeToken;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.ComponentMessageThrowable;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Nullable;
/**
* Parser that parses and suggests {@link PlotFlag plot flags}.
*/
public final class PlotFlagParser implements ArgumentParser<PlotPlayer<?>, PlotFlag<?, ?>>,
BlockingSuggestionProvider<PlotPlayer<?>> {
/**
* Returns a new parser that parses {@link PlotFlag plot flags}.
*
* @return the parser
*/
public static @NonNull ParserDescriptor<PlotPlayer<?>, PlotFlag<?, ?>> plotFlagParser() {
return ParserDescriptor.of(new PlotFlagParser(), new TypeToken<PlotFlag<?, ?>>() {
});
}
@Override
public @NonNull ArgumentParseResult<@NonNull PlotFlag<?, ?>> parse(
final @NonNull CommandContext<@NonNull PlotPlayer<?>> commandContext,
final @NonNull CommandInput commandInput
) {
final String flagName = commandInput.readString();
final PlotFlag<?, ?> flag = GlobalFlagContainer.getInstance().getFlagFromString(flagName);
if (flag == null) {
return ArgumentParseResult.failure(new PlotFlagParseException(commandContext));
}
return ArgumentParseResult.success(flag);
}
@Override
public @NonNull Iterable<@NonNull Suggestion> suggestions(
final @NonNull CommandContext<PlotPlayer<?>> context,
final @NonNull CommandInput input
) {
return GlobalFlagContainer.getInstance()
.getRecognizedPlotFlags()
.stream()
.filter(flag -> (!(flag instanceof InternalFlag)))
.map(PlotFlag::getName)
.map(Suggestion::simple)
.toList();
}
/**
* Exception thrown when an invalid flag name is supplied.
*/
public static final class PlotFlagParseException extends ParserException implements ComponentMessageThrowable {
private PlotFlagParseException(final @NonNull CommandContext<?> context) {
super(PlotFlagParser.class, context, TranslatableCaption.of("flag.not_valid_flag"));
}
@Override
public @NonNull Component componentMessage() {
// TODO(City): This sucks...
return ((TranslatableCaption) this.errorCaption()).toComponent(LocaleHolder.console());
}
}
}

View File

@ -0,0 +1,63 @@
package com.plotsquared.core.commands.processing;
import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext;
import cloud.commandframework.execution.postprocessor.CommandPostprocessor;
import cloud.commandframework.services.types.ConsumerService;
import com.plotsquared.core.commands.CommandRequirement;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
/**
* Processor that evaluates registered {@link CommandRequirement command requirements} before a command is executed.
*/
public final class CommandRequirementPostprocessor implements CommandPostprocessor<PlotPlayer<?>> {
private final Collection<@NonNull CommandRequirement> requirements = new ArrayList<>();
/**
* Requires a single requirement.
*
* @param requirement the requirement
*/
public void registerRequirement(final @NonNull CommandRequirement requirement) {
this.requirements.add(Objects.requireNonNull(requirement, "requirement"));
}
/**
* Registers the given {@code requirements}.
*
* @param requirements the requirements
*/
public void registerRequirements(final @NonNull Collection<@NonNull CommandRequirement> requirements) {
requirements.forEach(this::registerRequirement);
}
/**
* Registers the given {@code requirements}.
*
* @param requirements the requirements
*/
public void registerRequirements(final @NonNull CommandRequirement @NonNull... requirements) {
this.registerRequirements(Arrays.asList(requirements));
}
@Override
public void accept(final @NonNull CommandPostprocessingContext<PlotPlayer<?>> processingContext) {
for (final CommandRequirement requirement : this.requirements) {
if (!processingContext.command().commandMeta().getOrDefault(requirement.key(), false)) {
continue;
}
if (requirement.evaluate(processingContext.commandContext())) {
continue;
}
processingContext.commandContext().sender().sendMessage(requirement.failureCaption());
// Not allowed :(
ConsumerService.interrupt();
}
}
}

View File

@ -0,0 +1,66 @@
package com.plotsquared.core.commands.suggestions;
import cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandInput;
import cloud.commandframework.keys.CloudKey;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.flag.PlotFlag;
import com.plotsquared.core.plot.flag.types.ListFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Suggestion provider that provides context-aware {@link PlotFlag plot flag} value suggestions using
* {@link PlotFlag#getTabCompletions()}.
*/
public final class FlagValueSuggestionProvider implements BlockingSuggestionProvider.Strings<PlotPlayer<?>> {
private final CloudKey<PlotFlag<?, ?>> flagKey;
/**
* Creates a new suggestion provider.
*
* @param flagKey the key of the argument that contains the flag to provide value suggestions for
*/
public FlagValueSuggestionProvider(final @NonNull CloudKey<PlotFlag<?, ?>> flagKey) {
this.flagKey = Objects.requireNonNull(flagKey, "flagKey");
}
@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
@NonNull final CommandContext<PlotPlayer<?>> context,
@NonNull final CommandInput input
) {
final PlotFlag<?, ?> plotFlag = context.getOrDefault(this.flagKey, null);
if (plotFlag == null) {
return List.of();
}
final Collection<String> completions = plotFlag.getTabCompletions();
if (plotFlag instanceof ListFlag<?,?> && input.peekString().contains(",")) {
final String[] split = input.peekString().split(",");
final StringBuilder prefix = new StringBuilder();
for (int i = 0; i < split.length - i; i++) {
prefix.append(split[i]).append(",");
}
final String cmp;
if (!input.peekString().endsWith(",")) {
cmp = split[split.length - 1];
} else {
prefix.append(split[split.length - 1]).append(",");
cmp = "";
}
return completions.stream()
.filter(value -> value.startsWith(cmp.toLowerCase(Locale.ENGLISH)))
.map(value -> prefix + value)
.toList();
}
return completions;
}
}

View File

@ -18,6 +18,7 @@
*/
package com.plotsquared.core.configuration.caption;
import cloud.commandframework.captions.Caption;
import com.google.common.base.Objects;
import com.plotsquared.core.PlotSquared;
import net.kyori.adventure.text.Component;
@ -33,7 +34,7 @@ import java.util.regex.Pattern;
/**
* Caption that is user modifiable
*/
public final class TranslatableCaption implements NamespacedCaption {
public final class TranslatableCaption implements NamespacedCaption, Caption {
/**
* Default caption namespace
@ -72,6 +73,11 @@ public final class TranslatableCaption implements NamespacedCaption {
);
}
@Override
public @NonNull String key() {
return this.getKey();
}
/**
* Get a new {@link TranslatableCaption} instance
*

View File

@ -0,0 +1,20 @@
package com.plotsquared.core.inject.modules;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.multibindings.Multibinder;
import com.plotsquared.core.commands.PlotSquaredCommandBean;
import com.plotsquared.core.commands.command.setting.flag.FlagSetCommand;
public final class CommandModule extends AbstractModule {
@Override
protected void configure() {
final Multibinder<PlotSquaredCommandBean> commands = Multibinder.newSetBinder(
this.binder(),
PlotSquaredCommandBean.class
);
commands.addBinding().to(FlagSetCommand.class).in(Scopes.SINGLETON);
}
}

View File

@ -39,6 +39,7 @@ subprojects {
version = rootProject.version
repositories {
mavenLocal() // TODO(City): Remove once Cloud 2 is on central.
mavenCentral()
maven {

View File

@ -31,6 +31,7 @@ paperlib = "1.0.8"
informative-annotations = "1.4"
vault = "1.7.1"
serverlib = "2.3.4"
cloud = "2.0.0-SNAPSHOT"
# Gradle plugins
shadow = "8.1.1"
@ -77,6 +78,9 @@ informativeAnnotations = { group = "com.intellectualsites.informative-annotation
paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" }
vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" }
serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" }
cloud = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" }
cloudPaper = { group = "cloud.commandframework", name = "cloud-paper", version.ref = "cloud" }
cloudMinecraftExtras = { group = "cloud.commandframework", name = "cloud-minecraft-extras", version.ref = "cloud" }
[plugins]
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }