From 282aa6bf03ebe50cd4afa7a9887f7737aa0a6c86 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:15:58 -0500 Subject: [PATCH 01/13] Track party members by their type --- src/main/java/xyz/nucleoid/parties/Party.java | 88 +++++++++++-------- .../xyz/nucleoid/parties/PartyManager.java | 45 +++++----- .../xyz/nucleoid/parties/PartyMember.java | 23 +++++ 3 files changed, 97 insertions(+), 59 deletions(-) create mode 100644 src/main/java/xyz/nucleoid/parties/PartyMember.java diff --git a/src/main/java/xyz/nucleoid/parties/Party.java b/src/main/java/xyz/nucleoid/parties/Party.java index db93073..ffbb2e4 100644 --- a/src/main/java/xyz/nucleoid/parties/Party.java +++ b/src/main/java/xyz/nucleoid/parties/Party.java @@ -1,74 +1,81 @@ package xyz.nucleoid.parties; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.server.MinecraftServer; import xyz.nucleoid.plasmid.api.game.player.MutablePlayerSet; import xyz.nucleoid.plasmid.api.util.PlayerRef; import java.util.List; -import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; public final class Party { - private PlayerRef owner; - - private final List members = new ObjectArrayList<>(); - private final Set pendingMembers = new ObjectOpenHashSet<>(); - private final MutablePlayerSet memberPlayers; + private final Object2ObjectMap participantToParty; + private final Object2ObjectMap members = new Object2ObjectOpenHashMap<>(); + private final UUID uuid; - Party(MinecraftServer server, PlayerRef owner) { + Party(MinecraftServer server, Object2ObjectMap participantToParty, PlayerRef owner) { this.memberPlayers = new MutablePlayerSet(server); - this.setOwner(owner); + + this.participantToParty = participantToParty; + this.putMember(owner, PartyMember.Type.OWNER); this.uuid = UUID.randomUUID(); } - void setOwner(PlayerRef owner) { - this.owner = owner; - if (this.memberPlayers.add(owner)) { - this.members.add(owner); - } + PartyMember getMember(PlayerRef player) { + return this.members.get(player); } - boolean invite(PlayerRef player) { - if (this.memberPlayers.contains(player)) { - return false; - } - return this.pendingMembers.add(player); + private boolean matches(PlayerRef player, Predicate predicate) { + var member = this.members.get(player); + return member != null && predicate.test(member); } - boolean remove(PlayerRef player) { - if (this.memberPlayers.remove(player)) { - this.members.remove(player); - return true; - } - return this.pendingMembers.remove(player); + boolean isPending(PlayerRef player) { + return this.matches(player, PartyMember::isPending); } - boolean acceptInvite(PlayerRef player) { - if (this.pendingMembers.remove(player)) { - if (this.memberPlayers.add(player)) { - this.members.add(player); - } - return true; - } - return false; + boolean isParticipant(PlayerRef player) { + return this.matches(player, PartyMember::isParticipant); + } + + boolean isOwner(PlayerRef player) { + return this.matches(player, PartyMember::isOwner); } - public boolean contains(PlayerRef player) { - return this.memberPlayers.contains(player); + void putMember(PlayerRef player, PartyMember.Type type) { + var member = new PartyMember(this, player, type); + + if (member.isParticipant() && this.participantToParty.put(player, this) != null) { + throw new IllegalStateException("player is already in a party"); + } + + this.members.put(player, member); + + if (member.isParticipant()) { + this.memberPlayers.add(player); + } } - public boolean isOwner(PlayerRef from) { - return from.equals(this.owner); + PartyMember removeMember(PlayerRef player) { + var member = this.members.remove(player); + this.memberPlayers.remove(player); + + if (member != null && member.isParticipant() && !this.participantToParty.remove(player, this)) { + throw new IllegalStateException("player is not in this party"); + } + + return member; } public List getMembers() { - return this.members; + return new ObjectArrayList<>(this.members.keySet()); } public MutablePlayerSet getMemberPlayers() { @@ -78,4 +85,9 @@ public MutablePlayerSet getMemberPlayers() { public UUID getUuid() { return this.uuid; } + + @Override + public String toString() { + return "Party{members=" + members + ", uuid=" + uuid + "}"; + } } diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index e0595a2..5999b18 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -18,7 +18,7 @@ public final class PartyManager { private static PartyManager instance; private final MinecraftServer server; - private final Object2ObjectMap playerToParty = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectMap participantToParty = new Object2ObjectOpenHashMap<>(); private PartyManager(MinecraftServer server) { this.server = server; @@ -48,13 +48,14 @@ public static PartyManager get(MinecraftServer server) { public void onPlayerLogOut(ServerPlayerEntity player) { var ref = PlayerRef.of(player); - var party = this.playerToParty.remove(ref); + var party = this.getParty(ref); if (party == null) { return; } - if (party.remove(ref)) { - if (party.isOwner(ref)) { + var member = party.removeMember(ref); + if (member != null) { + if (member.isOwner()) { this.onPartyOwnerLogOut(player, party); } @@ -67,7 +68,7 @@ private void onPartyOwnerLogOut(ServerPlayerEntity player, Party party) { if (!members.isEmpty()) { var nextMember = members.get(0); - party.setOwner(nextMember); + party.putMember(nextMember, PartyMember.Type.OWNER); nextMember.ifOnline(this.server, nextPlayer -> { nextPlayer.sendMessage(PartyTexts.transferredReceiver(player), false); @@ -78,7 +79,9 @@ private void onPartyOwnerLogOut(ServerPlayerEntity player, Party party) { public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { var party = this.getOrCreateOwnParty(owner); if (party != null) { - if (party.invite(player)) { + var member = party.getMember(player); + if (member == null) { + party.putMember(player, PartyMember.Type.PENDING); return PartyResult.ok(party); } else { return PartyResult.err(PartyError.ALREADY_INVITED); @@ -98,8 +101,7 @@ public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { return PartyResult.err(PartyError.DOES_NOT_EXIST); } - if (party.remove(player)) { - this.playerToParty.remove(player, party); + if (party.removeMember(player) != null) { return PartyResult.ok(party); } @@ -107,7 +109,7 @@ public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { } public PartyResult acceptInvite(PlayerRef player, @Nullable Party party) { - if (this.playerToParty.containsKey(player)) { + if (this.participantToParty.containsKey(player)) { return PartyResult.err(PartyError.ALREADY_IN_PARTY); } @@ -115,8 +117,8 @@ public PartyResult acceptInvite(PlayerRef player, @Nullable Party party) { return PartyResult.err(PartyError.DOES_NOT_EXIST); } - if (party.acceptInvite(player)) { - this.playerToParty.put(player, party); + if (party.isPending(player)) { + party.putMember(player, PartyMember.Type.MEMBER); return PartyResult.ok(party); } @@ -136,8 +138,7 @@ public PartyResult leaveParty(PlayerRef player) { return this.disbandParty(player); } - if (party.remove(player)) { - this.playerToParty.remove(player, party); + if (party.removeMember(player) != null) { return PartyResult.ok(party); } else { return PartyResult.err(PartyError.NOT_IN_PARTY); @@ -150,11 +151,13 @@ public PartyResult transferParty(PlayerRef from, PlayerRef to) { return PartyResult.err(PartyError.DOES_NOT_EXIST); } - if (!party.contains(to)) { + if (!party.isParticipant(to)) { return PartyResult.err(PartyError.NOT_IN_PARTY); } - party.setOwner(to); + party.putMember(from, PartyMember.Type.MEMBER); + party.putMember(to, PartyMember.Type.OWNER); + return PartyResult.ok(party); } @@ -170,18 +173,18 @@ public PartyResult disbandParty(PlayerRef owner) { public void disbandParty(Party party) { for (PlayerRef member : party.getMembers()) { - this.playerToParty.remove(member, party); + this.participantToParty.remove(member, party); } } @Nullable public Party getParty(PlayerRef player) { - return this.playerToParty.get(player); + return this.participantToParty.get(player); } @Nullable public Party getParty(UUID uuid) { - for (Party party : this.playerToParty.values()) { + for (Party party : this.participantToParty.values()) { if (party.getUuid().equals(uuid)) { return party; } @@ -192,7 +195,7 @@ public Party getParty(UUID uuid) { @Nullable public Party getOwnParty(PlayerRef owner) { - var party = this.playerToParty.get(owner); + var party = this.participantToParty.get(owner); if (party != null && party.isOwner(owner)) { return party; } @@ -200,7 +203,7 @@ public Party getOwnParty(PlayerRef owner) { } private Party getOrCreateOwnParty(PlayerRef owner) { - var party = this.playerToParty.computeIfAbsent(owner, this::createParty); + var party = this.participantToParty.computeIfAbsent(owner, this::createParty); if (party.isOwner(owner)) { return party; } @@ -208,7 +211,7 @@ private Party getOrCreateOwnParty(PlayerRef owner) { } private Party createParty(PlayerRef owner) { - return new Party(this.server, owner); + return new Party(this.server, this.participantToParty, owner); } public Collection getPartyMembers(ServerPlayerEntity player) { diff --git a/src/main/java/xyz/nucleoid/parties/PartyMember.java b/src/main/java/xyz/nucleoid/parties/PartyMember.java new file mode 100644 index 0000000..75f7dcf --- /dev/null +++ b/src/main/java/xyz/nucleoid/parties/PartyMember.java @@ -0,0 +1,23 @@ +package xyz.nucleoid.parties; + +import xyz.nucleoid.plasmid.api.util.PlayerRef; + +public record PartyMember(Party party, PlayerRef player, Type type) { + public boolean isPending() { + return this.type == Type.PENDING; + } + + public boolean isParticipant() { + return !this.isPending(); + } + + public boolean isOwner() { + return this.type == Type.OWNER; + } + + enum Type { + PENDING, + MEMBER, + OWNER; + } +} From 9e3b0672495b8cc1e5f21205db0a894ab5656215 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:16:21 -0500 Subject: [PATCH 02/13] Prevent players from inviting themselves to a party --- src/main/java/xyz/nucleoid/parties/PartyError.java | 1 + src/main/java/xyz/nucleoid/parties/PartyManager.java | 4 ++++ src/main/java/xyz/nucleoid/parties/PartyTexts.java | 1 + src/main/resources/data/game_parties/lang/en_us.json | 1 + 4 files changed, 7 insertions(+) diff --git a/src/main/java/xyz/nucleoid/parties/PartyError.java b/src/main/java/xyz/nucleoid/parties/PartyError.java index 5e39b50..156334a 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyError.java +++ b/src/main/java/xyz/nucleoid/parties/PartyError.java @@ -4,6 +4,7 @@ public enum PartyError { DOES_NOT_EXIST, ALREADY_INVITED, ALREADY_IN_PARTY, + CANNOT_INVITE_SELF, CANNOT_REMOVE_SELF, NOT_IN_PARTY, NOT_INVITED diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index 5999b18..041669c 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -77,6 +77,10 @@ private void onPartyOwnerLogOut(ServerPlayerEntity player, Party party) { } public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { + if (owner.equals(player)) { + return PartyResult.err(PartyError.CANNOT_INVITE_SELF); + } + var party = this.getOrCreateOwnParty(owner); if (party != null) { var member = party.getMember(player); diff --git a/src/main/java/xyz/nucleoid/parties/PartyTexts.java b/src/main/java/xyz/nucleoid/parties/PartyTexts.java index f6539b4..4353844 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyTexts.java +++ b/src/main/java/xyz/nucleoid/parties/PartyTexts.java @@ -17,6 +17,7 @@ public static MutableText displayError(PartyError error, String player) { case DOES_NOT_EXIST -> Text.translatable("text.game_parties.party.error.does_not_exist"); case ALREADY_INVITED -> Text.translatable("text.game_parties.party.error.already_invited", player); case ALREADY_IN_PARTY -> Text.translatable("text.game_parties.party.error.already_in_party"); + case CANNOT_INVITE_SELF -> Text.translatable("text.game_parties.party.error.cannot_invite_self"); case CANNOT_REMOVE_SELF -> Text.translatable("text.game_parties.party.error.cannot_remove_self"); case NOT_IN_PARTY -> Text.translatable("text.game_parties.party.error.not_in_party", player); case NOT_INVITED -> Text.translatable("text.game_parties.party.error.not_invited"); diff --git a/src/main/resources/data/game_parties/lang/en_us.json b/src/main/resources/data/game_parties/lang/en_us.json index a9cdd5b..dbbc6a4 100644 --- a/src/main/resources/data/game_parties/lang/en_us.json +++ b/src/main/resources/data/game_parties/lang/en_us.json @@ -2,6 +2,7 @@ "text.game_parties.party.disband.success": "Your party has been disbanded!", "text.game_parties.party.error.already_in_party": "You are already in this party!", "text.game_parties.party.error.already_invited": "%s is already invited to this party!", + "text.game_parties.party.error.cannot_invite_self": "Cannot invite yourself to the party!", "text.game_parties.party.error.cannot_remove_self": "Cannot remove yourself from the party!", "text.game_parties.party.error.does_not_exist": "You do not control any party!", "text.game_parties.party.error.not_in_party": "%s is not in this party!", From 8118bd5364aa3c7fe0bc67a2edea57095c4dd227 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:16:49 -0500 Subject: [PATCH 03/13] Send feedback to players after they have left a party Fixes #12 --- src/main/java/xyz/nucleoid/parties/PartyCommand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index 5f7b04a..eb7509a 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -161,8 +161,9 @@ private static int leave(CommandContext ctx) throws Command if (result.isOk()) { var party = result.party(); - var message = PartyTexts.leaveSuccess(player); - party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); + var message = PartyTexts.leaveSuccess(player).formatted(Formatting.GOLD); + party.getMemberPlayers().sendMessage(message); + player.sendMessage(message, false); } else { var error = result.error(); source.sendError(PartyTexts.displayError(error, player)); From 2eaeefc4caa847412711c7a9d400293edb579daa Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:27:25 -0500 Subject: [PATCH 04/13] Split the error messages for not being in a party and not being the owner of the current party --- .../xyz/nucleoid/parties/PartyCommand.java | 8 +- .../java/xyz/nucleoid/parties/PartyError.java | 3 +- .../xyz/nucleoid/parties/PartyManager.java | 77 +++++++++---------- .../xyz/nucleoid/parties/PartyResult.java | 10 +++ .../java/xyz/nucleoid/parties/PartyTexts.java | 1 + .../data/game_parties/lang/en_us.json | 1 + 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index eb7509a..e03014c 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -132,18 +132,18 @@ private static int acceptInviteByUuid(CommandContext ctx) t var uuid = UuidArgumentType.getUuid(ctx, "party"); var partyManager = PartyManager.get(ctx.getSource().getServer()); - return acceptInvite(ctx, partyManager.getParty(uuid)); + return acceptInvite(ctx, PartyResult.ok(partyManager.getParty(uuid))); } - private static int acceptInvite(CommandContext ctx, Party party) throws CommandSyntaxException { + private static int acceptInvite(CommandContext ctx, PartyResult result) throws CommandSyntaxException { var source = ctx.getSource(); var player = source.getPlayer(); var partyManager = PartyManager.get(source.getServer()); - var result = partyManager.acceptInvite(PlayerRef.of(player), party); + result = result.map(party -> partyManager.acceptInvite(PlayerRef.of(player), party)); if (result.isOk()) { var message = PartyTexts.joinSuccess(player); - party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); + result.party().getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); } else { var error = result.error(); source.sendError(PartyTexts.displayError(error, player)); diff --git a/src/main/java/xyz/nucleoid/parties/PartyError.java b/src/main/java/xyz/nucleoid/parties/PartyError.java index 156334a..d5fce30 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyError.java +++ b/src/main/java/xyz/nucleoid/parties/PartyError.java @@ -7,5 +7,6 @@ public enum PartyError { CANNOT_INVITE_SELF, CANNOT_REMOVE_SELF, NOT_IN_PARTY, - NOT_INVITED + NOT_INVITED, + NOT_OWNER } diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index 041669c..5c5496a 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -81,8 +81,9 @@ public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { return PartyResult.err(PartyError.CANNOT_INVITE_SELF); } - var party = this.getOrCreateOwnParty(owner); - if (party != null) { + var result = this.getOrCreateOwnParty(owner); + + return result.map(party -> { var member = party.getMember(player); if (member == null) { party.putMember(player, PartyMember.Type.PENDING); @@ -90,9 +91,7 @@ public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { } else { return PartyResult.err(PartyError.ALREADY_INVITED); } - } - - return PartyResult.err(PartyError.DOES_NOT_EXIST); + }); } public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { @@ -100,16 +99,15 @@ public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { return PartyResult.err(PartyError.CANNOT_REMOVE_SELF); } - var party = this.getOwnParty(owner); - if (party == null) { - return PartyResult.err(PartyError.DOES_NOT_EXIST); - } + var result = this.getOwnParty(owner); - if (party.removeMember(player) != null) { - return PartyResult.ok(party); - } + return result.map(party -> { + if (party.removeMember(player) != null) { + return PartyResult.ok(party); + } - return PartyResult.err(PartyError.NOT_IN_PARTY); + return PartyResult.err(PartyError.NOT_IN_PARTY); + }); } public PartyResult acceptInvite(PlayerRef player, @Nullable Party party) { @@ -150,29 +148,27 @@ public PartyResult leaveParty(PlayerRef player) { } public PartyResult transferParty(PlayerRef from, PlayerRef to) { - var party = this.getOwnParty(from); - if (party == null) { - return PartyResult.err(PartyError.DOES_NOT_EXIST); - } + var result = this.getOwnParty(from); - if (!party.isParticipant(to)) { - return PartyResult.err(PartyError.NOT_IN_PARTY); - } + return result.map(party -> { + if (!party.isParticipant(to)) { + return PartyResult.err(PartyError.NOT_IN_PARTY); + } - party.putMember(from, PartyMember.Type.MEMBER); - party.putMember(to, PartyMember.Type.OWNER); + party.putMember(from, PartyMember.Type.MEMBER); + party.putMember(to, PartyMember.Type.OWNER); - return PartyResult.ok(party); + return PartyResult.ok(party); + }); } public PartyResult disbandParty(PlayerRef owner) { - var party = this.getOwnParty(owner); - if (party != null) { + var result = this.getOwnParty(owner); + + return result.map(party -> { this.disbandParty(party); return PartyResult.ok(party); - } else { - return PartyResult.err(PartyError.DOES_NOT_EXIST); - } + }); } public void disbandParty(Party party) { @@ -197,21 +193,24 @@ public Party getParty(UUID uuid) { return null; } - @Nullable - public Party getOwnParty(PlayerRef owner) { + public PartyResult getOwnParty(PlayerRef owner) { var party = this.participantToParty.get(owner); - if (party != null && party.isOwner(owner)) { - return party; + + if (party == null) { + return PartyResult.err(PartyError.DOES_NOT_EXIST); + } else if (!party.isOwner(owner)) { + PartyResult.err(PartyError.NOT_OWNER); } - return null; + + return PartyResult.ok(party); } - private Party getOrCreateOwnParty(PlayerRef owner) { + private PartyResult getOrCreateOwnParty(PlayerRef owner) { var party = this.participantToParty.computeIfAbsent(owner, this::createParty); if (party.isOwner(owner)) { - return party; + return PartyResult.ok(party); } - return null; + return PartyResult.err(PartyError.NOT_OWNER); } private Party createParty(PlayerRef owner) { @@ -219,9 +218,9 @@ private Party createParty(PlayerRef owner) { } public Collection getPartyMembers(ServerPlayerEntity player) { - var party = this.getOwnParty(PlayerRef.of(player)); - if (party != null) { - return Lists.newArrayList(party.getMemberPlayers()); + var result = this.getOwnParty(PlayerRef.of(player)); + if (result.isOk()) { + return Lists.newArrayList(result.party().getMemberPlayers()); } else { return Collections.singleton(player); } diff --git a/src/main/java/xyz/nucleoid/parties/PartyResult.java b/src/main/java/xyz/nucleoid/parties/PartyResult.java index 1869998..918b0db 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyResult.java +++ b/src/main/java/xyz/nucleoid/parties/PartyResult.java @@ -2,6 +2,8 @@ import org.jetbrains.annotations.Nullable; +import java.util.function.Function; + public final class PartyResult { private final Party party; private final PartyError error; @@ -27,6 +29,14 @@ public boolean isErr() { return this.error != null; } + public PartyResult map(Function mapper) { + if (this.party != null) { + return mapper.apply(this.party); + } else { + return this; + } + } + @Nullable public Party party() { return this.party; diff --git a/src/main/java/xyz/nucleoid/parties/PartyTexts.java b/src/main/java/xyz/nucleoid/parties/PartyTexts.java index 4353844..84d4255 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyTexts.java +++ b/src/main/java/xyz/nucleoid/parties/PartyTexts.java @@ -21,6 +21,7 @@ public static MutableText displayError(PartyError error, String player) { case CANNOT_REMOVE_SELF -> Text.translatable("text.game_parties.party.error.cannot_remove_self"); case NOT_IN_PARTY -> Text.translatable("text.game_parties.party.error.not_in_party", player); case NOT_INVITED -> Text.translatable("text.game_parties.party.error.not_invited"); + case NOT_OWNER -> Text.translatable("text.game_parties.party.error.not_owner"); }; } diff --git a/src/main/resources/data/game_parties/lang/en_us.json b/src/main/resources/data/game_parties/lang/en_us.json index dbbc6a4..3331027 100644 --- a/src/main/resources/data/game_parties/lang/en_us.json +++ b/src/main/resources/data/game_parties/lang/en_us.json @@ -7,6 +7,7 @@ "text.game_parties.party.error.does_not_exist": "You do not control any party!", "text.game_parties.party.error.not_in_party": "%s is not in this party!", "text.game_parties.party.error.not_invited": "You are not invited to this party!", + "text.game_parties.party.error.not_owner": "You do not control this party!", "text.game_parties.party.invited.receiver": "You have been invited to join %s's party! ", "text.game_parties.party.invited.receiver.click": "Click here to join", "text.game_parties.party.invited.receiver.hover": "Join %s's party!", From ab9501687eb902784696b547779e08cd9464fd03 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:33:42 -0500 Subject: [PATCH 05/13] Use player display names in error messages --- src/main/java/xyz/nucleoid/parties/PartyCommand.java | 3 ++- src/main/java/xyz/nucleoid/parties/PartyTexts.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index e03014c..1b7b9ca 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -8,6 +8,7 @@ import net.minecraft.command.argument.GameProfileArgumentType; import net.minecraft.command.argument.UuidArgumentType; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; import net.minecraft.util.Formatting; import xyz.nucleoid.plasmid.api.util.PlayerRef; @@ -89,7 +90,7 @@ private static int kickPlayer(CommandContext ctx) throws Co }); } else { var error = result.error(); - source.sendError(PartyTexts.displayError(error, profile.getName())); + source.sendError(PartyTexts.displayError(error, Text.literal(profile.getName()))); } } diff --git a/src/main/java/xyz/nucleoid/parties/PartyTexts.java b/src/main/java/xyz/nucleoid/parties/PartyTexts.java index 84d4255..3a68cba 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyTexts.java +++ b/src/main/java/xyz/nucleoid/parties/PartyTexts.java @@ -9,10 +9,10 @@ public final class PartyTexts { public static MutableText displayError(PartyError error, ServerPlayerEntity player) { - return displayError(error, player.getGameProfile().getName()); + return displayError(error, player.getDisplayName()); } - public static MutableText displayError(PartyError error, String player) { + public static MutableText displayError(PartyError error, Text player) { return switch (error) { case DOES_NOT_EXIST -> Text.translatable("text.game_parties.party.error.does_not_exist"); case ALREADY_INVITED -> Text.translatable("text.game_parties.party.error.already_invited", player); From bb08d357c1e851de71ba845d31ae49ad0fca8a7a Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:33:58 -0500 Subject: [PATCH 06/13] Fix party transfers causing an exception to be thrown --- src/main/java/xyz/nucleoid/parties/Party.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/Party.java b/src/main/java/xyz/nucleoid/parties/Party.java index ffbb2e4..9d254a9 100644 --- a/src/main/java/xyz/nucleoid/parties/Party.java +++ b/src/main/java/xyz/nucleoid/parties/Party.java @@ -52,8 +52,12 @@ boolean isOwner(PlayerRef player) { void putMember(PlayerRef player, PartyMember.Type type) { var member = new PartyMember(this, player, type); - if (member.isParticipant() && this.participantToParty.put(player, this) != null) { - throw new IllegalStateException("player is already in a party"); + if (member.isParticipant()) { + var existingParty = this.participantToParty.put(player, this); + + if (existingParty != null && existingParty != this) { + throw new IllegalStateException("player is already in a party"); + } } this.members.put(player, member); From 37bbc5b0ab96baa5c093e32f28c93a2418ceb4f7 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:50:51 -0500 Subject: [PATCH 07/13] Fix party kick feedback referencing the wrong player --- .../java/xyz/nucleoid/parties/PartyCommand.java | 16 +++++++++++----- .../java/xyz/nucleoid/parties/PartyTexts.java | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index 1b7b9ca..c016f58 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -78,16 +78,22 @@ private static int kickPlayer(CommandContext ctx) throws Co for (var profile : profiles) { var partyManager = PartyManager.get(source.getServer()); - var result = partyManager.kickPlayer(PlayerRef.of(owner), PlayerRef.of(profile)); + var ref = PlayerRef.of(profile); + var result = partyManager.kickPlayer(PlayerRef.of(owner), ref); if (result.isOk()) { var party = result.party(); - var message = PartyTexts.kickedSender(owner); - party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); + MutableText message; + var player = ref.getEntity(server); - PlayerRef.of(profile).ifOnline(server, player -> { + if (player == null) { + message = PartyTexts.kickedSender(Text.literal(profile.getName())); + } else { + message = PartyTexts.kickedSender(player.getDisplayName()); player.sendMessage(PartyTexts.kickedReceiver().formatted(Formatting.RED), false); - }); + } + + party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); } else { var error = result.error(); source.sendError(PartyTexts.displayError(error, Text.literal(profile.getName()))); diff --git a/src/main/java/xyz/nucleoid/parties/PartyTexts.java b/src/main/java/xyz/nucleoid/parties/PartyTexts.java index 3a68cba..e53f129 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyTexts.java +++ b/src/main/java/xyz/nucleoid/parties/PartyTexts.java @@ -45,8 +45,8 @@ public static MutableText transferredReceiver(ServerPlayerEntity transferredFrom return Text.translatable("text.game_parties.party.transferred.receiver", transferredFrom.getDisplayName()); } - public static MutableText kickedSender(ServerPlayerEntity player) { - return Text.translatable("text.game_parties.party.kicked.sender", player.getDisplayName()); + public static MutableText kickedSender(Text playerName) { + return Text.translatable("text.game_parties.party.kicked.sender", playerName); } public static MutableText kickedReceiver() { From 3de8958d3356056d4b86ae7c28dce4818e851a19 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:01:33 -0500 Subject: [PATCH 08/13] Use the not invited error when accepting invites from players without parties --- src/main/java/xyz/nucleoid/parties/PartyCommand.java | 2 +- src/main/java/xyz/nucleoid/parties/PartyManager.java | 6 +----- src/main/java/xyz/nucleoid/parties/PartyResult.java | 9 +++++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index c016f58..4c03d62 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -132,7 +132,7 @@ private static int acceptInviteByOwner(CommandContext ctx) var owner = EntityArgumentType.getPlayer(ctx, "owner"); var partyManager = PartyManager.get(ctx.getSource().getServer()); - return acceptInvite(ctx, partyManager.getOwnParty(PlayerRef.of(owner))); + return acceptInvite(ctx, partyManager.getOwnParty(PlayerRef.of(owner)).replaceError(() -> PartyError.NOT_INVITED)); } private static int acceptInviteByUuid(CommandContext ctx) throws CommandSyntaxException { diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index 5c5496a..415f8e2 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -110,15 +110,11 @@ public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { }); } - public PartyResult acceptInvite(PlayerRef player, @Nullable Party party) { + public PartyResult acceptInvite(PlayerRef player, Party party) { if (this.participantToParty.containsKey(player)) { return PartyResult.err(PartyError.ALREADY_IN_PARTY); } - if (party == null) { - return PartyResult.err(PartyError.DOES_NOT_EXIST); - } - if (party.isPending(player)) { party.putMember(player, PartyMember.Type.MEMBER); return PartyResult.ok(party); diff --git a/src/main/java/xyz/nucleoid/parties/PartyResult.java b/src/main/java/xyz/nucleoid/parties/PartyResult.java index 918b0db..84c0d0e 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyResult.java +++ b/src/main/java/xyz/nucleoid/parties/PartyResult.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.Nullable; import java.util.function.Function; +import java.util.function.Supplier; public final class PartyResult { private final Party party; @@ -37,6 +38,14 @@ public PartyResult map(Function mapper) { } } + public PartyResult replaceError(Supplier error) { + if (this.error != null) { + return PartyResult.err(error.get()); + } else { + return this; + } + } + @Nullable public Party party() { return this.party; From 1b9abdea667e2769af12534a5623dfe5b44f6024 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:10:33 -0500 Subject: [PATCH 09/13] Fix recursion in the string representation of parties --- src/main/java/xyz/nucleoid/parties/PartyMember.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/xyz/nucleoid/parties/PartyMember.java b/src/main/java/xyz/nucleoid/parties/PartyMember.java index 75f7dcf..135a491 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyMember.java +++ b/src/main/java/xyz/nucleoid/parties/PartyMember.java @@ -15,6 +15,11 @@ public boolean isOwner() { return this.type == Type.OWNER; } + @Override + public String toString() { + return this.player.id() + " (" + this.type + ")"; + } + enum Type { PENDING, MEMBER, From 9e26f97d646a711e3c197aea6370132754a907dc Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:27:46 -0500 Subject: [PATCH 10/13] Fix accepting party invites causing an exception to be thrown --- build.gradle | 2 +- .../xyz/nucleoid/parties/PartyCommand.java | 64 ++++++++---------- .../xyz/nucleoid/parties/PartyManager.java | 63 +++++++++-------- .../xyz/nucleoid/parties/PartyResult.java | 67 +++++++------------ 4 files changed, 85 insertions(+), 111 deletions(-) diff --git a/build.gradle b/build.gradle index 07b1242..4b4f94a 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ processResources { tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" - it.options.release = 16 + it.options.release = 21 } java { diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index 4c03d62..4afc8ac 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -8,6 +8,7 @@ import net.minecraft.command.argument.GameProfileArgumentType; import net.minecraft.command.argument.UuidArgumentType; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import xyz.nucleoid.plasmid.api.util.PlayerRef; @@ -54,17 +55,16 @@ private static int invitePlayer(CommandContext ctx) throws var partyManager = PartyManager.get(source.getServer()); var result = partyManager.invitePlayer(PlayerRef.of(owner), PlayerRef.of(player)); - if (result.isOk()) { + result.ifSuccessElse(party -> { source.sendFeedback(() -> PartyTexts.invitedSender(player).formatted(Formatting.GOLD), false); - var notification = PartyTexts.invitedReceiver(owner, result.party().getUuid()) + var notification = PartyTexts.invitedReceiver(owner, party.getUuid()) .formatted(Formatting.GOLD); player.sendMessage(notification, false); - } else { - var error = result.error(); + }, error -> { source.sendError(PartyTexts.displayError(error, player)); - } + }); return Command.SINGLE_SUCCESS; } @@ -80,9 +80,7 @@ private static int kickPlayer(CommandContext ctx) throws Co var partyManager = PartyManager.get(source.getServer()); var ref = PlayerRef.of(profile); var result = partyManager.kickPlayer(PlayerRef.of(owner), ref); - if (result.isOk()) { - var party = result.party(); - + result.ifSuccessElse(party -> { MutableText message; var player = ref.getEntity(server); @@ -94,10 +92,9 @@ private static int kickPlayer(CommandContext ctx) throws Co } party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); - } else { - var error = result.error(); + }, error -> { source.sendError(PartyTexts.displayError(error, Text.literal(profile.getName()))); - } + }); } return Command.SINGLE_SUCCESS; @@ -110,7 +107,7 @@ private static int transferToPlayer(CommandContext ctx) thr var partyManager = PartyManager.get(source.getServer()); var result = partyManager.transferParty(PlayerRef.of(oldOwner), PlayerRef.of(newOwner)); - if (result.isOk()) { + result.ifSuccessElse(party -> { source.sendFeedback( () -> PartyTexts.transferredSender(newOwner).formatted(Formatting.GOLD), false @@ -120,10 +117,9 @@ private static int transferToPlayer(CommandContext ctx) thr PartyTexts.transferredReceiver(oldOwner).formatted(Formatting.GOLD), false ); - } else { - var error = result.error(); + }, error -> { source.sendError(PartyTexts.displayError(error, newOwner)); - } + }); return Command.SINGLE_SUCCESS; } @@ -132,14 +128,14 @@ private static int acceptInviteByOwner(CommandContext ctx) var owner = EntityArgumentType.getPlayer(ctx, "owner"); var partyManager = PartyManager.get(ctx.getSource().getServer()); - return acceptInvite(ctx, partyManager.getOwnParty(PlayerRef.of(owner)).replaceError(() -> PartyError.NOT_INVITED)); + return acceptInvite(ctx, partyManager.getOwnParty(PlayerRef.of(owner), PartyError.NOT_INVITED)); } private static int acceptInviteByUuid(CommandContext ctx) throws CommandSyntaxException { var uuid = UuidArgumentType.getUuid(ctx, "party"); var partyManager = PartyManager.get(ctx.getSource().getServer()); - return acceptInvite(ctx, PartyResult.ok(partyManager.getParty(uuid))); + return acceptInvite(ctx, partyManager.getParty(uuid, PartyError.NOT_INVITED)); } private static int acceptInvite(CommandContext ctx, PartyResult result) throws CommandSyntaxException { @@ -147,14 +143,14 @@ private static int acceptInvite(CommandContext ctx, PartyRe var player = source.getPlayer(); var partyManager = PartyManager.get(source.getServer()); - result = result.map(party -> partyManager.acceptInvite(PlayerRef.of(player), party)); - if (result.isOk()) { - var message = PartyTexts.joinSuccess(player); - result.party().getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); - } else { - var error = result.error(); - source.sendError(PartyTexts.displayError(error, player)); - } + result + .map(party -> partyManager.acceptInvite(PlayerRef.of(player), party)) + .ifSuccessElse(party -> { + var message = PartyTexts.joinSuccess(player); + party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); + }, error -> { + source.sendError(PartyTexts.displayError(error, player)); + }); return Command.SINGLE_SUCCESS; } @@ -165,16 +161,13 @@ private static int leave(CommandContext ctx) throws Command var partyManager = PartyManager.get(source.getServer()); var result = partyManager.leaveParty(PlayerRef.of(player)); - if (result.isOk()) { - var party = result.party(); - + result.ifSuccessElse(party -> { var message = PartyTexts.leaveSuccess(player).formatted(Formatting.GOLD); party.getMemberPlayers().sendMessage(message); player.sendMessage(message, false); - } else { - var error = result.error(); + }, error -> { source.sendError(PartyTexts.displayError(error, player)); - } + }); return Command.SINGLE_SUCCESS; } @@ -185,15 +178,12 @@ private static int disband(CommandContext ctx) throws Comma var partyManager = PartyManager.get(source.getServer()); var result = partyManager.disbandParty(PlayerRef.of(owner)); - if (result.isOk()) { - var party = result.party(); - + result.ifSuccessElse(party -> { var message = PartyTexts.disbandSuccess(); party.getMemberPlayers().sendMessage(message.formatted(Formatting.GOLD)); - } else { - var error = result.error(); + }, error -> { source.sendError(PartyTexts.displayError(error, owner)); - } + }); return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index 415f8e2..b3e09e2 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -78,7 +78,7 @@ private void onPartyOwnerLogOut(ServerPlayerEntity player, Party party) { public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { if (owner.equals(player)) { - return PartyResult.err(PartyError.CANNOT_INVITE_SELF); + return new PartyResult.Error(PartyError.CANNOT_INVITE_SELF); } var result = this.getOrCreateOwnParty(owner); @@ -87,83 +87,83 @@ public PartyResult invitePlayer(PlayerRef owner, PlayerRef player) { var member = party.getMember(player); if (member == null) { party.putMember(player, PartyMember.Type.PENDING); - return PartyResult.ok(party); + return new PartyResult.Success(party); } else { - return PartyResult.err(PartyError.ALREADY_INVITED); + return new PartyResult.Error(PartyError.ALREADY_INVITED); } }); } public PartyResult kickPlayer(PlayerRef owner, PlayerRef player) { if (owner.equals(player)) { - return PartyResult.err(PartyError.CANNOT_REMOVE_SELF); + return new PartyResult.Error(PartyError.CANNOT_REMOVE_SELF); } - var result = this.getOwnParty(owner); + var result = this.getOwnParty(owner, null); return result.map(party -> { if (party.removeMember(player) != null) { - return PartyResult.ok(party); + return new PartyResult.Success(party); } - return PartyResult.err(PartyError.NOT_IN_PARTY); + return new PartyResult.Error(PartyError.NOT_IN_PARTY); }); } public PartyResult acceptInvite(PlayerRef player, Party party) { if (this.participantToParty.containsKey(player)) { - return PartyResult.err(PartyError.ALREADY_IN_PARTY); + return new PartyResult.Error(PartyError.ALREADY_IN_PARTY); } if (party.isPending(player)) { party.putMember(player, PartyMember.Type.MEMBER); - return PartyResult.ok(party); + return new PartyResult.Success(party); } - return PartyResult.err(PartyError.NOT_INVITED); + return new PartyResult.Error(PartyError.NOT_INVITED); } public PartyResult leaveParty(PlayerRef player) { var party = this.getParty(player); if (party == null) { - return PartyResult.err(PartyError.DOES_NOT_EXIST); + return new PartyResult.Error(PartyError.DOES_NOT_EXIST); } if (party.isOwner(player)) { if (party.getMembers().size() > 1) { - return PartyResult.err(PartyError.CANNOT_REMOVE_SELF); + return new PartyResult.Error(PartyError.CANNOT_REMOVE_SELF); } return this.disbandParty(player); } if (party.removeMember(player) != null) { - return PartyResult.ok(party); + return new PartyResult.Success(party); } else { - return PartyResult.err(PartyError.NOT_IN_PARTY); + return new PartyResult.Error(PartyError.NOT_IN_PARTY); } } public PartyResult transferParty(PlayerRef from, PlayerRef to) { - var result = this.getOwnParty(from); + var result = this.getOwnParty(from, null); return result.map(party -> { if (!party.isParticipant(to)) { - return PartyResult.err(PartyError.NOT_IN_PARTY); + return new PartyResult.Error(PartyError.NOT_IN_PARTY); } party.putMember(from, PartyMember.Type.MEMBER); party.putMember(to, PartyMember.Type.OWNER); - return PartyResult.ok(party); + return new PartyResult.Success(party); }); } public PartyResult disbandParty(PlayerRef owner) { - var result = this.getOwnParty(owner); + var result = this.getOwnParty(owner, null); return result.map(party -> { this.disbandParty(party); - return PartyResult.ok(party); + return new PartyResult.Success(party); }); } @@ -178,35 +178,34 @@ public Party getParty(PlayerRef player) { return this.participantToParty.get(player); } - @Nullable - public Party getParty(UUID uuid) { + public PartyResult getParty(UUID uuid, PartyError error) { for (Party party : this.participantToParty.values()) { if (party.getUuid().equals(uuid)) { - return party; + return new PartyResult.Success(party); } } - return null; + return new PartyResult.Error(error); } - public PartyResult getOwnParty(PlayerRef owner) { + public PartyResult getOwnParty(PlayerRef owner, @Nullable PartyError error) { var party = this.participantToParty.get(owner); if (party == null) { - return PartyResult.err(PartyError.DOES_NOT_EXIST); + return new PartyResult.Error(error == null ? PartyError.DOES_NOT_EXIST : error); } else if (!party.isOwner(owner)) { - PartyResult.err(PartyError.NOT_OWNER); + return new PartyResult.Error(error == null ? PartyError.NOT_OWNER : error); } - return PartyResult.ok(party); + return new PartyResult.Success(party); } private PartyResult getOrCreateOwnParty(PlayerRef owner) { var party = this.participantToParty.computeIfAbsent(owner, this::createParty); if (party.isOwner(owner)) { - return PartyResult.ok(party); + return new PartyResult.Success(party); } - return PartyResult.err(PartyError.NOT_OWNER); + return new PartyResult.Error(PartyError.NOT_OWNER); } private Party createParty(PlayerRef owner) { @@ -214,9 +213,9 @@ private Party createParty(PlayerRef owner) { } public Collection getPartyMembers(ServerPlayerEntity player) { - var result = this.getOwnParty(PlayerRef.of(player)); - if (result.isOk()) { - return Lists.newArrayList(result.party().getMemberPlayers()); + var result = this.getOwnParty(PlayerRef.of(player), null); + if (result instanceof PartyResult.Success success) { + return Lists.newArrayList(success.party().getMemberPlayers()); } else { return Collections.singleton(player); } diff --git a/src/main/java/xyz/nucleoid/parties/PartyResult.java b/src/main/java/xyz/nucleoid/parties/PartyResult.java index 84c0d0e..4a8ec5f 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyResult.java +++ b/src/main/java/xyz/nucleoid/parties/PartyResult.java @@ -1,58 +1,43 @@ package xyz.nucleoid.parties; -import org.jetbrains.annotations.Nullable; - +import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; -public final class PartyResult { - private final Party party; - private final PartyError error; +public sealed interface PartyResult permits PartyResult.Success, PartyResult.Error { + PartyResult map(Function mapper); - private PartyResult(Party party, PartyError error) { - this.party = party; - this.error = error; - } + void ifSuccessElse(Consumer onSuccess, Consumer onError); - public static PartyResult ok(Party party) { - return new PartyResult(party, null); - } - - public static PartyResult err(PartyError error) { - return new PartyResult(null, error); - } + record Success(Party party) implements PartyResult { + public Success { + Objects.requireNonNull(party); + } - public boolean isOk() { - return this.error == null; - } + @Override + public PartyResult map(Function mapper) { + return mapper.apply(this.party); + } - public boolean isErr() { - return this.error != null; + @Override + public void ifSuccessElse(Consumer onSuccess, Consumer onError) { + onSuccess.accept(this.party); + } } - public PartyResult map(Function mapper) { - if (this.party != null) { - return mapper.apply(this.party); - } else { - return this; + record Error(PartyError error) implements PartyResult { + public Error { + Objects.requireNonNull(error); } - } - public PartyResult replaceError(Supplier error) { - if (this.error != null) { - return PartyResult.err(error.get()); - } else { + @Override + public PartyResult map(Function mapper) { return this; } - } - - @Nullable - public Party party() { - return this.party; - } - @Nullable - public PartyError error() { - return this.error; + @Override + public void ifSuccessElse(Consumer onSuccess, Consumer onError) { + onError.accept(this.error); + } } } From 68d3634d1f5912dbff882f3342dbe9d83fd17fc8 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Tue, 31 Dec 2024 15:58:12 -0500 Subject: [PATCH 11/13] Add tests --- build.gradle | 22 +++ .../parties/test/CommandAssertion.java | 67 +++++++ .../parties/test/GamePartiesTest.java | 183 ++++++++++++++++++ .../parties/test/TrackingCommandOutput.java | 46 +++++ .../test/mixin/MinecraftServerAccessor.java | 14 ++ .../test/mixin/PlayerManagerAccessor.java | 15 ++ src/gametest/resources/fabric.mod.json | 12 ++ .../game_parties_testmod.mixins.json | 12 ++ src/main/java/xyz/nucleoid/parties/Party.java | 8 +- 9 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/mixin/MinecraftServerAccessor.java create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/mixin/PlayerManagerAccessor.java create mode 100644 src/gametest/resources/fabric.mod.json create mode 100644 src/gametest/resources/game_parties_testmod.mixins.json diff --git a/build.gradle b/build.gradle index 4b4f94a..531cddb 100644 --- a/build.gradle +++ b/build.gradle @@ -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" @@ -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 { diff --git a/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java b/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java new file mode 100644 index 0000000..e176f16 --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java @@ -0,0 +1,67 @@ +package xyz.nucleoid.parties.test; + +import com.mojang.brigadier.Command; +import net.minecraft.SharedConstants; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.test.TestContext; +import org.apache.commons.lang3.mutable.MutableInt; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class CommandAssertion { + private final TestContext context; + + private final ServerPlayerEntity player; + private final String command; + + private final List expectedFeedback = new ArrayList<>(); + + private CommandAssertion(TestContext context, ServerPlayerEntity player, String command) { + this.context = Objects.requireNonNull(context); + this.player = Objects.requireNonNull(player); + this.command = Objects.requireNonNull(command); + } + + public CommandAssertion expectFeedback(String feedback) { + this.expectedFeedback.add(Objects.requireNonNull(feedback)); + return this; + } + + public void executeSuccess() { + this.execute(Command.SINGLE_SUCCESS); + } + + public void execute(int expectedSuccessCount) { + var output = new TrackingCommandOutput(this.context); + var successCount = new MutableInt(); + + var source = player.getCommandSource() + .withOutput(output) + .withLevel(4) + .withReturnValueConsumer((successful, successCountx) -> { + successCount.setValue(successCountx); + }); + + withDevelopment(() -> { + this.player.getServer().getCommandManager().executeWithPrefix(source, this.command); + }); + + output.check(this.command, this.expectedFeedback); + this.context.assertEquals(successCount.intValue(), expectedSuccessCount, "'" + command + "' success count"); + } + + public static CommandAssertion builder(TestContext context, ServerPlayerEntity player, String command) { + return new CommandAssertion(context, player, command); + } + + private static void withDevelopment(Runnable runnable) { + boolean stored = SharedConstants.isDevelopment; + SharedConstants.isDevelopment = true; + + runnable.run(); + + SharedConstants.isDevelopment = stored; + } +} diff --git a/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java new file mode 100644 index 0000000..efa80ab --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java @@ -0,0 +1,183 @@ +package xyz.nucleoid.parties.test; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import net.fabricmc.fabric.api.entity.FakePlayer; +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.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); + createFakePlayer(context, 3); + var player4 = createFakePlayer(context, 4); + var player5 = createFakePlayer(context, 5); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback("There are no parties!") + .execute(0); + + CommandAssertion.builder(context, player1, "/party leave") + .expectFeedback("You do not control any party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party kick Player2") + .expectFeedback("You do not control any party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party transfer Player2") + .expectFeedback("You do not control any party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party invite Player1") + .expectFeedback("Cannot invite yourself to the party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party invite Player2") + .expectFeedback("Invited Player2 to the party") + .executeSuccess(); + + var partyManager = PartyManager.get(server); + partyManager.getAllParties().forEach(party -> party.setUuid(partyUuid)); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") + .expectFeedback(" - Player2 (pending)") + .expectFeedback(" - Player1 (owner)") + .execute(1); + + CommandAssertion.builder(context, player2, "/party accept Player3") + .expectFeedback("You are not invited to this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party accept Player1") + // Sends an untested message to all players in the party + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") + .expectFeedback(" - Player2") + .expectFeedback(" - Player1 (owner)") + .execute(1); + + CommandAssertion.builder(context, player1, "/party invite Player4") + .expectFeedback("Invited Player4 to the party") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party invite Player5") + .expectFeedback("Invited Player5 to the party") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party invite Player3") + .expectFeedback("You do not control this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party invite Player1") + .expectFeedback("Cannot invite yourself to the party!") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party transfer Player3") + .expectFeedback("You do not control this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party transfer Player2") + .expectFeedback("Your party has been transferred to Player2") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") + .expectFeedback(" - Player5 (pending)") + .expectFeedback(" - Player2 (owner)") + .expectFeedback(" - Player4 (pending)") + .expectFeedback(" - Player1") + .execute(1); + + // Selectors are used for game profile arguments to bypass oddities with looking up game profiles from API services + + CommandAssertion.builder(context, player1, "/party kick @a[name=Player3,limit=1]") + .expectFeedback("You do not control this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party kick @a[name=Player1,limit=1]") + // Sends an untested message to all players in the party + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party kick @a[name=Player1,limit=1]") + .expectFeedback("Player1 is not in this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party kick @a[name=Player2,limit=1]") + .expectFeedback("Cannot remove yourself from the party!") + .executeSuccess(); + + CommandAssertion.builder(context, player2, "/party kick @a[name=Player3,limit=1]") + .expectFeedback("Player3 is not in this party!") + .executeSuccess(); + + CommandAssertion.builder(context, player4, "/party accept 10a05e00-5992-448c-9bed-a14cb2a7a909") + // Sends an untested message to all players in the party + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") + .expectFeedback(" - Player5 (pending)") + .expectFeedback(" - Player2 (owner)") + .expectFeedback(" - Player4") + .execute(1); + + CommandAssertion.builder(context, player4, "/party leave") + // Sends an untested message to all players in the party + .executeSuccess(); + + CommandAssertion.builder(context, player5, "/party leave") + .expectFeedback("You do not control any party!") + .executeSuccess(); + + CommandAssertion.builder(context, player1, "/party list") + .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") + .expectFeedback(" - Player5 (pending)") + .expectFeedback(" - Player2 (owner)") + .execute(1); + + context.complete(); + } + + private static FakePlayer createFakePlayer(TestContext context, int id) { + var world = context.getWorld(); + + var username = "Player" + id; + var uuid = Uuids.getOfflinePlayerUuid(username); + + var profile = new GameProfile(uuid, username); + System.out.println("init profile " + profile.getName() + ": " + profile.getId()); + var player = FakePlayer.get(world, profile); + + var playerManager = world.getServer().getPlayerManager(); + + playerManager.getPlayerList().add(player); + ((PlayerManagerAccessor) playerManager).getPlayerMap().put(uuid, player); + + return player; + } +} diff --git a/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java b/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java new file mode 100644 index 0000000..f5742fa --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java @@ -0,0 +1,46 @@ +package xyz.nucleoid.parties.test; + +import com.google.common.collect.Lists; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.test.TestContext; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +public class TrackingCommandOutput implements CommandOutput { + private final TestContext context; + private final List actualFeedback = new ArrayList<>(); + + public TrackingCommandOutput(TestContext context) { + this.context = context; + } + + @Override + public void sendMessage(Text message) { + var string = message.getString(); + + for (var line : string.split("\n")) { + this.actualFeedback.add(line); + } + } + + @Override + public boolean shouldBroadcastConsoleToOps() { + return false; + } + + @Override + public boolean shouldReceiveFeedback() { + return true; + } + + @Override + public boolean shouldTrackOutput() { + return true; + } + + public void check(String command, List expectedFeedback) { + this.context.assertEquals(this.actualFeedback, Lists.newArrayList(expectedFeedback), "'" + command + "' feedback"); + } +} diff --git a/src/gametest/java/xyz/nucleoid/parties/test/mixin/MinecraftServerAccessor.java b/src/gametest/java/xyz/nucleoid/parties/test/mixin/MinecraftServerAccessor.java new file mode 100644 index 0000000..db9447d --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/mixin/MinecraftServerAccessor.java @@ -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); +} diff --git a/src/gametest/java/xyz/nucleoid/parties/test/mixin/PlayerManagerAccessor.java b/src/gametest/java/xyz/nucleoid/parties/test/mixin/PlayerManagerAccessor.java new file mode 100644 index 0000000..42d79d1 --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/mixin/PlayerManagerAccessor.java @@ -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 getPlayerMap(); +} diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json new file mode 100644 index 0000000..b06dab8 --- /dev/null +++ b/src/gametest/resources/fabric.mod.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "id": "game_parties_testmod", + "version": "${version}", + "name": "Game Parties Testmod", + "entrypoints": { + "fabric-gametest": ["xyz.nucleoid.parties.test.GamePartiesTest"] + }, + "mixins": [ + "game_parties_testmod.mixins.json" + ] +} diff --git a/src/gametest/resources/game_parties_testmod.mixins.json b/src/gametest/resources/game_parties_testmod.mixins.json new file mode 100644 index 0000000..758c396 --- /dev/null +++ b/src/gametest/resources/game_parties_testmod.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "xyz.nucleoid.parties.test.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "PlayerManagerAccessor", + "MinecraftServerAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/java/xyz/nucleoid/parties/Party.java b/src/main/java/xyz/nucleoid/parties/Party.java index 9d254a9..9e8c85c 100644 --- a/src/main/java/xyz/nucleoid/parties/Party.java +++ b/src/main/java/xyz/nucleoid/parties/Party.java @@ -1,5 +1,6 @@ package xyz.nucleoid.parties; +import com.google.common.annotations.VisibleForTesting; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -17,7 +18,7 @@ public final class Party { private final Object2ObjectMap participantToParty; private final Object2ObjectMap members = new Object2ObjectOpenHashMap<>(); - private final UUID uuid; + private UUID uuid; Party(MinecraftServer server, Object2ObjectMap participantToParty, PlayerRef owner) { this.memberPlayers = new MutablePlayerSet(server); @@ -90,6 +91,11 @@ public UUID getUuid() { return this.uuid; } + @VisibleForTesting + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + @Override public String toString() { return "Party{members=" + members + ", uuid=" + uuid + "}"; From 94aadb42da659307e654cd402203cb804f4dcc35 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:05:03 -0500 Subject: [PATCH 12/13] Sort listed party members by their type --- .../parties/test/GamePartiesTest.java | 12 +++++------ src/main/java/xyz/nucleoid/parties/Party.java | 4 ++-- .../xyz/nucleoid/parties/PartyCommand.java | 17 +++------------ .../xyz/nucleoid/parties/PartyManager.java | 6 +++--- .../xyz/nucleoid/parties/PartyMember.java | 21 ++++++++++++++++++- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java index efa80ab..f054c0d 100644 --- a/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java +++ b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java @@ -63,8 +63,8 @@ public void test(TestContext context) { CommandAssertion.builder(context, player1, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") - .expectFeedback(" - Player2 (pending)") .expectFeedback(" - Player1 (owner)") + .expectFeedback(" - Player2 (pending)") .execute(1); CommandAssertion.builder(context, player2, "/party accept Player3") @@ -77,8 +77,8 @@ public void test(TestContext context) { CommandAssertion.builder(context, player1, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") - .expectFeedback(" - Player2") .expectFeedback(" - Player1 (owner)") + .expectFeedback(" - Player2") .execute(1); CommandAssertion.builder(context, player1, "/party invite Player4") @@ -107,10 +107,10 @@ public void test(TestContext context) { CommandAssertion.builder(context, player1, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") - .expectFeedback(" - Player5 (pending)") .expectFeedback(" - Player2 (owner)") - .expectFeedback(" - Player4 (pending)") .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 @@ -141,9 +141,9 @@ public void test(TestContext context) { CommandAssertion.builder(context, player1, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") - .expectFeedback(" - Player5 (pending)") .expectFeedback(" - Player2 (owner)") .expectFeedback(" - Player4") + .expectFeedback(" - Player5 (pending)") .execute(1); CommandAssertion.builder(context, player4, "/party leave") @@ -156,8 +156,8 @@ public void test(TestContext context) { CommandAssertion.builder(context, player1, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") - .expectFeedback(" - Player5 (pending)") .expectFeedback(" - Player2 (owner)") + .expectFeedback(" - Player5 (pending)") .execute(1); context.complete(); diff --git a/src/main/java/xyz/nucleoid/parties/Party.java b/src/main/java/xyz/nucleoid/parties/Party.java index 9e8c85c..91f00d4 100644 --- a/src/main/java/xyz/nucleoid/parties/Party.java +++ b/src/main/java/xyz/nucleoid/parties/Party.java @@ -79,8 +79,8 @@ PartyMember removeMember(PlayerRef player) { return member; } - public List getMembers() { - return new ObjectArrayList<>(this.members.keySet()); + public List getMembers() { + return new ObjectArrayList<>(this.members.values()); } public MutablePlayerSet getMemberPlayers() { diff --git a/src/main/java/xyz/nucleoid/parties/PartyCommand.java b/src/main/java/xyz/nucleoid/parties/PartyCommand.java index ad3b98d..9f9990e 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyCommand.java +++ b/src/main/java/xyz/nucleoid/parties/PartyCommand.java @@ -99,22 +99,11 @@ private static int listParties(CommandContext ctx) throws C text.append(PartyTexts.listEntry(party.getUuid())); var members = new ArrayList<>(party.getMembers()); - members.sort(Comparator.comparing(PlayerRef::id)); + members.sort(null); - for (var memberRef : members) { + for (var member : members) { text.append(ScreenTexts.LINE_BREAK); - - var member = party.getMember(memberRef); - - if (member != null) { - if (member.isOwner()) { - text.append(PartyTexts.listMemberEntryType(memberRef, server, PartyTexts.listMemberTypeOwner().formatted(Formatting.LIGHT_PURPLE))); - } else if (member.isParticipant()) { - text.append(PartyTexts.listMemberEntry(memberRef, server)); - } else { - text.append(PartyTexts.listMemberEntryType(memberRef, server, PartyTexts.listMemberTypePending().formatted(Formatting.GRAY))); - } - } + text.append(member.getListEntry(server)); } } diff --git a/src/main/java/xyz/nucleoid/parties/PartyManager.java b/src/main/java/xyz/nucleoid/parties/PartyManager.java index e38bb4a..eed58a2 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyManager.java +++ b/src/main/java/xyz/nucleoid/parties/PartyManager.java @@ -87,9 +87,9 @@ private void onPartyOwnerLogOut(ServerPlayerEntity player, Party party) { if (!members.isEmpty()) { var nextMember = members.get(0); - party.putMember(nextMember, PartyMember.Type.OWNER); + party.putMember(nextMember.player(), PartyMember.Type.OWNER); - nextMember.ifOnline(this.server, nextPlayer -> { + nextMember.player().ifOnline(this.server, nextPlayer -> { nextPlayer.sendMessage(PartyTexts.transferredReceiver(player), false); }); } @@ -187,7 +187,7 @@ public PartyResult disbandParty(PlayerRef owner) { } public void disbandParty(Party party) { - for (PlayerRef member : party.getMembers()) { + for (var member : party.getMembers()) { this.participantToParty.remove(member, party); } } diff --git a/src/main/java/xyz/nucleoid/parties/PartyMember.java b/src/main/java/xyz/nucleoid/parties/PartyMember.java index 135a491..a2916f1 100644 --- a/src/main/java/xyz/nucleoid/parties/PartyMember.java +++ b/src/main/java/xyz/nucleoid/parties/PartyMember.java @@ -1,8 +1,11 @@ package xyz.nucleoid.parties; +import net.minecraft.server.MinecraftServer; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import xyz.nucleoid.plasmid.api.util.PlayerRef; -public record PartyMember(Party party, PlayerRef player, Type type) { +public record PartyMember(Party party, PlayerRef player, Type type) implements Comparable { public boolean isPending() { return this.type == Type.PENDING; } @@ -15,6 +18,22 @@ public boolean isOwner() { return this.type == Type.OWNER; } + public Text getListEntry(MinecraftServer server) { + if (this.isOwner()) { + return PartyTexts.listMemberEntryType(this.player, server, PartyTexts.listMemberTypeOwner().formatted(Formatting.LIGHT_PURPLE)); + } else if (this.isParticipant()) { + return PartyTexts.listMemberEntry(this.player, server); + } else { + return PartyTexts.listMemberEntryType(this.player, server, PartyTexts.listMemberTypePending().formatted(Formatting.GRAY)); + } + } + + @Override + public int compareTo(PartyMember o) { + int result = o.type.compareTo(this.type); + return result != 0 ? result : this.player.id().compareTo(o.player.id()); + } + @Override public String toString() { return this.player.id() + " (" + this.type + ")"; From 85c718483d7ecb9fce0a6ab29aa9801c4202857b Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Sun, 5 Jan 2025 01:32:26 -0500 Subject: [PATCH 13/13] Add test assertions for messages sent to the entire party --- .../parties/test/CommandAssertion.java | 52 ++++++++++--- .../parties/test/GamePartiesTest.java | 78 ++++++++++--------- .../parties/test/TrackingCommandOutput.java | 46 ----------- .../test/TrackingFakePlayerEntity.java | 33 ++++++++ 4 files changed, 117 insertions(+), 92 deletions(-) delete mode 100644 src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java create mode 100644 src/gametest/java/xyz/nucleoid/parties/test/TrackingFakePlayerEntity.java diff --git a/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java b/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java index e176f16..d8b8000 100644 --- a/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java +++ b/src/gametest/java/xyz/nucleoid/parties/test/CommandAssertion.java @@ -2,30 +2,55 @@ import com.mojang.brigadier.Command; import net.minecraft.SharedConstants; -import net.minecraft.server.network.ServerPlayerEntity; 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 ServerPlayerEntity player; + private final TrackingFakePlayerEntity player; private final String command; - private final List expectedFeedback = new ArrayList<>(); + private final Map> recipientsToExpectedMessages = new HashMap<>(); - private CommandAssertion(TestContext context, ServerPlayerEntity player, String command) { + private CommandAssertion(TestContext context, TrackingFakePlayerEntity player, Set 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) { - this.expectedFeedback.add(Objects.requireNonNull(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; } @@ -34,11 +59,9 @@ public void executeSuccess() { } public void execute(int expectedSuccessCount) { - var output = new TrackingCommandOutput(this.context); var successCount = new MutableInt(); var source = player.getCommandSource() - .withOutput(output) .withLevel(4) .withReturnValueConsumer((successful, successCountx) -> { successCount.setValue(successCountx); @@ -48,12 +71,21 @@ public void execute(int expectedSuccessCount) { this.player.getServer().getCommandManager().executeWithPrefix(source, this.command); }); - output.check(this.command, this.expectedFeedback); + 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, ServerPlayerEntity player, String command) { - return new CommandAssertion(context, player, command); + public static CommandAssertion builder(TestContext context, TrackingFakePlayerEntity player, Set players, String command) { + return new CommandAssertion(context, player, players, command); } private static void withDevelopment(Runnable runnable) { diff --git a/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java index f054c0d..f74d6e0 100644 --- a/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java +++ b/src/gametest/java/xyz/nucleoid/parties/test/GamePartiesTest.java @@ -2,7 +2,6 @@ import com.mojang.authlib.GameProfile; import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; -import net.fabricmc.fabric.api.entity.FakePlayer; import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.test.GameTest; @@ -14,6 +13,7 @@ import xyz.nucleoid.parties.test.mixin.PlayerManagerAccessor; import java.net.Proxy; +import java.util.Set; import java.util.UUID; public class GamePartiesTest { @@ -30,82 +30,88 @@ public void test(TestContext context) { var player1 = createFakePlayer(context, 1); var player2 = createFakePlayer(context, 2); - createFakePlayer(context, 3); + var player3 = createFakePlayer(context, 3); var player4 = createFakePlayer(context, 4); var player5 = createFakePlayer(context, 5); - CommandAssertion.builder(context, player1, "/party list") + 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, "/party leave") + CommandAssertion.builder(context, player1, players, "/party leave") .expectFeedback("You do not control any party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party kick Player2") + CommandAssertion.builder(context, player1, players, "/party kick Player2") .expectFeedback("You do not control any party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party transfer Player2") + CommandAssertion.builder(context, player1, players, "/party transfer Player2") .expectFeedback("You do not control any party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party invite Player1") + CommandAssertion.builder(context, player1, players, "/party invite Player1") .expectFeedback("Cannot invite yourself to the party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party invite Player2") + 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, "/party list") + 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, "/party accept Player3") + CommandAssertion.builder(context, player2, players, "/party accept Player3") .expectFeedback("You are not invited to this party!") .executeSuccess(); - CommandAssertion.builder(context, player2, "/party accept Player1") - // Sends an untested message to all players in the party + CommandAssertion.builder(context, player2, players, "/party accept Player1") + .expectMessage("Player2 has joined the party!", player1, player2) .executeSuccess(); - CommandAssertion.builder(context, player1, "/party list") + 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, "/party invite Player4") + 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, "/party invite Player5") + 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, "/party invite Player3") + CommandAssertion.builder(context, player2, players, "/party invite Player3") .expectFeedback("You do not control this party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party invite Player1") + CommandAssertion.builder(context, player1, players, "/party invite Player1") .expectFeedback("Cannot invite yourself to the party!") .executeSuccess(); - CommandAssertion.builder(context, player2, "/party transfer Player3") + CommandAssertion.builder(context, player2, players, "/party transfer Player3") .expectFeedback("You do not control this party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party transfer Player2") + 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, "/party list") + CommandAssertion.builder(context, player1, players, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") .expectFeedback(" - Player2 (owner)") .expectFeedback(" - Player1") @@ -115,46 +121,47 @@ public void test(TestContext context) { // Selectors are used for game profile arguments to bypass oddities with looking up game profiles from API services - CommandAssertion.builder(context, player1, "/party kick @a[name=Player3,limit=1]") + CommandAssertion.builder(context, player1, players, "/party kick @a[name=Player3,limit=1]") .expectFeedback("You do not control this party!") .executeSuccess(); - CommandAssertion.builder(context, player2, "/party kick @a[name=Player1,limit=1]") - // Sends an untested message to all players in the party + 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, "/party kick @a[name=Player1,limit=1]") + CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player1,limit=1]") .expectFeedback("Player1 is not in this party!") .executeSuccess(); - CommandAssertion.builder(context, player2, "/party kick @a[name=Player2,limit=1]") + CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player2,limit=1]") .expectFeedback("Cannot remove yourself from the party!") .executeSuccess(); - CommandAssertion.builder(context, player2, "/party kick @a[name=Player3,limit=1]") + CommandAssertion.builder(context, player2, players, "/party kick @a[name=Player3,limit=1]") .expectFeedback("Player3 is not in this party!") .executeSuccess(); - CommandAssertion.builder(context, player4, "/party accept 10a05e00-5992-448c-9bed-a14cb2a7a909") - // Sends an untested message to all players in the party + 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, "/party list") + 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, "/party leave") - // Sends an untested message to all players in the party + CommandAssertion.builder(context, player4, players, "/party leave") + .expectMessage("Player4 has left the party!", player2, player4) .executeSuccess(); - CommandAssertion.builder(context, player5, "/party leave") + CommandAssertion.builder(context, player5, players, "/party leave") .expectFeedback("You do not control any party!") .executeSuccess(); - CommandAssertion.builder(context, player1, "/party list") + CommandAssertion.builder(context, player1, players, "/party list") .expectFeedback(" - Party [10a05e00-5992-448c-9bed-a14cb2a7a909]") .expectFeedback(" - Player2 (owner)") .expectFeedback(" - Player5 (pending)") @@ -163,15 +170,14 @@ public void test(TestContext context) { context.complete(); } - private static FakePlayer createFakePlayer(TestContext context, int id) { + 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); - System.out.println("init profile " + profile.getName() + ": " + profile.getId()); - var player = FakePlayer.get(world, profile); + var player = new TrackingFakePlayerEntity(world, profile); var playerManager = world.getServer().getPlayerManager(); diff --git a/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java b/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java deleted file mode 100644 index f5742fa..0000000 --- a/src/gametest/java/xyz/nucleoid/parties/test/TrackingCommandOutput.java +++ /dev/null @@ -1,46 +0,0 @@ -package xyz.nucleoid.parties.test; - -import com.google.common.collect.Lists; -import net.minecraft.server.command.CommandOutput; -import net.minecraft.test.TestContext; -import net.minecraft.text.Text; - -import java.util.ArrayList; -import java.util.List; - -public class TrackingCommandOutput implements CommandOutput { - private final TestContext context; - private final List actualFeedback = new ArrayList<>(); - - public TrackingCommandOutput(TestContext context) { - this.context = context; - } - - @Override - public void sendMessage(Text message) { - var string = message.getString(); - - for (var line : string.split("\n")) { - this.actualFeedback.add(line); - } - } - - @Override - public boolean shouldBroadcastConsoleToOps() { - return false; - } - - @Override - public boolean shouldReceiveFeedback() { - return true; - } - - @Override - public boolean shouldTrackOutput() { - return true; - } - - public void check(String command, List expectedFeedback) { - this.context.assertEquals(this.actualFeedback, Lists.newArrayList(expectedFeedback), "'" + command + "' feedback"); - } -} diff --git a/src/gametest/java/xyz/nucleoid/parties/test/TrackingFakePlayerEntity.java b/src/gametest/java/xyz/nucleoid/parties/test/TrackingFakePlayerEntity.java new file mode 100644 index 0000000..ceeebed --- /dev/null +++ b/src/gametest/java/xyz/nucleoid/parties/test/TrackingFakePlayerEntity.java @@ -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 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 consumeMessages() { + var result = new ArrayList<>(this.messages); + this.messages.clear(); + + return result; + } +}