diff --git a/README.md b/README.md
index eb077072..c6c27767 100644
--- a/README.md
+++ b/README.md
@@ -202,11 +202,10 @@ However! Before we give functionality to our brilliant example game, we need to
An example offer listener may look like:
```java
activity.listen(GamePlayerEvents.OFFER, offer -> {
- ServerPlayerEntity player = offer.player();
- return offer.accept(world, new Vec3d(0.0, 64.0, 0.0))
- .and(() -> {
- player.changeGameMode(GameMode.ADVENTURE);
- });
+ return offer.accept(world, new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(player -> {
+ player.changeGameMode(GameMode.ADVENTURE);
+ });
});
```
@@ -271,9 +270,8 @@ public final class ExampleGame {
}
private PlayerOfferResult onPlayerOffer(PlayerOffer offer) {
- ServerPlayerEntity player = offer.player();
- return offer.accept(this.world, new Vec3d(0.0, 64.0, 0.0))
- .and(() -> {
+ return offer.accept(this.world, new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(player -> {
player.changeGameMode(GameMode.ADVENTURE);
});
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java b/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java
index 7e71bdb3..db15c11f 100644
--- a/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java
+++ b/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java
@@ -287,19 +287,17 @@ private static void joinAllPlayersToGame(ServerCommandSource source, GameSpace g
.collect(Collectors.toList());
var intent = JoinIntent.ANY;
- var screen = gameSpace.getPlayers().screenJoins(players, intent);
- if (screen.isOk()) {
- for (var player : players) {
- gameSpace.getPlayers().offer(player, intent);
- }
- } else {
- source.sendError(screen.errorCopy().formatted(Formatting.RED));
+ var result = gameSpace.getPlayers().offer(players, intent);
+ if (result.isError()) {
+ source.sendError(result.errorCopy().formatted(Formatting.RED));
}
}
private static void tryJoinGame(ServerPlayerEntity player, GameSpace gameSpace) {
- var results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
- results.sendErrorsTo(player);
+ var result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ if (result.isError()) {
+ player.sendMessage(result.errorCopy().formatted(Formatting.RED));
+ }
}
private static GameSpace getJoinableGameSpace() throws CommandSyntaxException {
diff --git a/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java b/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java
index 000bdb17..65939d37 100644
--- a/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java
+++ b/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java
@@ -39,8 +39,10 @@ public GameJoinUi(ServerPlayerEntity player) {
private static void tryJoinGame(ServerPlayerEntity player, GameSpace gameSpace) {
player.server.execute(() -> {
- var results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
- results.sendErrorsTo(player);
+ var result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ if (result.isError()) {
+ player.sendMessage(result.errorCopy().formatted(Formatting.RED));
+ }
});
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java b/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java
index 1dd79dbb..080312c0 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java
@@ -17,32 +17,31 @@
*/
public interface GameSpacePlayers extends PlayerSet {
/**
- * Screens a group of players and returns whether the collective group should be allowed into the game.
+ * Simulates offer to join a player or group of players and returns whether they should be allowed into the game.
*
- * This logic is controlled through the active {@link GameActivity} through {@link GamePlayerEvents#SCREEN_JOINS}.
+ * This logic is controlled through the active {@link GameActivity} through {@link GamePlayerEvents#OFFER}.
*
* @param players the group of players trying to join
* @param intent the intent of the players trying to join, such as whether they want to participate or spectate
- * @return a {@link GameResult} describing whether this group can join this game, or an error if not
- * @see GamePlayerEvents#SCREEN_JOINS
- * @see GameSpacePlayers#offer(ServerPlayerEntity, JoinIntent)
+ * @return a {@link GameResult} describing whether these players can join this game, or an error if not
+ * @see GameSpacePlayers#offer(Collection, JoinIntent)
* @see xyz.nucleoid.plasmid.game.player.GamePlayerJoiner
*/
- GameResult screenJoins(Collection players, JoinIntent intent);
+ GameResult simulateOffer(Collection players, JoinIntent intent);
/**
- * Offers an individual player to join this game. If accepted, they will be teleported into the game, and if not
+ * Offers a player or group of players to join this game. If accepted, they will be teleported into the game, and if not
* an error {@link GameResult} will be returned.
*
* This logic is controlled through the active {@link GameActivity} through {@link GamePlayerEvents#OFFER}.
*
- * @param player the player trying to join
+ * @param players the players trying to join
* @param intent the intent of the players trying to join, such as whether they want to participate or spectate
- * @return a {@link GameResult} describing whether this player joined the game, or an error if not
+ * @return a {@link GameResult} describing whether these players joined the game, or an error if not
* @see GamePlayerEvents#OFFER
* @see xyz.nucleoid.plasmid.game.player.GamePlayerJoiner
*/
- GameResult offer(ServerPlayerEntity player, JoinIntent intent);
+ GameResult offer(Collection players, JoinIntent intent);
/**
* Attempts to remove the given {@link ServerPlayerEntity} from this {@link GameSpace}.
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java b/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java
index 838e75c7..bef63af3 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java
@@ -22,8 +22,8 @@
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
import xyz.nucleoid.plasmid.game.manager.GameSpaceManager;
-import xyz.nucleoid.plasmid.game.player.PlayerOffer;
-import xyz.nucleoid.plasmid.game.player.PlayerOfferResult;
+import xyz.nucleoid.plasmid.game.player.JoinOffer;
+import xyz.nucleoid.plasmid.game.player.JoinOfferResult;
import xyz.nucleoid.plasmid.game.rule.GameRuleType;
import xyz.nucleoid.plasmid.util.compatibility.AfkDisplayCompatibility;
@@ -89,7 +89,6 @@ public static GameWaitingLobby addTo(GameActivity activity, PlayerConfig playerC
activity.listen(GameActivityEvents.TICK, lobby::onTick);
activity.listen(GameActivityEvents.REQUEST_START, lobby::requestStart);
- activity.listen(GamePlayerEvents.SCREEN_JOINS, (players, intent) -> lobby.screenJoins(players));
activity.listen(GamePlayerEvents.OFFER, lobby::offerPlayer);
activity.listen(GamePlayerEvents.REMOVE, lobby::onRemovePlayer);
@@ -170,17 +169,9 @@ private GameResult requestStart() {
}
}
- private GameResult screenJoins(Collection players) {
- int newPlayerCount = this.gameSpace.getPlayers().size() + players.size();
+ private JoinOfferResult offerPlayer(JoinOffer offer) {
+ int newPlayerCount = this.gameSpace.getPlayers().size() + offer.players().size();
if (newPlayerCount > this.playerConfig.maxPlayers()) {
- return GameResult.error(GameTexts.Join.gameFull());
- }
-
- return GameResult.ok();
- }
-
- private PlayerOfferResult offerPlayer(PlayerOffer offer) {
- if (this.isFull()) {
return offer.reject(GameTexts.Join.gameFull());
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/event/GamePlayerEvents.java b/src/main/java/xyz/nucleoid/plasmid/game/event/GamePlayerEvents.java
index a347fa98..43f71623 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/event/GamePlayerEvents.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/event/GamePlayerEvents.java
@@ -1,21 +1,16 @@
package xyz.nucleoid.plasmid.game.event;
-import com.mojang.authlib.GameProfile;
import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
-import net.minecraft.util.math.Vec3d;
import xyz.nucleoid.plasmid.game.GameActivity;
-import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.GameSpace;
import xyz.nucleoid.plasmid.game.GameTexts;
-import xyz.nucleoid.plasmid.game.player.JoinIntent;
-import xyz.nucleoid.plasmid.game.player.PlayerOffer;
-import xyz.nucleoid.plasmid.game.player.PlayerOfferResult;
+import xyz.nucleoid.plasmid.game.player.JoinAcceptor;
+import xyz.nucleoid.plasmid.game.player.JoinAcceptorResult;
+import xyz.nucleoid.plasmid.game.player.JoinOffer;
+import xyz.nucleoid.plasmid.game.player.JoinOfferResult;
import xyz.nucleoid.stimuli.event.StimulusEvent;
-import java.util.Collection;
-
/**
* Events relating to players being added and removed from a {@link GameSpace} or {@link GameActivity}.
*/
@@ -102,53 +97,52 @@ public final class GamePlayerEvents {
});
/**
- * Called when a group of players try to join this game. This should be used to reject multiple players as a group,
- * such as when a party tries to join but has too many players to fit into the game.
+ * Called when a group of {@link ServerPlayerEntity} tries to join this game.
*
- * This is called before {@link GamePlayerEvents#OFFER} which handles specifically bringing a player into the game.
+ * Games must respond to this event in order for players to be able to join by returning either
+ * {@link JoinOffer#accept()} or {@link JoinOffer#reject(Text)}.
*
- * @see GamePlayerEvents#OFFER
+ * @see JoinOffer
+ * @see JoinOfferResult
+ * @see GamePlayerEvents#ACCEPT
*/
- public static final StimulusEvent SCREEN_JOINS = StimulusEvent.create(ScreenJoins.class, ctx -> (players, intent) -> {
+ public static final StimulusEvent OFFER = StimulusEvent.create(Offer.class, ctx -> offer -> {
try {
for (var listener : ctx.getListeners()) {
- var result = listener.screenJoins(players, intent);
- if (result.isError()) {
+ var result = listener.onOfferPlayers(offer);
+ if (!(result instanceof JoinOfferResult.Pass)) {
return result;
}
}
- return GameResult.ok();
+ return offer.pass();
} catch (Throwable throwable) {
ctx.handleException(throwable);
- return GameResult.error(GameTexts.Join.unexpectedError());
+ return offer.reject(GameTexts.Join.unexpectedError());
}
});
/**
- * Called when a single {@link ServerPlayerEntity} tries to join this game. This event is responsible for bringing
- * the player into the {@link GameSpace} world in the correct location.
+ * Called when a group of {@link ServerPlayerEntity} is accepted to join this game. This event is responsible for bringing
+ * the players into the {@link GameSpace} world in the correct location.
*
- * Games must respond to this event in order for a player to be able to join by returning either
- * {@link PlayerOffer#accept(ServerWorld, Vec3d)} or {@link PlayerOffer#reject(Text)}.
+ * Games must respond to this event in order for players to be able to join.
*
- * @see PlayerOffer
- * @see PlayerOfferResult
- * @see GamePlayerEvents#SCREEN_JOINS
+ * @see JoinAcceptor
+ * @see JoinAcceptorResult
* @see GamePlayerEvents#JOIN
*/
- public static final StimulusEvent OFFER = StimulusEvent.create(Offer.class, ctx -> offer -> {
+ public static final StimulusEvent ACCEPT = StimulusEvent.create(Accept.class, ctx -> accept -> {
try {
for (var listener : ctx.getListeners()) {
- var result = listener.onOfferPlayer(offer);
- if (!(result instanceof PlayerOfferResult.Pass)) {
+ var result = listener.onAcceptPlayers(accept);
+ if (!(result instanceof JoinAcceptorResult.Pass)) {
return result;
}
}
- return offer.pass();
} catch (Throwable throwable) {
ctx.handleException(throwable);
- return offer.reject(GameTexts.Join.unexpectedError());
}
+ return accept.pass();
});
/**
@@ -175,12 +169,12 @@ public interface Remove {
void onRemovePlayer(ServerPlayerEntity player);
}
- public interface ScreenJoins {
- GameResult screenJoins(Collection players, JoinIntent intent);
+ public interface Offer {
+ JoinOfferResult onOfferPlayers(JoinOffer offer);
}
- public interface Offer {
- PlayerOfferResult onOfferPlayer(PlayerOffer offer);
+ public interface Accept {
+ JoinAcceptorResult onAcceptPlayers(JoinAcceptor acceptor);
}
public interface Name {
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java b/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java
index f6abc17f..4ea9ad9b 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java
@@ -1,10 +1,8 @@
package xyz.nucleoid.plasmid.game.manager;
import com.google.common.collect.Lists;
-import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import me.lucko.fabric.api.permissions.v0.Permissions;
-import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
@@ -19,11 +17,11 @@
import xyz.nucleoid.plasmid.game.config.GameConfig;
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
-import xyz.nucleoid.plasmid.game.player.JoinIntent;
-import xyz.nucleoid.plasmid.game.player.LocalPlayerOffer;
-import xyz.nucleoid.plasmid.game.player.PlayerOfferResult;
+import xyz.nucleoid.plasmid.game.player.JoinAcceptorResult;
+import xyz.nucleoid.plasmid.game.player.LocalJoinAcceptor;
+import xyz.nucleoid.plasmid.game.player.LocalJoinOffer;
+import xyz.nucleoid.plasmid.game.player.JoinOfferResult;
-import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;
@@ -196,36 +194,23 @@ public GameBehavior getBehavior() {
return this.state;
}
- GameResult screenJoins(Collection players, JoinIntent intent) {
- var result = this.attemptScreenJoins(players.stream().map(PlayerEntity::getGameProfile).toList(), intent);
-
- if (result.isError()) {
- this.players.attemptGarbageCollection();
- }
-
- return result;
- }
-
- private GameResult attemptScreenJoins(Collection players, JoinIntent intent) {
- if (this.closed) {
- return GameResult.error(GameTexts.Join.gameClosed());
- }
-
- return this.state.invoker(GamePlayerEvents.SCREEN_JOINS).screenJoins(players, intent);
- }
-
- PlayerOfferResult offerPlayer(LocalPlayerOffer offer) {
+ JoinOfferResult offerPlayers(LocalJoinOffer offer) {
if (this.closed) {
return offer.reject(GameTexts.Join.gameClosed());
- } else if (this.manager.inGame(offer.player())) {
+ } else if (offer.serverPlayers().stream().anyMatch(this.manager::inGame)) {
return offer.reject(GameTexts.Join.inOtherGame());
- } else if (!Permissions.check(offer.player(), "plasmid.join_game", true)) {
+ } else if (offer.serverPlayers().stream().anyMatch(p -> !Permissions.check(p, "plasmid.join_game", true))) {
return offer.reject(GameTexts.Join.notAllowed());
}
- return this.state.invoker(GamePlayerEvents.OFFER).onOfferPlayer(offer);
+ return this.state.invoker(GamePlayerEvents.OFFER).onOfferPlayers(offer);
}
+ JoinAcceptorResult acceptPlayers(LocalJoinAcceptor acceptor) {
+ return this.state.invoker(GamePlayerEvents.ACCEPT).onAcceptPlayers(acceptor);
+ }
+
+
void onAddPlayer(ServerPlayerEntity player) {
this.state.propagatingInvoker(GamePlayerEvents.JOIN).onAddPlayer(player);
this.state.propagatingInvoker(GamePlayerEvents.ADD).onAddPlayer(player);
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpacePlayers.java b/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpacePlayers.java
index 593dcd36..bf7ab28c 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpacePlayers.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpacePlayers.java
@@ -6,10 +6,7 @@
import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.GameSpacePlayers;
import xyz.nucleoid.plasmid.game.GameTexts;
-import xyz.nucleoid.plasmid.game.player.JoinIntent;
-import xyz.nucleoid.plasmid.game.player.LocalPlayerOffer;
-import xyz.nucleoid.plasmid.game.player.MutablePlayerSet;
-import xyz.nucleoid.plasmid.game.player.PlayerOfferResult;
+import xyz.nucleoid.plasmid.game.player.*;
import xyz.nucleoid.plasmid.game.player.isolation.IsolatingPlayerTeleporter;
import java.util.Collection;
@@ -28,13 +25,23 @@ public final class ManagedGameSpacePlayers implements GameSpacePlayers {
}
@Override
- public GameResult screenJoins(Collection players, JoinIntent intent) {
- return this.space.screenJoins(players, intent);
+ public GameResult simulateOffer(Collection players, JoinIntent intent) {
+ if (players.stream().anyMatch(this.set::contains)) {
+ return GameResult.error(GameTexts.Join.alreadyJoined());
+ }
+
+ var offer = new LocalJoinOffer(players, intent);
+
+ return switch (this.space.offerPlayers(offer)) {
+ case JoinOfferResult.Accept accept -> GameResult.ok();
+ case JoinOfferResult.Reject reject -> GameResult.error(reject.reason());
+ default -> GameResult.error(GameTexts.Join.genericError());
+ };
}
@Override
- public GameResult offer(ServerPlayerEntity player, JoinIntent intent) {
- var result = this.attemptOffer(player, intent);
+ public GameResult offer(Collection players, JoinIntent intent) {
+ var result = this.attemptOffer(players, intent);
if (result.isError()) {
this.attemptGarbageCollection();
@@ -43,31 +50,41 @@ public GameResult offer(ServerPlayerEntity player, JoinIntent intent) {
return result;
}
- private GameResult attemptOffer(ServerPlayerEntity player, JoinIntent intent) {
- if (this.set.contains(player)) {
+ private GameResult attemptOffer(Collection players, JoinIntent intent) {
+ if (players.stream().anyMatch(this.set::contains)) {
return GameResult.error(GameTexts.Join.alreadyJoined());
}
- var offer = new LocalPlayerOffer(player, intent);
+ var offer = new LocalJoinOffer(players, intent);
- switch (this.space.offerPlayer(offer)) {
- case LocalPlayerOffer.Accept accept -> {
+ return switch (this.space.offerPlayers(offer)) {
+ case JoinOfferResult.Accept accept -> this.accept(players, intent);
+ case JoinOfferResult.Reject reject -> GameResult.error(reject.reason());
+ default -> GameResult.error(GameTexts.Join.genericError());
+ };
+ }
+
+ private GameResult accept(Collection players, JoinIntent intent) {
+ var acceptor = new LocalJoinAcceptor(players, intent);
+
+ switch (this.space.acceptPlayers(acceptor)) {
+ case LocalJoinAcceptor.Teleport teleport -> {
try {
- this.teleporter.teleportIn(player, accept::applyJoin);
- this.set.add(player);
- this.space.onAddPlayer(player);
+ var joiningSet = new MutablePlayerSet(this.space.getServer());
+ for (var player : players) {
+ this.teleporter.teleportIn(player, teleport::applyTeleport);
+ this.set.add(player);
+ this.space.onAddPlayer(player);
+ joiningSet.add(player);
+ }
+ teleport.runCallbacks(joiningSet);
return GameResult.ok();
} catch (Throwable throwable) {
return GameResult.error(GameTexts.Join.unexpectedError());
}
}
- case PlayerOfferResult.Reject reject -> {
- return GameResult.error(reject.reason());
- }
- default -> {
- return GameResult.error(GameTexts.Join.genericError());
- }
+ default -> throw new IllegalStateException("Accept event must be handled");
}
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/GamePlayerJoiner.java b/src/main/java/xyz/nucleoid/plasmid/game/player/GamePlayerJoiner.java
index cd76e5cf..627d25a2 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/player/GamePlayerJoiner.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/GamePlayerJoiner.java
@@ -1,25 +1,23 @@
package xyz.nucleoid.plasmid.game.player;
-import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
import xyz.nucleoid.plasmid.event.GameEvents;
import xyz.nucleoid.plasmid.game.GameOpenException;
+import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.GameSpace;
import xyz.nucleoid.plasmid.game.GameTexts;
import java.util.Collection;
-import java.util.Map;
import java.util.Set;
/**
* Utility class for joining players to a {@link GameSpace}. This handles all logic such as collecting all party
- * members, screening, and offering players to the {@link GameSpace}.
+ * members, and offering players to the {@link GameSpace}.
*/
public final class GamePlayerJoiner {
- public static Results tryJoin(ServerPlayerEntity player, GameSpace gameSpace, JoinIntent intent) {
+ public static GameResult tryJoin(ServerPlayerEntity player, GameSpace gameSpace, JoinIntent intent) {
try {
var players = collectPlayersForJoin(player, gameSpace);
return tryJoinAll(players, gameSpace, intent);
@@ -37,29 +35,12 @@ private static Set collectPlayersForJoin(ServerPlayerEntity
return players;
}
- private static Results tryJoinAll(Collection players, GameSpace gameSpace, JoinIntent intent) {
- var results = new Results();
-
- var screenResult = gameSpace.getPlayers().screenJoins(players, intent);
- if (screenResult.isError()) {
- results.globalError = screenResult.error();
- return results;
- }
-
- for (var player : players) {
- var result = gameSpace.getPlayers().offer(player, intent);
- if (result.isError()) {
- results.playerErrors.put(player, result.error());
- }
- }
-
- return results;
+ private static GameResult tryJoinAll(Collection players, GameSpace gameSpace, JoinIntent intent) {
+ return gameSpace.getPlayers().offer(players, intent);
}
- public static Results handleJoinException(Throwable throwable) {
- var results = new Results();
- results.globalError = getFeedbackForException(throwable);
- return results;
+ public static GameResult handleJoinException(Throwable throwable) {
+ return GameResult.error(getFeedbackForException(throwable));
}
private static Text getFeedbackForException(Throwable throwable) {
@@ -70,26 +51,4 @@ private static Text getFeedbackForException(Throwable throwable) {
return GameTexts.Join.unexpectedError();
}
}
-
- public static final class Results {
- public Text globalError;
- public final Map playerErrors = new Reference2ObjectOpenHashMap<>();
- public boolean isSuccessful;
-
- public void sendErrorsTo(ServerPlayerEntity player) {
- if (this.globalError != null) {
- player.sendMessage(this.globalError.copy().formatted(Formatting.RED), false);
- } else if (!this.playerErrors.isEmpty()) {
- player.sendMessage(
- GameTexts.Join.partyJoinError(this.playerErrors.size()).formatted(Formatting.RED),
- false
- );
-
- for (var entry : this.playerErrors.entrySet()) {
- Text error = entry.getValue().copy().formatted(Formatting.RED);
- entry.getKey().sendMessage(error, false);
- }
- }
- }
- }
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptor.java b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptor.java
new file mode 100644
index 00000000..1c5045db
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptor.java
@@ -0,0 +1,127 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.Vec3d;
+import xyz.nucleoid.plasmid.game.GameSpace;
+import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
+import xyz.nucleoid.plasmid.util.PlayerPos;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Represents an agent which is responsible for bringing a player or group of players
+ * into the {@link GameSpace} world in the correct location.
+ *
+ * This object should be used in order to construct a {@link JoinAcceptorResult} object to return from a listener to the
+ * {@link GamePlayerEvents#ACCEPT} event.
+ *
+ * @see GameSpace
+ * @see GamePlayerEvents#ACCEPT
+ */
+public interface JoinAcceptor {
+ /**
+ * @return the set of {@link GameProfile} of the players that are joining to this {@link GameSpace}
+ */
+ Set players();
+
+ /**
+ * @return the {@link UUID profile UUID} of the players that are joining to this {@link GameSpace}
+ */
+ default Set playerIds() {
+ return this.players()
+ .stream()
+ .map(GameProfile::getId)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return the usernames of the players that are joining to this {@link GameSpace}
+ */
+ default Set playerNames() {
+ return this.players()
+ .stream()
+ .map(GameProfile::getName)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return the {@link JoinIntent 'intent'} of the players, such as whether they want to participate or spectate
+ * @see JoinIntent
+ */
+ JoinIntent intent();
+
+ /**
+ * Returns a result that completes this join by teleporting the players.
+ *
+ * The result of this function must be returned within a
+ * {@link GamePlayerEvents#ACCEPT} listener.
+ *
+ * @param positions the map of positions where the players should be teleported to
+ * @return a "teleport" result
+ * @throws IllegalArgumentException when positions are not specified for all joining players
+ * @see JoinAcceptorResult.Teleport#thenRun(Consumer)
+ * @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
+ */
+ JoinAcceptorResult.Teleport teleport(Map positions);
+
+ /**
+ * Returns a result that completes this join by teleporting the players.
+ *
+ * The result of this function must be returned within a
+ * {@link GamePlayerEvents#ACCEPT} listener.
+ *
+ * @param positions a function that for given player returns position where the player should be teleported to
+ * @return a "teleport" result
+ * @throws IllegalArgumentException when positions are not specified for all joining players
+ * @see JoinAcceptorResult.Teleport#thenRun(Consumer)
+ * @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
+ */
+ JoinAcceptorResult.Teleport teleport(Function positions);
+
+ /**
+ * Returns a result that completes this join by teleporting the players.
+ *
+ * The result of this function must be returned within a
+ * {@link GamePlayerEvents#ACCEPT} listener.
+ *
+ * @param world the world that all the players should be teleported to
+ * @param position the position that all the players should be teleported to
+ * @param yaw the 'yaw' angle that all the players should be teleported to
+ * @param pitch the 'pitch' angle that all the players should be teleported to
+ * @return a "teleport" result
+ * @see JoinAcceptorResult.Teleport#thenRun(Consumer)
+ * @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
+ */
+ JoinAcceptorResult.Teleport teleport(ServerWorld world, Vec3d position, float yaw, float pitch);
+
+ /**
+ * Returns a result that completes this join by teleporting the players.
+ *
+ * The result of this function must be returned within a
+ * {@link GamePlayerEvents#ACCEPT} listener.
+ *
+ * @param world the world that all the players should be teleported to
+ * @param position the position that all the players should be teleported to
+ * @return a "teleport" result
+ * @see JoinAcceptorResult.Teleport#thenRun(Consumer)
+ * @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
+ */
+ default JoinAcceptorResult.Teleport teleport(ServerWorld world, Vec3d position) {
+ return this.teleport(world, position, 0, 0);
+ }
+
+ /**
+ * Returns a result that does nothing, passing on any handling to any other listener.
+ *
+ * @return a "passing" result
+ */
+ default JoinAcceptorResult pass() {
+ return JoinAcceptorResult.PASS;
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptorResult.java b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptorResult.java
new file mode 100644
index 00000000..6ad082d0
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptorResult.java
@@ -0,0 +1,22 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import net.minecraft.server.network.ServerPlayerEntity;
+
+import java.util.function.Consumer;
+
+public sealed interface JoinAcceptorResult permits JoinAcceptorResult.Pass, JoinAcceptorResult.Teleport {
+ Pass PASS = new Pass();
+
+ final class Pass implements JoinAcceptorResult {
+ private Pass() {
+ }
+ }
+
+ non-sealed interface Teleport extends JoinAcceptorResult {
+ Teleport thenRun(Consumer consumer);
+
+ default Teleport thenRunForEach(Consumer consumer) {
+ return this.thenRun(players -> players.forEach(consumer));
+ }
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinIntent.java b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinIntent.java
index adaade71..7cdc94ab 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinIntent.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinIntent.java
@@ -6,8 +6,7 @@
/**
* Represents the "intention" of a player or group of players joining a {@link GameSpace}.
* It is up to the game implementation to respect this intent in the way that is appropriate for their game. This may be
- * accomplished by handling the {@link GamePlayerEvents#SCREEN_JOINS 'Screen Joins'} and
- * {@link GamePlayerEvents#OFFER 'Player Offer'} events.
+ * accomplished by handling the {@link GamePlayerEvents#OFFER 'Join Offer'} events.
*/
public enum JoinIntent {
/**
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOffer.java b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOffer.java
new file mode 100644
index 00000000..b49f6b83
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOffer.java
@@ -0,0 +1,86 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.text.Text;
+import xyz.nucleoid.plasmid.game.GameSpace;
+import xyz.nucleoid.plasmid.game.GameTexts;
+import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
+
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a request for of a player or group of players to join a {@link GameSpace}.
+ *
+ * This object should be used in order to construct a {@link JoinOfferResult} object to return from a listener to the
+ * {@link GamePlayerEvents#OFFER} event.
+ *
+ * @see GameSpace
+ * @see GamePlayerEvents#OFFER
+ */
+public interface JoinOffer {
+ /**
+ * @return the set of {@link GameProfile} of the players that are requesting access to this {@link GameSpace}
+ */
+ Set players();
+
+ /**
+ * @return the {@link UUID profile UUID} of the players that are requesting access to this {@link GameSpace}
+ */
+ default Set playerIds() {
+ return this.players()
+ .stream()
+ .map(GameProfile::getId)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return the usernames of the players that are requesting access to this {@link GameSpace}
+ */
+ default Set playerNames() {
+ return this.players()
+ .stream()
+ .map(GameProfile::getName)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return the {@link JoinIntent 'intent'} of the players, such as whether they want to participate or spectate
+ * @see JoinIntent
+ */
+ JoinIntent intent();
+
+ /**
+ * Returns an offer result that accepts this offer and allows the players into this {@link GameSpace}.
+ *
+ * This function does not do anything on its own, but its result must be returned within a
+ * {@link GamePlayerEvents#OFFER} listener.
+ *
+ * @return an "accept" offer result
+ */
+ default JoinOfferResult.Accept accept() {
+ return JoinOfferResult.ACCEPT;
+ }
+
+ /**
+ * Returns an offer result that rejects this offer and does not allow the players into this {@link GameSpace}.
+ *
+ * This function does not do anything on its own, but its result must be returned within a
+ * {@link GamePlayerEvents#OFFER} listener.
+ *
+ * @param reason a text message that explains why these players were rejected
+ * @return a "reject" offer result
+ * @see GameTexts.Join
+ */
+ JoinOfferResult.Reject reject(Text reason);
+
+ /**
+ * Returns an offer result that does nothing with this offer, passing on any handling to any other listener.
+ *
+ * @return a "passing" offer result
+ */
+ default JoinOfferResult pass() {
+ return JoinOfferResult.PASS;
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOfferResult.java b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOfferResult.java
new file mode 100644
index 00000000..e3c75f18
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinOfferResult.java
@@ -0,0 +1,22 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import net.minecraft.text.Text;
+
+public sealed interface JoinOfferResult permits JoinOfferResult.Pass, JoinOfferResult.Accept, JoinOfferResult.Reject {
+ Pass PASS = new Pass();
+ Accept ACCEPT = new Accept();
+
+ final class Pass implements JoinOfferResult {
+ private Pass() {
+ }
+ }
+
+ final class Accept implements JoinOfferResult {
+ private Accept() {
+ }
+ }
+
+ non-sealed interface Reject extends JoinOfferResult {
+ Text reason();
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinAcceptor.java b/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinAcceptor.java
new file mode 100644
index 00000000..ca56aa1e
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinAcceptor.java
@@ -0,0 +1,106 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.GameMode;
+import xyz.nucleoid.plasmid.util.PlayerPos;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public record LocalJoinAcceptor(Collection serverPlayers, JoinIntent intent) implements JoinAcceptor {
+ @Override
+ public Set players() {
+ return this.serverPlayers
+ .stream()
+ .map(PlayerEntity::getGameProfile)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set playerIds() {
+ return this.serverPlayers
+ .stream()
+ .map(player -> player.getGameProfile().getId())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set playerNames() {
+ return this.serverPlayers
+ .stream()
+ .map(player -> player.getGameProfile().getName())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public JoinAcceptorResult.Teleport teleport(Map positions) {
+ if (this.serverPlayers.stream().anyMatch(player -> !positions.containsKey(player.getUuid()))) {
+ throw new IllegalArgumentException("Positions for all players must be specified");
+ }
+ return new LocalJoinAcceptor.Teleport(positions);
+ }
+
+ @Override
+ public JoinAcceptorResult.Teleport teleport(Function positions) {
+ return new LocalJoinAcceptor.Teleport(
+ this.serverPlayers.stream().collect(Collectors.toMap(
+ ServerPlayerEntity::getUuid,
+ player -> positions.apply(player.getGameProfile())
+ ))
+ );
+ }
+
+ @Override
+ public JoinAcceptorResult.Teleport teleport(ServerWorld world, Vec3d position, float yaw, float pitch) {
+ var playerPos = new PlayerPos(world, position, yaw, pitch);
+ return new LocalJoinAcceptor.Teleport(
+ this.serverPlayers.stream().collect(Collectors.toMap(
+ ServerPlayerEntity::getUuid,
+ player -> playerPos
+ ))
+ );
+ }
+
+ public static class Teleport implements JoinAcceptorResult.Teleport {
+ private final Map positions;
+
+ private final List> thenRun = new ArrayList<>();
+
+ Teleport(Map positions) {
+ this.positions = positions;
+ }
+
+ @Override
+ public JoinAcceptorResult.Teleport thenRun(Consumer consumer) {
+ this.thenRun.add(consumer);
+ return this;
+ }
+
+ public void runCallbacks(PlayerSet players) {
+ for (var consumer : this.thenRun) {
+ consumer.accept(players);
+ }
+ }
+
+ public ServerWorld applyTeleport(ServerPlayerEntity player) {
+ var pos = this.positions.get(player.getUuid());
+
+ player.changeGameMode(GameMode.SURVIVAL);
+ player.refreshPositionAndAngles(
+ pos.x(),
+ pos.y(),
+ pos.z(),
+ pos.yaw(),
+ pos.pitch()
+ );
+
+ return pos.world();
+ }
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinOffer.java b/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinOffer.java
new file mode 100644
index 00000000..f23ac5a7
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/game/player/LocalJoinOffer.java
@@ -0,0 +1,40 @@
+package xyz.nucleoid.plasmid.game.player;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.text.Text;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public record LocalJoinOffer(Collection serverPlayers, JoinIntent intent) implements JoinOffer {
+ @Override
+ public Set players() {
+ return this.serverPlayers
+ .stream()
+ .map(PlayerEntity::getGameProfile)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set playerIds() {
+ return this.serverPlayers
+ .stream()
+ .map(player -> player.getGameProfile().getId())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set playerNames() {
+ return this.serverPlayers
+ .stream()
+ .map(player -> player.getGameProfile().getName())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public JoinOfferResult.Reject reject(Text reason) {
+ return () -> reason;
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/LocalPlayerOffer.java b/src/main/java/xyz/nucleoid/plasmid/game/player/LocalPlayerOffer.java
deleted file mode 100644
index 81e2cdbb..00000000
--- a/src/main/java/xyz/nucleoid/plasmid/game/player/LocalPlayerOffer.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package xyz.nucleoid.plasmid.game.player;
-
-import com.mojang.authlib.GameProfile;
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.server.world.ServerWorld;
-import net.minecraft.text.Text;
-import net.minecraft.util.math.Vec3d;
-import net.minecraft.world.GameMode;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public record LocalPlayerOffer(ServerPlayerEntity player, JoinIntent intent) implements PlayerOffer {
- @Override
- public GameProfile profile() {
- return this.player.getGameProfile();
- }
-
- @Override
- public PlayerOfferResult.Accept accept(ServerWorld world, Vec3d position, float yaw, float pitch) {
- return new Accept(world, position, yaw, pitch);
- }
-
- @Override
- public PlayerOfferResult.Reject reject(Text reason) {
- return () -> reason;
- }
-
- public static class Accept implements PlayerOfferResult.Accept {
- private final ServerWorld world;
- private final Vec3d position;
- private final float yaw;
- private final float pitch;
-
- private final List> thenRun = new ArrayList<>();
-
- Accept(ServerWorld world, Vec3d position, float yaw, float pitch) {
- this.world = world;
- this.position = position;
- this.yaw = yaw;
- this.pitch = pitch;
- }
-
- @Override
- public Accept thenRun(Consumer consumer) {
- this.thenRun.add(consumer);
- return this;
- }
-
- public ServerWorld applyJoin(ServerPlayerEntity player) {
- player.changeGameMode(GameMode.SURVIVAL);
- player.refreshPositionAndAngles(this.position.x, this.position.y, this.position.z, this.yaw, this.pitch);
-
- for (Consumer consumer : this.thenRun) {
- consumer.accept(player);
- }
-
- return this.world;
- }
- }
-}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOffer.java b/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOffer.java
deleted file mode 100644
index 1b82d964..00000000
--- a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOffer.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package xyz.nucleoid.plasmid.game.player;
-
-import com.mojang.authlib.GameProfile;
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.server.world.ServerWorld;
-import net.minecraft.text.Text;
-import net.minecraft.util.math.Vec3d;
-import xyz.nucleoid.plasmid.game.GameSpace;
-import xyz.nucleoid.plasmid.game.GameTexts;
-import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
-
-import java.util.UUID;
-import java.util.function.Consumer;
-
-/**
- * Represents a request for a {@link ServerPlayerEntity} to join a {@link GameSpace}.
- *
- * This object should be used in order to construct a {@link PlayerOfferResult} object to return from a listener to the
- * {@link GamePlayerEvents#OFFER} event.
- *
- * @see GameSpace
- * @see GamePlayerEvents#OFFER
- */
-public interface PlayerOffer {
- /**
- * @return the {@link GameProfile} of the player that is requesting access to this {@link GameSpace}
- */
- GameProfile profile();
-
- /**
- * @return the {@link UUID profile UUID} of the player that is requesting access to this {@link GameSpace}
- */
- default UUID playerId() {
- return this.profile().getId();
- }
-
- /**
- * @return the username of the player that is requesting access to this {@link GameSpace}
- */
- default String playerName() {
- return this.profile().getName();
- }
-
- /**
- * @return the {@link JoinIntent 'intent'} of the player, such as whether they want to participate or spectate
- * @see JoinIntent
- */
- JoinIntent intent();
-
- /**
- * Returns an offer result that accepts this player offer and allows the player into this {@link GameSpace}.
- *
- * This function does not do anything on its own, but its result must be returned within a
- * {@link GamePlayerEvents#OFFER} listener.
- *
- * @param world the world that the player should be teleported to when accepted
- * @param position the position that the player should be teleported to when accepted
- * @param yaw the 'yaw' angle that the player should be teleported to when accepted
- * @param pitch the 'pitch' angle that the player should be teleported to when accepted
- * @return an "accept" offer result
- * @see PlayerOfferResult.Accept#thenRun(Consumer)
- */
- PlayerOfferResult.Accept accept(ServerWorld world, Vec3d position, float yaw, float pitch);
-
- /**
- * Returns an offer result that accepts this player offer and allows the player into this {@link GameSpace}.
- *
- * This function does not do anything on its own, but its result must be returned within a
- * {@link GamePlayerEvents#OFFER} listener.
- *
- * @param world the world that the player should be teleported to when accepted
- * @param position the position that the player should be teleported to when accepted
- * @return an "accept" offer result
- * @see PlayerOfferResult.Accept#thenRun(Consumer)
- */
- default PlayerOfferResult.Accept accept(ServerWorld world, Vec3d position) {
- return this.accept(world, position, 0.0f, 0.0f);
- }
-
- /**
- * Returns an offer result that rejects this player offer and does not allow the player into this {@link GameSpace}.
- *
- * This function does not do anything on its own, but its result must be returned within a
- * {@link GamePlayerEvents#OFFER} listener.
- *
- * @param reason a text message that explains why this player was rejected
- * @return a "reject" offer result
- * @see GameTexts.Join
- */
- PlayerOfferResult.Reject reject(Text reason);
-
- /**
- * Returns an offer result that does nothing with this player offer, passing on any handling to any other listener.
- *
- * @return a "passing" offer result
- */
- default PlayerOfferResult pass() {
- return PlayerOfferResult.PASS;
- }
-}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOfferResult.java b/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOfferResult.java
deleted file mode 100644
index ed123e85..00000000
--- a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOfferResult.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package xyz.nucleoid.plasmid.game.player;
-
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.text.Text;
-
-import java.util.function.Consumer;
-
-public sealed interface PlayerOfferResult permits PlayerOfferResult.Pass, PlayerOfferResult.Accept, PlayerOfferResult.Reject {
- Pass PASS = new Pass();
-
- final class Pass implements PlayerOfferResult {
- private Pass() {
- }
- }
-
- non-sealed interface Accept extends PlayerOfferResult {
- PlayerOfferResult.Accept thenRun(Consumer consumer);
- }
-
- non-sealed interface Reject extends PlayerOfferResult {
- Text reason();
- }
-}
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/ConcurrentGamePortalBackend.java b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/ConcurrentGamePortalBackend.java
index 5afc7b7f..f0d84194 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/ConcurrentGamePortalBackend.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/ConcurrentGamePortalBackend.java
@@ -3,6 +3,8 @@
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Formatting;
+import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.config.GameConfig;
import xyz.nucleoid.plasmid.game.manager.GameSpaceManager;
import xyz.nucleoid.plasmid.game.manager.ManagedGameSpace;
@@ -29,9 +31,9 @@ public RegistryEntry> game() {
public void applyTo(ServerPlayerEntity player) {
for (var gameSpace : GameSpaceManager.get().getOpenGameSpaces()) {
if (gameSpace.getMetadata().sourceConfig().equals(this.game)) {
- var results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ var result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
- if (results.globalError == null && results.playerErrors.get(player) == null) {
+ if (result.isOk()) {
return;
}
}
@@ -41,14 +43,16 @@ public void applyTo(ServerPlayerEntity player) {
.thenCompose(Function.identity())
.handleAsync((gameSpace, throwable) -> {
this.gameFuture = null;
- GamePlayerJoiner.Results results;
+ GameResult result;
if (gameSpace != null) {
- results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
} else {
- results = GamePlayerJoiner.handleJoinException(throwable);
+ result = GamePlayerJoiner.handleJoinException(throwable);
}
- results.sendErrorsTo(player);
+ if (result.isError()) {
+ player.sendMessage(result.errorCopy().formatted(Formatting.RED), false);
+ }
return null;
}, player.server);
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/NewGamePortalBackend.java b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/NewGamePortalBackend.java
index 2d633c86..2f4cc81f 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/NewGamePortalBackend.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/NewGamePortalBackend.java
@@ -3,6 +3,8 @@
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Formatting;
+import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.config.GameConfig;
import xyz.nucleoid.plasmid.game.manager.GameSpaceManager;
import xyz.nucleoid.plasmid.game.manager.ManagedGameSpace;
@@ -18,14 +20,16 @@ public void applyTo(ServerPlayerEntity player) {
CompletableFuture.supplyAsync(() -> this.openGame(player.server))
.thenCompose(Function.identity())
.handleAsync((gameSpace, throwable) -> {
- GamePlayerJoiner.Results results;
+ GameResult result;
if (gameSpace != null) {
- results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
} else {
- results = GamePlayerJoiner.handleJoinException(throwable);
+ result = GamePlayerJoiner.handleJoinException(throwable);
}
- results.sendErrorsTo(player);
+ if (result.isError()) {
+ player.sendMessage(result.errorCopy().formatted(Formatting.RED), false);
+ }
return null;
}, player.server);
diff --git a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/SingleGamePortalBackend.java b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/SingleGamePortalBackend.java
index a16b1986..ce2cb575 100644
--- a/src/main/java/xyz/nucleoid/plasmid/game/portal/game/SingleGamePortalBackend.java
+++ b/src/main/java/xyz/nucleoid/plasmid/game/portal/game/SingleGamePortalBackend.java
@@ -3,8 +3,10 @@
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Formatting;
import xyz.nucleoid.plasmid.game.GameCloseReason;
import xyz.nucleoid.plasmid.game.GameLifecycle;
+import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.GameSpace;
import xyz.nucleoid.plasmid.game.config.GameConfig;
import xyz.nucleoid.plasmid.game.manager.GameSpaceManager;
@@ -28,14 +30,16 @@ public void applyTo(ServerPlayerEntity player) {
CompletableFuture.supplyAsync(() -> this.getOrOpen(player.server))
.thenCompose(Function.identity())
.handleAsync((gameSpace, throwable) -> {
- GamePlayerJoiner.Results results;
+ GameResult result;
if (gameSpace != null) {
- results = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
+ result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY);
} else {
- results = GamePlayerJoiner.handleJoinException(throwable);
+ result = GamePlayerJoiner.handleJoinException(throwable);
}
- results.sendErrorsTo(player);
+ if (result.isError()) {
+ player.sendMessage(result.errorCopy().formatted(Formatting.RED), false);
+ }
return null;
}, player.server);
diff --git a/src/main/java/xyz/nucleoid/plasmid/util/PlayerPos.java b/src/main/java/xyz/nucleoid/plasmid/util/PlayerPos.java
new file mode 100644
index 00000000..c7f2ad92
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/util/PlayerPos.java
@@ -0,0 +1,11 @@
+package xyz.nucleoid.plasmid.util;
+
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.Vec3d;
+
+public record PlayerPos(ServerWorld world, double x, double y, double z, float yaw, float pitch) {
+
+ public PlayerPos(ServerWorld world, Vec3d position, float yaw, float pitch) {
+ this(world, position.x, position.y, position.z, yaw, pitch);
+ }
+}
diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java b/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java
index 8eaf7d57..54b12d38 100644
--- a/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java
+++ b/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java
@@ -27,6 +27,7 @@
import xyz.nucleoid.plasmid.game.common.config.PlayerConfig;
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
+import xyz.nucleoid.plasmid.game.player.JoinOffer;
import xyz.nucleoid.plasmid.game.rule.GameRuleType;
import xyz.nucleoid.plasmid.game.world.generator.TemplateChunkGenerator;
import xyz.nucleoid.stimuli.event.player.PlayerC2SPacketEvent;
@@ -58,9 +59,12 @@ public static GameOpenProcedure open(GameOpenContext context) {
.setGameRule(GameRules.KEEP_INVENTORY, true);
return context.openWithWorld(worldConfig, (activity, world) -> {
- activity.listen(GamePlayerEvents.OFFER, offer ->
- offer.accept(world, new Vec3d(0.0, 65.0, 0.0))
- .thenRun(player -> player.changeGameMode(GameMode.ADVENTURE))
+ activity.listen(GamePlayerEvents.OFFER, JoinOffer::accept);
+ activity.listen(GamePlayerEvents.ACCEPT, acceptor ->
+ acceptor.teleport(world, new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(joiningPlayer -> {
+ joiningPlayer.changeGameMode(GameMode.ADVENTURE);
+ })
);
GameWaitingLobby.addTo(activity, new PlayerConfig(1, 99));
@@ -170,9 +174,12 @@ private static GameResult startGame(GameSpace gameSpace) {
player.networkHandler.sendPacket(new PlayerPositionLookS2CPacket(0, 0, 0, 0, 0f, Set.of(), 0));
});
- activity.listen(GamePlayerEvents.OFFER, offer ->
- offer.accept(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0))
- .thenRun(joiningPlayer -> joiningPlayer.changeGameMode(GameMode.ADVENTURE))
+ activity.listen(GamePlayerEvents.OFFER, JoinOffer::accept);
+ activity.listen(GamePlayerEvents.ACCEPT, acceptor ->
+ acceptor.teleport(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(joiningPlayer -> {
+ joiningPlayer.changeGameMode(GameMode.ADVENTURE);
+ })
);
});
diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java
index eaf63ee5..8bfa6143 100644
--- a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java
+++ b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java
@@ -27,6 +27,7 @@
import xyz.nucleoid.plasmid.game.common.team.*;
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
+import xyz.nucleoid.plasmid.game.player.JoinOffer;
import xyz.nucleoid.plasmid.game.rule.GameRuleType;
import xyz.nucleoid.plasmid.game.stats.GameStatisticBundle;
import xyz.nucleoid.plasmid.game.stats.StatisticKey;
@@ -58,9 +59,12 @@ public static GameOpenProcedure open(GameOpenContext context) {
.setGameRule(GameRules.KEEP_INVENTORY, true);
return context.openWithWorld(worldConfig, (activity, world) -> {
- activity.listen(GamePlayerEvents.OFFER, offer ->
- offer.accept(world, new Vec3d(0.0, 65.0, 0.0))
- .thenRun(player -> player.changeGameMode(GameMode.ADVENTURE))
+ activity.listen(GamePlayerEvents.OFFER, JoinOffer::accept);
+ activity.listen(GamePlayerEvents.ACCEPT, acceptor ->
+ acceptor.teleport(world, new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(joiningPlayer -> {
+ joiningPlayer.changeGameMode(GameMode.ADVENTURE);
+ })
);
GameWaitingLobby.addTo(activity, new PlayerConfig(1, 99));
diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java
index 5c170f52..b4f6fb0d 100644
--- a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java
+++ b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java
@@ -26,6 +26,7 @@
import xyz.nucleoid.plasmid.game.common.team.TeamManager;
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
+import xyz.nucleoid.plasmid.game.player.JoinOffer;
import xyz.nucleoid.plasmid.game.rule.GameRuleType;
import xyz.nucleoid.plasmid.game.stats.GameStatisticBundle;
import xyz.nucleoid.plasmid.game.stats.StatisticKey;
@@ -51,9 +52,12 @@ public static GameOpenProcedure open(GameOpenContext context) {
.setGameRule(GameRules.KEEP_INVENTORY, true);
return context.openWithWorld(worldConfig, (activity, world) -> {
- activity.listen(GamePlayerEvents.OFFER, offer ->
- offer.accept(world, new Vec3d(0.0, 65.0, 0.0))
- .thenRun(player -> player.changeGameMode(GameMode.ADVENTURE))
+ activity.listen(GamePlayerEvents.OFFER, JoinOffer::accept);
+ activity.listen(GamePlayerEvents.ACCEPT, acceptor ->
+ acceptor.teleport(world, new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(joiningPlayer -> {
+ joiningPlayer.changeGameMode(GameMode.ADVENTURE);
+ })
);
GameWaitingLobby.addTo(activity, new PlayerConfig(1, 99));
@@ -127,9 +131,12 @@ private static GameResult startGame(GameSpace gameSpace, int iter) {
return ActionResult.FAIL;
});
- activity.listen(GamePlayerEvents.OFFER, offer ->
- offer.accept(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0))
- .thenRun(player -> player.changeGameMode(GameMode.ADVENTURE))
+ activity.listen(GamePlayerEvents.OFFER, JoinOffer::accept);
+ activity.listen(GamePlayerEvents.ACCEPT, acceptor ->
+ acceptor.teleport(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0))
+ .thenRunForEach(joiningPlayer -> {
+ joiningPlayer.changeGameMode(GameMode.ADVENTURE);
+ })
);
});