From 2d1831cbdea2f96697756888bbfd10184a3f45c9 Mon Sep 17 00:00:00 2001 From: Patbox <39821509+Patbox@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:33:40 +0100 Subject: [PATCH] Add leave message, add events to modify join and leave messages --- .../nucleoid/plasmid/api/game/GameTexts.java | 15 +++++ .../api/game/common/GameWaitingLobby.java | 60 +++++++++++++++++-- .../api/game/event/GamePlayerEvents.java | 49 +++++++++++++++ .../impl/game/manager/ManagedGameSpace.java | 24 ++++++-- .../resources/data/plasmid/lang/en_us.json | 4 ++ .../xyz/nucleoid/plasmid/test/JankGame.java | 3 + .../xyz/nucleoid/plasmid/test/TestConfig.java | 4 +- .../xyz/nucleoid/plasmid/test/TestGame.java | 2 +- .../plasmid/game/test_2_players_required.json | 7 +++ 9 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 src/testmod/resources/data/testmod/plasmid/game/test_2_players_required.json diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/GameTexts.java b/src/main/java/xyz/nucleoid/plasmid/api/game/GameTexts.java index 84ebf563..3a4adcc0 100644 --- a/src/main/java/xyz/nucleoid/plasmid/api/game/GameTexts.java +++ b/src/main/java/xyz/nucleoid/plasmid/api/game/GameTexts.java @@ -112,6 +112,10 @@ public static MutableText success(ServerPlayerEntity player) { return Text.translatable("text.plasmid.game.join", player.getDisplayName()); } + public static MutableText successSpectator(ServerPlayerEntity player) { + return Text.translatable("text.plasmid.game.join.spectate", player.getDisplayName()); + } + public static MutableText link(GameSpace gameSpace) { var hover = Text.translatable("text.plasmid.join_link_hover", GameConfig.name(gameSpace.getMetadata().sourceConfig())); @@ -166,6 +170,17 @@ public static MutableText participantsOnly() { } } + public static final class Leave { + public static MutableText participant(ServerPlayerEntity player) { + return Text.translatable("text.plasmid.game.leave", player.getDisplayName()); + } + + public static MutableText spectator(ServerPlayerEntity player) { + return Text.translatable("text.plasmid.game.leave.spectate", player.getDisplayName()); + } + } + + public static final class Kick { public static MutableText kick(ServerCommandSource source, ServerPlayerEntity target) { return source.isExecutedByPlayer() ? kickBy(source.getPlayer(), target) : kick(target); diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/common/GameWaitingLobby.java b/src/main/java/xyz/nucleoid/plasmid/api/game/common/GameWaitingLobby.java index a6a44470..452f65cc 100644 --- a/src/main/java/xyz/nucleoid/plasmid/api/game/common/GameWaitingLobby.java +++ b/src/main/java/xyz/nucleoid/plasmid/api/game/common/GameWaitingLobby.java @@ -96,6 +96,8 @@ public static GameWaitingLobby addTo(GameActivity activity, WaitingLobbyConfig p activity.listen(GamePlayerEvents.OFFER, lobby::offerPlayer); activity.listen(GamePlayerEvents.ADD, lobby::onAddPlayer); activity.listen(GamePlayerEvents.REMOVE, lobby::onRemovePlayer); + activity.listen(GamePlayerEvents.JOIN_MESSAGE, lobby::onJoinMessage); + activity.listen(GamePlayerEvents.LEAVE_MESSAGE, lobby::onLeaveMessage); activity.listen(GameWaitingLobbyEvents.BUILD_UI_LAYOUT, lobby::onBuildUiLayout); activity.listen(GameActivityEvents.STATE_UPDATE, lobby::updateState); @@ -118,6 +120,52 @@ public static GameWaitingLobby addTo(GameActivity activity, WaitingLobbyConfig p return lobby; } + @Nullable + private Text onJoinMessage(ServerPlayerEntity player, Text currentText, Text defaultText) { + if (currentText == null || (this.playerConfig.thresholdPlayers() == 1 && this.playerConfig.minPlayers() == 1) || this.playerConfig.playerConfig().maxPlayers().isEmpty() || this.gameSpace.getPlayers().spectators().contains(player)) { + return currentText; + } + var count = this.gameSpace.getPlayers().participants().size(); + var canStart = count >= this.playerConfig.minPlayers() && (this.isActiveFull(this.gameSpace.getPlayers().size()) || this.isReady(count)); + + if (canStart) { + return currentText; + } + + var required = Math.max(Math.min(this.playerConfig.thresholdPlayers(), + this.gameSpace.getServer().getCurrentPlayerCount() + ), this.playerConfig.minPlayers()) - count; + + return Text.empty() + .append(currentText) + .append(" ") + .append(Text.translatable("text.plasmid.game.waiting_lobby.players_needed_to_start", required).formatted(Formatting.YELLOW)); + } + + @Nullable + private Text onLeaveMessage(ServerPlayerEntity player, Text currentText, Text defaultText) { + if (currentText == null || (this.playerConfig.thresholdPlayers() == 1 && this.playerConfig.minPlayers() == 1) || this.playerConfig.playerConfig().maxPlayers().isEmpty() || this.gameSpace.getPlayers().spectators().contains(player)) { + return currentText; + } + + var count = this.gameSpace.getPlayers().participants().size() - 1; + var canStart = count >= this.playerConfig.minPlayers() && (this.isActiveFull(this.gameSpace.getPlayers().size() - 1) || this.isReady(count)); + + + if (canStart) { + return currentText; + } + + var required = Math.max(Math.min(this.playerConfig.thresholdPlayers(), + this.gameSpace.getServer().getCurrentPlayerCount() + ), this.playerConfig.minPlayers()) - count; + + return Text.empty() + .append(currentText) + .append(" ") + .append(Text.translatable("text.plasmid.game.waiting_lobby.players_needed_to_start", required).formatted(Formatting.YELLOW)); + } + private GameSpaceState.Builder updateState(GameSpaceState.Builder builder) { return builder.state(this.getTargetCountdownDuration() != -1 ? GameSpaceState.State.STARTING : GameSpaceState.State.WAITING); } @@ -240,9 +288,9 @@ private long getTargetCountdownDuration() { } if (this.gameSpace.getPlayers().participants().size() >= this.playerConfig.minPlayers()) { - if (this.isActiveFull()) { + if (this.isActiveFull(this.gameSpace.getPlayers().size())) { return countdown.fullSeconds() * 20L; - } else if (this.isReady()) { + } else if (this.isReady(this.gameSpace.getPlayers().participants().size())) { return countdown.readySeconds() * 20L; } } @@ -317,22 +365,22 @@ private long getRemainingTicks(long time) { return Math.max(this.countdownStart + this.countdownDuration - time, 0); } - private boolean isReady() { - return this.gameSpace.getPlayers().participants().size() >= this.playerConfig.thresholdPlayers(); + private boolean isReady(int count) { + return count >= this.playerConfig.thresholdPlayers(); } private boolean isFull() { return this.limiter.isFull(); } - private boolean isActiveFull() { + private boolean isActiveFull(int count) { if (this.isFull()) { return true; } // if all players on the server are in this lobby var server = this.gameSpace.getServer(); - if (this.gameSpace.getPlayers().size() >= server.getCurrentPlayerCount()) { + if (count >= server.getCurrentPlayerCount()) { return true; } diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java b/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java index 9a41b605..b6c54df4 100644 --- a/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java +++ b/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java @@ -2,6 +2,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import xyz.nucleoid.plasmid.api.game.GameSpace; import xyz.nucleoid.plasmid.api.game.player.JoinAcceptor; import xyz.nucleoid.plasmid.api.game.player.JoinAcceptorResult; @@ -161,6 +162,44 @@ public final class GamePlayerEvents { } }); + /** + * Called when join message of {@link ServerPlayerEntity} is created. + * Can be used to manipulate it in game. + * This event is invoked after game handles player being added, but before the global join event + * + * Event returns a Text to set it or {@code null} to disable it. + */ + public static final StimulusEvent JOIN_MESSAGE = StimulusEvent.create(JoinMessage.class, ctx -> (player, current, defaultText) -> { + try { + for (var listener : ctx.getListeners()) { + current = listener.onJoinMessageCreation(player, current, defaultText); + } + return current; + } catch (Throwable throwable) { + ctx.handleException(throwable); + return defaultText; + } + }); + + /** + * Called when leave message of {@link ServerPlayerEntity} is created. + * Can be used to manipulate it in game. + * This event is invoked before game handles player being removed + + * Event returns a Text to set it or {@code null} to disable it. + */ + public static final StimulusEvent LEAVE_MESSAGE = StimulusEvent.create(LeaveMessage.class, ctx -> (player, current, defaultText) -> { + try { + for (var listener : ctx.getListeners()) { + current = listener.onLeaveMessageCreation(player, current, defaultText); + } + return current; + } catch (Throwable throwable) { + ctx.handleException(throwable); + return defaultText; + } + }); + public interface Add { void onAddPlayer(ServerPlayerEntity player); } @@ -180,4 +219,14 @@ public interface Accept { public interface Name { Text onDisplayNameCreation(ServerPlayerEntity player, Text currentText, Text vanillaText); } + + public interface JoinMessage { + @Nullable + Text onJoinMessageCreation(ServerPlayerEntity player, @Nullable Text currentText, Text defaultText); + } + + public interface LeaveMessage { + @Nullable + Text onLeaveMessageCreation(ServerPlayerEntity player, @Nullable Text currentText, Text defaultText); + } } diff --git a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java index f7c56caf..c551714b 100644 --- a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java +++ b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java @@ -225,20 +225,36 @@ void onAddPlayer(ServerPlayerEntity player) { this.lifecycle.onAddPlayer(this, player); - var joinMessage = GameTexts.Join.success(player) - .formatted(Formatting.YELLOW); - this.players.sendMessage(joinMessage); - + var spectator = this.players.spectators().contains(player); + Text joinMessage = (spectator ? GameTexts.Join.successSpectator(player) : GameTexts.Join.success(player)).formatted(Formatting.YELLOW); + joinMessage = this.state.invoker(GamePlayerEvents.JOIN_MESSAGE).onJoinMessageCreation(player, joinMessage, joinMessage); GameEvents.PLAYER_JOIN.invoker().onPlayerJoin(this, player); + + if (joinMessage != null) { + this.players.sendMessage(joinMessage); + } } void onPlayerRemove(ServerPlayerEntity player) { + var spectator = this.players.spectators().contains(player); + Text leaveMessage = (spectator ? GameTexts.Leave.spectator(player) : GameTexts.Leave.participant(player)).formatted(Formatting.YELLOW); + leaveMessage = this.state.invoker(GamePlayerEvents.LEAVE_MESSAGE).onLeaveMessageCreation(player, leaveMessage, leaveMessage); + this.state.invoker(GamePlayerEvents.LEAVE).onRemovePlayer(player); this.state.invoker(GamePlayerEvents.REMOVE).onRemovePlayer(player); this.lifecycle.onRemovePlayer(this, player); + GameEvents.PLAYER_LEFT.invoker().onPlayerLeft(this, player); this.manager.removePlayerFromGameSpace(this, player); + + if (leaveMessage != null) { + for (var receiver : this.players) { + if (receiver != player) { + receiver.sendMessage(leaveMessage); + } + } + } } void onAddWorld(RuntimeWorldHandle worldHandle) { diff --git a/src/main/resources/data/plasmid/lang/en_us.json b/src/main/resources/data/plasmid/lang/en_us.json index f4294ab5..571939d3 100644 --- a/src/main/resources/data/plasmid/lang/en_us.json +++ b/src/main/resources/data/plasmid/lang/en_us.json @@ -31,9 +31,12 @@ "text.plasmid.game.portal.disconnect.entity": "Disconnected '%1$s' from all portals", "text.plasmid.game.portal.player_count": "%s players", "text.plasmid.game.join": "%s has joined the game lobby!", + "text.plasmid.game.join.spectate": "%s is spectating the game lobby!", "text.plasmid.game.join.error": "An unexpected exception occurred while joining game!", "text.plasmid.game.join.no_game_open": "No games are open!", "text.plasmid.game.join.party.error": "%s players were unable to join this game!", + "text.plasmid.game.leave": "%s has left the game lobby!", + "text.plasmid.game.leave.spectate": "%s is no longer spectating the game lobby!", "text.plasmid.game.kick": "%s was kicked from the game!", "text.plasmid.game.kick.by": "%s was kicked from the game by %s!", "text.plasmid.game.list": "Registered games:", @@ -60,6 +63,7 @@ "text.plasmid.game.waiting_lobby.bar.waiting": "Waiting for players...", "text.plasmid.game.waiting_lobby.leave_game": "Leave Game", "text.plasmid.game.waiting_lobby.sidebar.players": "Players: %s%s%s", + "text.plasmid.game.waiting_lobby.players_needed_to_start": "%s more player(s) is needed to start!", "text.plasmid.join_result.already_joined": "You have already joined this game!", "text.plasmid.join_result.error": "An unexpected error occurred while adding you to this game!", "text.plasmid.join_result.generic_error": "You cannot join this game!", diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java b/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java index 2e857fff..9e14e15d 100644 --- a/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java +++ b/src/testmod/java/xyz/nucleoid/plasmid/test/JankGame.java @@ -82,6 +82,9 @@ public static GameOpenProcedure open(GameOpenContext context) { return EventResult.DENY; }); + activity.listen(GamePlayerEvents.JOIN_MESSAGE, (player, text, text2) -> null); + activity.listen(GamePlayerEvents.LEAVE_MESSAGE, (player, text, text2) -> null); + activity.listen(GameActivityEvents.REQUEST_START, () -> startGame(activity.getGameSpace())); }); diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/TestConfig.java b/src/testmod/java/xyz/nucleoid/plasmid/test/TestConfig.java index ffd00989..46e70457 100644 --- a/src/testmod/java/xyz/nucleoid/plasmid/test/TestConfig.java +++ b/src/testmod/java/xyz/nucleoid/plasmid/test/TestConfig.java @@ -9,12 +9,14 @@ import net.minecraft.registry.RegistryCodecs; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.entry.RegistryEntryList; +import xyz.nucleoid.plasmid.api.game.common.config.WaitingLobbyConfig; import java.util.Optional; -public record TestConfig(int integer, BlockState state, Optional> items, int teamCount) { +public record TestConfig(int integer, WaitingLobbyConfig players, BlockState state, Optional> items, int teamCount) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( Codec.INT.optionalFieldOf("integer", 0).forGetter(TestConfig::integer), + WaitingLobbyConfig.CODEC.optionalFieldOf("players", new WaitingLobbyConfig(1, 99)).forGetter(TestConfig::players), BlockState.CODEC.optionalFieldOf("state", Blocks.BLUE_STAINED_GLASS.getDefaultState()).forGetter(TestConfig::state), RegistryCodecs.entryList(RegistryKeys.ITEM).optionalFieldOf("items").forGetter(TestConfig::items), Codec.INT.optionalFieldOf("team_count", 0).forGetter(TestConfig::teamCount) diff --git a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java index 4da5a263..49991e52 100644 --- a/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java +++ b/src/testmod/java/xyz/nucleoid/plasmid/test/TestGame.java @@ -83,7 +83,7 @@ public static GameOpenProcedure open(GameOpenContext context) { }) ); - GameWaitingLobby.addTo(activity, new WaitingLobbyConfig(1, 99)); + GameWaitingLobby.addTo(activity, context.config().players()); int teamCount = context.config().teamCount(); diff --git a/src/testmod/resources/data/testmod/plasmid/game/test_2_players_required.json b/src/testmod/resources/data/testmod/plasmid/game/test_2_players_required.json new file mode 100644 index 00000000..6c12ea7d --- /dev/null +++ b/src/testmod/resources/data/testmod/plasmid/game/test_2_players_required.json @@ -0,0 +1,7 @@ +{ + "type": "testmod:test", + "players": { + "max": 16, + "min": 2 + } +}