Skip to content

Commit

Permalink
Merge pull request #1 from misode/fake-players
Browse files Browse the repository at this point in the history
Dummy players
  • Loading branch information
misode authored Dec 25, 2023
2 parents a2ecf35 + c6c7bd9 commit 4748957
Show file tree
Hide file tree
Showing 16 changed files with 685 additions and 37 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ Tests can also be run automatically, for instance in a CI environment. When `-Dp
* `assert <condition>`: if condition is unsuccessful, fails the current test and returns from the function
* `assert not <condition>`: if condition is successful, fails the current test and returns from the function

### `dummy`
* `dummy <name> spawn`: spawns a new dummy
* `dummy <name> respawn`: respawns the dummy after it has been killed
* `dummy <name> leave`: makes the dummy leave the server
* `dummy <name> jump`: makes the dummy jump, if currently on ground
* `dummy <name> sneak [true|false]`: makes the dummy hold shift or un-shift (not the same as currently crouching)
* `dummy <name> sprint [true|false]`: makes the dummy sprint or un-sprint
* `dummy <name> drop [all]`: makes the dummy drop the current mainhand, either one item or the entire stack
* `dummy <name> swap`: makes the dummy swap its mainhand and offhand
* `dummy <name> selectslot`: makes the dummy select a different hotbar slot
* `dummy <name> use item`: makes the dummy use its hand item, either mainhand or offhand
* `dummy <name> use block <pos> [<direction>]`: makes the dummy use its hand item on a block position
* `dummy <name> use entity <entity>`: makes the dummy use its hand item on an entity
* `dummy <name> attack <entity>`: makes the dummy attack an entity with its mainhand
* `dummy <name> mine <pos>`: makes the dummy mine a block

## Conditions
* `block <pos> <block>`: checks if the block at the specified position matches the block predicate
* `entity <selector>`: checks if the selector matches any entity (can also find entities outside the structure bounds)
Expand All @@ -58,3 +74,4 @@ Tests can be customized by placing certain directives as special comments at the
* `@batch`: the batch name for this test, defaults to `packtestBatch`
* `@timeout`: an integer specifying the timeout, defaults to `100`
* `@optional`: whether this test is allowed to fail, defaults to `false`, if there is no value after the directive it is considered as `true`
* `@dummy`: whether to spawn a dummy at the start of the test and set `@s` to this dummy, taking a position which defaults to `~0.5 ~ ~0.5`
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ repositories {
}

loom {
accessWidenerPath = file("src/main/resources/packtest.accesswidener")

splitEnvironmentSourceSets()

mods {
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/io/github/misode/packtest/PackTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package io.github.misode.packtest;

import io.github.misode.packtest.commands.AssertCommand;
import io.github.misode.packtest.commands.FailCommand;
import io.github.misode.packtest.commands.SucceedCommand;
import io.github.misode.packtest.commands.*;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestServer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.slf4j.Logger;
Expand All @@ -27,9 +28,14 @@ public static boolean isAutoColoringEnabled() {

@Override
public void onInitialize() {
ArgumentTypeRegistry.registerArgumentType(
new ResourceLocation("packtest", "direction"),
DirectionArgument.class,
SingletonArgumentInfo.contextFree(DirectionArgument::direction));
CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, environment) -> {
AssertCommand.register(dispatcher, buildContext);
FailCommand.register(dispatcher);
DummyCommand.register(dispatcher);
SucceedCommand.register(dispatcher, buildContext);
});
}
Expand Down
50 changes: 43 additions & 7 deletions src/main/java/io/github/misode/packtest/PackTestFunction.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package io.github.misode.packtest;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.github.misode.packtest.dummy.Dummy;
import net.minecraft.commands.*;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.commands.execution.ExecutionContext;
import net.minecraft.commands.functions.CommandFunction;
import net.minecraft.commands.functions.InstantiatedFunction;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraft.gametest.framework.TestFunction;
Expand All @@ -24,7 +29,7 @@
import java.util.regex.Pattern;

public class PackTestFunction {
private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("#\\s*@(\\w+)(?:\\s+(\\S+))?");
private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^#\\s*@(\\w+)(?:\\s+(.+))?$");
private static final String DEFAULT_BATCH = "packtestBatch";
private static final String DEFAULT_TEMPLATE = "packtest:empty";
private final ResourceLocation id;
Expand Down Expand Up @@ -83,6 +88,21 @@ private int getTimeout() {
return Optional.ofNullable(this.directives.get("timeout")).map(Integer::parseInt).orElse(100);
}

private Optional<Vec3> getDummyPos(CommandSourceStack source) {
String dummyValue = this.directives.get("dummy");
if (dummyValue == null) {
return Optional.empty();
}
if (dummyValue.equals("true")) {
dummyValue = "~0.5 ~ ~0.5";
}
try {
return Optional.of(Vec3Argument.vec3().parse(new StringReader(dummyValue)).getPosition(source));
} catch (CommandSyntaxException e) {
return Optional.empty();
}
}

private boolean isRequired() {
return Optional.ofNullable(this.directives.get("optional")).map(s -> !Boolean.parseBoolean(s)).orElse(true);
}
Expand Down Expand Up @@ -115,19 +135,35 @@ private Consumer<GameTestHelper> createTestBody(int permissionLevel, CommandDisp
.withSuppressedOutput()
.withCallback((success, result) -> hasFailed.set(!success));

InstantiatedFunction<CommandSourceStack> instantiatedFn;
try {
InstantiatedFunction<CommandSourceStack> instantiatedFn = function.instantiate(null, dispatcher, sourceStack);
Commands.executeCommandInContext(sourceStack, execution -> ExecutionContext.queueInitialFunctionCall(
execution,
instantiatedFn,
sourceStack,
CommandResultCallback.EMPTY));
instantiatedFn = function.instantiate(null, dispatcher, sourceStack);
} catch (FunctionInstantiationException e) {
String message = e.messageComponent().getString();
helper.fail("Failed to instantiate test function: " + message);
return;
}

Vec3 dummyPos = this.getDummyPos(sourceStack).orElse(null);
Dummy dummy;
if (dummyPos != null) {
try {
PackTest.LOGGER.info("Directive position {} {}", dummyPos, helper.getLevel().getBlockState(BlockPos.containing(dummyPos.x, dummyPos.y, dummyPos.z)));
dummy = Dummy.createRandom(helper.getLevel().getServer(), helper.getLevel().dimension(), dummyPos);
dummy.setOnGround(true); // little hack because we know the dummy will be on the ground
sourceStack = sourceStack.withEntity(dummy);
} catch (IllegalArgumentException e) {
helper.fail("Failed to initialize test with dummy");
}
}

CommandSourceStack finalSourceStack = sourceStack;
Commands.executeCommandInContext(sourceStack, execution -> ExecutionContext.queueInitialFunctionCall(
execution,
instantiatedFn,
finalSourceStack,
CommandResultCallback.EMPTY));

if (hasFailed.get()) {
helper.fail("Test failed without a message");
} else if (!((PackTestHelper)helper).packtest$isFinalCheckAdded()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.misode.packtest;

public interface PackTestPlayerName {
String packtest$getPlayerName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.github.misode.packtest.commands;

import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

public class DirectionArgument implements ArgumentType<Direction> {
private static final DynamicCommandExceptionType ERROR_UNKNOWN_DIRECTION = new DynamicCommandExceptionType(
dir -> Component.literal("Unknown direction " + dir)
);

public static DirectionArgument direction() {
return new DirectionArgument();
}

public static Direction getDirection(CommandContext<CommandSourceStack> ctx, String name) {
return ctx.getArgument(name, Direction.class);
}

@Override
public Direction parse(StringReader reader) throws CommandSyntaxException {
String str = reader.readUnquotedString();
Direction direction = Direction.byName(str);
if (direction == null) {
throw ERROR_UNKNOWN_DIRECTION.create(str);
}
return direction;
}

@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> ctx, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(Arrays.stream(Direction.values()).map(Direction::getName), builder);
}
}
Loading

0 comments on commit 4748957

Please sign in to comment.