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/build.gradle b/build.gradle index e33b3eb3..380f6119 100644 --- a/build.gradle +++ b/build.gradle @@ -87,25 +87,25 @@ dependencies { modApi "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - modApi include('xyz.nucleoid:server-translations-api:2.3.0+1.20.5-rc2') - modApi include('xyz.nucleoid:packet-tweaker:0.5.1+1.20.6') - modApi include('xyz.nucleoid:fantasy:0.6.2+1.20.6') - modApi include('xyz.nucleoid:more-codecs:0.3.3+1.20.2') - modApi include('xyz.nucleoid:stimuli:0.4.11+1.20.6') - modApi include('xyz.nucleoid:map-templates:0.1.9+1.20.4') - modApi include('xyz.nucleoid:substrate:0.2.2+1.20.1') - modApi 'eu.pb4:polymer-core:0.8.2+1.20.6' - modApi 'eu.pb4:polymer-resource-pack:0.8.2+1.20.6' - modApi 'eu.pb4:polymer-blocks:0.8.2+1.20.6' - modApi 'eu.pb4:polymer-virtual-entity:0.8.2+1.20.6' - modApi include('eu.pb4:sgui:1.5.1+1.20.5') - modApi include('eu.pb4:sidebar-api:0.4.0+1.20.5') - modApi include("eu.pb4:placeholder-api:2.4.0-pre.1+1.20.5") - modApi include("eu.pb4:map-canvas-api:0.3.0+1.20.6") - modApi include("eu.pb4:player-data-api:0.5.0+1.20.5") - modApi include("eu.pb4:predicate-api:0.4.0+1.20.5") - - modImplementation include("me.lucko:fabric-permissions-api:0.2-SNAPSHOT") + modApi include("xyz.nucleoid:server-translations-api:${project.server_translations_version}") + modApi include("xyz.nucleoid:packet-tweaker:${project.packet_tweaker_version}") + modApi include("xyz.nucleoid:fantasy:${project.fantasy_version}") + modApi include("xyz.nucleoid:more-codecs:${project.more_codecs_version}") + modApi include("xyz.nucleoid:stimuli:${project.stimuli_version}") + modApi include("xyz.nucleoid:map-templates:${project.map_templates_version}") + modApi include("xyz.nucleoid:substrate:${project.substrate_version}") + modApi "eu.pb4:polymer-core:${project.polymer_version}" + modApi "eu.pb4:polymer-resource-pack:${project.polymer_version}" + modApi "eu.pb4:polymer-blocks:${project.polymer_version}" + modApi "eu.pb4:polymer-virtual-entity:${project.polymer_version}" + modApi include("eu.pb4:sgui:${project.sgui_version}") + modApi include("eu.pb4:sidebar-api:${project.sidebar_api_version}") + modApi include("eu.pb4:placeholder-api:${project.placeholder_api_version}") + modApi include("eu.pb4:map-canvas-api:${project.map_canvas_api_version}") + modApi include("eu.pb4:player-data-api:${project.player_data_api_version}") + modApi include("eu.pb4:predicate-api:${project.predicate_api_version}") + + modImplementation include("me.lucko:fabric-permissions-api:${project.permission_api_version}") modCompileOnly('xyz.nucleoid:disguiselib-fabric:1.3.2') modCompileOnly('maven.modrinth:afkdisplay:1.1.0') diff --git a/gradle.properties b/gradle.properties index 59f55f52..fa5e867f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,23 @@ minecraft_version=1.20.6 yarn_mappings=1.20.6+build.1 loader_version=0.15.10 -#Fabric api +# Dependencies fabric_version=0.99.0+1.20.6 +polymer_version=0.8.2+1.20.6 +server_translations_version=2.3.0+1.20.5-rc2 +packet_tweaker_version=0.5.1+1.20.6 +fantasy_version=0.6.2+1.20.6 +more_codecs_version=0.3.3+1.20.2 +stimuli_version=0.4.11+1.20.6 +map_templates_version=0.1.9+1.20.4 +substrate_version=0.2.2+1.20.1 +sgui_version=1.5.1+1.20.5 +sidebar_api_version=0.4.0+1.20.5 +placeholder_api_version=2.4.0-pre.1+1.20.5 +map_canvas_api_version=0.3.0+1.20.6 +player_data_api_version=0.5.0+1.20.5 +predicate_api_version=0.4.0+1.20.5 +permission_api_version=0.2-SNAPSHOT # Mod Properties mod_version=0.6 diff --git a/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java b/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java index 1f6d7328..db15c11f 100644 --- a/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java +++ b/src/main/java/xyz/nucleoid/plasmid/command/GameCommand.java @@ -9,7 +9,6 @@ import com.mojang.logging.LogUtils; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.command.argument.NbtCompoundArgumentType; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtOps; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.command.ServerCommandSource; @@ -30,6 +29,7 @@ import xyz.nucleoid.plasmid.game.config.GameConfigs; import xyz.nucleoid.plasmid.game.manager.GameSpaceManager; import xyz.nucleoid.plasmid.game.player.GamePlayerJoiner; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import xyz.nucleoid.plasmid.util.Scheduler; import java.util.Comparator; @@ -286,19 +286,18 @@ private static void joinAllPlayersToGame(ServerCommandSource source, GameSpace g .filter(player -> !GameSpaceManager.get().inGame(player)) .collect(Collectors.toList()); - var screen = gameSpace.getPlayers().screenJoins(players); - if (screen.isOk()) { - for (var player : players) { - gameSpace.getPlayers().offer(player); - } - } else { - source.sendError(screen.errorCopy().formatted(Formatting.RED)); + var intent = JoinIntent.ANY; + 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); - 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 2efba57c..65939d37 100644 --- a/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java +++ b/src/main/java/xyz/nucleoid/plasmid/command/ui/GameJoinUi.java @@ -15,6 +15,7 @@ import xyz.nucleoid.plasmid.game.manager.GameSpaceManager; import xyz.nucleoid.plasmid.game.manager.ManagedGameSpace; import xyz.nucleoid.plasmid.game.player.GamePlayerJoiner; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import xyz.nucleoid.plasmid.util.Guis; import java.util.ArrayList; @@ -38,8 +39,10 @@ public GameJoinUi(ServerPlayerEntity player) { private static void tryJoinGame(ServerPlayerEntity player, GameSpace gameSpace) { player.server.execute(() -> { - var results = GamePlayerJoiner.tryJoin(player, gameSpace); - 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 6f6b0697..080312c0 100644 --- a/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java +++ b/src/main/java/xyz/nucleoid/plasmid/game/GameSpacePlayers.java @@ -2,6 +2,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import xyz.nucleoid.plasmid.game.event.GamePlayerEvents; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import xyz.nucleoid.plasmid.game.player.PlayerOps; import xyz.nucleoid.plasmid.game.player.PlayerSet; @@ -16,30 +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 - * @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) + * @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 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); + 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 - * @return a {@link GameResult} describing whether this player joined the game, or an error if not + * @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 these players joined the game, or an error if not * @see GamePlayerEvents#OFFER * @see xyz.nucleoid.plasmid.game.player.GamePlayerJoiner */ - GameResult offer(ServerPlayerEntity player); + 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 e15a2f48..bef63af3 100644 --- a/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java +++ b/src/main/java/xyz/nucleoid/plasmid/game/common/GameWaitingLobby.java @@ -1,5 +1,6 @@ package xyz.nucleoid.plasmid.game.common; +import com.mojang.authlib.GameProfile; import net.minecraft.entity.boss.BossBar; import net.minecraft.screen.ScreenTexts; import net.minecraft.server.network.ServerPlayerEntity; @@ -21,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; @@ -88,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, lobby::screenJoins); activity.listen(GamePlayerEvents.OFFER, lobby::offerPlayer); activity.listen(GamePlayerEvents.REMOVE, lobby::onRemovePlayer); @@ -169,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 db12c502..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,19 +1,16 @@ package xyz.nucleoid.plasmid.game.event; 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.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}. */ @@ -100,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 -> { + public static final StimulusEvent OFFER = StimulusEvent.create(Offer.class, ctx -> offer -> { try { for (var listener : ctx.getListeners()) { - var result = listener.screenJoins(players); - 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.isTerminal()) { + 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(); }); /** @@ -173,12 +169,12 @@ public interface Remove { void onRemovePlayer(ServerPlayerEntity player); } - public interface ScreenJoins { - GameResult screenJoins(Collection players); + 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 894642a1..4ea9ad9b 100644 --- a/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java +++ b/src/main/java/xyz/nucleoid/plasmid/game/manager/ManagedGameSpace.java @@ -17,10 +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.PlayerOffer; -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; @@ -193,36 +194,23 @@ public GameBehavior getBehavior() { return this.state; } - GameResult screenJoins(Collection players) { - var result = this.attemptScreenJoins(players); - - if (result.isError()) { - this.players.attemptGarbageCollection(); - } - - return result; - } - - private GameResult attemptScreenJoins(Collection players) { - if (this.closed) { - return GameResult.error(GameTexts.Join.gameClosed()); - } - - return this.state.invoker(GamePlayerEvents.SCREEN_JOINS).screenJoins(players); - } - - PlayerOfferResult offerPlayer(PlayerOffer 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 bcd79d7f..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,8 +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.MutablePlayerSet; -import xyz.nucleoid.plasmid.game.player.PlayerOffer; +import xyz.nucleoid.plasmid.game.player.*; import xyz.nucleoid.plasmid.game.player.isolation.IsolatingPlayerTeleporter; import java.util.Collection; @@ -26,13 +25,23 @@ public final class ManagedGameSpacePlayers implements GameSpacePlayers { } @Override - public GameResult screenJoins(Collection players) { - return this.space.screenJoins(players); + 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) { - var result = this.attemptOffer(player); + public GameResult offer(Collection players, JoinIntent intent) { + var result = this.attemptOffer(players, intent); if (result.isError()) { this.attemptGarbageCollection(); @@ -41,32 +50,41 @@ public GameResult offer(ServerPlayerEntity player) { return result; } - private GameResult attemptOffer(ServerPlayerEntity player) { - 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 PlayerOffer(player); - var result = this.space.offerPlayer(offer); + var offer = new LocalJoinOffer(players, intent); - var reject = result.asReject(); - if (reject != null) { - return GameResult.error(reject.reason()); - } - - var accept = result.asAccept(); - if (accept != null) { - try { - this.teleporter.teleportIn(player, accept::applyJoin); - this.set.add(player); - this.space.onAddPlayer(player); + 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()); + }; + } - return GameResult.ok(); - } catch (Throwable throwable) { - return GameResult.error(GameTexts.Join.unexpectedError()); + private GameResult accept(Collection players, JoinIntent intent) { + var acceptor = new LocalJoinAcceptor(players, intent); + + switch (this.space.acceptPlayers(acceptor)) { + case LocalJoinAcceptor.Teleport teleport -> { + try { + 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()); + } } - } else { - 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 8085154a..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,28 +1,26 @@ 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) { + public static GameResult tryJoin(ServerPlayerEntity player, GameSpace gameSpace, JoinIntent intent) { try { var players = collectPlayersForJoin(player, gameSpace); - return tryJoinAll(players, gameSpace); + return tryJoinAll(players, gameSpace, intent); } catch (Throwable throwable) { return handleJoinException(throwable); } @@ -37,29 +35,12 @@ private static Set collectPlayersForJoin(ServerPlayerEntity return players; } - private static Results tryJoinAll(Collection players, GameSpace gameSpace) { - var results = new Results(); - - var screenResult = gameSpace.getPlayers().screenJoins(players); - if (screenResult.isError()) { - results.globalError = screenResult.error(); - return results; - } - - for (var player : players) { - var result = gameSpace.getPlayers().offer(player); - 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 new file mode 100644 index 00000000..7cdc94ab --- /dev/null +++ b/src/main/java/xyz/nucleoid/plasmid/game/player/JoinIntent.java @@ -0,0 +1,41 @@ +package xyz.nucleoid.plasmid.game.player; + +import xyz.nucleoid.plasmid.game.GameSpace; +import xyz.nucleoid.plasmid.game.event.GamePlayerEvents; + +/** + * 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#OFFER 'Join Offer'} events. + */ +public enum JoinIntent { + /** + * The player has no particular intention. Generally, this should be considered as a preference to participate. + */ + ANY, + /** + * The player intends to join the game to participate. If they cannot be joined as a participant, they should not + * be allowed to join. + */ + PLAY, + /** + * The player intends to join the game to spectate. Unless the game does not support spectators, this player should + * generally always be accepted. + */ + SPECTATE, + ; + + /** + * @return {@code true} if the player may join as a participant under any circumstances + */ + public boolean canPlay() { + return this != SPECTATE; + } + + /** + * @return {@code true} if the player may join as a spectator under any circumstances + */ + public boolean canSpectate() { + return this != PLAY; + } +} 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/PlayerOffer.java b/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOffer.java deleted file mode 100644 index e0838bc0..00000000 --- a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOffer.java +++ /dev/null @@ -1,66 +0,0 @@ -package xyz.nucleoid.plasmid.game.player; - -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; - -/** - * 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 record PlayerOffer(ServerPlayerEntity player) { - /** - * @return the player that is requesting access to this {@link GameSpace}. - */ - @Override - public ServerPlayerEntity player() { - return this.player; - } - - /** - * 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#and(Runnable) - */ - public PlayerOfferResult.Accept accept(ServerWorld world, Vec3d position) { - return new PlayerOfferResult.Accept(world, position); - } - - /** - * 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 - */ - public PlayerOfferResult.Reject reject(Text reason) { - return new PlayerOfferResult.Reject(reason); - } - - /** - * Returns an offer result that does nothing with this player offer, passing on any handling to any other listener. - * - * @return a "pass" offer result - */ - public PlayerOfferResult pass() { - return PlayerOfferResult.Pass.INSTANCE; - } -} 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 5679a843..00000000 --- a/src/main/java/xyz/nucleoid/plasmid/game/player/PlayerOfferResult.java +++ /dev/null @@ -1,85 +0,0 @@ -package xyz.nucleoid.plasmid.game.player; - -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 org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public interface PlayerOfferResult { - @Nullable - default Accept asAccept() { - return null; - } - - @Nullable - default Reject asReject() { - return null; - } - - boolean isTerminal(); - - final class Pass implements PlayerOfferResult { - static final Pass INSTANCE = new Pass(); - - private Pass() { - } - - @Override - public boolean isTerminal() { - return false; - } - } - - final class Accept implements PlayerOfferResult { - private final ServerWorld world; - private final Vec3d position; - - private final List and = new ArrayList<>(); - - Accept(ServerWorld world, Vec3d position) { - this.world = world; - this.position = position; - } - - public PlayerOfferResult.Accept and(Runnable and) { - this.and.add(and); - return this; - } - - public ServerWorld applyJoin(ServerPlayerEntity player) { - player.changeGameMode(GameMode.SURVIVAL); - player.refreshPositionAndAngles(this.position.x, this.position.y, this.position.z, 0.0F, 0.0F); - - this.and.forEach(Runnable::run); - - return this.world; - } - - @Override - public Accept asAccept() { - return this; - } - - @Override - public boolean isTerminal() { - return true; - } - } - - record Reject(Text reason) implements PlayerOfferResult { - @Override - public Reject asReject() { - return this; - } - - @Override - public boolean isTerminal() { - return true; - } - } -} 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 9ac7a93f..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,10 +3,13 @@ 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; import xyz.nucleoid.plasmid.game.player.GamePlayerJoiner; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -28,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); + var result = GamePlayerJoiner.tryJoin(player, gameSpace, JoinIntent.ANY); - if (results.globalError == null && results.playerErrors.get(player) == null) { + if (result.isOk()) { return; } } @@ -40,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); + 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 2abcac09..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,10 +3,13 @@ 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; import xyz.nucleoid.plasmid.game.player.GamePlayerJoiner; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -17,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); + 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 bf7614b6..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,13 +3,16 @@ 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; import xyz.nucleoid.plasmid.game.manager.ManagedGameSpace; import xyz.nucleoid.plasmid.game.player.GamePlayerJoiner; +import xyz.nucleoid.plasmid.game.player.JoinIntent; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -27,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); + 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 a0f260e4..54b12d38 100644 --- a/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java +++ b/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java @@ -2,7 +2,6 @@ import eu.pb4.polymer.common.api.PolymerCommonUtils; import eu.pb4.polymer.virtualentity.api.VirtualEntityUtils; -import eu.pb4.sidebars.api.SidebarUtils; import it.unimi.dsi.fastutil.ints.IntList; import net.minecraft.block.BlockState; import net.minecraft.entity.MovementType; @@ -13,9 +12,7 @@ import net.minecraft.network.packet.c2s.play.VehicleMoveC2SPacket; import net.minecraft.network.packet.s2c.play.*; import net.minecraft.particle.ParticleTypes; -import net.minecraft.screen.ScreenTexts; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.math.Vec3d; @@ -30,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; @@ -61,11 +59,13 @@ public static GameOpenProcedure open(GameOpenContext context) { .setGameRule(GameRules.KEEP_INVENTORY, true); return context.openWithWorld(worldConfig, (activity, world) -> { - activity.listen(GamePlayerEvents.OFFER, offer -> { - var player = offer.player(); - return offer.accept(world, new Vec3d(0.0, 65.0, 0.0)) - .and(() -> 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)); @@ -174,10 +174,13 @@ private static GameResult startGame(GameSpace gameSpace) { player.networkHandler.sendPacket(new PlayerPositionLookS2CPacket(0, 0, 0, 0, 0f, Set.of(), 0)); }); - activity.listen(GamePlayerEvents.OFFER, offer -> { - return offer.accept(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0)) - .and(() -> offer.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); + }) + ); }); return GameResult.ok(); diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java index a6acbc8b..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; @@ -34,7 +35,6 @@ import xyz.nucleoid.plasmid.util.WoodType; import xyz.nucleoid.stimuli.event.player.PlayerDeathEvent; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; @@ -59,11 +59,13 @@ public static GameOpenProcedure open(GameOpenContext context) { .setGameRule(GameRules.KEEP_INVENTORY, true); return context.openWithWorld(worldConfig, (activity, world) -> { - activity.listen(GamePlayerEvents.OFFER, offer -> { - var player = offer.player(); - return offer.accept(world, new Vec3d(0.0, 65.0, 0.0)) - .and(() -> 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 3f618751..b4f6fb0d 100644 --- a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java +++ b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGameWithResourcePack.java @@ -9,7 +9,6 @@ import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.Identifier; -import net.minecraft.util.Unit; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameMode; import net.minecraft.world.GameRules; @@ -27,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; @@ -52,11 +52,13 @@ public static GameOpenProcedure open(GameOpenContext context) { .setGameRule(GameRules.KEEP_INVENTORY, true); return context.openWithWorld(worldConfig, (activity, world) -> { - activity.listen(GamePlayerEvents.OFFER, offer -> { - var player = offer.player(); - return offer.accept(world, new Vec3d(0.0, 65.0, 0.0)) - .and(() -> 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)); @@ -129,11 +131,13 @@ private static GameResult startGame(GameSpace gameSpace, int iter) { return ActionResult.FAIL; }); - activity.listen(GamePlayerEvents.OFFER, offer -> { - var player = offer.player(); - return offer.accept(gameSpace.getWorlds().iterator().next(), new Vec3d(0.0, 65.0, 0.0)) - .and(() -> 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); + }) + ); }); return GameResult.ok();