Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to the Command API to support implementation #2066

Merged
merged 3 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 102 additions & 52 deletions src/main/java/org/spongepowered/api/command/Command.java

Large diffs are not rendered by default.

131 changes: 101 additions & 30 deletions src/main/java/org/spongepowered/api/command/CommandCause.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,27 @@
import org.spongepowered.api.SystemSubject;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.event.CauseStackManager;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.EventContext;
import org.spongepowered.api.event.cause.EventContextKeys;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.permission.SubjectCollection;
import org.spongepowered.api.service.permission.SubjectData;
import org.spongepowered.api.service.permission.SubjectReference;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.text.channel.MessageReceiver;
import org.spongepowered.api.util.Tristate;
import org.spongepowered.api.world.Locatable;
import org.spongepowered.api.world.ServerLocation;
import org.spongepowered.math.vector.Vector3d;
import org.spongepowered.plugin.PluginContainer;

import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
* The {@link CommandCause} represents the {@link Cause} of a command, and
Expand All @@ -61,14 +71,17 @@
* plate, the direct cause will be the command block. However, the player
* in question will also be present in the cause stack, allowing command
* providers to obtain richer information about the invocation of their command.
* This is inline with how {@link Cause}s work in Sponge and its events, for
* more information about how Causes work, see the {@link Cause} and
* {@link CauseStackManager} javadocs and associated documentation.
* </p>
*
* <p>The {@link EventContext} that is attached to {@link Cause#getContext()}
* <strong>may</strong> offer other indications as to how the command should
* be handled, in addition to using the provided cause stack:</p>
*
* <ul>
* <li>{@link EventContextKeys#MESSAGE_TARGET}, which indicates the
* <li>{@link EventContextKeys#MESSAGE_CHANNEL}, which indicates the
* where messages that should be sent back to the invoker should be sent
* to (typically messages indicating the status of the command execution);
* </li>
Expand All @@ -82,6 +95,10 @@
* the command should take into account when executing.</li>
* </ul>
*
* <p>This object acts as a {@link Subject}, and simply redirects all calls
* inherited by this interface to the subject returned by {@link #getSubject()}.
* </p>
*
* <p>There are utility methods on this interface that provide hints as to what
* the implementation will select for different tasks, for example, the
* implementation will use the result of {@link #getSubject()} for permission
Expand All @@ -93,15 +110,15 @@
* be taken a guarantee of what may be present, however, they indicate what
* typically would be of interest to command API consumers.</p>
*/
public interface CommandCause {
public interface CommandCause extends Subject {

/**
* Creates a {@link CommandCause} from the provided {@link Cause}
*
* @param cause The {@link Cause}
* @return The {@link CommandCause}
*/
static CommandCause of(Cause cause) {
static CommandCause of(final Cause cause) {
return Sponge.getRegistry().getFactoryRegistry().provideFactory(Factory.class).create(cause);
}

Expand Down Expand Up @@ -141,14 +158,14 @@ default Subject getSubject() {
}

/**
* Gets the {@link MessageReceiver} that should be the target for any
* Gets the {@link MessageChannel} that should be the target for any
* messages sent by the command (by default).
*
* <p>The {@link MessageReceiver} will be selected in the following way
* <p>The {@link MessageChannel} will be selected in the following way
* from the {@link Cause} in {@link #getCause()}:</p>
*
* <ul>
* <li>The {@link EventContextKeys#MESSAGE_TARGET}, if any</li>
* <li>The {@link EventContextKeys#MESSAGE_CHANNEL}, if any</li>
* <li>A message channel containing the <strong>first</strong>
* {@link MessageReceiver} in the {@link Cause}</li>
* <li>The SystemSubject {@link MessageReceiver}</li>
Expand All @@ -161,10 +178,10 @@ default Subject getSubject() {
*
* @return The {@link MessageReceiver} to send any messages to.
*/
default MessageReceiver getMessageReceiver() {
return getCause().getContext()
.get(EventContextKeys.MESSAGE_TARGET)
.orElseGet(() -> getCause().first(MessageReceiver.class).orElseGet(Sponge::getSystemSubject));
default MessageChannel getMessageChannel() {
return this.getCause().getContext()
.get(EventContextKeys.MESSAGE_CHANNEL)
.orElseGet(() -> MessageChannel.to(this.getCause().first(MessageReceiver.class).orElseGet(Sponge::getSystemSubject)));
}

/**
Expand All @@ -175,29 +192,24 @@ default MessageReceiver getMessageReceiver() {
* <ul>
* <li>The {@link EventContextKeys#LOCATION}, if any</li>
* <li>{@link #getTargetBlock()}</li>
* <li>The {@link EventContextKeys#MESSAGE_TARGET}, if it is
* {@link Locatable}</li>
* <li>the location of the first locatable in the {@link Cause}</li>
* </ul>
*
* @return The {@link ServerLocation}, if it exists
*/
@SuppressWarnings("unchecked")
default Optional<ServerLocation> getLocation() {
Cause cause = getCause();
EventContext eventContext = cause.getContext();
final Cause cause = this.getCause();
final EventContext eventContext = cause.getContext();
if (eventContext.containsKey(EventContextKeys.LOCATION)) {
return eventContext.get(EventContextKeys.LOCATION);
}

Optional<ServerLocation> optionalLocation = getTargetBlock().flatMap(BlockSnapshot::getLocation);
final Optional<ServerLocation> optionalLocation = this.getTargetBlock().flatMap(BlockSnapshot::getLocation);
if (optionalLocation.isPresent()) {
return optionalLocation;
}

return eventContext.get(EventContextKeys.MESSAGE_TARGET)
.filter(x -> x instanceof Locatable)
.flatMap(x -> ((Locatable) x).getLocation().onServer());
return cause.first(Locatable.class).map(Locatable::getServerLocation);
}

/**
Expand All @@ -207,25 +219,19 @@ default Optional<ServerLocation> getLocation() {
*
* <ul>
* <li>The {@link EventContextKeys#ROTATION}, if any</li>
* <li>The {@link EventContextKeys#MESSAGE_TARGET}, if it has a
* rotation</li>
* <li>the rotation of the first {@link Entity} in the {@link Cause}</li>
* </ul>
*
* @return The {@link Vector3d} rotation, if it exists
*/
default Optional<Vector3d> getRotation() {
Cause cause = getCause();
EventContext eventContext = cause.getContext();
final Cause cause = this.getCause();
final EventContext eventContext = cause.getContext();
if (eventContext.containsKey(EventContextKeys.ROTATION)) {
return eventContext.get(EventContextKeys.ROTATION);
}

return Optional.ofNullable(
eventContext.get(EventContextKeys.MESSAGE_TARGET)
.filter(x -> x instanceof Entity)
.map(x -> ((Entity) x).getRotation())
.orElseGet(() -> cause.first(Entity.class).map(Entity::getRotation).orElse(null)));
return cause.first(Entity.class).map(Entity::getRotation);
}

/**
Expand All @@ -241,8 +247,73 @@ default Optional<Vector3d> getRotation() {
* @return The {@link BlockSnapshot} if applicable, or an empty optional.
*/
default Optional<BlockSnapshot> getTargetBlock() {
return Optional.ofNullable(getCause().getContext().get(EventContextKeys.BLOCK_TARGET)
.orElseGet(() -> getCause().first(BlockSnapshot.class).orElse(null)));
return Optional.ofNullable(this.getCause().getContext().get(EventContextKeys.BLOCK_TARGET)
.orElseGet(() -> this.getCause().first(BlockSnapshot.class).orElse(null)));
}

@Override
default SubjectCollection getContainingCollection() {
return getSubject().getContainingCollection();
}

@Override
default SubjectReference asSubjectReference() {
return getSubject().asSubjectReference();
}

@Override
default boolean isSubjectDataPersisted() {
return getSubject().isSubjectDataPersisted();
}

@Override
default SubjectData getSubjectData() {
return getSubject().getSubjectData();
}

@Override
default SubjectData getTransientSubjectData() {
return getSubject().getTransientSubjectData();
}

@Override
default Tristate getPermissionValue(Set<Context> contexts, String permission) {
return getSubject().getPermissionValue(contexts, permission);
}

@Override
default boolean isChildOf(Set<Context> contexts, SubjectReference parent) {
return getSubject().isChildOf(contexts, parent);
}

@Override
default List<SubjectReference> getParents(Set<Context> contexts) {
return getSubject().getParents();
}

@Override
default Optional<String> getOption(Set<Context> contexts, String key) {
return getSubject().getOption(contexts, key);
}

@Override
default String getIdentifier() {
return getSubject().getIdentifier();
}

@Override
default Set<Context> getActiveContexts() {
return getSubject().getActiveContexts();
}

/**
* Sends a message to the {@link MessageChannel} as given by
* {@link #getMessageChannel()}.
*
* @param message The message to send.
*/
default void sendMessage(Text message) {
getMessageChannel().send(message);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class ArgumentParseException extends CommandException {
* @param source The source string being parsed
* @param position The current position in the source string
*/
public ArgumentParseException(Text message, String source, int position) {
public ArgumentParseException(final Text message, final String source, final int position) {
super(message, true);
this.source = source;
this.position = position;
Expand All @@ -58,15 +58,15 @@ public ArgumentParseException(Text message, String source, int position) {
* @param source The source string being parsed
* @param position The current position in the source string
*/
public ArgumentParseException(Text message, Throwable cause, String source, int position) {
public ArgumentParseException(final Text message, final Throwable cause, final String source, final int position) {
super(message, cause, true);
this.source = source;
this.position = position;
}

@Override
public Text getText() {
Text superText = super.getText();
final Text superText = super.getText();
if (this.source == null || this.source.isEmpty()) {
return super.getText();
} else if (superText == null) {
Expand All @@ -91,8 +91,8 @@ public String getAnnotatedPosition() {
int position = this.position;
if (source.length() > 80) {
if (position >= 37) {
int startPos = position - 37;
int endPos = Math.min(source.length(), position + 37);
final int startPos = position - 37;
final int endPos = Math.min(source.length(), position + 37);
if (endPos < source.length()) {
source = "..." + source.substring(startPos, endPos) + "...";
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
/**
* Indicates that a command could not be registered.
*/
public class FailedRegistrationException extends RuntimeException {
public class CommandFailedRegistrationException extends RuntimeException {

private static final long serialVersionUID = -783923658025L;

public FailedRegistrationException(String message) {
public CommandFailedRegistrationException(String message) {
super(message);
}

public FailedRegistrationException(String message, Throwable inner) {
public CommandFailedRegistrationException(String message, Throwable inner) {
super(message, inner);
}

Expand Down
Loading