Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Improve party management #20

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ repositories {
maven { url = "https://maven.gegy.dev/" }
}

sourceSets {
gametest {
compileClasspath += main.compileClasspath
runtimeClasspath += main.runtimeClasspath
}
}

loom {
runs {
gametest {
server()
name "Game Test"
vmArg "-Dfabric-api.gametest"
vmArg "-Dfabric-api.gametest.report-file=${project.buildDir}/junit.xml"
runDir "build/gametest"
source sourceSets.gametest
}
}
}

dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
Expand All @@ -24,6 +44,8 @@ dependencies {

modImplementation 'xyz.nucleoid:plasmid:0.6.3-SNAPSHOT+1.21.4'
modImplementation include('xyz.nucleoid:more-codecs:0.3.5+1.21.2')

gametestImplementation sourceSets.main.output
}

processResources {
Expand All @@ -36,7 +58,7 @@ processResources {

tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
it.options.release = 16
it.options.release = 21
}

java {
Expand Down
99 changes: 99 additions & 0 deletions src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package xyz.nucleoid.parties.test;

import com.mojang.brigadier.Command;
import net.minecraft.SharedConstants;
import net.minecraft.test.TestContext;
import org.apache.commons.lang3.mutable.MutableInt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class CommandAssertion {
private final TestContext context;

private final TrackingFakePlayerEntity player;
private final String command;

private final Map<TrackingFakePlayerEntity, List<String>> recipientsToExpectedMessages = new HashMap<>();

private CommandAssertion(TestContext context, TrackingFakePlayerEntity player, Set<TrackingFakePlayerEntity> players, String command) {
this.context = Objects.requireNonNull(context);
this.player = Objects.requireNonNull(player);
this.command = Objects.requireNonNull(command);

if (!players.contains(player)) {
throw new IllegalArgumentException("player must be in players set");
}

for (var recipient : players) {
this.recipientsToExpectedMessages.put(recipient, new ArrayList<>());
}
}

public CommandAssertion expectFeedback(String feedback) {
return this.expectMessage(feedback, this.player);
}

public CommandAssertion expectMessage(String message, TrackingFakePlayerEntity... recipients) {
Objects.requireNonNull(message);

for (var recipient : recipients) {
var messages = this.recipientsToExpectedMessages.get(recipient);

if (messages == null) {
throw new IllegalArgumentException("recipient must be in players set");
}

messages.add(message);
}

return this;
}

public void executeSuccess() {
this.execute(Command.SINGLE_SUCCESS);
}

public void execute(int expectedSuccessCount) {
var successCount = new MutableInt();

var source = player.getCommandSource()
.withLevel(4)
.withReturnValueConsumer((successful, successCountx) -> {
successCount.setValue(successCountx);
});

withDevelopment(() -> {
this.player.getServer().getCommandManager().executeWithPrefix(source, this.command);
});

for (var entry : this.recipientsToExpectedMessages.entrySet()) {
var recipient = entry.getKey();

var expectedMessages = entry.getValue();
var actualMessages = recipient.consumeMessages();

var name = recipient == this.player ? "feedback" : "messages to " + recipient.getNameForScoreboard();
this.context.assertEquals(actualMessages, expectedMessages, "'" + command + "' " + name);
}

this.context.assertEquals(successCount.intValue(), expectedSuccessCount, "'" + command + "' success count");
}

public static CommandAssertion builder(TestContext context, TrackingFakePlayerEntity player, Set<TrackingFakePlayerEntity> players, String command) {
return new CommandAssertion(context, player, players, command);
}

private static void withDevelopment(Runnable runnable) {
boolean stored = SharedConstants.isDevelopment;
SharedConstants.isDevelopment = true;

runnable.run();

SharedConstants.isDevelopment = stored;
}
}
189 changes: 189 additions & 0 deletions src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package xyz.nucleoid.parties.test;

import com.mojang.authlib.GameProfile;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.ApiServices;
import net.minecraft.util.Uuids;
import xyz.nucleoid.parties.PartyManager;
import xyz.nucleoid.parties.test.mixin.MinecraftServerAccessor;
import xyz.nucleoid.parties.test.mixin.PlayerManagerAccessor;

import java.net.Proxy;
import java.util.Set;
import java.util.UUID;

public class GamePartiesTest {
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
public void test(TestContext context) {
var server = context.getWorld().getServer();

var gameDir = FabricLoader.getInstance().getGameDir().toFile();

var apiServices = ApiServices.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), gameDir);
((MinecraftServerAccessor) (Object) server).setApiServices(apiServices);

var partyUuid = UUID.fromString("10a05e00-5992-448c-9bed-a14cb2a7a909");

var player1 = createFakePlayer(context, 1);
var player2 = createFakePlayer(context, 2);
var player3 = createFakePlayer(context, 3);
var player4 = createFakePlayer(context, 4);
var player5 = createFakePlayer(context, 5);

var players = Set.of(player1, player2, player3, player4, player5);

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback("There are no parties!")
.execute(0);

CommandAssertion.builder(context, player1, players, "/party leave")
.expectFeedback("You do not control any party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party kick Player2")
.expectFeedback("You do not control any party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party transfer Player2")
.expectFeedback("You do not control any party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party invite Player1")
.expectFeedback("Cannot invite yourself to the party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party invite Player2")
.expectFeedback("Invited Player2 to the party")
.expectMessage("You have been invited to join Player1's party! Click here to join", player2)
.executeSuccess();

var partyManager = PartyManager.get(server);
partyManager.getAllParties().forEach(party -> party.setUuid(partyUuid));

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]")
.expectFeedback(" - Player1 (owner)")
.expectFeedback(" - Player2 (pending)")
.execute(1);

CommandAssertion.builder(context, player2, players, "/party accept Player3")
.expectFeedback("You are not invited to this party!")
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party accept Player1")
.expectMessage("Player2 has joined the party!", player1, player2)
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]")
.expectFeedback(" - Player1 (owner)")
.expectFeedback(" - Player2")
.execute(1);

CommandAssertion.builder(context, player1, players, "/party invite Player4")
.expectFeedback("Invited Player4 to the party")
.expectMessage("You have been invited to join Player1's party! Click here to join", player4)
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party invite Player5")
.expectFeedback("Invited Player5 to the party")
.expectMessage("You have been invited to join Player1's party! Click here to join", player5)
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party invite Player3")
.expectFeedback("You do not control this party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party invite Player1")
.expectFeedback("Cannot invite yourself to the party!")
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party transfer Player3")
.expectFeedback("You do not control this party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party transfer Player2")
.expectFeedback("Your party has been transferred to Player2")
.expectMessage("Player1's party has been transferred to you", player2)
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]")
.expectFeedback(" - Player2 (owner)")
.expectFeedback(" - Player1")
.expectFeedback(" - Player5 (pending)")
.expectFeedback(" - Player4 (pending)")
.execute(1);

// Selectors are used for game profile arguments to bypass oddities with looking up game profiles from API services

CommandAssertion.builder(context, player1, players, "/party kick @a[name=Player3,limit=1]")
.expectFeedback("You do not control this party!")
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player1,limit=1]")
.expectFeedback("Player1 has been kicked from the party")
.expectMessage("You have been kicked from the party", player1)
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player1,limit=1]")
.expectFeedback("Player1 is not in this party!")
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player2,limit=1]")
.expectFeedback("Cannot remove yourself from the party!")
.executeSuccess();

CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player3,limit=1]")
.expectFeedback("Player3 is not in this party!")
.executeSuccess();

CommandAssertion.builder(context, player4, players, "/party accept 10a05e00-5992-448c-9bed-a14cb2a7a909")
.expectMessage("Player4 has joined the party!", player2, player4)
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]")
.expectFeedback(" - Player2 (owner)")
.expectFeedback(" - Player4")
.expectFeedback(" - Player5 (pending)")
.execute(1);

CommandAssertion.builder(context, player4, players, "/party leave")
.expectMessage("Player4 has left the party!", player2, player4)
.executeSuccess();

CommandAssertion.builder(context, player5, players, "/party leave")
.expectFeedback("You do not control any party!")
.executeSuccess();

CommandAssertion.builder(context, player1, players, "/party list")
.expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]")
.expectFeedback(" - Player2 (owner)")
.expectFeedback(" - Player5 (pending)")
.execute(1);

context.complete();
}

private static TrackingFakePlayerEntity createFakePlayer(TestContext context, int id) {
var world = context.getWorld();

var username = "Player" + id;
var uuid = Uuids.getOfflinePlayerUuid(username);

var profile = new GameProfile(uuid, username);
var player = new TrackingFakePlayerEntity(world, profile);

var playerManager = world.getServer().getPlayerManager();

playerManager.getPlayerList().add(player);
((PlayerManagerAccessor) playerManager).getPlayerMap().put(uuid, player);

return player;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package xyz.nucleoid.parties.test;

import com.mojang.authlib.GameProfile;
import net.fabricmc.fabric.api.entity.FakePlayer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;

import java.util.ArrayList;
import java.util.List;

public class TrackingFakePlayerEntity extends FakePlayer {
private final List<String> messages = new ArrayList<>();

protected TrackingFakePlayerEntity(ServerWorld world, GameProfile profile) {
super(world, profile);
}

@Override
public void sendMessageToClient(Text message, boolean overlay) {
var string = message.getString();

for (var line : string.split("\n")) {
this.messages.add(line);
}
}

public List<String> consumeMessages() {
var result = new ArrayList<>(this.messages);
this.messages.clear();

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package xyz.nucleoid.parties.test.mixin;

import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ApiServices;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(MinecraftServer.class)
public interface MinecraftServerAccessor {
@Accessor
@Mutable
void setApiServices(ApiServices apiServices);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package xyz.nucleoid.parties.test.mixin;

import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

import java.util.Map;
import java.util.UUID;

@Mixin(PlayerManager.class)
public interface PlayerManagerAccessor {
@Accessor
Map<UUID, ServerPlayerEntity> getPlayerMap();
}
Loading