-
-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement
DynamicMultiLiteralArgument
Acts like `MultiLiteralArgument`, but literals can be changed and differ for each CommandSender Resolves #513 Uses Paper's `AsyncPlayerSendCommandsEvent` and Velocity's `PlayerAvailableCommandsEvent` to change the client's view of the command tree. Note that a similar event does not exist on Spigot, so the suggestions are not dynamic, though the parsing still is. `DifferentClientNode` class added to handle creating client-server command tree de-syncs (I believe this can be used to make `FlagsArgument` platform-agnostic)
- Loading branch information
1 parent
07bae2e
commit 52b47f2
Showing
15 changed files
with
689 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
...-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package dev.jorel.commandapi.arguments; | ||
|
||
import com.mojang.brigadier.builder.ArgumentBuilder; | ||
import dev.jorel.commandapi.commandnodes.DynamicMultiLiteralArgumentBuilder; | ||
|
||
import java.util.List; | ||
|
||
public interface DynamicMultiLiteralArgumentCommon<Argument | ||
/// @cond DOX | ||
extends AbstractArgument<?, ?, ?, ?> | ||
/// @endcond | ||
, CommandSender> { | ||
// DynamicMultiLiteralArgument info | ||
@FunctionalInterface | ||
interface LiteralsCreator<CommandSender> { | ||
List<String> createLiterals(CommandSender sender); | ||
} | ||
|
||
LiteralsCreator<CommandSender> getLiteralsCreator(); | ||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
// LINKED METHODS // | ||
// These automatically link to methods in AbstractArgument (make sure they have the same signature) // | ||
/////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
|
||
/** | ||
* Links to {@link AbstractArgument#getNodeName()}. | ||
*/ | ||
String getNodeName(); | ||
|
||
/** | ||
* Links to {@link AbstractArgument#isListed()}. | ||
*/ | ||
boolean isListed(); | ||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
// OVERRIDING METHODS // | ||
// A DynamicMultiLiteralArgument has special logic that should override the implementations in AbstractArgument // | ||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. | ||
* <p> | ||
* We want to use DynamicMultiLiteralArgumentBuilder rather than RequiredArgumentBuilder. | ||
*/ | ||
default <Source> ArgumentBuilder<Source, ?> createArgumentBuilder(List<Argument> previousArguments, List<String> previousArgumentNames) { | ||
String name = getNodeName(); | ||
boolean isListed = isListed(); | ||
LiteralsCreator<CommandSender> literalsCreator = getLiteralsCreator(); | ||
|
||
previousArguments.add((Argument) this); | ||
if (isListed()) previousArgumentNames.add(name); | ||
|
||
return DynamicMultiLiteralArgumentBuilder.dynamicMultiLiteral(name, isListed, literalsCreator); | ||
} | ||
} |
148 changes: 148 additions & 0 deletions
148
commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package dev.jorel.commandapi.commandnodes; | ||
|
||
import com.mojang.brigadier.Command; | ||
import com.mojang.brigadier.RedirectModifier; | ||
import com.mojang.brigadier.StringReader; | ||
import com.mojang.brigadier.arguments.ArgumentType; | ||
import com.mojang.brigadier.context.CommandContext; | ||
import com.mojang.brigadier.context.CommandContextBuilder; | ||
import com.mojang.brigadier.exceptions.CommandSyntaxException; | ||
import com.mojang.brigadier.suggestion.Suggestions; | ||
import com.mojang.brigadier.suggestion.SuggestionsBuilder; | ||
import com.mojang.brigadier.tree.ArgumentCommandNode; | ||
import com.mojang.brigadier.tree.CommandNode; | ||
import dev.jorel.commandapi.CommandAPIHandler; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.Predicate; | ||
|
||
public abstract class DifferentClientNode<Source, T> extends ArgumentCommandNode<Source, T> { | ||
// Rewrite node trees for the client | ||
public static <Source> void rewriteAllChildren(Source client, CommandNode<Source> parent) { | ||
// Copy the children, as we do expect them to be modified | ||
List<CommandNode<Source>> children = new ArrayList<>(parent.getChildren()); | ||
for (CommandNode<Source> child : children) { | ||
rewriteChild(client, parent, child); | ||
} | ||
} | ||
|
||
public static <Source> void rewriteChild(Source client, CommandNode<Source> parent, CommandNode<Source> child) { | ||
// Get the node creator | ||
DifferentClientNode<Source, ?> clientNodeCreator = null; | ||
if (child instanceof DifferentClientNode<?, ?>) { | ||
// child is directly a DifferentClientNode | ||
clientNodeCreator = (DifferentClientNode<Source, ?>) child; | ||
} else if (child instanceof ArgumentCommandNode<Source, ?> argument && argument.getType() instanceof Type<?, ?>) { | ||
// child was copied from a DifferentClientNode using `createBuilder` (real node hidden in ArgumentType) | ||
Type<Source, ?> type = (Type<Source, ?>) argument.getType(); | ||
clientNodeCreator = type.node; | ||
} | ||
|
||
if (clientNodeCreator != null) { | ||
// Get the new client nodes | ||
List<CommandNode<Source>> clientNodes = clientNodeCreator.rewriteNodeForClient(client); | ||
|
||
// Inject client node | ||
Map<String, CommandNode<Source>> children = CommandAPIHandler.getCommandNodeChildren(parent); | ||
children.remove(child.getName()); | ||
for (CommandNode<Source> clientNode : clientNodes) { | ||
children.put(clientNode.getName(), clientNode); | ||
} | ||
} | ||
|
||
// Modify all children | ||
rewriteAllChildren(client, child); | ||
} | ||
|
||
// Node information | ||
private final ArgumentType<T> type; | ||
|
||
public DifferentClientNode( | ||
String name, ArgumentType<T> type, | ||
Command<Source> command, Predicate<Source> requirement, | ||
CommandNode<Source> redirect, RedirectModifier<Source> modifier, boolean forks | ||
) { | ||
// Platforms often copy this node as a normal ArgumentCommandNode, | ||
// but we can hide a reference to ourselves in the ArgumentType used | ||
// when ArgumentCommandNode#createBuilder is called | ||
super(name, new Type<Source, T>(), command, requirement, redirect, modifier, forks, null); | ||
|
||
((Type<Source, T>) super.getType()).node = this; | ||
|
||
// This type actually represents this argument when serializing nodes to json | ||
// and also helps this argument blend in with ambiguity checks | ||
this.type = type; | ||
} | ||
|
||
// Handle type nonsense | ||
private static class Type<Source, T> implements ArgumentType<T> { | ||
private DifferentClientNode<Source, T> node; | ||
|
||
@Override | ||
public T parse(StringReader stringReader) { | ||
throw new IllegalStateException("Not supposed to be called"); | ||
} | ||
} | ||
|
||
@Override | ||
public ArgumentType<T> getType() { | ||
return type; | ||
} | ||
|
||
@Override | ||
public boolean isValidInput(String input) { | ||
try { | ||
StringReader reader = new StringReader(input); | ||
this.type.parse(reader); | ||
return !reader.canRead() || reader.peek() == ' '; | ||
} catch (CommandSyntaxException var3) { | ||
return false; | ||
} | ||
} | ||
|
||
@Override | ||
public Collection<String> getExamples() { | ||
return this.type.getExamples(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) return true; | ||
if (!(obj instanceof DifferentClientNode<?, ?> other)) return false; | ||
|
||
return this.type.equals(other.type) && super.equals(other); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = this.type.hashCode(); | ||
result = 31 * result + super.hashCode(); | ||
return result; | ||
} | ||
|
||
// Special client-side nodes | ||
|
||
/** | ||
* Transforms this node into one the client should see. | ||
* | ||
* @param client The client who the new node is for. If this is null, | ||
* the generated node should just be for any general client. | ||
* @return The version of this {@link CommandNode} the client should see. This is a list, so one server | ||
* node may become many on the client. If the list is empty, the client will not see any nodes. | ||
*/ | ||
public abstract List<CommandNode<Source>> rewriteNodeForClient(Source client); | ||
|
||
// Require inheritors to redefine these methods from ArgumentCommandNode | ||
@Override | ||
public abstract void parse(StringReader reader, CommandContextBuilder<Source> contextBuilder) throws CommandSyntaxException; | ||
|
||
@Override | ||
public abstract CompletableFuture<Suggestions> listSuggestions(CommandContext<Source> context, SuggestionsBuilder builder) throws CommandSyntaxException; | ||
|
||
@Override | ||
public abstract String toString(); | ||
} |
58 changes: 58 additions & 0 deletions
58
...e/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package dev.jorel.commandapi.commandnodes; | ||
|
||
import com.mojang.brigadier.builder.ArgumentBuilder; | ||
import com.mojang.brigadier.tree.CommandNode; | ||
import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; | ||
|
||
public class DynamicMultiLiteralArgumentBuilder<CommandSender, Source> extends ArgumentBuilder<Source, DynamicMultiLiteralArgumentBuilder<CommandSender, Source>> { | ||
// Build | ||
private final String name; | ||
private final boolean isListed; | ||
private final LiteralsCreator<CommandSender> literalsCreator; | ||
|
||
public DynamicMultiLiteralArgumentBuilder(String name, boolean isListed, LiteralsCreator<CommandSender> literalsCreator) { | ||
this.name = name; | ||
this.isListed = isListed; | ||
this.literalsCreator = literalsCreator; | ||
} | ||
|
||
public static <CommandSender, Source> DynamicMultiLiteralArgumentBuilder<CommandSender, Source> dynamicMultiLiteral( | ||
String name, boolean isListed, LiteralsCreator<CommandSender> literalsCreator | ||
) { | ||
return new DynamicMultiLiteralArgumentBuilder<>(name, isListed, literalsCreator); | ||
} | ||
|
||
// Getters | ||
@Override | ||
protected DynamicMultiLiteralArgumentBuilder<CommandSender, Source> getThis() { | ||
return this; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public boolean isListed() { | ||
return isListed; | ||
} | ||
|
||
public LiteralsCreator<CommandSender> getLiteralsCreator() { | ||
return literalsCreator; | ||
} | ||
|
||
// Create node | ||
@Override | ||
public DynamicMultiLiteralCommandNode<CommandSender, Source> build() { | ||
final DynamicMultiLiteralCommandNode<CommandSender, Source> result = new DynamicMultiLiteralCommandNode<>( | ||
name, isListed, literalsCreator, | ||
getCommand(), getRequirement(), | ||
getRedirect(), getRedirectModifier(), isFork() | ||
); | ||
|
||
for (final CommandNode<Source> argument : getArguments()) { | ||
result.addChild(argument); | ||
} | ||
|
||
return result; | ||
} | ||
} |
Oops, something went wrong.