Skip to content

Commit

Permalink
feature: use cloud v2 for commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret committed Dec 31, 2023
1 parent ead7acd commit 259fe31
Show file tree
Hide file tree
Showing 19 changed files with 794 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Bukkit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ dependencies {

// Adventure
implementation(libs.adventureBukkit)

// Cloud
implementation(libs.cloudPaper)
}

tasks.processResources {
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -64,6 +65,7 @@
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;
Expand All @@ -83,6 +85,7 @@
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;
Expand Down Expand Up @@ -293,6 +296,8 @@ public void onEnable() {
new PermissionModule(),
new WorldManagerModule(),
new PlotSquaredModule(),
new CommandModule(),
new CloudModule(this),
new BukkitModule(this),
new BackupModule()
);
Expand Down Expand Up @@ -388,6 +393,8 @@ public void onEnable() {
// Commands
if (Settings.Enabled_Components.COMMANDS) {
this.registerCommands();
// Register the commands.
this.injector().getInstance(PlotSquaredCommandManager.class).registerDefaultCommands();
}

// Permissions
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
4 changes: 4 additions & 0 deletions Core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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
);
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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() {
}
}
Loading

0 comments on commit 259fe31

Please sign in to comment.