Merge branch 'master' into command-parser-rework

# Conflicts:
#	src/main/java/net/minestom/server/command/CommandManager.java
#	src/main/java/net/minestom/server/command/GraphBuilder.java
#	src/main/java/net/minestom/server/command/GraphConverter.java
#	src/main/java/net/minestom/server/command/Node.java
#	src/main/java/net/minestom/server/command/NodeGraph.java
#	src/test/java/net/minestom/server/command/ArgumentTypeTest.java
#	src/test/java/net/minestom/server/command/NodeGraphTest.java
This commit is contained in:
Noel Németh 2022-07-13 23:06:45 +02:00
commit 6884c11e38
29 changed files with 1516 additions and 427 deletions

View File

@ -8,7 +8,7 @@ kotlin = "1.6.20"
hydrazine = "1.7.2"
dependencyGetter = "v1.0.1"
minestomData = "3e211f3953"
hephaistos = "2.4.4"
hephaistos = "2.4.8"
jetbrainsAnnotations = "23.0.0"
# Terminal / Logging
@ -102,4 +102,4 @@ kotlin = ["kotlin-stdlib-jdk8", "kotlin-reflect"]
flare = ["flare", "flare-fastutil"]
adventure = ["adventure-api", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain"]
logging = ["tinylog-api", "tinylog-impl", "tinylog-slf4j"]
terminal = ["jline", "jline-jansi"]
terminal = ["jline", "jline-jansi"]

View File

@ -293,7 +293,7 @@ final class BlockCollision {
continue;
final boolean intersects;
if (type == EntityType.PLAYER) {
if (entity instanceof Player) {
// Ignore spectators
if (((Player) entity).getGameMode() == GameMode.SPECTATOR)
continue;

View File

@ -157,8 +157,7 @@ public final class CommandManager {
* @return the {@link DeclareCommandsPacket} for {@code player}
*/
public @NotNull DeclareCommandsPacket createDeclareCommandsPacket(@NotNull Player player) {
final NodeGraph nodeGraph = GraphBuilder.forPlayer(this.dispatcher.getCommands(), player);
System.out.println(nodeGraph.exportGarphvizDot()); //TODO remove before merging
return GraphConverter.createPacket(nodeGraph);
final Graph merged = Graph.merge(dispatcher.getCommands());
return GraphConverter.createPacket(merged, player);
}
}

View File

@ -0,0 +1,61 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.Argument;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
sealed interface Graph permits GraphImpl {
static @NotNull Builder builder(@NotNull Argument<?> argument) {
return new GraphImpl.BuilderImpl(argument);
}
static @NotNull Graph fromCommand(@NotNull Command command) {
return GraphImpl.fromCommand(command);
}
static @NotNull Graph merge(@NotNull Collection<@NotNull Command> commands) {
return GraphImpl.merge(commands);
}
static @NotNull Graph merge(@NotNull List<@NotNull Graph> graphs) {
return GraphImpl.merge(graphs);
}
static @NotNull Graph merge(@NotNull Graph @NotNull ... graphs) {
return merge(List.of(graphs));
}
@NotNull Node root();
boolean compare(@NotNull Graph graph, @NotNull Comparator comparator);
sealed interface Node permits GraphImpl.NodeImpl {
@NotNull Argument<?> argument();
@UnknownNullability Executor executor();
@NotNull List<@NotNull Node> next();
}
sealed interface Executor extends Predicate<CommandSender> permits GraphImpl.ExecutorImpl {
// TODO execute the node
}
sealed interface Builder permits GraphImpl.BuilderImpl {
@NotNull Builder append(@NotNull Argument<?> argument, @NotNull Consumer<Builder> consumer);
@NotNull Builder append(@NotNull Argument<?> argument);
@NotNull Graph build();
}
enum Comparator {
TREE
}
}

View File

@ -1,187 +0,0 @@
package net.minestom.server.command;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
import net.minestom.server.command.builder.arguments.number.ArgumentLong;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.exception.IllegalCommandStructureException;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public final class GraphBuilder {
private static final List<Class<? extends Argument<?>>> argPriorities = List.of(
ArgumentInteger.class, ArgumentLong.class, ArgumentFloat.class, ArgumentDouble.class //TODO the rest
);
private final AtomicInteger idSource = new AtomicInteger();
private final ObjectList<Node> nodes = new ObjectArrayList<>();
private final Node root = rootNode();
private GraphBuilder() {
//no instance
}
private Node rootNode() {
final Node rootNode = Node.root(idSource.getAndIncrement());
nodes.add(rootNode);
return rootNode;
}
private Node createLiteralNode(String name, @Nullable Node parent, boolean executable, @Nullable String[] aliases,
@Nullable AtomicInteger redirectTo, @Nullable Argument<?> argOverride) {
if (aliases != null) {
final Node node = createLiteralNode(name, parent, executable, null, null, argOverride);
for (String alias : aliases) {
createLiteralNode(alias, parent, executable, null, new AtomicInteger(node.id()), argOverride);
}
return node;
} else {
final Node literalNode = Node.literal(idSource.getAndIncrement(), name, executable, redirectTo,
Objects.requireNonNullElseGet(argOverride, () -> new ArgumentLiteral(name)));
nodes.add(literalNode);
if (parent != null) parent.addChildren(literalNode);
return literalNode;
}
}
private Node[] createArgumentNode(Argument<?> argument, boolean executable, @Nullable AtomicInteger redirectTarget) {
// TODO Ensure node args are overridden properly where necessary
final Node[] nodes;
if (argument instanceof ArgumentEnum<?> argumentEnum) {
return argumentEnum.entries().stream().map(x -> createLiteralNode(x, null, executable, null, null, argumentEnum)).toArray(Node[]::new);
} else if (argument instanceof ArgumentGroup argumentGroup) {
return argumentGroup.group().stream().map(x -> createArgumentNode(x, executable, redirectTarget)).flatMap(Stream::of).toArray(Node[]::new);
} else if (argument instanceof ArgumentLoop<?> argumentLoop) {
final AtomicInteger target = new AtomicInteger(idSource.get() - 1);
return argumentLoop.arguments().stream().map(x -> createArgumentNode(x, executable, target)).flatMap(Stream::of).toArray(Node[]::new);
} else {
if (argument instanceof ArgumentCommand) {
return new Node[]{createLiteralNode(argument.getId(), null, false, null, new AtomicInteger(0), argument)};
}
nodes = new Node[] {Node.argument(idSource.getAndIncrement(), argument, executable, redirectTarget)};
}
this.nodes.addAll(Arrays.asList(nodes));
return nodes;
}
private void finalizeStructure(boolean forParsing) {
nodes.sort(Comparator.comparing(Node::id));
if (forParsing) {
for (Node node : nodes) {
node.children().sort((k1, k2) -> Integer.compare(argPriorities.indexOf(nodes.get(k1).arg().getClass()),
argPriorities.indexOf(nodes.get(k2).arg().getClass())));
}
}
}
/**
* Creates the nodes for the given command
*
* @param command the command to add
* @param parent where to append the command's root (literal) node
* @param player a player if we should filter commands
*/
private void createCommand(Command command, Node parent, @Nullable Player player) {
if (player != null) {
// Check if user can use the command
final CommandCondition condition = command.getCondition();
if (condition != null && !condition.canUse(player, null)) return;
}
// Create the command's root node
final Node cmdNode = createLiteralNode(command.getName(), parent,
command.getDefaultExecutor() != null, command.getAliases(), null, null);
cmdNode.executionInfo().set(new Node.ExecutionInfo(command.getCondition(), command.getDefaultExecutor()));
// Add syntax to the command
for (CommandSyntax syntax : command.getSyntaxes()) {
final CommandCondition syntaxCondition = syntax.getCommandCondition();
if (player != null) {
// Check if user can use the syntax
if (syntaxCondition != null && !syntaxCondition.canUse(player, null)) continue;
}
boolean executable = false;
Node[] lastArgNodes = new Node[] {cmdNode}; // First arg links to cmd root
@NotNull Argument<?>[] arguments = syntax.getArguments();
for (int i = 0; i < arguments.length; i++) {
Argument<?> argument = arguments[i];
// Determine if command is executable here
if (executable && argument.getDefaultValue() == null) {
// Optional arg was followed by a non-optional
throw new IllegalCommandStructureException("Optional argument was followed by a non-optional one.");
}
if (!executable && i < arguments.length-1 && arguments[i+1].getDefaultValue() != null || i+1 == arguments.length) {
executable = true;
}
// Append current node to previous
final Node[] argNodes = createArgumentNode(argument, executable, null);
for (Node lastArgNode : lastArgNodes) {
lastArgNode.addChildren(argNodes);
}
// Populate execution info
for (Node argNode : argNodes) {
argNode.executionInfo().set(new Node.ExecutionInfo(
// Syntax or command condition
syntaxCondition == null ? command.getCondition() : syntaxCondition,
// Syntax executor or default
executable ? syntax.getExecutor() : command.getDefaultExecutor()));
}
lastArgNodes = argNodes;
}
}
// Add subcommands
for (Command subcommand : command.getSubcommands()) {
createCommand(subcommand, cmdNode, player);
}
}
public static NodeGraph forPlayer(@NotNull Set<Command> commands, Player player) {
final GraphBuilder builder = new GraphBuilder();
if (GraphBuilder.class.desiredAssertionStatus()) {
// Detect infinite recursion
for (Command command : commands) {
final HashSet<Command> processed = new HashSet<>();
final Stack<Command> stack = new Stack<>();
stack.push(command);
while (!stack.isEmpty()) {
final Command pop = stack.pop();
if (!processed.add(pop)) {
throw new IllegalCommandStructureException("Infinite recursion detected in command: "+command.getName());
} else {
stack.addAll(pop.getSubcommands());
}
}
builder.createCommand(command, builder.root, player);
}
} else {
for (Command command : commands) {
builder.createCommand(command, builder.root, player);
}
}
builder.finalizeStructure(player == null);
return new NodeGraph(builder.nodes, builder.root);
}
public static NodeGraph forServer(@NotNull Set<Command> commands) {
return forPlayer(commands, null);
}
}

View File

@ -1,41 +1,164 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.arguments.ArgumentLiteral;
import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
final class GraphConverter {
private GraphConverter() {
//no instance
}
public static DeclareCommandsPacket.Node getPacketNode(Node node) {
final DeclareCommandsPacket.Node packetNode = new DeclareCommandsPacket.Node();
packetNode.children = node.children().toIntArray();
final Argument<?> arg = node.arg();
DeclareCommandsPacket.NodeType type = arg == null ? DeclareCommandsPacket.NodeType.ROOT :
arg instanceof ArgumentLiteral ? DeclareCommandsPacket.NodeType.LITERAL :
DeclareCommandsPacket.NodeType.ARGUMENT;
packetNode.flags = DeclareCommandsPacket.getFlag(type, node.executable(), node.redirectTarget() != null,
type == DeclareCommandsPacket.NodeType.ARGUMENT && arg.hasSuggestion());
packetNode.name = arg == null ? null : arg.getId();
if (node.redirectTarget() != null) {
packetNode.redirectedNode = node.redirectTarget().get();
@Contract("_, _ -> new")
public static DeclareCommandsPacket createPacket(Graph graph, @Nullable Player player) {
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
List<Consumer<Integer>> rootRedirect = new ArrayList<>();
final AtomicInteger idSource = new AtomicInteger(0);
final int rootId = append(graph.root(), nodes, rootRedirect, idSource, null, null, player)[0];
for (var i : rootRedirect) {
i.accept(rootId);
}
if (type == DeclareCommandsPacket.NodeType.ARGUMENT) {
packetNode.properties = arg.nodeProperties();
packetNode.parser = arg.parser();
if (arg.hasSuggestion()) {
//noinspection ConstantConditions
packetNode.suggestionsType = arg.suggestionType().getIdentifier();
}
}
return packetNode;
return new DeclareCommandsPacket(nodes, rootId);
}
@Contract("_ -> new")
public static DeclareCommandsPacket createPacket(NodeGraph graph) {
return new DeclareCommandsPacket(graph.nodes().stream().map(GraphConverter::getPacketNode).toList(), graph.root().id());
private static int[] append(Graph.Node graphNode, List<DeclareCommandsPacket.Node> to,
List<Consumer<Integer>> rootRedirect, AtomicInteger id, @Nullable AtomicInteger redirect,
List<Runnable> redirectSetters, @Nullable Player player) {
final Graph.Executor executor = graphNode.executor();
if (player != null && executor != null) {
if (!executor.test(player)) return new int[0];
}
final Argument<?> argument = graphNode.argument();
final List<Graph.Node> children = graphNode.next();
final DeclareCommandsPacket.Node node = new DeclareCommandsPacket.Node();
int[] packetNodeChildren = new int[children.size()];
for (int i = 0; i < packetNodeChildren.length; i++) {
final int[] append = append(children.get(i), to, rootRedirect, id, redirect, redirectSetters, player);
if (append.length == 1) {
packetNodeChildren[i] = append[0];
} else {
packetNodeChildren = Arrays.copyOf(packetNodeChildren, packetNodeChildren.length + append.length - 1);
System.arraycopy(append, 0, packetNodeChildren, i, append.length);
i += append.length;
}
}
node.children = packetNodeChildren;
if (argument instanceof ArgumentLiteral literal) {
if (literal.getId().isEmpty()) {
node.flags = 0; //root
} else {
node.flags = literal(false, false);
node.name = argument.getId();
if (redirect != null) {
node.flags |= 0x8;
redirectSetters.add(() -> node.redirectedNode = redirect.get());
}
}
to.add(node);
return new int[]{id.getAndIncrement()};
} else {
if (argument instanceof ArgumentCommand) {
node.flags = literal(false, true);
node.name = argument.getId();
rootRedirect.add(i -> node.redirectedNode = i);
to.add(node);
return new int[]{id.getAndIncrement()};
} else if (argument instanceof ArgumentEnum<?> || (argument instanceof ArgumentWord word && word.hasRestrictions())) {
List<String> entries = argument instanceof ArgumentEnum<?> ? ((ArgumentEnum<?>) argument).entries() : Arrays.stream(((ArgumentWord) argument).getRestrictions()).toList();
final int[] res = new int[entries.size()];
for (int i = 0; i < res.length; i++) {
String entry = entries.get(i);
final DeclareCommandsPacket.Node subNode = new DeclareCommandsPacket.Node();
subNode.children = node.children;
subNode.flags = literal(false, false);
subNode.name = entry;
if (redirect != null) {
subNode.flags |= 0x8;
redirectSetters.add(() -> subNode.redirectedNode = redirect.get());
}
to.add(subNode);
res[i] = id.getAndIncrement();
}
return res;
} else if (argument instanceof ArgumentGroup special) {
List<Argument<?>> entries = special.group();
int[] res = null;
int[] last = new int[0];
for (int i = 0; i < entries.size(); i++) {
Argument<?> entry = entries.get(i);
if (i == entries.size() - 1) {
// Last will be the parent of next args
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, rootRedirect, id, redirect, redirectSetters, player);
for (int n : l) {
to.get(n).children = node.children;
}
for (int n : last) {
to.get(n).children = l;
}
return res == null ? l : res;
} else if (i == 0) {
// First will be the children & parent of following
res = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, rootRedirect, id, null, redirectSetters, player);
last = res;
} else {
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, rootRedirect, id, null, redirectSetters, player);
for (int n : last) {
to.get(n).children = l;
}
last = l;
}
}
throw new RuntimeException("Arg group must have child args.");
} else if (argument instanceof ArgumentLoop special) {
AtomicInteger r = new AtomicInteger();
List<Runnable> setters = new ArrayList<>();
int[] res = new int[special.arguments().size()];
List<?> arguments = special.arguments();
for (int i = 0; i < arguments.size(); i++) {
Object arg = arguments.get(i);
final int[] append = append(new GraphImpl.NodeImpl((Argument<?>) arg, null, List.of()), to, rootRedirect, id, r, setters, player);
if (append.length == 1) {
res[i] = append[0];
} else {
res = Arrays.copyOf(res, res.length + append.length - 1);
System.arraycopy(append, 0, res, i, append.length);
i += append.length;
}
}
r.set(id.get());
setters.forEach(Runnable::run);
return res;
} else {
node.flags = arg(false, argument.hasSuggestion());
node.name = argument.getId();
node.parser = argument.parser();
node.properties = argument.nodeProperties();
if (redirect != null) {
node.flags |= 0x8;
redirectSetters.add(() -> node.redirectedNode = redirect.get());
}
to.add(node);
return new int[]{id.getAndIncrement()};
}
}
}
private static byte literal(boolean executable, boolean hasRedirect) {
return DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, hasRedirect, false);
}
private static byte arg(boolean executable, boolean hasSuggestion) {
return DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.ARGUMENT, executable, false, hasSuggestion);
}
}

View File

@ -0,0 +1,153 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.condition.CommandCondition;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
record GraphImpl(NodeImpl root) implements Graph {
static GraphImpl fromCommand(Command command) {
return new GraphImpl(NodeImpl.command(command));
}
static Graph merge(Collection<Command> commands) {
return new GraphImpl(NodeImpl.rootCommands(commands));
}
static GraphImpl merge(List<Graph> graphs) {
final List<Node> children = graphs.stream().map(Graph::root).toList();
final NodeImpl root = new NodeImpl(Literal(""), null, children);
return new GraphImpl(root);
}
@Override
public boolean compare(@NotNull Graph graph, @NotNull Comparator comparator) {
return compare(root, graph.root(), comparator);
}
record BuilderImpl(Argument<?> argument, List<BuilderImpl> children) implements Graph.Builder {
public BuilderImpl(Argument<?> argument) {
this(argument, new ArrayList<>());
}
@Override
public Graph.@NotNull Builder append(@NotNull Argument<?> argument, @NotNull Consumer<Graph.Builder> consumer) {
BuilderImpl builder = new BuilderImpl(argument);
consumer.accept(builder);
this.children.add(builder);
return this;
}
@Override
public Graph.@NotNull Builder append(@NotNull Argument<?> argument) {
this.children.add(new BuilderImpl(argument, List.of()));
return this;
}
@Override
public @NotNull GraphImpl build() {
return new GraphImpl(NodeImpl.fromBuilder(this));
}
}
record NodeImpl(Argument<?> argument, ExecutorImpl executor, List<Graph.Node> next) implements Graph.Node {
static NodeImpl fromBuilder(BuilderImpl builder) {
final List<BuilderImpl> children = builder.children;
Node[] nodes = new NodeImpl[children.size()];
for (int i = 0; i < children.size(); i++) nodes[i] = fromBuilder(children.get(i));
return new NodeImpl(builder.argument, null, List.of(nodes));
}
static NodeImpl command(Command command) {
return ConversionNode.fromCommand(command).toNode();
}
static NodeImpl rootCommands(Collection<Command> commands) {
return ConversionNode.rootConv(commands).toNode();
}
}
record ExecutorImpl(Predicate<CommandSender> predicate) implements Graph.Executor {
@Override
public boolean test(CommandSender commandSender) {
return predicate.test(commandSender);
}
static ExecutorImpl fromCommand(Command command) {
final CommandCondition condition = command.getCondition();
if (condition == null) return null;
return new ExecutorImpl(commandSender -> condition.canUse(commandSender, null));
}
static ExecutorImpl fromSyntax(CommandSyntax syntax) {
final CommandCondition condition = syntax.getCommandCondition();
if (condition == null) return null;
return new ExecutorImpl(commandSender -> condition.canUse(commandSender, null));
}
}
private record ConversionNode(Argument<?> argument, ExecutorImpl executor,
Map<Argument<?>, ConversionNode> nextMap) {
ConversionNode(Argument<?> argument, ExecutorImpl executor) {
this(argument, executor, new LinkedHashMap<>());
}
private NodeImpl toNode() {
Node[] nodes = new NodeImpl[nextMap.size()];
int i = 0;
for (var entry : nextMap.values()) nodes[i++] = entry.toNode();
return new NodeImpl(argument, executor, List.of(nodes));
}
static ConversionNode fromCommand(Command command) {
ConversionNode root = new ConversionNode(Literal(command.getName()), ExecutorImpl.fromCommand(command));
// Syntaxes
for (CommandSyntax syntax : command.getSyntaxes()) {
ConversionNode syntaxNode = root;
for (Argument<?> arg : syntax.getArguments()) {
boolean last = arg == syntax.getArguments()[syntax.getArguments().length - 1];
syntaxNode = syntaxNode.nextMap.computeIfAbsent(arg, argument -> {
var ex = last ? ExecutorImpl.fromSyntax(syntax) : null;
return new ConversionNode(argument, ex);
});
}
}
// Subcommands
for (Command subcommand : command.getSubcommands()) {
root.nextMap.put(Literal(subcommand.getName()), fromCommand(subcommand));
}
return root;
}
static ConversionNode rootConv(Collection<Command> commands) {
Map<Argument<?>, ConversionNode> next = new LinkedHashMap<>(commands.size());
for (Command command : commands) {
final ConversionNode conv = fromCommand(command);
next.put(conv.argument, conv);
}
return new ConversionNode(Literal(""), null, next);
}
}
static boolean compare(@NotNull Node first, Node second, @NotNull Comparator comparator) {
return switch (comparator) {
case TREE -> {
if (!first.argument().equals(second.argument())) yield false;
if (first.next().size() != second.next().size()) yield false;
for (int i = 0; i < first.next().size(); i++) {
if (!compare(first.next().get(i), second.next().get(i), comparator)) {
yield false;
}
}
yield true;
}
};
}
}

View File

@ -1,51 +0,0 @@
package net.minestom.server.command;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minestom.server.command.builder.CommandExecutor;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.arguments.ArgumentLiteral;
import net.minestom.server.command.builder.condition.CommandCondition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
record Node(int id, IntList children, boolean executable, Argument<?> arg, Argument<?> realArg, AtomicInteger redirectTarget,
AtomicReference<ExecutionInfo> executionInfo) {
public static Node root(int id) {
return new Node(id, new IntArrayList(), false, null, null, null,
new AtomicReference<>());
}
public static Node literal(int id, String name, boolean executable, @Nullable AtomicInteger redirectTarget) {
final ArgumentLiteral literal = new ArgumentLiteral(name);
return new Node(id, new IntArrayList(), executable, literal, literal, redirectTarget, new AtomicReference<>());
}
public static Node literal(int id, String name, boolean executable, @Nullable AtomicInteger redirectTarget, @NotNull Argument<?> backingArg) {
return new Node(id, new IntArrayList(), executable, new ArgumentLiteral(name), backingArg, redirectTarget, new AtomicReference<>());
}
public static Node argument(int id, Argument<?> argument, boolean executable, @Nullable AtomicInteger redirectTarget) {
return new Node(id, new IntArrayList(), executable, argument, argument, redirectTarget, new AtomicReference<>());
}
public void addChildren(Node ...nodes) {
for (Node node : nodes) {
children.add(node.id);
}
}
public boolean isParentOf(Node node) {
return children.contains(node.id());
}
public boolean isRoot() {
return arg == null;
}
public record ExecutionInfo(@Nullable CommandCondition condition, @Nullable CommandExecutor executor) {}
}

View File

@ -1,63 +0,0 @@
package net.minestom.server.command;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public record NodeGraph(List<Node> nodes, Node root) {
public Node resolveId(int id) {
return nodes.get(id);
}
public List<Node> getChildren(Node node) {
return node.children().intStream().mapToObj(this::resolveId).toList();
}
public @Nullable Node getRedirectTarget(Node node) {
if (node.redirectTarget() == null) return null;
return resolveId(node.redirectTarget().get());
}
public String exportGarphvizDot() {
final StringBuilder builder = new StringBuilder();
final char statementSeparator = ';';
builder.append("digraph G {");
builder.append("rankdir=LR");
builder.append(statementSeparator);
for (Node node : nodes) {
final AtomicInteger redirectTarget = node.redirectTarget();
builder.append(node.id());
builder.append(" [label=");
builder.append(graphvizName(node));
if (node.isRoot()) {
builder.append(",shape=rectangle");
}
if (node.executable()) {
builder.append(",bgcolor=gray,style=filled");
}
builder.append("]");
builder.append(statementSeparator);
if (node.children().isEmpty() && redirectTarget == null) continue;
builder.append(node.id());
builder.append(" -> { ");
if (!node.children().isEmpty()) {
builder.append(node.children().intStream().mapToObj(String::valueOf).collect(Collectors.joining(" ")));
builder.append(" }");
builder.append(statementSeparator);
} else {
builder.append(redirectTarget.get());
builder.append(" } [style = dotted]");
builder.append(statementSeparator);
}
}
builder.append("}");
return builder.toString();
}
private static String graphvizName(Node node) {
return '"' + (node.isRoot() ? "root" : node.arg().getId()) + '"';
}
}

View File

@ -203,7 +203,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
// Items picking
if (canPickupItem() && itemPickupCooldown.isReady(time)) {
itemPickupCooldown.refreshLastUpdate(time);
final Point loweredPosition = position.sub(0, -.5, 0);
final Point loweredPosition = position.sub(0, .5, 0);
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.width(),
EntityTracker.Target.ITEMS, itemEntity -> {
if (this instanceof Player player && !itemEntity.isViewer(player)) return;

View File

@ -333,7 +333,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Experience orb pickup
if (experiencePickupCooldown.isReady(time)) {
experiencePickupCooldown.refreshLastUpdate(time);
final Point loweredPosition = position.sub(0, -.5, 0);
final Point loweredPosition = position.sub(0, .5, 0);
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.width(),
EntityTracker.Target.EXPERIENCE_ORBS, experienceOrb -> {
if (expandedBoundingBox.intersectEntity(loweredPosition, experienceOrb)) {
@ -1446,17 +1446,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
}
CloseWindowPacket closeWindowPacket;
if (openInventory == null) {
closeWindowPacket = new CloseWindowPacket((byte) 0);
} else {
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
openInventory.removeViewer(this); // Clear cache
this.openInventory = null;
if (openInventory == getOpenInventory()) {
CloseWindowPacket closeWindowPacket;
if (openInventory == null) {
closeWindowPacket = new CloseWindowPacket((byte) 0);
} else {
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
openInventory.removeViewer(this); // Clear cache
this.openInventory = null;
}
sendPacket(closeWindowPacket);
inventory.update();
this.didCloseInventory = true;
}
sendPacket(closeWindowPacket);
inventory.update();
this.didCloseInventory = true;
}
/**

View File

@ -1,6 +1,7 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.network.packet.server.ServerPacket;
import org.jetbrains.annotations.ApiStatus;
@ -12,10 +13,10 @@ import org.jetbrains.annotations.NotNull;
* Currently, do not support viewable packets.
*/
@ApiStatus.Experimental
public class PlayerPacketOutEvent implements PlayerEvent {
public class PlayerPacketOutEvent implements PlayerEvent, CancellableEvent {
private final Player player;
private final ServerPacket packet;
private boolean cancelled;
public PlayerPacketOutEvent(Player player, ServerPacket packet) {
this.player = player;
@ -30,4 +31,14 @@ public class PlayerPacketOutEvent implements PlayerEvent {
public @NotNull ServerPacket getPacket() {
return packet;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
}

View File

@ -349,7 +349,9 @@ public class PlayerSocketConnection extends PlayerConnection {
// Outgoing event
if (player != null && outgoing.hasListener()) {
final ServerPacket serverPacket = SendablePacket.extractServerPacket(packet);
outgoing.call(new PlayerPacketOutEvent(player, serverPacket));
PlayerPacketOutEvent event = new PlayerPacketOutEvent(player, serverPacket);
outgoing.call(event);
if (event.isCancelled()) return;
}
// Write packet
if (packet instanceof ServerPacket serverPacket) {

View File

@ -97,7 +97,10 @@ public final class Server {
public void stop() {
this.stop = true;
try {
this.serverSocket.close();
if(serverSocket != null) {
this.serverSocket.close();
}
if (socketAddress instanceof UnixDomainSocketAddress unixDomainSocketAddress) {
Files.deleteIfExists(unixDomainSocketAddress.getPath());
}

View File

@ -172,6 +172,9 @@ public final class PacketUtils {
if (compressed) {
final int dataLength = readBuffer.readVarInt();
final int payloadLength = packetLength - (readBuffer.readerOffset() - readerStart);
if (payloadLength < 0) {
throw new DataFormatException("Negative payload length " + payloadLength);
}
if (dataLength == 0) {
// Data is too small to be compressed, payload is following
decompressedSize = payloadLength;

View File

@ -207,11 +207,8 @@ public class ArgumentTypeTest {
@Test
public void testArgumentResourceLocation() {
var arg = ArgumentType.ResourceLocation("resource_location");
assertArg(arg, "minecraft:resource_location_example", "minecraft:resource_location_example");
assertInvalidArg(arg, "minecraft:");
assertEquals("ResourceLocation<resource_location>", arg.toString());
}
@Test
@ -423,11 +420,11 @@ public class ArgumentTypeTest {
@Test
public void testArgumentStringArray() {
var arg = ArgumentType.StringArray("string_array");
assertArrayEquals(new String[]{"example", "text"}, arg.parse(new CommandReader("example text")).value());
assertArrayEquals(new String[]{"some", "more", "placeholder", "text"}, arg.parse(new CommandReader("some more placeholder text")).value());
// assertArrayEquals(new String[]{""}, arg.parse(new CommandReader("")).value()); Is it parser responsibility?
assertArrayEquals(new String[0], arg.parse(new CommandReader(" ")).value());
assertArrayEquals(new String[0], arg.parse(new CommandReader(" ")).value());
assertArrayArg(arg, new String[]{"example", "text"}, "example text");
assertArrayArg(arg, new String[]{"some", "more", "placeholder", "text"}, "some more placeholder text");
assertArrayArg(arg, new String[]{""}, "");
assertArrayArg(arg, new String[0], " ");
assertArrayArg(arg, new String[0], " ");
}
@Test
@ -446,6 +443,14 @@ public class ArgumentTypeTest {
assertEquals(expected, arg.parse(new CommandReader(input)).value());
}
private static <T> void assertArrayArg(Argument<T[]> arg, T[] expected, String input) {
assertArrayEquals(expected, arg.parse(input));
}
private static <T> void assertValidArg(Argument<T> arg, String input) {
assertDoesNotThrow(() -> arg.parse(input));
}
private static <T> void assertInvalidArg(Argument<T> arg, String input) {
assertNull(arg.parse(new CommandReader(input)).value());
}

View File

@ -0,0 +1,205 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.junit.jupiter.api.Test;
import java.util.Set;
import java.util.UUID;
@SuppressWarnings("ConstantConditions")
public class CommandPacketFilteringTest {
private static final Player PLAYER = new Player(UUID.randomUUID(), "", null);
@Test
public void singleCommandFilteredFalse() {
final Command foo = new Command("foo");
foo.setCondition(((sender, commandString) -> false));
assertFiltering(foo, "");
}
@Test
public void singleCommandFilteredTrue() {
final Command foo = new Command("foo");
foo.setCondition(((sender, commandString) -> true));
assertFiltering(foo, """
foo=%
0->foo
""");
}
@Test
public void singleCommandUnfiltered() {
final Command foo = new Command("foo");
assertFiltering(foo, """
foo=%
0->foo
""");
}
@Test
public void singleCommandFilteredTrueWithFilteredSubcommandTrueWithFilteredSyntaxFalse() {
final Command foo = new Command("foo");
foo.setCondition((sender, commandString) -> true);
final Command bar = new Command("bar");
bar.setCondition((sender, commandString) -> true);
foo.addSubcommand(bar);
bar.addConditionalSyntax((sender, commandString) -> false, null, ArgumentType.Literal("baz"));
assertFiltering(foo, """
foo bar=%
0->foo
foo->bar
""");
}
@Test
public void singleCommandFilteredTrueWithFilteredSubcommandFalse() {
final Command foo = new Command("foo");
foo.setCondition((sender, commandString) -> true);
final Command bar = new Command("bar");
bar.setCondition((sender, commandString) -> false);
foo.addSubcommand(bar);
assertFiltering(foo, """
foo=%
0->foo
""");
}
@Test
public void singleCommandFilteredTrueWithFilteredSubcommandTrue() {
final Command foo = new Command("foo");
foo.setCondition((sender, commandString) -> true);
final Command bar = new Command("bar");
bar.setCondition((sender, commandString) -> true);
foo.addSubcommand(bar);
assertFiltering(foo, """
foo bar=%
0->foo
foo->bar
""");
}
@Test
public void singleCommandFilteredTrueWithFilteredSubcommandTrueWithFilteredSyntaxBoth() {
final Command foo = new Command("foo");
foo.setCondition((sender, commandString) -> true);
final Command bar = new Command("bar");
bar.setCondition((sender, commandString) -> true);
foo.addSubcommand(bar);
bar.addConditionalSyntax((sender, commandString) -> true, null, ArgumentType.Literal("true"));
bar.addConditionalSyntax((sender, commandString) -> false, null, ArgumentType.Literal("false"));
assertFiltering(foo, """
foo bar true=%
0->foo
foo->bar
bar->true
""");
}
@Test
public void singleCommandConditionalArgGroupTrue() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> true, null, ArgumentType.Group("test", ArgumentType.Literal("bar")));
assertFiltering(foo, """
foo bar=%
0->foo
foo->bar
""");
}
@Test
public void singleCommandConditionalArgGroupFalse() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> false, null, ArgumentType.Group("test", ArgumentType.Literal("foo")));
assertFiltering(foo, """
foo=%
0->foo
""");
}
@Test
public void singleCommandUnconditionalArgGroup() {
final Command foo = new Command("foo");
foo.addSyntax(null, ArgumentType.Group("test", ArgumentType.Literal("bar")));
assertFiltering(foo, """
foo bar=%
0->foo
foo->bar
""");
}
@Test
public void singleCommandConditionalArgGroupTrue2() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> true, null, ArgumentType.Group("test", ArgumentType.Literal("bar"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo bar baz=%
0->foo
foo->bar
bar->baz
""");
}
@Test
public void singleCommandConditionalArgGroupFalse2() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> false, null, ArgumentType.Group("test", ArgumentType.Literal("foo"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo=%
0->foo
""");
}
@Test
public void singleCommandUnconditionalArgGroup2() {
final Command foo = new Command("foo");
foo.addSyntax(null, ArgumentType.Group("test", ArgumentType.Literal("bar"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo bar baz=%
0->foo
foo->bar
bar->baz
""");
}
@Test
public void singleCommandUnconditionalArgLoop() {
final Command foo = new Command("foo");
foo.addSyntax(null, ArgumentType.Loop("test", ArgumentType.Literal("bar"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo bar baz=%
0->foo
foo->bar baz
bar baz+>foo
""");
}
@Test
public void singleCommandConditionalArgLoopTrue() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> true, null, ArgumentType.Loop("test", ArgumentType.Literal("bar"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo bar baz=%
0->foo
foo->bar baz
bar baz+>foo
""");
}
@Test
public void singleCommandConditionalArgLoopFalse() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, commandString) -> false, null, ArgumentType.Loop("test", ArgumentType.Literal("bar"), ArgumentType.Literal("baz")));
assertFiltering(foo, """
foo=%
0->foo
""");
}
private void assertFiltering(Command command, String expectedStructure) {
final DeclareCommandsPacket packet = GraphConverter.createPacket(Graph.merge(Set.of(command)), PLAYER);
CommandTestUtils.assertPacket(packet, expectedStructure);
}
}

View File

@ -0,0 +1,175 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class CommandPacketTest {
@Test
public void singleCommandWithOneSyntax() {
final Command foo = new Command("foo");
foo.addSyntax(CommandPacketTest::dummyExecutor, ArgumentType.Integer("bar"));
final DeclareCommandsPacket packet = GraphConverter.createPacket(Graph.merge(Graph.fromCommand(foo)), null);
assertEquals(3, packet.nodes().size());
final DeclareCommandsPacket.Node root = packet.nodes().get(packet.rootIndex());
assertNotNull(root);
assertNodeType(DeclareCommandsPacket.NodeType.ROOT, root.flags);
assertEquals(1, root.children.length);
final DeclareCommandsPacket.Node cmd = packet.nodes().get(root.children[0]);
assertNotNull(cmd);
assertNodeType(DeclareCommandsPacket.NodeType.LITERAL, cmd.flags);
assertEquals(1, cmd.children.length);
assertEquals("foo", cmd.name);
final DeclareCommandsPacket.Node arg = packet.nodes().get(cmd.children[0]);
assertNotNull(arg);
assertNodeType(DeclareCommandsPacket.NodeType.ARGUMENT, arg.flags);
assertEquals(0, arg.children.length);
assertEquals("bar", arg.name);
}
@Test
public void executeLike() {
enum Dimension {OVERWORLD, THE_NETHER, THE_END}
final Command execute = new Command("execute");
execute.addSyntax(CommandPacketTest::dummyExecutor, ArgumentType.Loop("params",
ArgumentType.Group("facing", ArgumentType.Literal("facing"), ArgumentType.RelativeVec3("pos")),
ArgumentType.Group("at", ArgumentType.Literal("at"), ArgumentType.Entity("targets")),
ArgumentType.Group("as", ArgumentType.Literal("as"), ArgumentType.Entity("targets")),
ArgumentType.Group("in", ArgumentType.Literal("in"), ArgumentType.Enum("dimension", Dimension.class)),
ArgumentType.Group("run", ArgumentType.Command("run"))
));
var graph = Graph.fromCommand(execute);
assertPacketGraph("""
execute facing at as in run=%
overworld the_nether the_end=§
0->execute
atEnt asEnt=targets minecraft:entity 0
execute->facing at as in run
at->atEnt
as->asEnt
in->overworld the_nether the_end
pos=! minecraft:vec3
facing->pos
pos atEnt asEnt overworld the_nether the_end+>execute
run+>0
""", graph);
}
@Test
public void singleCommandTwoEnum() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Enum("bar", A.class), b -> b.append(ArgumentType.Enum("baz", B.class)))
.build();
assertPacketGraph("""
foo=%
a b c d e f=§
0->foo
foo->a b c
a b c->d e f
""", graph);
}
@Test
public void singleCommandRestrictedWord() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Word("bar").from("A", "B", "C"))
.build();
assertPacketGraph("""
foo=%
a b c=§
0->foo
foo->a b c
""", graph);
}
@Test
public void singleCommandWord() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Word("bar"))
.build();
assertPacketGraph("""
foo=%
bar=! brigadier:string 0
0->foo
foo->bar
""", graph);
}
@Test
public void singleCommandCommandAfterEnum() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Enum("bar", A.class), b -> b.append(ArgumentType.Command("baz")))
.build();
assertPacketGraph("""
foo baz=%
a b c=§
0->foo
foo->a b c
a b c->baz
baz+>0
""", graph);
}
@Test
public void twoCommandIntEnumInt() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Integer("int1"), b -> b.append(ArgumentType.Enum("test", A.class), c -> c.append(ArgumentType.Integer("int2"))))
.build();
var graph2 = Graph.builder(ArgumentType.Literal("bar"))
.append(ArgumentType.Integer("int3"), b -> b.append(ArgumentType.Enum("test", B.class), c -> c.append(ArgumentType.Integer("int4"))))
.build();
assertPacketGraph("""
foo bar=%
0->foo bar
a b c d e f=§
int1 int2 int3 int4=! brigadier:integer 0
foo->int1
bar->int3
int1->a b c
int3->d e f
a b c->int2
d e f->int4
""", graph, graph2);
}
@Test
public void singleCommandTwoGroupOfIntInt() {
var graph = Graph.builder(ArgumentType.Literal("foo"))
.append(ArgumentType.Group("1", ArgumentType.Integer("int1"), ArgumentType.Integer("int2")),
b -> b.append(ArgumentType.Group("2", ArgumentType.Integer("int3"), ArgumentType.Integer("int4"))))
.build();
assertPacketGraph("""
foo=%
int1 int2 int3 int4=! brigadier:integer 0
0->foo
foo->int1
int1->int2
int2->int3
int3->int4
""", graph);
}
static void assertPacketGraph(String expected, Graph... graphs) {
var packet = GraphConverter.createPacket(Graph.merge(graphs), null);
CommandTestUtils.assertPacket(packet, expected);
}
enum A {A, B, C}
enum B {D, E, F}
enum C {G, H, I, J, K}
private static void assertNodeType(DeclareCommandsPacket.NodeType expected, byte flags) {
assertEquals(expected, DeclareCommandsPacket.NodeType.values()[flags & 0x03]);
}
private static void dummyExecutor(CommandSender sender, CommandContext context) {
}
}

View File

@ -3,6 +3,8 @@ package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Enchantment;
import org.junit.jupiter.api.Test;
import java.lang.String;
@ -109,6 +111,43 @@ public class CommandSyntaxSingleTest {
assertSyntax(groupLoop, "1 2 3 4 5", ExpectedExecution.DEFAULT);
}
@Test
public void singleLoopDoubleGroup() {
List<Argument<?>> groupLoop = List.of(
Loop("loop",
Group("group", BlockState("block"), Enchantment("enchant")),
Group("group2", Enchantment("enchant"), BlockState("block"))
)
);
// block enchant
{
var input = "minecraft:stone minecraft:sharpness";
var context = new CommandContext(input);
context.setArg("block", Block.STONE, "minecraft:stone");
context.setArg("enchant", Enchantment.SHARPNESS, "minecraft:sharpness");
assertSyntax(groupLoop, input, ExpectedExecution.SYNTAX, Map.of("loop", List.of(context)));
}
// enchant block block enchant
{
var context1 = new CommandContext("minecraft:sharpness minecraft:stone");
var context2 = new CommandContext("minecraft:grass minecraft:efficiency");
context1.setArg("enchant", Enchantment.SHARPNESS, "minecraft:sharpness");
context1.setArg("block", Block.STONE, "minecraft:stone");
context2.setArg("block", Block.GRASS, "minecraft:grass");
context2.setArg("enchant", Enchantment.EFFICIENCY, "minecraft:efficiency");
var input = context1.getInput() + " " + context2.getInput();
assertSyntax(groupLoop, input, ExpectedExecution.SYNTAX, Map.of("loop", List.of(context1, context2)));
}
// Incomplete loop
assertSyntax(groupLoop, "minecraft:sharpness", ExpectedExecution.DEFAULT);
assertSyntax(groupLoop, "minecraft:sharpness minecraft:sharpness", ExpectedExecution.DEFAULT);
assertSyntax(groupLoop, "minecraft:stone", ExpectedExecution.DEFAULT);
assertSyntax(groupLoop, "minecraft:stone minecraft:stone", ExpectedExecution.DEFAULT);
}
private static void assertSyntax(List<Argument<?>> args, String input, ExpectedExecution expectedExecution, Map<String, Object> expectedValues) {
final String commandName = "name";

View File

@ -0,0 +1,277 @@
package net.minestom.server.command;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.opentest4j.AssertionFailedError;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
public class CommandTestUtils {
public static void assertPacket(DeclareCommandsPacket packet, String expectedStructure) {
final List<NodeStructure.TestNode> expectedList = NodeStructure.fromString("0\n0=$root$\n" + expectedStructure);
final List<NodeStructure.TestNode> actualList = NodeStructure.fromString(NodeStructure.packetToString(packet));
try {
assertEquals(expectedList.size(), actualList.size(), "Different node counts");
assertTrue(actualList.containsAll(expectedList), "Packet doesn't contain all expected nodes.");
} catch (AssertionFailedError error) {
fail("Graphs didn't match. Actual graph from packet: " + CommandTestUtils.exportGarphvizDot(packet, false));
}
}
static class NodeStructure {
private static final Map<Character, Function<String, Collection<String>>> functions = Map.of(
'!', s -> {
final String[] strings = splitDeclaration(s);
final ArrayList<String> result = new ArrayList<>();
for (String s1 : strings[0].split(" ")) {
result.add(s1+"="+(strings[1].replaceAll("!", s1)));
}
return result;
},
'%', s -> {
final String[] strings = splitDeclaration(s);
final ArrayList<String> result = new ArrayList<>();
for (String s1 : strings[0].split(" ")) {
result.add(s1+"="+(strings[1].replaceAll("%", "'"+s1+"'")));
}
return result;
},
'§', s -> {
final String[] strings = splitDeclaration(s);
final ArrayList<String> result = new ArrayList<>();
for (String s1 : strings[0].split(" ")) {
result.add(s1+"="+(strings[1].replaceAll("§", "'"+(s1.toUpperCase(Locale.ROOT))+"'")));
}
return result;
}
);
private static final Set<Character> placeholders = functions.keySet();
static String packetToString(DeclareCommandsPacket packet) {
final char lineSeparator = '\n';
final StringBuilder builder = new StringBuilder();
builder.append(packet.rootIndex());
builder.append(lineSeparator);
@NotNull List<DeclareCommandsPacket.Node> nodes = packet.nodes();
for (int i = 0; i < nodes.size(); i++) {
DeclareCommandsPacket.Node node = nodes.get(i);
builder.append(i);
builder.append('=');
// Meta
if ((node.flags & 0x3) == 0) {
builder.append("$root$");
} else {
if ((node.flags & 0x3) == 1) {
builder.append("'");
builder.append(node.name);
builder.append("'");
} else {
builder.append(node.name);
builder.append(' ');
builder.append(node.parser);
if (node.properties != null) {
builder.append(' ');
builder.append(new BigInteger(node.properties).toString(16));
}
}
}
if ((node.flags & 0x4) == 0x4) {
builder.append(" executable");
}
if ((node.flags & 0x10) == 0x10) {
builder.append(' ');
builder.append(node.suggestionsType);
}
builder.append(lineSeparator);
if (node.children.length > 0) {
builder.append(i);
builder.append("->");
builder.append(Arrays.stream(node.children).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
builder.append(lineSeparator);
}
if ((node.flags & 0x8) == 0x8) {
builder.append(i);
builder.append("+>");
builder.append(node.redirectedNode);
builder.append(lineSeparator);
}
}
return builder.toString();
}
private static String[] splitDeclaration(String input) {
return input.split("=", 2);
}
private static List<String> preProcessString(String string) {
final List<String> strings = Arrays.stream(string.split("\n")).toList();
final ArrayList<String> result = new ArrayList<>();
for (String s : strings) {
if (s.indexOf('=') > -1) {
boolean match = false;
for (Character placeholder : placeholders) {
if (s.indexOf(placeholder) > -1) {
result.addAll(functions.get(placeholder).apply(s));
match = true;
break;
}
}
if (!match) {
final int spaceIndex = s.indexOf(" ");
if (spaceIndex > -1 && spaceIndex < s.indexOf('=')) {
final String[] split = s.split("=", 2);
for (String s1 : split[0].split(" ")) {
result.add(s1+"="+split[1]);
}
} else {
result.add(s);
}
}
} else {
final int spaceIndex = s.indexOf(" ");
if (spaceIndex > -1 && spaceIndex < s.indexOf('-')) {
final String[] split = s.split("-", 2);
for (String s1 : split[0].split(" ")) {
result.add(s1+"-"+split[1]);
}
} else if (spaceIndex > -1 && spaceIndex < s.indexOf('+')) {
final String[] split = s.split("\\+", 2);
for (String s1 : split[0].split(" ")) {
result.add(s1+"+"+split[1]);
}
} else {
result.add(s);
}
}
}
return result;
}
static List<TestNode> fromString(String input) {
Map<String, String[]> references = new HashMap<>();
Map<String, TestNode> nodes = new HashMap<>();
final List<String> strings = preProcessString(input);
String rootId = strings.get(0);
for (String s : strings.stream().skip(0).toList()) {
if (s.length() < 3) continue; //invalid line
final int declareSeparator = s.indexOf('=');
if (declareSeparator > -1) {
final String id = s.substring(0, declareSeparator);
final String meta = s.substring(declareSeparator + 1);
nodes.put(id, new TestNode(new ArrayList<>(), meta, new AtomicReference<>()));
} else {
final int childSeparator = s.indexOf('-');
if (childSeparator > -1) {
references.put(s.substring(0, childSeparator), s.substring(childSeparator + 2).split(" "));
} else {
final int redirectSeparator = s.indexOf('+');
references.put(s.substring(0, redirectSeparator), new String[]{null, s.substring(redirectSeparator + 2)});
}
}
}
final ArrayList<TestNode> result = new ArrayList<>();
List<Runnable> redirectSetters = new ArrayList<>();
resolveNode(rootId, references, nodes, result, new HashMap<>(), redirectSetters, "");
redirectSetters.forEach(Runnable::run);
return result;
}
private static String resolveNode(String id, Map<String, String[]> references,
Map<String, TestNode> nodes, ArrayList<TestNode> result,
Map<String, String> nameToMetaPath,
List<Runnable> redirectSetters, String metaPath) {
final TestNode node = nodes.get(id);
final String[] refs = references.get(id);
final String path = metaPath + "#" + node.meta;
if (refs == null) {
result.add(node);
nameToMetaPath.put(id, path);
return path;
} else if (refs[0] == null) {
redirectSetters.add(() -> node.redirect.set(nameToMetaPath.get(refs[1])));
} else {
for (String ref : refs) {
node.children.add(resolveNode(ref, references, nodes, result, nameToMetaPath, redirectSetters, path));
}
}
result.add(node);
nameToMetaPath.put(id, path);
return path;
}
record TestNode(List<String> children, String meta, AtomicReference<String> redirect) {
@Override
public boolean equals(Object obj) {
if (obj instanceof TestNode that) {
return this.meta.equals(that.meta) && Objects.equals(this.redirect.get(), that.redirect.get()) &&
this.children.containsAll(that.children) && this.children.size() == that.children.size();
} else {
return false;
}
}
}
}
static String exportGarphvizDot(DeclareCommandsPacket packet, boolean prettyPrint) {
final StringBuilder builder = new StringBuilder();
final char statementSeparator = ';';
builder.append("digraph G {");
builder.append("rankdir=LR");
builder.append(statementSeparator);
builder.append(packet.rootIndex());
builder.append(" [label=\"root\",shape=rectangle]");
builder.append(statementSeparator);
@NotNull List<DeclareCommandsPacket.Node> nodes = packet.nodes();
for (int i = 0; i < nodes.size(); i++) {
DeclareCommandsPacket.Node node = nodes.get(i);
if ((node.flags & 0x3) != 0) {
builder.append(i);
builder.append(" [label=");
builder.append('"');
if ((node.flags & 0x3) == 1) {
builder.append("'");
builder.append(node.name);
builder.append("'");
} else {
builder.append(node.name);
}
builder.append('"');
if ((node.flags & 0x4) == 0x4) {
builder.append(",bgcolor=gray,style=filled");
}
builder.append("]");
builder.append(statementSeparator);
}
if (node.children.length == 0 && (node.flags & 0x8) == 0) continue;
builder.append(i);
builder.append(" -> { ");
if ((node.flags & 0x8) == 0) {
builder.append(Arrays.stream(node.children).mapToObj(Integer::toString).collect(Collectors.joining(" ")));
builder.append(" }");
builder.append(statementSeparator);
} else {
builder.append(node.redirectedNode);
builder.append(" } [style = dotted]");
builder.append(statementSeparator);
}
}
builder.append("}");
if (prettyPrint)
return builder.toString()
.replaceFirst("\\{r", "{\n r")
.replaceAll(";", "\n ")
.replaceFirst(" {2}}$", "}\n");
else
return builder.toString();
}
}

View File

@ -0,0 +1,77 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import org.junit.jupiter.api.Test;
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
import static org.junit.jupiter.api.Assertions.*;
public class GraphConversionExecutorTest {
@Test
public void empty() {
final Command foo = new Command("foo");
var graph = Graph.fromCommand(foo);
assertNull(graph.root().executor());
}
@Test
public void defaultCondition() {
final Command foo = new Command("foo");
// Constant true
{
foo.setCondition((sender, commandString) -> true);
var graph = Graph.fromCommand(foo);
var executor = graph.root().executor();
assertNotNull(executor);
assertTrue(executor.test(null));
}
// Constant false
{
foo.setCondition((sender, commandString) -> false);
var graph = Graph.fromCommand(foo);
var executor = graph.root().executor();
assertNotNull(executor);
assertFalse(executor.test(null));
}
}
@Test
public void emptySyntaxCondition() {
final Command foo = new Command("foo");
foo.addSyntax(GraphConversionExecutorTest::dummyExecutor, Literal("first"));
var graph = Graph.fromCommand(foo);
assertEquals(1, graph.root().next().size());
assertNull(graph.root().next().get(0).executor());
}
@Test
public void syntaxConditionTrue() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, context) -> true,
GraphConversionExecutorTest::dummyExecutor, Literal("first"));
var graph = Graph.fromCommand(foo);
assertEquals(1, graph.root().next().size());
var executor = graph.root().next().get(0).executor();
assertNotNull(executor);
assertTrue(executor.test(null));
}
@Test
public void syntaxConditionFalse() {
final Command foo = new Command("foo");
foo.addConditionalSyntax((sender, context) -> false,
GraphConversionExecutorTest::dummyExecutor, Literal("first"));
var graph = Graph.fromCommand(foo);
assertEquals(1, graph.root().next().size());
var executor = graph.root().next().get(0).executor();
assertNotNull(executor);
assertFalse(executor.test(null));
}
private static void dummyExecutor(CommandSender sender, CommandContext context) {
}
}

View File

@ -0,0 +1,115 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import org.junit.jupiter.api.Test;
import static net.minestom.server.command.builder.arguments.ArgumentType.Enum;
import static net.minestom.server.command.builder.arguments.ArgumentType.Integer;
import static net.minestom.server.command.builder.arguments.ArgumentType.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class GraphConversionTest {
@Test
public void empty() {
final Command foo = new Command("foo");
var graph = Graph.builder(Literal("foo")).build();
assertEqualsGraph(graph, foo);
}
@Test
public void singleLiteral() {
final Command foo = new Command("foo");
var first = Literal("first");
foo.addSyntax(GraphConversionTest::dummyExecutor, first);
var graph = Graph.builder(Literal("foo"))
.append(first).build();
assertEqualsGraph(graph, foo);
}
@Test
public void literalsPath() {
final Command foo = new Command("foo");
var first = Literal("first");
var second = Literal("second");
foo.addSyntax(GraphConversionTest::dummyExecutor, first);
foo.addSyntax(GraphConversionTest::dummyExecutor, second);
var graph = Graph.builder(Literal("foo"))
.append(first).append(second)
.build();
assertEqualsGraph(graph, foo);
}
@Test
public void doubleSyntax() {
enum A {A, B, C, D, E}
final Command foo = new Command("foo");
var bar = Literal("bar");
var baz = Literal("baz");
var a = Enum("a", A.class);
foo.addSyntax(GraphConversionTest::dummyExecutor, bar);
foo.addSyntax(GraphConversionTest::dummyExecutor, baz, a);
var graph = Graph.builder(Literal("foo"))
.append(bar)
.append(baz, builder ->
builder.append(a))
.build();
assertEqualsGraph(graph, foo);
}
@Test
public void doubleSyntaxMerge() {
final Command foo = new Command("foo");
var bar = Literal("bar");
var number = Integer("number");
foo.addSyntax(GraphConversionTest::dummyExecutor, bar);
foo.addSyntax(GraphConversionTest::dummyExecutor, bar, number);
// The two syntax shall start from the same node
var graph = Graph.builder(Literal("foo"))
.append(bar, builder -> builder.append(number))
.build();
assertEqualsGraph(graph, foo);
}
@Test
public void subcommand() {
final Command main = new Command("main");
final Command sub = new Command("sub");
var bar = Literal("bar");
var number = Integer("number");
sub.addSyntax(GraphConversionTest::dummyExecutor, bar);
sub.addSyntax(GraphConversionTest::dummyExecutor, bar, number);
main.addSubcommand(sub);
// The two syntax shall start from the same node
var graph = Graph.builder(Literal("main"))
.append(Literal("sub"), builder ->
builder.append(bar, builder1 -> builder1.append(number)))
.build();
assertEqualsGraph(graph, main);
}
private static void assertEqualsGraph(Graph expected, Command command) {
final Graph actual = Graph.fromCommand(command);
assertTrue(expected.compare(actual, Graph.Comparator.TREE), () -> {
System.out.println("Expected: " + expected);
System.out.println("Actual: " + actual);
return "";
});
}
private static void dummyExecutor(CommandSender sender, CommandContext context) {
}
}

View File

@ -0,0 +1,53 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import org.junit.jupiter.api.Test;
import java.util.List;
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class GraphMergeTest {
@Test
public void commands() {
var foo = new Command("foo");
var bar = new Command("bar");
var result = Graph.builder(Literal(""))
.append(Literal("foo"))
.append(Literal("bar"))
.build();
assertEqualsGraph(result, Graph.merge(List.of(foo, bar)));
}
@Test
public void empty() {
var graph1 = Graph.builder(Literal("foo")).build();
var graph2 = Graph.builder(Literal("bar")).build();
var result = Graph.builder(Literal(""))
.append(Literal("foo"))
.append(Literal("bar"))
.build();
assertEqualsGraph(result, Graph.merge(graph1, graph2));
}
@Test
public void literals() {
var graph1 = Graph.builder(Literal("foo")).append(Literal("1")).build();
var graph2 = Graph.builder(Literal("bar")).append(Literal("2")).build();
var result = Graph.builder(Literal(""))
.append(Literal("foo"), builder -> builder.append(Literal("1")))
.append(Literal("bar"), builder -> builder.append(Literal("2")))
.build();
assertEqualsGraph(result, Graph.merge(graph1, graph2));
}
private static void assertEqualsGraph(Graph expected, Graph actual) {
assertTrue(expected.compare(actual, Graph.Comparator.TREE), () -> {
System.out.println("Expected: " + expected);
System.out.println("Actual: " + actual);
return "";
});
}
}

View File

@ -0,0 +1,79 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import org.junit.jupiter.api.Test;
import java.util.List;
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
import static org.junit.jupiter.api.Assertions.*;
public class GraphTest {
@Test
public void empty() {
var result = Graph.builder(Literal(""))
.build();
var node = result.root();
assertEquals(Literal(""), node.argument());
assertTrue(node.next().isEmpty());
}
@Test
public void next() {
var result = Graph.builder(Literal(""))
.append(Literal("foo"))
.build();
var node = result.root();
assertEquals(Literal(""), node.argument());
assertEquals(1, node.next().size());
assertEquals(Literal("foo"), node.next().get(0).argument());
}
@Test
public void immutableNextBuilder() {
var result = Graph.builder(Literal(""))
.append(Literal("foo"))
.append(Literal("bar"))
.build();
var node = result.root();
assertThrows(Exception.class, () -> result.root().next().add(node));
assertThrows(Exception.class, () -> result.root().next().get(0).next().add(node));
}
@Test
public void immutableNextCommand() {
final Command foo = new Command("foo");
var first = Literal("first");
foo.addSyntax(GraphTest::dummyExecutor, first);
var result = Graph.fromCommand(foo);
var node = result.root();
assertThrows(Exception.class, () -> result.root().next().add(node));
assertThrows(Exception.class, () -> result.root().next().get(0).next().add(node));
}
@Test
public void immutableNextCommands() {
final Command foo, bar;
{
var first = Literal("first");
foo = new Command("foo");
foo.addSyntax(GraphTest::dummyExecutor, first);
bar = new Command("foo");
bar.addSyntax(GraphTest::dummyExecutor, first);
}
var result = Graph.merge(List.of(foo, bar));
var node = result.root();
assertThrows(Exception.class, () -> result.root().next().add(node));
assertThrows(Exception.class, () -> result.root().next().get(0).next().add(node));
}
private static void dummyExecutor(CommandSender sender, CommandContext context) {
}
}

View File

@ -1,47 +0,0 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket.NodeType;
import org.junit.jupiter.api.Test;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class NodeGraphTest {
@Test
public void singleCommandWithOneSyntax() {
final Command foo = new Command("foo");
foo.addSyntax(NodeGraphTest::dummyExecutor, ArgumentType.Integer("bar"));
final DeclareCommandsPacket packet = GraphConverter.createPacket(GraphBuilder.forServer(Set.of(foo)));
assertEquals(3, packet.nodes().size());
final DeclareCommandsPacket.Node root = packet.nodes().get(packet.rootIndex());
assertNotNull(root);
assertNodeType(NodeType.ROOT, root.flags);
assertEquals(1, root.children.length);
final DeclareCommandsPacket.Node cmd = packet.nodes().get(root.children[0]);
assertNotNull(cmd);
assertNodeType(NodeType.LITERAL, cmd.flags);
assertEquals(1, cmd.children.length);
assertEquals("foo", cmd.name);
final DeclareCommandsPacket.Node arg = packet.nodes().get(cmd.children[0]);
assertNotNull(arg);
assertNodeType(NodeType.ARGUMENT, arg.flags);
assertEquals(0, arg.children.length);
assertEquals("bar", arg.name);
}
private static void assertNodeType(NodeType expected, byte flags) {
assertEquals(expected, NodeType.values()[flags & 0x03]);
}
private static void dummyExecutor(CommandSender sender, CommandContext context) {
}
}

View File

@ -1,14 +1,12 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.exception.IllegalCommandStructureException;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SubcommandTest {
@ -67,17 +65,4 @@ public class SubcommandTest {
assertFalse(parentExecuted.get());
assertFalse(childExecuted.get());
}
@Test
public void testRecursionDetection() {
final Command foo = new Command("foo");
final Command bar = new Command("bar");
bar.addSubcommand(foo);
assertDoesNotThrow(() -> GraphBuilder.forServer(Set.of(foo, bar)));
foo.addSubcommand(bar);
assertTimeout(Duration.ofSeconds(5), () -> assertThrows(IllegalCommandStructureException.class,
() -> GraphBuilder.forServer(Set.of(foo, bar)), "Builder didn't detect infinite recursion."),
"Is your stack fine?!");
}
}

View File

@ -4,6 +4,10 @@ import net.minestom.server.api.Env;
import net.minestom.server.api.EnvTest;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.item.PickupItemEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -48,4 +52,31 @@ public class EntityBoundingBoxIntegrationTest {
player.setPose(Entity.Pose.FALL_FLYING);
assertEquals(0.4, player.getEyeHeight());
}
@Test
public void pickupItem(Env env) {
final var instance = env.createFlatInstance();
final var listener = env.listen(PickupItemEvent.class);
final var spawnPos = new Pos(0, 42, 0);
final var entity = new LivingEntity(EntityType.ZOMBIE);
entity.setCanPickupItem(true);
entity.setInstance(instance, spawnPos).join();
var time = System.currentTimeMillis();
dropItem(instance, spawnPos);
listener.followup();
entity.update(time += 1_000L);
dropItem(instance, spawnPos.sub(.5));
listener.followup();
entity.update(time += 1_000L);
}
private void dropItem(final Instance instance, final Pos position) {
final var entity = new ItemEntity(ItemStack.of(Material.STONE));
entity.hasPhysics = false;
entity.setNoGravity(true);
entity.setInstance(instance, position).join();
}
}

View File

@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.minestom.server.api.Env;
import net.minestom.server.api.EnvTest;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
@ -12,9 +12,7 @@ import net.minestom.server.network.packet.server.play.SetSlotPacket;
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
@EnvTest
public class InventoryIntegrationTest {
@ -75,7 +73,7 @@ public class InventoryIntegrationTest {
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
assertEquals(instance, player.getInstance());
Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, Component.empty());
player.openInventory(inventory);
assertEquals(inventory, player.getOpenInventory());
@ -112,4 +110,39 @@ public class InventoryIntegrationTest {
equipmentTracker.assertEmpty();
}
@Test
public void closeInventoryTest(Env env) {
var instance = env.createFlatInstance();
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
final var inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
player.openInventory(inventory);
assertSame(inventory, player.getOpenInventory());
player.closeInventory();
assertNull(player.getOpenInventory());
}
@Test
public void openInventoryOnItemDropFromInventoryClosingTest(Env env) {
var instance = env.createFlatInstance();
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
var listener = env.listen(ItemDropEvent.class);
final var firstInventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
player.openInventory(firstInventory);
assertSame(firstInventory, player.getOpenInventory());
firstInventory.setCursorItem(player, ItemStack.of(Material.STONE));
listener.followup();
player.closeInventory();
assertNull(player.getOpenInventory());
player.openInventory(firstInventory);
firstInventory.setCursorItem(player, ItemStack.of(Material.STONE));
final var secondInventory = new Inventory(InventoryType.CHEST_1_ROW, "title");
listener.followup(event -> event.getPlayer().openInventory(secondInventory));
player.closeInventory();
assertSame(secondInventory, player.getOpenInventory());
}
}

View File

@ -26,7 +26,7 @@ public class ServerAddressTest {
}
@Test
public void unixAddressTest() throws IOException, InterruptedException {
public void unixAddressTest() throws IOException {
UnixDomainSocketAddress address = UnixDomainSocketAddress.of("minestom.sock");
var server = new Server(new PacketProcessor());
server.init(address);
@ -39,4 +39,10 @@ public class ServerAddressTest {
assertDoesNotThrow(server::stop);
assertFalse(Files.exists(address.getPath()), "The socket file should be deleted");
}
@Test
public void noAddressTest() throws IOException {
var server = new Server(new PacketProcessor());
assertDoesNotThrow(server::stop);
}
}