implement a command and write a sample parser

This commit is contained in:
Alexander Söderberg 2023-03-04 07:51:10 +01:00
parent f3f44b55c4
commit f175c24523
No known key found for this signature in database
10 changed files with 381 additions and 3 deletions

View File

@ -0,0 +1,86 @@
package com.plotsquared.core.commands;
import cloud.commandframework.annotations.Argument;
import cloud.commandframework.annotations.CommandMethod;
import cloud.commandframework.annotations.CommandPermission;
import com.google.inject.Inject;
import com.plotsquared.core.commands.arguments.PlotMember;
import com.plotsquared.core.commands.requirements.Requirement;
import com.plotsquared.core.commands.requirements.RequirementType;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.PlayerManager;
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;
class CommandAdd implements PlotSquaredCommandContainer {
private final EventDispatcher eventDispatcher;
@Inject
CommandAdd(final @NonNull EventDispatcher eventDispatcher) {
this.eventDispatcher = eventDispatcher;
}
@Requirement(RequirementType.PLAYER)
@Requirement(RequirementType.IS_OWNER)
@CommandPermission("plots.add")
@CommandMethod("plot add [target]")
public void commandAdd(
final @NonNull PlotPlayer<?> sender,
@Argument("target") final PlotMember target,
final @NonNull Plot plot
) {
if (target instanceof PlotMember.Everyone) {
if (!sender.hasPermission(Permission.PERMISSION_TRUST_EVERYONE) && !sender.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_TRUST)) {
sender.sendMessage(
TranslatableCaption.of("errors.invalid_player"),
TagResolver.resolver("value", Tag.inserting(
PlayerManager.resolveName(target.uuid()).toComponent(sender)
))
);
return;
}
} else if (plot.isOwner(target.uuid())) {
sender.sendMessage(
TranslatableCaption.of("member.already_added"),
TagResolver.resolver("player", Tag.inserting(
PlayerManager.resolveName(target.uuid()).toComponent(sender)
))
);
return;
} else if (plot.getMembers().contains(target.uuid())) {
sender.sendMessage(
TranslatableCaption.of("member.already_added"),
TagResolver.resolver("player", Tag.inserting(
PlayerManager.resolveName(target.uuid()).toComponent(sender)
))
);
return;
} else if (plot.getMembers().size() >= sender.hasPermissionRange(Permission.PERMISSION_ADD, Settings.Limit.MAX_PLOTS)) {
sender.sendMessage(
TranslatableCaption.of("members.plot_max_members_added"),
TagResolver.resolver("amount", Tag.inserting(Component.text(plot.getMembers().size())))
);
return;
}
if (target instanceof PlotMember.Player) {
if (!plot.removeTrusted(target.uuid())) {
if (plot.getDenied().contains(target.uuid())) {
plot.removeDenied(target.uuid());
}
}
}
plot.addMember(target.uuid());
this.eventDispatcher.callMember(sender, plot, target.uuid(), true);
sender.sendMessage(TranslatableCaption.of("member.member_added"));
}
}

View File

@ -0,0 +1,8 @@
package com.plotsquared.core.commands;
import cloud.commandframework.captions.Caption;
public final class PlotSquaredCaptionKeys {
public static final Caption ARGUMENT_PARSE_FAILURE_TARGET = Caption.of("argument.parse.failure.target");
}

View File

@ -0,0 +1,8 @@
package com.plotsquared.core.commands;
/**
* Indicates that a class contains commands.
*/
public interface PlotSquaredCommandContainer {
}

View File

@ -23,6 +23,7 @@ import cloud.commandframework.annotations.AnnotationParser;
import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.meta.SimpleCommandMeta;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.plotsquared.core.commands.parsers.PlotMemberParser;
import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.player.PlotPlayer;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@ -63,8 +64,13 @@ public class PlotSquaredCommandManager {
* Initializes all the known commands. * Initializes all the known commands.
*/ */
public void initializeCommands() { public void initializeCommands() {
final Stream<Class<?>> commandClasses = Stream.of( // We start by scanning the parsers.
); Stream.of(
commandClasses.map(injector::getInstance).forEach(this::scanClass); PlotMemberParser.class
).map(this.injector::getInstance).forEach(this::scanClass);
// Then we scan the commands.
Stream.of(
CommandAdd.class
).map(this.injector::getInstance).forEach(this::scanClass);
} }
} }

View File

@ -0,0 +1,76 @@
package com.plotsquared.core.commands.arguments;
import cloud.commandframework.context.CommandContext;
import com.plotsquared.core.commands.parsers.PlotMemberParser;
import com.plotsquared.core.database.DBFunc;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
public sealed interface PlotMember {
PlotMember EVERYONE = new Everyone();
default @NonNull UUID uuid(@NonNull CommandContext<PlotPlayer<?>> context) {
return this.uuid();
}
@NonNull UUID uuid();
sealed interface PlayerLike extends PlotMember {
}
record Player(@NonNull UUID uuid) implements PlayerLike {
}
final class LazyPlayer implements PlayerLike {
private final String candidate;
private final UuidSupplier uuidSupplier;
private @MonotonicNonNull UUID cachedUuid = null;
public LazyPlayer(
final @NonNull String candidate,
final @NonNull UuidSupplier uuidSupplier
) {
this.candidate = candidate;
this.uuidSupplier = uuidSupplier;
}
public @NonNull UUID uuid() {
throw new UnsupportedOperationException();
}
@Override
public synchronized @NonNull UUID uuid(final @NonNull CommandContext<PlotPlayer<?>> context) {
if (this.cachedUuid == null) {
try {
this.cachedUuid = this.uuidSupplier.uuid();
} catch (Exception ignored) {
}
// The player didn't exist :-(
if (this.cachedUuid == null) {
throw new PlotMemberParser.TargetParseException(this.candidate, context);
}
}
return this.cachedUuid;
}
@FunctionalInterface
public interface UuidSupplier {
@Nullable UUID uuid() throws Exception;
}
}
final class Everyone implements PlotMember {
@Override
public @NonNull UUID uuid() {
return DBFunc.EVERYONE;
}
}
}

View File

@ -0,0 +1,106 @@
package com.plotsquared.core.commands.parsers;
import cloud.commandframework.annotations.parsers.Parser;
import cloud.commandframework.captions.CaptionVariable;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
import cloud.commandframework.exceptions.parsing.ParserException;
import com.google.inject.Inject;
import com.plotsquared.core.commands.PlotSquaredCaptionKeys;
import com.plotsquared.core.commands.arguments.PlotMember;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.inject.annotations.ImpromptuPipeline;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.uuid.UUIDMapping;
import com.plotsquared.core.uuid.UUIDPipeline;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.Serial;
import java.util.Queue;
import java.util.UUID;
public class PlotMemberParser {
private final UUIDPipeline uuidPipeline;
@Inject
public PlotMemberParser(@ImpromptuPipeline final @NonNull UUIDPipeline uuidPipeline) {
this.uuidPipeline = uuidPipeline;
}
@Parser
public @NonNull PlotMember parse(
final @NonNull CommandContext<PlotPlayer<?>> context,
final @NonNull Queue<@NonNull String> input
) {
final var candidate = input.peek();
if (candidate == null) {
throw new NoInputProvidedException(this.getClass(), context);
}
if ("*".equals(candidate)) {
return PlotMember.EVERYONE;
} else if (candidate.length() > 16) {
try {
return new PlotMember.Player(UUID.fromString(candidate));
} catch (IllegalArgumentException ignored) {
throw new TargetParseException(candidate, context);
}
}
if (Settings.Paper_Components.PAPER_LISTENERS) {
try {
return this.uuidPipeline.getUUID(candidate, Settings.UUID.NON_BLOCKING_TIMEOUT)
.get()
.map(UUIDMapping::getUuid)
.map(PlotMember.Player::new)
.orElseThrow();
} catch (Exception e) {
throw new TargetParseException(candidate, context);
}
} else {
return new PlotMember.LazyPlayer(
candidate,
() -> this.uuidPipeline.getUUID(candidate, Settings.UUID.NON_BLOCKING_TIMEOUT)
.get()
.map(UUIDMapping::getUuid)
.orElse(null)
);
}
}
public static final class TargetParseException extends ParserException {
@Serial
private static final long serialVersionUID = 927476591631527552L;
private final String input;
/**
* Construct a new Player parse exception
*
* @param input String input
* @param context Command context
*/
public TargetParseException(
final @NonNull String input,
final @NonNull CommandContext<?> context
) {
super(
PlotMemberParser.class,
context,
PlotSquaredCaptionKeys.ARGUMENT_PARSE_FAILURE_TARGET,
CaptionVariable.of("input", input)
);
this.input = input;
}
/**
* Get the supplied input
*
* @return String value
*/
public @NonNull String getInput() {
return this.input;
}
}
}

View File

@ -0,0 +1,17 @@
package com.plotsquared.core.commands.requirements;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Repeatable(Requirements.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Requirement {
@NonNull RequirementType value();
}

View File

@ -0,0 +1,36 @@
package com.plotsquared.core.commands.requirements;
import com.plotsquared.core.configuration.caption.Caption;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
public enum RequirementType {
PLAYER(""),
IN_PLOT("errors.not_in_plot"),
PLOT_HAS_OWNER("info.plot_unowned", IN_PLOT),
IS_OWNER("permission.no_plot_perms", PLOT_HAS_OWNER);
private final Caption caption;
private @NonNull Set<@NonNull RequirementType> inheritedRequirements;
RequirementType(
final String caption,
final @NonNull RequirementType... inheritedRequirements
) {
this.caption = TranslatableCaption.of(caption);
this.inheritedRequirements = EnumSet.copyOf(Arrays.asList(inheritedRequirements));
}
public @NonNull Set<@NonNull RequirementType> inheritedRequirements() {
return Collections.unmodifiableSet(this.inheritedRequirements);
}
public @NonNull Caption caption() {
return this.caption;
}
}

View File

@ -0,0 +1,15 @@
package com.plotsquared.core.commands.requirements;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Requirements {
@NonNull Requirement @NonNull[] value();
}

View File

@ -36,6 +36,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -273,6 +274,25 @@ public class UUIDPipeline {
return this.getUUIDs(requests).orTimeout(timeout, TimeUnit.MILLISECONDS); return this.getUUIDs(requests).orTimeout(timeout, TimeUnit.MILLISECONDS);
} }
/**
* Asynchronously attempt to fetch the mapping from a name.
* <p>
* This will timeout after the specified time and throws a {@link TimeoutException}
* if this happens
*
* @param username Name
* @param timeout Timeout in milliseconds
* @return Mapping
*/
public @NonNull CompletableFuture<Optional<UUIDMapping>> getUUID(
final @NonNull String username,
final long timeout
) {
return this.getUUIDs(List.of(username), timeout).thenApply(
results -> results.stream().findFirst()
);
}
/** /**
* Asynchronously attempt to fetch the mapping from a list of UUIDs * Asynchronously attempt to fetch the mapping from a list of UUIDs
* *