Skip to content

Commit

Permalink
Add chat listener and "assert chat" command
Browse files Browse the repository at this point in the history
  • Loading branch information
misode committed Dec 27, 2023
1 parent 81de9b3 commit a3fe4c2
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 9 deletions.
41 changes: 41 additions & 0 deletions src/main/java/io/github/misode/packtest/ChatListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.misode.packtest;

import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.compress.utils.Lists;

import java.util.List;
import java.util.function.Predicate;

public class ChatListener {

private static final List<ChatListener> listeners = Lists.newArrayList();

public static void broadcast(ServerPlayer player, Component chatMessage) {
Message message = new Message(player.getName().getString(), chatMessage.getString());
ChatListener.listeners.forEach(l -> l.messages.add(message));
}

public ChatListener() {
ChatListener.listeners.add(this);
}

public final List<Message> messages = Lists.newArrayList();

public void stop() {
ChatListener.listeners.remove(this);
}

public List<String> filter(Predicate<Message> predicate) {
return this.messages.stream()
.filter(predicate)
.map(m -> m.content)
.toList();
}

public void reset() {
this.messages.clear();
}

public record Message(String player, String content) {}
}
4 changes: 4 additions & 0 deletions src/main/java/io/github/misode/packtest/PackTestFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ private Consumer<GameTestHelper> createTestBody(int permissionLevel) {
}
}

ChatListener chatListener = new ChatListener();
((PackTestInfo)((PackTestHelper)helper).packtest$getInfo()).packtest$setChatListener(chatListener);
helper.onEachTick(chatListener::reset);

GameTestSequence sequence = helper.startSequence();
for (Step step : this.steps) {
step.register(sequence, source);
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/io/github/misode/packtest/PackTestHelper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.misode.packtest;

import net.minecraft.gametest.framework.GameTestInfo;

public interface PackTestHelper {
boolean packtest$isFinalCheckAdded();
GameTestInfo packtest$getInfo();
}
6 changes: 6 additions & 0 deletions src/main/java/io/github/misode/packtest/PackTestInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.misode.packtest;

public interface PackTestInfo {
void packtest$setChatListener(ChatListener listener);
ChatListener packtest$getChatListener();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.ContextChain;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import io.github.misode.packtest.PackTestArgumentSource;
import io.github.misode.packtest.PackTestSourceStack;
import io.github.misode.packtest.*;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
Expand All @@ -25,8 +26,10 @@
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.commands.data.DataCommands;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import net.minecraft.world.level.storage.loot.LootContext;
Expand All @@ -42,15 +45,21 @@
import net.minecraft.world.scores.Scoreboard;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;

public class AssertCommand {
private static final SimpleCommandExceptionType ERROR_NO_HELPER = new SimpleCommandExceptionType(
Component.literal("Not inside a test")
);
private static final SuggestionProvider<CommandSourceStack> SUGGEST_PREDICATE = (ctx, suggestions) -> {
LootDataManager lootData = ctx.getSource().getServer().getLootData();
return SharedSuggestionProvider.suggestResource(lootData.getKeys(LootDataType.PREDICATE), suggestions);
Expand Down Expand Up @@ -92,14 +101,18 @@ public static void addConditions(LiteralArgumentBuilder<CommandSourceStack> buil
.then(literal("matches")
.then(argument("range", RangeArgument.intRange())
.executes(expect.apply(AssertCommand::assertScoreRange))))
)));
)))
.then(literal("chat")
.then(argument("pattern", StringArgumentType.string())
.executes(expect.apply(AssertCommand::assertChatUnfiltered))
.then(argument("receivers", EntityArgument.players())
.executes(expect.apply(AssertCommand::assertChatFiltered)))));

for(DataCommands.DataProvider dataProvider : DataCommands.SOURCE_PROVIDERS) {
builder.then(dataProvider.wrap(literal("data"),
dataBuilder -> dataBuilder.then(argument("path", NbtPathArgument.nbtPath())
.executes(expect.apply(ctx -> assertData(ctx, dataProvider))))));
}

}

private static AssertResult assertBlock(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException {
Expand Down Expand Up @@ -203,6 +216,32 @@ private static AssertResult assertData(CommandContext<CommandSourceStack> ctx, D
return result(path.countMatching(data) > 0, path.asString() + " to match", data.getAsString());
}

private static AssertResult assertChatUnfiltered(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException {
return assertChat(ctx, m -> true);
}

private static AssertResult assertChatFiltered(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException {
Collection<ServerPlayer> receivers = EntityArgument.getPlayers(ctx, "receivers");
List<String> receiverNames = receivers.stream().map(p -> p.getName().getString()).toList();
return assertChat(ctx, m -> receiverNames.contains(m.player()));
}

private static AssertResult assertChat(CommandContext<CommandSourceStack> ctx, Predicate<ChatListener.Message> filter) throws CommandSyntaxException {
String pattern = StringArgumentType.getString(ctx, "pattern");
GameTestHelper helper = ((PackTestSourceStack)ctx.getSource()).packtest$getHelper();
if (helper == null) {
throw ERROR_NO_HELPER.create();
}
ChatListener chatListener = ((PackTestInfo)((PackTestHelper)helper).packtest$getInfo()).packtest$getChatListener();
Predicate<String> predicate = Pattern.compile(pattern).asPredicate();
List<String> matching = chatListener.filter(m -> filter.test(m) && predicate.test(m.content()));
List<String> all = matching.isEmpty() ? chatListener.filter(filter) : matching;
String got = all.isEmpty() ? "no messages"
: all.size() == 1 ? all.get(0)
: all.get(all.size() - 1) + " and " + (all.size() - 1) + " more";
return result(!matching.isEmpty(), pattern + " in chat", got);
}

static class AssertCustomExecutor implements CustomCommandExecutor.CommandAdapter<CommandSourceStack> {
private final boolean expectOk;
private final AssertPredicate predicate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import io.github.misode.packtest.PackTestHelper;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestInfo;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
Expand All @@ -12,10 +14,11 @@
@Mixin(GameTestHelper.class)
public class GameTestHelperMixin implements PackTestHelper {
@Shadow
private boolean finalCheckAdded;
@Final
private GameTestInfo testInfo;

@Unique
public boolean packtest$isFinalCheckAdded() {
return this.finalCheckAdded;
public GameTestInfo packtest$getInfo() {
return this.testInfo;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
package io.github.misode.packtest.mixin;

import io.github.misode.packtest.ChatListener;
import io.github.misode.packtest.PackTestInfo;
import net.minecraft.gametest.framework.GameTestInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Adds chat listener field and accessors. Removes the listener when finishing.
* Prevents crash when test has already started.
*/
@Mixin(GameTestInfo.class)
public abstract class GameTestInfoMixin {
public class GameTestInfoMixin implements PackTestInfo {

@Unique
private ChatListener chatListener;

@Override
public void packtest$setChatListener(ChatListener chatListener) {
this.chatListener = chatListener;
}

@Override
public ChatListener packtest$getChatListener() {
return this.chatListener;
}

@Inject(method = "startTest", cancellable = true, at = @At(value = "INVOKE", target = "Ljava/lang/IllegalStateException;<init>(Ljava/lang/String;)V"))
private void startTest(CallbackInfo ci) {
ci.cancel();
}

@Inject(method = "finish", at = @At("HEAD"))
private void finish(CallbackInfo ci) {
this.chatListener.stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.misode.packtest.mixin;

import io.github.misode.packtest.ChatListener;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Logs all chat messages sent to players
*/
@Mixin(ServerPlayer.class)
public class ServerPlayerMixin {

@Inject(method = "sendSystemMessage(Lnet/minecraft/network/chat/Component;Z)V", at = @At("HEAD"))
private void sendSystemMessage(Component message, boolean bl, CallbackInfo ci) {
ChatListener.broadcast((ServerPlayer)(Object)this, message);
}
}
1 change: 1 addition & 0 deletions src/main/resources/packtest.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"MinecraftServerMixin",
"PlayerListMixin",
"ReloadableServerResourcesMixin",
"ServerPlayerMixin",
"StructureUtilsMixin",
"TestCommandMixin"
],
Expand Down

0 comments on commit a3fe4c2

Please sign in to comment.