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); + }) ); });